Skip to content

Commit

Permalink
Merge pull request #8 from Joffref/fix-parse-no-comments
Browse files Browse the repository at this point in the history
fix: attribute without comments
  • Loading branch information
Joffref authored Nov 9, 2023
2 parents fc53639 + 6ae0ea4 commit 73214dd
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 27 deletions.
68 changes: 44 additions & 24 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,59 @@ package parser
import (
"fmt"
"go/ast"
"go/types"

"golang.org/x/tools/go/packages"
)

func Parse(pkg *packages.Package, typeName string) (ParsedType, error) {
p := ParsedType{
Type: Type(typeName),
}

func Parse(pkg *packages.Package, structName string) (Struct, error) {
for ident := range pkg.TypesInfo.Defs {
if ident.Name == typeName {
typeSpec, isTypeSpec := ident.Obj.Decl.(*ast.TypeSpec)
if !isTypeSpec {
return ParsedType{}, fmt.Errorf("%s is not a type", typeName)
}
structDeclaration, isStruct := typeSpec.Type.(*ast.StructType)
if !isStruct {
return ParsedType{}, fmt.Errorf("%s is not a struct", typeName)
if ident.Name == structName {
structType, err := identAsStructType(ident)
if err != nil {
return Struct{}, err
}

for _, field := range structDeclaration.Fields.List {
comments := make([]string, len(field.Doc.List))
for i, comment := range field.Doc.List {
comments[i] = comment.Text[2:]
}
p.Attributes = append(p.Attributes, Attributes{
Name: field.Names[0].Name,
Type: Type(pkg.TypesInfo.TypeOf(field.Type).String()),
Comments: comments,
})
return Struct{
Type: Type(structName),
Attributes: structAttributes(pkg.TypesInfo, structType),
}, nil
}
}
return Struct{}, fmt.Errorf("struct %s not found in package %s", structName, pkg.Name)
}

func identAsStructType(ident *ast.Ident) (*ast.StructType, error) {
typeSpec, isTypeSpec := ident.Obj.Decl.(*ast.TypeSpec)
if !isTypeSpec {
return nil, fmt.Errorf("%s is not a type", ident.Name)
}

structDeclaration, isStruct := typeSpec.Type.(*ast.StructType)
if !isStruct {
return nil, fmt.Errorf("%s is not a struct", ident.Name)
}

return structDeclaration, nil
}

func structAttributes(typesInfo *types.Info, structType *ast.StructType) []Attribute {
attributes := make([]Attribute, len(structType.Fields.List))

for i, field := range structType.Fields.List {
comments := []string{}
if field.Doc != nil {
for _, comment := range field.Doc.List {
comments = append(comments, comment.Text[2:])
}
}

attributes[i] = Attribute{
Name: field.Names[0].Name,
Type: Type(typesInfo.TypeOf(field.Type).String()),
Comments: comments,
}
}

return p, nil
return attributes
}
159 changes: 159 additions & 0 deletions internal/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package parser_test

import (
"os"
"path"
"reflect"
"testing"

"github.com/Joffref/genz/internal/parser"
"golang.org/x/tools/go/packages"
)

func TestParseSuccess(t *testing.T) {
testCases := map[string]struct {
goCode string
structName string
expectedStruct parser.Struct
}{
"basic struct": {
goCode: `
package main
type A struct {}
`,
structName: "A",
expectedStruct: parser.Struct{
Type: parser.Type("A"),
Attributes: []parser.Attribute{},
},
},
"struct with one attribute": {
goCode: `
package main
type A struct {
foo string
}
`,
structName: "A",
expectedStruct: parser.Struct{
Type: parser.Type("A"),
Attributes: []parser.Attribute{
{
Name: "foo",
Type: "string",
Comments: []string{},
},
},
},
},
"struct with two attributes": {
goCode: `
package main
type A struct {
foo string
bar uint
}
`,
structName: "A",
expectedStruct: parser.Struct{
Type: parser.Type("A"),
Attributes: []parser.Attribute{
{
Name: "foo",
Type: "string",
Comments: []string{},
},
{
Name: "bar",
Type: "uint",
Comments: []string{},
},
},
},
},
"attribute with doc": {
goCode: `
package main
type A struct {
//comment 1
//comment 2
foo string
}
`,
structName: "A",
expectedStruct: parser.Struct{
Type: parser.Type("A"),
Attributes: []parser.Attribute{
{
Name: "foo",
Type: "string",
Comments: []string{"comment 1", "comment 2"},
},
},
},
},
"attribute with inline comment": {
goCode: `
package main
type A struct {
foo string // foo
}
`,
structName: "A",
expectedStruct: parser.Struct{
Type: parser.Type("A"),
Attributes: []parser.Attribute{
{
Name: "foo",
Type: "string",
Comments: []string{},
},
},
},
},
}

for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()

pkg := createPkgWithCode(t, tc.goCode)

gotStruct, err := parser.Parse(pkg, tc.structName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if !reflect.DeepEqual(gotStruct, tc.expectedStruct) {
t.Fatalf("expected %s, got %s", tc.expectedStruct, gotStruct)
}
})
}
}

func createPkgWithCode(t *testing.T, goCode string) *packages.Package {
t.Helper()

tmp := t.TempDir()
err := os.WriteFile(path.Join(tmp, "main.go"), []byte(goCode), 0644)
if err != nil {
t.Fatalf("failed while writing file: %v", err)
}

cfg := &packages.Config{Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax, Tests: false}
pkgs, err := packages.Load(cfg, path.Join(tmp, "main.go"))
if err != nil {
t.Fatalf("failed to load package: %v", err)
}
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}

return pkgs[0]
}
6 changes: 3 additions & 3 deletions internal/parser/types.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package parser

type (
ParsedType struct {
Struct struct {
Type Type

Attributes []Attributes
Attributes []Attribute
}

Attributes struct {
Attribute struct {
Name string
Type Type

Expand Down

0 comments on commit 73214dd

Please sign in to comment.