Skip to content
This repository has been archived by the owner on Jan 14, 2025. It is now read-only.

Commit

Permalink
fix: Support for multiple structs in one file (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
ginokent authored Nov 10, 2023
2 parents 7ef5d77 + 3288775 commit ba2cb2c
Show file tree
Hide file tree
Showing 15 changed files with 388 additions and 111 deletions.
44 changes: 23 additions & 21 deletions internal/arcgen/lang/go/dump_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,29 @@ import (
"github.com/kunitsucom/arcgen/internal/logs"
)

func dumpSource(fset *token.FileSet, arcSrcSet ARCSourceSet) {
for _, arcSrc := range arcSrcSet {
logs.Trace.Print("== Source ================================================================================================================================")
_, _ = io.WriteString(logs.Trace.LineWriter("r.CommentGroup.Text: "), arcSrc.CommentGroup.Text())
logs.Trace.Print("-- CommentGroup --------------------------------------------------------------------------------------------------------------------------------")
{
commentGroupAST := bytes.NewBuffer(nil)
goast.Fprint(commentGroupAST, fset, arcSrc.CommentGroup, goast.NotNilFilter)
_, _ = logs.Trace.LineWriter("").Write(commentGroupAST.Bytes())
}
logs.Trace.Print("-- TypeSpec --------------------------------------------------------------------------------------------------------------------------------")
{
typeSpecAST := bytes.NewBuffer(nil)
goast.Fprint(typeSpecAST, fset, arcSrc.TypeSpec, goast.NotNilFilter)
_, _ = logs.Trace.LineWriter("").Write(typeSpecAST.Bytes())
}
logs.Trace.Print("-- StructType --------------------------------------------------------------------------------------------------------------------------------")
{
structTypeAST := bytes.NewBuffer(nil)
goast.Fprint(structTypeAST, fset, arcSrc.StructType, goast.NotNilFilter)
_, _ = logs.Trace.LineWriter("").Write(structTypeAST.Bytes())
func dumpSource(fset *token.FileSet, arcSrcSet *ARCSourceSet) {
if arcSrcSet != nil {
for _, arcSrc := range arcSrcSet.ARCSources {
logs.Trace.Print("== Source ================================================================================================================================")
_, _ = io.WriteString(logs.Trace.LineWriter("r.CommentGroup.Text: "), arcSrc.CommentGroup.Text())
logs.Trace.Print("-- CommentGroup --------------------------------------------------------------------------------------------------------------------------------")
{
commentGroupAST := bytes.NewBuffer(nil)
goast.Fprint(commentGroupAST, fset, arcSrc.CommentGroup, goast.NotNilFilter)
_, _ = logs.Trace.LineWriter("").Write(commentGroupAST.Bytes())
}
logs.Trace.Print("-- TypeSpec --------------------------------------------------------------------------------------------------------------------------------")
{
typeSpecAST := bytes.NewBuffer(nil)
goast.Fprint(typeSpecAST, fset, arcSrc.TypeSpec, goast.NotNilFilter)
_, _ = logs.Trace.LineWriter("").Write(typeSpecAST.Bytes())
}
logs.Trace.Print("-- StructType --------------------------------------------------------------------------------------------------------------------------------")
{
structTypeAST := bytes.NewBuffer(nil)
goast.Fprint(structTypeAST, fset, arcSrc.StructType, goast.NotNilFilter)
_, _ = logs.Trace.LineWriter("").Write(structTypeAST.Bytes())
}
}
}
}
69 changes: 56 additions & 13 deletions internal/arcgen/lang/go/extract_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,63 @@ import (
apperr "github.com/kunitsucom/arcgen/pkg/errors"
)

//nolint:gocognit,cyclop
func extractSource(_ context.Context, fset *token.FileSet, f *goast.File) (ARCSourceSet, error) {
arcSrcSet := make(ARCSourceSet, 0)
//nolint:cyclop,funlen,gocognit
func extractSource(_ context.Context, fset *token.FileSet, f *goast.File) (*ARCSourceSet, error) {
// NOTE: Use map to avoid duplicate entries.
arcSrcMap := make(map[string]*ARCSource)

goast.Inspect(f, func(node goast.Node) bool {
switch n := node.(type) {
case *goast.TypeSpec:
typeSpec := n
switch t := n.Type.(type) {
case *goast.StructType:
structType := t
if hasColumnTagGo(t) {
pos := fset.Position(structType.Pos())
logs.Debug.Printf("🔍: %s: type=%s", pos.String(), n.Name.Name)
arcSrcMap[pos.String()] = &ARCSource{
Source: pos,
Package: f.Name,
TypeSpec: typeSpec,
StructType: structType,
}
}
return false
default: // noop
}
default: // noop
}
return true
})

// Since it is not possible to extract the comment group associated with the position of struct,
// search for the struct associated with the comment group and overwrite it.
for commentedNode, commentGroups := range goast.NewCommentMap(fset, f, f.Comments) {
for _, commentGroup := range commentGroups {
CommentGroupLoop:
for _, commentLine := range commentGroup.List {
commentGroup := commentGroup // MEMO: Using the variable on range scope `commentGroup` in function literal (scopelint)
logs.Trace.Printf("commentLine=%s: %s", filepathz.Short(fset.Position(commentGroup.Pos()).String()), commentLine.Text)
// NOTE: If the comment line matches the ColumnTagGo, it is assumed to be a comment line for the struct.
if matches := ColumnTagGoCommentLineRegex().FindStringSubmatch(commentLine.Text); len(matches) > _ColumnTagGoCommentLineRegexContentIndex {
s := &ARCSource{
Position: fset.Position(commentLine.Pos()),
Package: f.Name,
CommentGroup: commentGroup,
}
goast.Inspect(commentedNode, func(node goast.Node) bool {
switch n := node.(type) {
case *goast.TypeSpec:
s.TypeSpec = n
typeSpec := n
switch t := n.Type.(type) {
case *goast.StructType:
s.StructType = t
structType := t
if hasColumnTagGo(t) {
logs.Debug.Printf("🔍: %s: type=%s", fset.Position(t.Pos()).String(), n.Name.Name)
arcSrcSet = append(arcSrcSet, s)
pos := fset.Position(structType.Pos())
logs.Debug.Printf("🖋️: %s: overwrite with comment group: type=%s", fset.Position(t.Pos()).String(), n.Name.Name)
arcSrcMap[pos.String()] = &ARCSource{
Source: pos,
Package: f.Name,
TypeSpec: typeSpec,
StructType: structType,
CommentGroup: commentGroup,
}
}
return false
default: // noop
Expand All @@ -54,7 +86,18 @@ func extractSource(_ context.Context, fset *token.FileSet, f *goast.File) (ARCSo
}
}

if len(arcSrcSet) == 0 {
arcSrcSet := &ARCSourceSet{
Filename: fset.Position(f.Pos()).Filename,
PackageName: f.Name.Name,
Source: fset.Position(f.Pos()),
ARCSources: make([]*ARCSource, 0),
}

for _, arcSrc := range arcSrcMap {
arcSrcSet.ARCSources = append(arcSrcSet.ARCSources, arcSrc)
}

if len(arcSrcSet.ARCSources) == 0 {
return nil, errorz.Errorf("column-tag-go=%s: %w", config.ColumnTagGo(), apperr.ErrColumnTagGoAnnotationNotFoundInSource)
}

Expand Down
151 changes: 80 additions & 71 deletions internal/arcgen/lang/go/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go/ast"
"go/format"
"go/token"
"io"
"os"
"reflect"
"strconv"
Expand All @@ -17,9 +18,10 @@ import (

"github.com/kunitsucom/arcgen/internal/arcgen/lang/util"
"github.com/kunitsucom/arcgen/internal/config"
"github.com/kunitsucom/arcgen/internal/logs"
)

//nolint:cyclop
//nolint:cyclop,funlen
func Generate(ctx context.Context, src string) error {
arcSrcSets, err := parse(ctx, src)
if err != nil {
Expand All @@ -29,15 +31,23 @@ func Generate(ctx context.Context, src string) error {
newFile := token.NewFileSet()

for _, arcSrcSet := range arcSrcSets {
for _, arcSrc := range arcSrcSet {
filePrefix := strings.TrimSuffix(arcSrc.Position.Filename, fileSuffix)
filename := fmt.Sprintf("%s.%s.gen%s", filePrefix, config.ColumnTagGo(), fileSuffix)
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return errorz.Errorf("os.Open: %w", err)
}
filePrefix := strings.TrimSuffix(arcSrcSet.Filename, fileSuffix)
filename := fmt.Sprintf("%s.%s.gen%s", filePrefix, config.ColumnTagGo(), fileSuffix)
osFile, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return errorz.Errorf("os.OpenFile: %w", err)
}

packageName := arcSrc.Package.Name
astFile := &ast.File{
// package
Name: &ast.Ident{
Name: arcSrcSet.PackageName,
},
// methods
Decls: []ast.Decl{},
}

for _, arcSrc := range arcSrcSet.ARCSources {
structName := arcSrc.TypeSpec.Name.Name
tableName := extractTableNameFromCommentGroup(arcSrc.CommentGroup)
columnNames := func() []string {
Expand All @@ -47,6 +57,7 @@ func Generate(ctx context.Context, src string) error {
tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
switch columnName := tag.Get(config.ColumnTagGo()); columnName {
case "", "-":
logs.Trace.Printf("SKIP: %s: field.Names=%s, columnName=%q", arcSrc.Source.String(), field.Names, columnName)
// noop
default:
columnNames = append(columnNames, columnName)
Expand All @@ -56,101 +67,99 @@ func Generate(ctx context.Context, src string) error {
return columnNames
}()

node := generateASTFile(packageName, structName, tableName, config.MethodPrefixGlobal(), config.MethodPrefixColumn(), columnNames)
appendAST(astFile, structName, tableName, config.MethodPrefixGlobal(), config.MethodPrefixColumn(), columnNames)
}

buf := bytes.NewBuffer(nil)
if err := format.Node(buf, newFile, node); err != nil {
return errorz.Errorf("format.Node: %w", err)
}
buf := bytes.NewBuffer(nil)
if err := format.Node(buf, newFile, astFile); err != nil {
return errorz.Errorf("format.Node: %w", err)
}

// add header comment
s := strings.Replace(
buf.String(),
"package "+packageName+"\n",
fmt.Sprintf("// Code generated by arcgen. DO NOT EDIT.\n//\n// source: %s:%d\n\npackage "+packageName+"\n", filepathz.Short(arcSrc.Position.Filename), arcSrc.Position.Line),
1,
)
// add blank line between methods
s = strings.ReplaceAll(s, "\n}\nfunc ", "\n}\n\nfunc ")
// add header comment
content := "" +
"// Code generated by arcgen. DO NOT EDIT." + "\n" +
"//" + "\n" +
fmt.Sprintf("// source: %s", filepathz.Short(arcSrcSet.Source.Filename)) + "\n" +
"\n" +
buf.String()

// write to file
if _, err := f.WriteString(s); err != nil {
return errorz.Errorf("f.WriteString: %w", err)
}
// add blank line between methods
content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ")

// write to file
if _, err := io.WriteString(osFile, content); err != nil {
return errorz.Errorf("io.WriteString: %w", err)
}
}

return nil
}

func extractTableNameFromCommentGroup(commentGroup *ast.CommentGroup) string {
for _, comment := range commentGroup.List {
if matches := util.RegexIndexTableName.Regex.FindStringSubmatch(comment.Text); len(matches) > util.RegexIndexTableName.Index {
return matches[util.RegexIndexTableName.Index]
if commentGroup != nil {
for _, comment := range commentGroup.List {
if matches := util.RegexIndexTableName.Regex.FindStringSubmatch(comment.Text); len(matches) > util.RegexIndexTableName.Index {
return matches[util.RegexIndexTableName.Index]
}
}
}
return fmt.Sprintf("ERROR: TABLE NAME IN COMMENT `// \"%s\": table: *` NOT FOUND: comment=%q", config.ColumnTagGo(), commentGroup.Text())

logs.Debug.Printf("WARN: table name in comment not found: `// \"%s\": table: *`: comment=%q", config.ColumnTagGo(), commentGroup.Text())
return ""
}

//nolint:funlen
func generateASTFile(packageName string, structName string, tableName string, prefixGlobal string, prefixColumn string, columnNames []string) *ast.File {
file := &ast.File{
// package
Name: &ast.Ident{
Name: packageName,
},
// methods
Decls: []ast.Decl{
&ast.FuncDecl{
Recv: &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{
{
Name: "s",
},
func appendAST(file *ast.File, structName string, tableName string, prefixGlobal string, prefixColumn string, columnNames []string) {
if tableName != "" {
file.Decls = append(file.Decls, &ast.FuncDecl{
Recv: &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{
{
Name: "s",
},
Type: &ast.StarExpr{
X: &ast.Ident{
Name: structName, // MEMO: struct name
},
},
Type: &ast.StarExpr{
X: &ast.Ident{
Name: structName, // MEMO: struct name
},
},
},
},
Name: &ast.Ident{
Name: prefixGlobal + "TableName",
},
Type: &ast.FuncType{
Params: &ast.FieldList{},
Results: &ast.FieldList{
List: []*ast.Field{
{
Type: &ast.Ident{
Name: "string",
},
},
Name: &ast.Ident{
Name: prefixGlobal + "TableName",
},
Type: &ast.FuncType{
Params: &ast.FieldList{},
Results: &ast.FieldList{
List: []*ast.Field{
{
Type: &ast.Ident{
Name: "string",
},
},
},
},
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.ReturnStmt{
Results: []ast.Expr{
&ast.Ident{
Name: strconv.Quote(tableName),
},
},
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.ReturnStmt{
Results: []ast.Expr{
&ast.Ident{
Name: strconv.Quote(tableName),
},
},
},
},
},
},
})
}

file.Decls = append(file.Decls, generateASTColumnMethods(structName, prefixGlobal, prefixColumn, columnNames)...)

return file
return //nolint:gosimple
}

//nolint:funlen
Expand Down
Loading

0 comments on commit ba2cb2c

Please sign in to comment.