Skip to content

Commit

Permalink
chore: In amino.pkg, add optional WithComments, use them in GenerateP…
Browse files Browse the repository at this point in the history
…roto3MessagePartial (gnolang#1235)

This PR resolves issue gnolang#1157 by adding support for the optional
`WithComments`. See the issue for motivation.

- In `amino.pkg.Type`, add optional fields `Comment` and `
FieldComments` .
- Add the `Package` method `WithComments` which can optionally be used
after `WithTypes`. This reads the Go source file and scans the AST for
struct and field comments which are added to the `Type` object.
- Update `GenerateProto3MessagePartial` to copy the comments to the
`P3Doc` and related `P3Field` objects which already have an optional
`Comment` field for comments that are already included in the Protobuf
file.
- Add test file `comments_test.go` .

As shown in the test, you can use `WithComments` after `WithTypes`:

    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")))

If your Go struct looks like:

    // message comment
    type TestMessageName struct {
        // field comment 1
        FieldName1 string
        // field comment 2
        FieldName2 []uint64
    }

then your Protobuf file has:

    // message comment
    message TestMessageName {
        // field comment 1
        string FieldName1 = 1;
        // field comment 2
        repeated uint64 FieldName2 = 2;
    }

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Signed-off-by: Jeff Thompson <jeff@thefirst.org>
Co-authored-by: Morgan Bazalgette <morgan@morganbaz.com>
  • Loading branch information
2 people authored and gfanton committed Jan 18, 2024
1 parent 4743911 commit f44bc99
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 2 deletions.
61 changes: 61 additions & 0 deletions tm2/pkg/amino/genproto/comments_test.go
Original file line number Diff line number Diff line change
@@ -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"];
}`)
}
12 changes: 12 additions & 0 deletions tm2/pkg/amino/genproto/genproto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down
72 changes: 70 additions & 2 deletions tm2/pkg/amino/pkg/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package pkg

import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"reflect"
"regexp"
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit f44bc99

Please sign in to comment.