diff --git a/tm2/pkg/amino/genproto/comments_test.go b/tm2/pkg/amino/genproto/comments_test.go new file mode 100644 index 00000000000..5910a244b6b --- /dev/null +++ b/tm2/pkg/amino/genproto/comments_test.go @@ -0,0 +1,61 @@ +package genproto + +import ( + "path" + "testing" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/jaekwon/testify/assert" +) + +// message comment +type TestMessageName struct { + // field comment 1 + FieldName1 string + // field comment 2 + FieldName2 []uint64 +} + +// message comment 2 +type TestMessageName2 struct { + // another field comment + FieldName string +} + +func TestComments(t *testing.T) { + pkg := amino.RegisterPackage( + amino.NewPackage( + "github.com/gnolang/gno/tm2/pkg/amino/genproto", + "amino_test", + amino.GetCallersDirname(), + ).WithTypes( + &TestMessageName{}, + &TestMessageName2{}, + // Add comments from this same source file. + ).WithComments(path.Join(amino.GetCallersDirname(), "comments_test.go"))) + + p3c := NewP3Context() + p3c.RegisterPackage(pkg) + p3c.ValidateBasic() + p3doc := p3c.GenerateProto3SchemaForTypes(pkg, pkg.ReflectTypes()...) + proto3Schema := p3doc.Print() + assert.Equal(t, proto3Schema, `syntax = "proto3"; +package amino_test; + +option go_package = "github.com/gnolang/gno/tm2/pkg/amino/genproto/pb"; + +// messages +// message comment +message TestMessageName { + // field comment 1 + string field_name1 = 1 [json_name = "FieldName1"]; + // field comment 2 + repeated uint64 field_name2 = 2 [json_name = "FieldName2"]; +} + +// message comment 2 +message TestMessageName2 { + // another field comment + string field_name = 1 [json_name = "FieldName"]; +}`) +} diff --git a/tm2/pkg/amino/genproto/genproto.go b/tm2/pkg/amino/genproto/genproto.go index 01452639245..fb71f32783e 100644 --- a/tm2/pkg/amino/genproto/genproto.go +++ b/tm2/pkg/amino/genproto/genproto.go @@ -190,6 +190,15 @@ func (p3c *P3Context) GenerateProto3MessagePartial(p3doc *P3Doc, rt reflect.Type p3msg.Name = info.Name // not rinfo. + var fieldComments map[string]string + if rinfo.Package != nil { + if pkgType, ok := rinfo.Package.GetType(rt); ok { + p3msg.Comment = pkgType.Comment + // We will check for optional field comments below. + fieldComments = pkgType.FieldComments + } + } + // Append to p3msg.Fields, fields of the struct. for _, field := range rsfields { // rinfo. fp3, fp3IsRepeated, implicit := typeToP3Type(info.Package, field.TypeInfo, field.FieldOptions) @@ -209,6 +218,9 @@ func (p3c *P3Context) GenerateProto3MessagePartial(p3doc *P3Doc, rt reflect.Type JSONName: field.JSONName, Number: field.FieldOptions.BinFieldNum, } + if fieldComments != nil { + p3Field.Comment = fieldComments[field.Name] + } p3msg.Fields = append(p3msg.Fields, p3Field) } diff --git a/tm2/pkg/amino/pkg/pkg.go b/tm2/pkg/amino/pkg/pkg.go index fad46c4cad7..72e9f891489 100644 --- a/tm2/pkg/amino/pkg/pkg.go +++ b/tm2/pkg/amino/pkg/pkg.go @@ -2,6 +2,9 @@ package pkg import ( "fmt" + "go/ast" + "go/parser" + "go/token" "path/filepath" "reflect" "regexp" @@ -11,8 +14,10 @@ import ( type Type struct { Type reflect.Type - Name string // proto3 name (override) - PointerPreferred bool // whether pointer is preferred for decoding interface. + Name string // proto3 name (override) + PointerPreferred bool // whether pointer is preferred for decoding interface. + Comment string // optional doc comment for the type + FieldComments map[string]string // If not nil, the optional doc comment for each field name } func (t *Type) FullName(pkg *Package) string { @@ -196,6 +201,69 @@ func (pkg *Package) WithP3SchemaFile(file string) *Package { return pkg } +// Parse the Go code in filename and scan the AST looking for struct doc comments. +// Find the Type in pkg.Types and set its Comment and FieldComments, which are +// used by genproto.GenerateProto3MessagePartial to set the Comment in the P3Doc +// and related P3Field objects. +func (pkg *Package) WithComments(filename string) *Package { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) + if err != nil { + panic(err) + } + + ast.Inspect(f, func(node ast.Node) bool { + genDecl, ok := node.(*ast.GenDecl) + if !ok { + return true + } + for _, spec := range genDecl.Specs { + typeSpec, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + + pkgType := pkg.getTypeByName(typeSpec.Name.Name) + if pkgType == nil { + continue + } + if genDecl.Doc != nil { + // Set the type comment. + pkgType.Comment = strings.TrimSpace(genDecl.Doc.Text()) + } + + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + continue + } + for _, field := range structType.Fields.List { + if field.Names != nil && len(field.Names) == 1 && field.Doc != nil { + // Set the field comment. + if pkgType.FieldComments == nil { + pkgType.FieldComments = make(map[string]string) + } + + pkgType.FieldComments[field.Names[0].Name] = strings.TrimSpace(field.Doc.Text()) + } + } + } + return true + }) + + return pkg +} + +// Get the Type by name. If not found, return nil. +func (pkg *Package) getTypeByName(name string) *Type { + for _, t := range pkg.Types { + if t.Name == name { + return t + } + } + + return nil +} + // Result cannot be modified. func (pkg *Package) GetType(rt reflect.Type) (t Type, ok bool) { if rt.Kind() == reflect.Ptr {