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

fix: Support for multiple structs in one file #2

Merged
merged 5 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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