From 56322592b49bdce559abfc8dae0d2c023c6dee3f Mon Sep 17 00:00:00 2001 From: ginokent <29125616+ginokent@users.noreply.github.com> Date: Fri, 10 Nov 2023 07:23:50 +0900 Subject: [PATCH 1/5] test: add test --- internal/arcgen/lang/go/generate.go | 86 +++++++++++-------- internal/arcgen/lang/go/generate_test.go | 83 ++++++++++++++++++ internal/arcgen/lang/go/tests/.gitignore | 1 + .../arcgen/lang/go/tests/column-tag-go.source | 3 + internal/arcgen/lang/go/tests/common.golden | 29 +++++++ internal/arcgen/lang/go/tests/common.source | 27 ++++++ .../tests/directory.dbtest.gen.dir/.gitignore | 1 + internal/arcgen/lang/go/tests/directory.dir | 10 +++ .../lang/go/tests/no-column-tag-go.source | 1 + internal/arcgen/lang/go/tests/no.errsource | 0 10 files changed, 204 insertions(+), 37 deletions(-) create mode 100644 internal/arcgen/lang/go/generate_test.go create mode 100644 internal/arcgen/lang/go/tests/.gitignore create mode 100644 internal/arcgen/lang/go/tests/column-tag-go.source create mode 100644 internal/arcgen/lang/go/tests/common.golden create mode 100644 internal/arcgen/lang/go/tests/common.source create mode 100644 internal/arcgen/lang/go/tests/directory.dbtest.gen.dir/.gitignore create mode 100644 internal/arcgen/lang/go/tests/directory.dir create mode 100644 internal/arcgen/lang/go/tests/no-column-tag-go.source create mode 100644 internal/arcgen/lang/go/tests/no.errsource diff --git a/internal/arcgen/lang/go/generate.go b/internal/arcgen/lang/go/generate.go index 98bcb60..7168d73 100644 --- a/internal/arcgen/lang/go/generate.go +++ b/internal/arcgen/lang/go/generate.go @@ -7,6 +7,7 @@ import ( "go/ast" "go/format" "go/token" + "io" "os" "reflect" "strconv" @@ -17,6 +18,7 @@ import ( "github.com/kunitsucom/arcgen/internal/arcgen/lang/util" "github.com/kunitsucom/arcgen/internal/config" + "github.com/kunitsucom/arcgen/internal/logs" ) //nolint:cyclop @@ -34,50 +36,60 @@ func Generate(ctx context.Context, src string) error { 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) + return errorz.Errorf("os.OpenFile: %w", err) } - packageName := arcSrc.Package.Name - structName := arcSrc.TypeSpec.Name.Name - tableName := extractTableNameFromCommentGroup(arcSrc.CommentGroup) - columnNames := func() []string { - columnNames := make([]string, 0) - for _, field := range arcSrc.StructType.Fields.List { - if field.Tag != nil { - tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`")) - switch columnName := tag.Get(config.ColumnTagGo()); columnName { - case "", "-": - // noop - default: - columnNames = append(columnNames, columnName) - } - } - } - return columnNames - }() - - node := generateASTFile(packageName, 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) + if err := generate(ctx, f, newFile, arcSrc); err != nil { + return errorz.Errorf("generate: %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 ") + return nil +} - // write to file - if _, err := f.WriteString(s); err != nil { - return errorz.Errorf("f.WriteString: %w", err) +func generate(_ context.Context, w io.Writer, newFile *token.FileSet, arcSrc *ARCSource) error { + packageName := arcSrc.Package.Name + structName := arcSrc.TypeSpec.Name.Name + tableName := extractTableNameFromCommentGroup(arcSrc.CommentGroup) + columnNames := func() []string { + columnNames := make([]string, 0) + for _, field := range arcSrc.StructType.Fields.List { + if field.Tag != nil { + 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.Position.String(), field.Names, columnName) + // noop + default: + columnNames = append(columnNames, columnName) + } } } + return columnNames + }() + + node := generateASTFile(packageName, 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) + } + + // add header comment + content := "" + + "// Code generated by arcgen. DO NOT EDIT." + "\n" + + "//" + "\n" + + fmt.Sprintf("// source: %s:%d", filepathz.Short(arcSrc.Position.Filename), arcSrc.Position.Line) + "\n" + + "\n" + + buf.String() + + // add blank line between methods + content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ") + + // write to file + if _, err := io.WriteString(w, content); err != nil { + return errorz.Errorf("io.WriteString: %w", err) } return nil diff --git a/internal/arcgen/lang/go/generate_test.go b/internal/arcgen/lang/go/generate_test.go new file mode 100644 index 0000000..27d05cb --- /dev/null +++ b/internal/arcgen/lang/go/generate_test.go @@ -0,0 +1,83 @@ +//nolint:testpackage +package arcgengo + +import ( + "context" + "testing" + + "github.com/kunitsucom/util.go/testing/require" + + "github.com/kunitsucom/arcgen/internal/config" + "github.com/kunitsucom/arcgen/internal/contexts" +) + +//nolint:paralleltest +func TestGenerate(t *testing.T) { + t.Run("success,tests", func(t *testing.T) { + ctx := contexts.WithArgs(context.Background(), []string{ + "ddlgen", + "--column-tag-go=dbtest", + "--src=tests", + }) + + backup := fileSuffix + t.Cleanup(func() { fileSuffix = backup }) + + _, err := config.Load(ctx) + require.NoError(t, err) + + fileSuffix = ".source" + require.NoError(t, Generate(ctx, config.Source())) + }) + + t.Run("failure,no.errsource", func(t *testing.T) { + ctx := contexts.WithArgs(context.Background(), []string{ + "ddlgen", + "--column-tag-go=dbtest", + "--src=tests/no.errsource", + }) + + backup := fileSuffix + t.Cleanup(func() { fileSuffix = backup }) + + _, err := config.Load(ctx) + require.NoError(t, err) + + fileSuffix = ".source" + require.ErrorsContains(t, Generate(ctx, config.Source()), "expected 'package', found 'EOF'") + }) + + t.Run("failure,no.errsource", func(t *testing.T) { + ctx := contexts.WithArgs(context.Background(), []string{ + "ddlgen", + "--column-tag-go=dbtest", + "--src=tests/no-such-file-or-directory", + }) + + backup := fileSuffix + t.Cleanup(func() { fileSuffix = backup }) + + _, err := config.Load(ctx) + require.NoError(t, err) + + fileSuffix = ".source" + require.ErrorsContains(t, Generate(ctx, config.Source()), "no such file or directory") + }) + + t.Run("failure,directory.dir", func(t *testing.T) { + ctx := contexts.WithArgs(context.Background(), []string{ + "ddlgen", + "--column-tag-go=dbtest", + "--src=tests/directory.dir", + }) + + backup := fileSuffix + t.Cleanup(func() { fileSuffix = backup }) + + _, err := config.Load(ctx) + require.NoError(t, err) + + fileSuffix = ".dir" + require.ErrorsContains(t, Generate(ctx, config.Source()), "is a directory") + }) +} diff --git a/internal/arcgen/lang/go/tests/.gitignore b/internal/arcgen/lang/go/tests/.gitignore new file mode 100644 index 0000000..1c53163 --- /dev/null +++ b/internal/arcgen/lang/go/tests/.gitignore @@ -0,0 +1 @@ +common.dbtest.gen.source diff --git a/internal/arcgen/lang/go/tests/column-tag-go.source b/internal/arcgen/lang/go/tests/column-tag-go.source new file mode 100644 index 0000000..770a542 --- /dev/null +++ b/internal/arcgen/lang/go/tests/column-tag-go.source @@ -0,0 +1,3 @@ +package tests + +// dbtest: table: `Tables` diff --git a/internal/arcgen/lang/go/tests/common.golden b/internal/arcgen/lang/go/tests/common.golden new file mode 100644 index 0000000..dd761b7 --- /dev/null +++ b/internal/arcgen/lang/go/tests/common.golden @@ -0,0 +1,29 @@ +// Code generated by arcgen. DO NOT EDIT. +// +// source: tests/common.source:6 + +package main + +func (s *User) GetTableName() string { + return "`Users`" +} + +func (s *User) GetColumnNames() []string { + return []string{"Id", "Name", "Email", "Age"} +} + +func (s *User) GetColumnName_Id() string { + return "Id" +} + +func (s *User) GetColumnName_Name() string { + return "Name" +} + +func (s *User) GetColumnName_Email() string { + return "Email" +} + +func (s *User) GetColumnName_Age() string { + return "Age" +} diff --git a/internal/arcgen/lang/go/tests/common.source b/internal/arcgen/lang/go/tests/common.source new file mode 100644 index 0000000..58295ee --- /dev/null +++ b/internal/arcgen/lang/go/tests/common.source @@ -0,0 +1,27 @@ +package main + +type ( + // User is a user. + // + // dbtest: table: `Users` + User struct { + // ID is a user ID. + ID string `dbtest:"Id"` + // Name is a user name. + Name string `dbtest:"Name"` + // Email is a user email. + Email string `dbtest:"Email"` + // Age is a user age. + Age int `dbtest:"Age"` + // Ignore is a ignore field. + Ignore string `dbtest:"-"` + } + + // dbtest: table: `SliceUsers` + SliceUser []*User + + // dbtest: table: `InvalidUsers` + InvalidUser struct { + ID string + } +) diff --git a/internal/arcgen/lang/go/tests/directory.dbtest.gen.dir/.gitignore b/internal/arcgen/lang/go/tests/directory.dbtest.gen.dir/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/internal/arcgen/lang/go/tests/directory.dbtest.gen.dir/.gitignore @@ -0,0 +1 @@ +* diff --git a/internal/arcgen/lang/go/tests/directory.dir b/internal/arcgen/lang/go/tests/directory.dir new file mode 100644 index 0000000..01df974 --- /dev/null +++ b/internal/arcgen/lang/go/tests/directory.dir @@ -0,0 +1,10 @@ +package main + +type ( + // ReadOnly is a struct. + // + // dbtest: table: ReadOnly + ReadOnly struct { + Name string `dbtest:"ReadOnly"` + } +) diff --git a/internal/arcgen/lang/go/tests/no-column-tag-go.source b/internal/arcgen/lang/go/tests/no-column-tag-go.source new file mode 100644 index 0000000..ca8701d --- /dev/null +++ b/internal/arcgen/lang/go/tests/no-column-tag-go.source @@ -0,0 +1 @@ +package tests diff --git a/internal/arcgen/lang/go/tests/no.errsource b/internal/arcgen/lang/go/tests/no.errsource new file mode 100644 index 0000000..e69de29 From 98f81805d6385c3acf8e0862d8004a6e67f27049 Mon Sep 17 00:00:00 2001 From: ginokent <29125616+ginokent@users.noreply.github.com> Date: Fri, 10 Nov 2023 07:31:05 +0900 Subject: [PATCH 2/5] fix: If no table name comment, the table name method is not generated. --- internal/arcgen/lang/go/generate.go | 69 +++++++++++++++-------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/internal/arcgen/lang/go/generate.go b/internal/arcgen/lang/go/generate.go index 7168d73..c67fe25 100644 --- a/internal/arcgen/lang/go/generate.go +++ b/internal/arcgen/lang/go/generate.go @@ -101,7 +101,8 @@ func extractTableNameFromCommentGroup(commentGroup *ast.CommentGroup) string { 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 @@ -112,52 +113,54 @@ func generateASTFile(packageName string, structName string, tableName string, pr Name: packageName, }, // methods - Decls: []ast.Decl{ - &ast.FuncDecl{ - Recv: &ast.FieldList{ - List: []*ast.Field{ - { - Names: []*ast.Ident{ - { - Name: "s", - }, + Decls: []ast.Decl{}, + } + + 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)...) From 8de0c64df817e2d3283a5895e6798ef69a62ece3 Mon Sep 17 00:00:00 2001 From: ginokent <29125616+ginokent@users.noreply.github.com> Date: Fri, 10 Nov 2023 07:39:37 +0900 Subject: [PATCH 3/5] test: fix golden file for TDD --- internal/arcgen/lang/go/generate_test.go | 19 ++++++++++++++++ internal/arcgen/lang/go/tests/common.golden | 24 +++++++++++++++++++++ internal/arcgen/lang/go/tests/common.source | 5 +++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/internal/arcgen/lang/go/generate_test.go b/internal/arcgen/lang/go/generate_test.go index 27d05cb..237404a 100644 --- a/internal/arcgen/lang/go/generate_test.go +++ b/internal/arcgen/lang/go/generate_test.go @@ -3,8 +3,11 @@ package arcgengo import ( "context" + "io" + "os" "testing" + "github.com/kunitsucom/util.go/testing/assert" "github.com/kunitsucom/util.go/testing/require" "github.com/kunitsucom/arcgen/internal/config" @@ -28,6 +31,22 @@ func TestGenerate(t *testing.T) { fileSuffix = ".source" require.NoError(t, Generate(ctx, config.Source())) + + { + expectedFile, err := os.Open("tests/common.golden") + require.NoError(t, err) + expectedBytes, err := io.ReadAll(expectedFile) + require.NoError(t, err) + expected := string(expectedBytes) + + actualFile, err := os.Open("tests/common.dbtest.gen.source") + require.NoError(t, err) + actualBytes, err := io.ReadAll(actualFile) + require.NoError(t, err) + actual := string(actualBytes) + + assert.Equal(t, expected, actual) + } }) t.Run("failure,no.errsource", func(t *testing.T) { diff --git a/internal/arcgen/lang/go/tests/common.golden b/internal/arcgen/lang/go/tests/common.golden index dd761b7..3bfc8bd 100644 --- a/internal/arcgen/lang/go/tests/common.golden +++ b/internal/arcgen/lang/go/tests/common.golden @@ -27,3 +27,27 @@ func (s *User) GetColumnName_Email() string { func (s *User) GetColumnName_Age() string { return "Age" } + +func (s *Users) GetTableName() string { + return "`Users`" +} + +func (s *Users) GetColumnNames() []string { + return []string{"Id", "Name", "Email", "Age"} +} + +func (s *Users) GetColumnName_Id() string { + return "Id" +} + +func (s *Users) GetColumnName_Name() string { + return "Name" +} + +func (s *Users) GetColumnName_Email() string { + return "Email" +} + +func (s *Users) GetColumnName_Age() string { + return "Age" +} diff --git a/internal/arcgen/lang/go/tests/common.source b/internal/arcgen/lang/go/tests/common.source index 58295ee..2b68a8f 100644 --- a/internal/arcgen/lang/go/tests/common.source +++ b/internal/arcgen/lang/go/tests/common.source @@ -17,8 +17,9 @@ type ( Ignore string `dbtest:"-"` } - // dbtest: table: `SliceUsers` - SliceUser []*User + // Users is a slice of User. + // + Users []*User // dbtest: table: `InvalidUsers` InvalidUser struct { From a4e320ace9a6f550f1ac94e89dd31f40d195ea18 Mon Sep 17 00:00:00 2001 From: ginokent <29125616+ginokent@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:03:54 +0900 Subject: [PATCH 4/5] fix: Support for multiple structs in one file --- internal/arcgen/lang/go/dump_source.go | 44 +++---- internal/arcgen/lang/go/extract_source.go | 69 ++++++++--- internal/arcgen/lang/go/generate.go | 122 ++++++++++---------- internal/arcgen/lang/go/generate_test.go | 23 ++++ internal/arcgen/lang/go/parse.go | 2 +- internal/arcgen/lang/go/source.go | 13 ++- internal/arcgen/lang/go/tests/.gitignore | 2 +- internal/arcgen/lang/go/tests/common.golden | 22 +--- internal/arcgen/lang/go/tests/common.source | 8 ++ internal/config/config.go | 2 +- 10 files changed, 185 insertions(+), 122 deletions(-) diff --git a/internal/arcgen/lang/go/dump_source.go b/internal/arcgen/lang/go/dump_source.go index f2320e8..2639490 100644 --- a/internal/arcgen/lang/go/dump_source.go +++ b/internal/arcgen/lang/go/dump_source.go @@ -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()) + } } } } diff --git a/internal/arcgen/lang/go/extract_source.go b/internal/arcgen/lang/go/extract_source.go index 9be6434..c0ac853 100644 --- a/internal/arcgen/lang/go/extract_source.go +++ b/internal/arcgen/lang/go/extract_source.go @@ -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 @@ -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) } diff --git a/internal/arcgen/lang/go/generate.go b/internal/arcgen/lang/go/generate.go index c67fe25..ca175a5 100644 --- a/internal/arcgen/lang/go/generate.go +++ b/internal/arcgen/lang/go/generate.go @@ -31,91 +31,85 @@ 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.OpenFile: %w", err) - } - - if err := generate(ctx, f, newFile, arcSrc); err != nil { - return errorz.Errorf("generate: %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) } - } - return nil -} + astFile := &ast.File{ + // package + Name: &ast.Ident{ + Name: arcSrcSet.PackageName, + }, + // methods + Decls: []ast.Decl{}, + } -func generate(_ context.Context, w io.Writer, newFile *token.FileSet, arcSrc *ARCSource) error { - packageName := arcSrc.Package.Name - structName := arcSrc.TypeSpec.Name.Name - tableName := extractTableNameFromCommentGroup(arcSrc.CommentGroup) - columnNames := func() []string { - columnNames := make([]string, 0) - for _, field := range arcSrc.StructType.Fields.List { - if field.Tag != nil { - 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.Position.String(), field.Names, columnName) - // noop - default: - columnNames = append(columnNames, columnName) + for _, arcSrc := range arcSrcSet.ARCSources { + structName := arcSrc.TypeSpec.Name.Name + tableName := extractTableNameFromCommentGroup(arcSrc.CommentGroup) + columnNames := func() []string { + columnNames := make([]string, 0) + for _, field := range arcSrc.StructType.Fields.List { + if field.Tag != nil { + 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) + } + } } - } - } - return columnNames - }() + 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 - content := "" + - "// Code generated by arcgen. DO NOT EDIT." + "\n" + - "//" + "\n" + - fmt.Sprintf("// source: %s:%d", filepathz.Short(arcSrc.Position.Filename), arcSrc.Position.Line) + "\n" + - "\n" + - buf.String() + // 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() - // add blank line between methods - content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ") + // add blank line between methods + content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ") - // write to file - if _, err := io.WriteString(w, content); err != nil { - return errorz.Errorf("io.WriteString: %w", err) + // 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] + } } } + 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{}, - } - +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{ @@ -165,7 +159,7 @@ func generateASTFile(packageName string, structName string, tableName string, pr file.Decls = append(file.Decls, generateASTColumnMethods(structName, prefixGlobal, prefixColumn, columnNames)...) - return file + return } //nolint:funlen diff --git a/internal/arcgen/lang/go/generate_test.go b/internal/arcgen/lang/go/generate_test.go index 237404a..b47f1a8 100644 --- a/internal/arcgen/lang/go/generate_test.go +++ b/internal/arcgen/lang/go/generate_test.go @@ -20,6 +20,8 @@ func TestGenerate(t *testing.T) { ctx := contexts.WithArgs(context.Background(), []string{ "ddlgen", "--column-tag-go=dbtest", + "--method-prefix-global=Get", + // "--src=tests/common.source", "--src=tests", }) @@ -53,6 +55,7 @@ func TestGenerate(t *testing.T) { ctx := contexts.WithArgs(context.Background(), []string{ "ddlgen", "--column-tag-go=dbtest", + "--method-prefix-global=Get", "--src=tests/no.errsource", }) @@ -70,6 +73,25 @@ func TestGenerate(t *testing.T) { ctx := contexts.WithArgs(context.Background(), []string{ "ddlgen", "--column-tag-go=dbtest", + "--method-prefix-global=Get", + "--src=tests", + }) + + backup := fileSuffix + t.Cleanup(func() { fileSuffix = backup }) + + _, err := config.Load(ctx) + require.NoError(t, err) + + fileSuffix = ".errsource" + require.ErrorsContains(t, Generate(ctx, config.Source()), "expected 'package', found 'EOF'") + }) + + t.Run("failure,no-such-file-or-directory", func(t *testing.T) { + ctx := contexts.WithArgs(context.Background(), []string{ + "ddlgen", + "--column-tag-go=dbtest", + "--method-prefix-global=Get", "--src=tests/no-such-file-or-directory", }) @@ -87,6 +109,7 @@ func TestGenerate(t *testing.T) { ctx := contexts.WithArgs(context.Background(), []string{ "ddlgen", "--column-tag-go=dbtest", + "--method-prefix-global=Get", "--src=tests/directory.dir", }) diff --git a/internal/arcgen/lang/go/parse.go b/internal/arcgen/lang/go/parse.go index 74dc158..7f2c0e7 100644 --- a/internal/arcgen/lang/go/parse.go +++ b/internal/arcgen/lang/go/parse.go @@ -72,7 +72,7 @@ func walkDirFn(ctx context.Context, arcSrcSets *ARCSourceSets) func(path string, } } -func parseFile(ctx context.Context, filename string) (ARCSourceSet, error) { +func parseFile(ctx context.Context, filename string) (*ARCSourceSet, error) { fset := token.NewFileSet() rootNode, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { diff --git a/internal/arcgen/lang/go/source.go b/internal/arcgen/lang/go/source.go index 194d6c7..8317ad6 100644 --- a/internal/arcgen/lang/go/source.go +++ b/internal/arcgen/lang/go/source.go @@ -12,16 +12,21 @@ import ( type ( ARCSource struct { - Position token.Position - Package *ast.Ident + Source token.Position // TODO: Unnecessary? + Package *ast.Ident // TypeSpec is used to guess the table name if the CREATE TABLE annotation is not found. TypeSpec *ast.TypeSpec // StructType is used to determine the column name. If the tag specified by --column-tag-go is not found, the field name is used. StructType *ast.StructType CommentGroup *ast.CommentGroup } - ARCSourceSet []*ARCSource - ARCSourceSets []ARCSourceSet + ARCSourceSet struct { + Source token.Position + Filename string + PackageName string + ARCSources []*ARCSource + } + ARCSourceSets []*ARCSourceSet ) //nolint:gochecknoglobals diff --git a/internal/arcgen/lang/go/tests/.gitignore b/internal/arcgen/lang/go/tests/.gitignore index 1c53163..edf5b78 100644 --- a/internal/arcgen/lang/go/tests/.gitignore +++ b/internal/arcgen/lang/go/tests/.gitignore @@ -1 +1 @@ -common.dbtest.gen.source +*.gen.source diff --git a/internal/arcgen/lang/go/tests/common.golden b/internal/arcgen/lang/go/tests/common.golden index 3bfc8bd..b2103d9 100644 --- a/internal/arcgen/lang/go/tests/common.golden +++ b/internal/arcgen/lang/go/tests/common.golden @@ -1,6 +1,6 @@ // Code generated by arcgen. DO NOT EDIT. // -// source: tests/common.source:6 +// source: tests/common.source package main @@ -28,26 +28,14 @@ func (s *User) GetColumnName_Age() string { return "Age" } -func (s *Users) GetTableName() string { - return "`Users`" +func (s *Group) GetColumnNames() []string { + return []string{"Id", "Name"} } -func (s *Users) GetColumnNames() []string { - return []string{"Id", "Name", "Email", "Age"} -} - -func (s *Users) GetColumnName_Id() string { +func (s *Group) GetColumnName_Id() string { return "Id" } -func (s *Users) GetColumnName_Name() string { +func (s *Group) GetColumnName_Name() string { return "Name" } - -func (s *Users) GetColumnName_Email() string { - return "Email" -} - -func (s *Users) GetColumnName_Age() string { - return "Age" -} diff --git a/internal/arcgen/lang/go/tests/common.source b/internal/arcgen/lang/go/tests/common.source index 2b68a8f..69b5d04 100644 --- a/internal/arcgen/lang/go/tests/common.source +++ b/internal/arcgen/lang/go/tests/common.source @@ -19,10 +19,18 @@ type ( // Users is a slice of User. // + // dbtest: table: `Users` Users []*User // dbtest: table: `InvalidUsers` InvalidUser struct { ID string } + + // Group is a group. + // + Group struct { + ID string `dbtest:"Id"` + Name string `dbtest:"Name"` + } ) diff --git a/internal/config/config.go b/internal/config/config.go index c08a135..20f1b0b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -158,7 +158,7 @@ func load(ctx context.Context) (cfg *config, err error) { //nolint:unparam Name: _OptionMethodPrefixGlobal, Environment: _EnvKeyMethodPrefixGlobal, Description: "global method prefix", - Default: cliz.Default("Get"), + Default: cliz.Default(""), }, &cliz.StringOption{ Name: _OptionMethodPrefixColumn, From 3288775a594d8c523d4ef7c68e66847d37940a21 Mon Sep 17 00:00:00 2001 From: ginokent <29125616+ginokent@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:04:26 +0900 Subject: [PATCH 5/5] fix: lint --- internal/arcgen/lang/go/generate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/arcgen/lang/go/generate.go b/internal/arcgen/lang/go/generate.go index ca175a5..5ce41a6 100644 --- a/internal/arcgen/lang/go/generate.go +++ b/internal/arcgen/lang/go/generate.go @@ -21,7 +21,7 @@ import ( "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 { @@ -159,7 +159,7 @@ func appendAST(file *ast.File, structName string, tableName string, prefixGlobal file.Decls = append(file.Decls, generateASTColumnMethods(structName, prefixGlobal, prefixColumn, columnNames)...) - return + return //nolint:gosimple } //nolint:funlen