From e522f293c97a35d11c437c878fb756e43a062570 Mon Sep 17 00:00:00 2001 From: gogacoder Date: Sun, 16 Feb 2025 14:35:50 +0700 Subject: [PATCH 1/2] fix: malformed code pasted if reimplement is true solution: replace go/ast with dst --- cmd/crudgen/main.go | 1 + go.mod | 11 ++- go.sum | 20 +++++- internal/parser.go | 31 ++++++--- internal/writer.go | 160 ++++++++++++++++++++++---------------------- 5 files changed, 128 insertions(+), 95 deletions(-) diff --git a/cmd/crudgen/main.go b/cmd/crudgen/main.go index 73349f9..e5e6681 100644 --- a/cmd/crudgen/main.go +++ b/cmd/crudgen/main.go @@ -25,6 +25,7 @@ func ImplementServices(mainPkgDir string, reimplement bool) { } func main() { + log.SetFlags(0) projectPath := flag.String("p", ".", "project path") reimplement := flag.Bool("f", false, "pass -f to allow tool to overwrite exist functions and service structure") flag.Parse() diff --git a/go.mod b/go.mod index 76a9623..0deb250 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,12 @@ module git.gogacoder.ru/NTO/crudgen -go 1.23 +go 1.22.12 -require golang.org/x/tools v0.28.0 +require github.com/dave/dst v0.27.3 + +require ( + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect +) diff --git a/go.sum b/go.sum index b2b1080..705b82f 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,18 @@ -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= +github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= +github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg= +github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= diff --git a/internal/parser.go b/internal/parser.go index 169de1c..eda28a3 100644 --- a/internal/parser.go +++ b/internal/parser.go @@ -1,17 +1,21 @@ package internal import ( - "go/ast" "go/parser" "go/token" "os" "path/filepath" "strings" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" ) +// GetStructNames reads all .go files from modelsDir and returns the names +// of all top-level struct types found, using the DST (dave/dst) library. func GetStructNames(modelsDir string) ([]string, error) { var structs []string - fset := token.NewFileSet() + fileSet := token.NewFileSet() files, err := os.ReadDir(modelsDir) if err != nil { @@ -24,32 +28,37 @@ func GetStructNames(modelsDir string) ([]string, error) { } filePath := filepath.Join(modelsDir, file.Name()) - fileAst, err := parser.ParseFile(fset, filePath, nil, parser.AllErrors) + // Using decorator.ParseFile from the DST library instead of parser.ParseFile + fileDst, err := decorator.ParseFile(fileSet, filePath, nil, parser.AllErrors) if err != nil { return nil, err } - for _, decl := range fileAst.Decls { - genDecl, ok := decl.(*ast.GenDecl) + // Traverse the DST to find struct types + for _, decl := range fileDst.Decls { + genDecl, ok := decl.(*dst.GenDecl) if !ok { continue } + // We only care about type declarations if genDecl.Tok != token.TYPE { continue } + + // Iterate over all type specs in the GenDecl for _, spec := range genDecl.Specs { - typeSpec, ok := spec.(*ast.TypeSpec) + typeSpec, ok := spec.(*dst.TypeSpec) if !ok { continue } - if _, ok := typeSpec.Type.(*ast.StructType); ok { - if typeSpec.Name != nil { - structs = append(structs, typeSpec.Name.Name) - } + + // Check whether the type is a StructType + if _, ok := typeSpec.Type.(*dst.StructType); ok && typeSpec.Name != nil { + structs = append(structs, typeSpec.Name.Name) } } } } - return structs, nil + return structs, nil } diff --git a/internal/writer.go b/internal/writer.go index fab0515..80eebdf 100644 --- a/internal/writer.go +++ b/internal/writer.go @@ -4,9 +4,7 @@ import ( "bytes" "errors" "fmt" - "go/ast" "go/parser" - "go/printer" "go/token" "html/template" "log" @@ -14,17 +12,18 @@ import ( "path/filepath" "strings" - "golang.org/x/tools/go/ast/astutil" + "github.com/dave/dst" + "github.com/dave/dst/decorator" ) -func ImplementServiceStruct(modelName string, file *ast.File, reimplement bool) { +func ImplementServiceStruct(modelName string, file *dst.File, reimplement bool) { serviceName := modelName + "Service" isServiceStructDefined := false var insertPos int - var decls []ast.Decl + var decls []dst.Decl for i, decl := range file.Decls { - genDecl, ok := decl.(*ast.GenDecl) + genDecl, ok := decl.(*dst.GenDecl) if !ok { continue } @@ -38,15 +37,15 @@ func ImplementServiceStruct(modelName string, file *ast.File, reimplement bool) } for _, spec := range genDecl.Specs { - typeSpec, ok := spec.(*ast.TypeSpec) + typeSpec, ok := spec.(*dst.TypeSpec) if !ok { continue } - if _, ok := typeSpec.Type.(*ast.StructType); ok { + if _, ok := typeSpec.Type.(*dst.StructType); ok { if typeSpec.Name != nil && typeSpec.Name.Name == serviceName { isServiceStructDefined = true if reimplement { - decls = decls[:1] + decls = decls[:len(decls)-1] } } } @@ -57,29 +56,33 @@ func ImplementServiceStruct(modelName string, file *ast.File, reimplement bool) return } - serviceStruct := &ast.GenDecl{ + serviceStruct := &dst.GenDecl{ Tok: token.TYPE, - Specs: []ast.Spec{ - &ast.TypeSpec{ - Name: ast.NewIdent(serviceName), - Type: &ast.StructType{ - Fields: &ast.FieldList{}, + Specs: []dst.Spec{ + &dst.TypeSpec{ + Name: dst.NewIdent(serviceName), + Type: &dst.StructType{ + Fields: &dst.FieldList{}, }, }, }, } - file.Decls = append(decls[:insertPos], append([]ast.Decl{serviceStruct}, decls[insertPos:]...)...) + file.Decls = append(decls[:insertPos], append([]dst.Decl{serviceStruct}, decls[insertPos:]...)...) + + if err := ReloadAst(file); err != nil { + log.Fatalf(err.Error()) + } } -func ImplementModelAlias(modelName string, file *ast.File) { +func ImplementModelAlias(modelName string, file *dst.File) { isAliasDefined := false aliasTypeStandard := fmt.Sprintf("models.%s", CapitalizeFirst(modelName)) var insertPos int - var decls []ast.Decl + var decls []dst.Decl for i, decl := range file.Decls { - genDecl, ok := decl.(*ast.GenDecl) + genDecl, ok := decl.(*dst.GenDecl) if !ok { continue } @@ -96,19 +99,19 @@ func ImplementModelAlias(modelName string, file *ast.File) { continue } - if typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec); ok { + if typeSpec, ok := genDecl.Specs[0].(*dst.TypeSpec); ok { if typeSpec.Name != nil && typeSpec.Name.Name == modelName { - if linkedType, ok := typeSpec.Type.(*ast.SelectorExpr); ok { - pkg, ok := linkedType.X.(*ast.Ident) + if linkedType, ok := typeSpec.Type.(*dst.SelectorExpr); ok { + pkg, ok := linkedType.X.(*dst.Ident) if !ok { log.Printf("Defined alias `%s` with unknown type", typeSpec.Name) - decls = decls[:1] + decls = decls[:len(decls)-1] continue } if linkedType.Sel == nil { log.Printf("Defined alias `%s` with unknown type", typeSpec.Name) - decls = decls[:1] + decls = decls[:len(decls)-1] continue } @@ -118,7 +121,7 @@ func ImplementModelAlias(modelName string, file *ast.File) { typeSpec.Name, pkg, ) - decls = decls[:1] + decls = decls[:len(decls)-1] continue } @@ -129,27 +132,27 @@ func ImplementModelAlias(modelName string, file *ast.File) { linkedType.Sel.Name, aliasTypeStandard, ) - decls = decls[:1] + decls = decls[:len(decls)-1] continue } isAliasDefined = true } else { log.Printf("Defined alias %s with unknown type", typeSpec.Name) - decls = decls[:1] + decls = decls[:len(decls)-1] } } } } - typeAlias := ast.GenDecl{ + typeAlias := dst.GenDecl{ Tok: token.TYPE, - Specs: []ast.Spec{ - &ast.TypeSpec{ - Name: &ast.Ident{ + Specs: []dst.Spec{ + &dst.TypeSpec{ + Name: &dst.Ident{ Name: modelName, }, - Assign: 1, // quick and dirty - Type: &ast.Ident{ + Assign: true, // quick and dirty + Type: &dst.Ident{ Name: aliasTypeStandard, }, }, @@ -157,28 +160,31 @@ func ImplementModelAlias(modelName string, file *ast.File) { } if !isAliasDefined { - file.Decls = append(decls[:insertPos], append([]ast.Decl{&typeAlias}, decls[insertPos:]...)...) + file.Decls = append(decls[:insertPos], append([]dst.Decl{&typeAlias}, decls[insertPos:]...)...) + } + if err := ReloadAst(file); err != nil { + log.Fatalf(err.Error()) } } -func importExists(fset *token.FileSet, file *ast.File, importPath string) bool { - for _, group := range astutil.Imports(fset, file) { - for _, imp := range group { - if imp.Name == nil && imp.Path.Value == `"`+importPath+`"` { - return true - } +func importExists(file *dst.File, importPath string) bool { + for _, imp := range file.Imports { + if imp.Path.Value == `"`+importPath+`"` { + return true } } return false } -func MaintainImports(fileSet *token.FileSet, file *ast.File) error { +func MaintainImports(file *dst.File) error { for _, importPath := range ServiceImports { - if !importExists(fileSet, file, importPath) { - if !astutil.AddImport(fileSet, file, importPath) { - err := fmt.Sprintf("%s: Failed to add import: %s", fileSet.Position(file.Pos()), importPath) - return errors.New(err) - } + if !importExists(file, importPath) { + file.Imports = append(file.Imports, &dst.ImportSpec{ + Path: &dst.BasicLit{ + Kind: token.STRING, + Value: `"` + importPath + `"`, + }, + }) } } return nil @@ -201,27 +207,27 @@ func GenerateCrudMethodCode(methodName string, context CrudTemplatesContext) str return buffer.String() } -func MethodCodeToDeclaration(methodCode string) (ast.FuncDecl, error) { +func MethodCodeToDeclaration(methodCode string) (*dst.FuncDecl, error) { fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "src.go", methodCode, parser.SkipObjectResolution) + file, err := decorator.ParseFile(fset, "src.go", methodCode, parser.AllErrors) if err != nil { - return ast.FuncDecl{}, err + return nil, err } - methodDecl := ast.FuncDecl{} - ast.Inspect(file, func(node ast.Node) bool { - funcDecl, ok := node.(*ast.FuncDecl) + var methodDecl *dst.FuncDecl + dst.Inspect(file, func(node dst.Node) bool { + funcDecl, ok := node.(*dst.FuncDecl) if !ok { return true } - methodDecl = *funcDecl + methodDecl = funcDecl return false }) return methodDecl, nil } -func ImplementCrudMethods(modelName string, serviceName string, file *ast.File, reimplement bool) error { +func ImplementCrudMethods(modelName string, serviceName string, file *dst.File, reimplement bool) error { templateContext := CrudTemplatesContext{ ServiceName: serviceName, EntityType: modelName, @@ -231,21 +237,29 @@ func ImplementCrudMethods(modelName string, serviceName string, file *ast.File, for _, methodName := range []string{CreateMethod, GetAllMethod, GetByIdMethod, UpdateMethod, DeleteMethod, CountMethod} { methodCode := GenerateCrudMethodCode(methodName, templateContext) methodDecl, err := MethodCodeToDeclaration(methodCode) + fmt.Printf("%s\n", methodCode) if err != nil { fmt.Println(methodDecl) panic(err) } - err = ImplementMethod(file, &methodDecl, reimplement) + err = ImplementMethod(file, methodDecl, reimplement) if err != nil { return err } + if err := ReloadAst(file); err != nil { + return err + } } return nil } -func ImplementMethod(file *ast.File, methodDecl *ast.FuncDecl, reimplement bool) error { - var decls []ast.Decl +func ReloadAst(_ *dst.File) error { + return nil +} + +func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) error { + var decls []dst.Decl methodImplemented := false methodStructure := methodDecl.Recv.List[0].Names[0].Name @@ -255,7 +269,7 @@ func ImplementMethod(file *ast.File, methodDecl *ast.FuncDecl, reimplement bool) for _, decl := range file.Decls { decls = append(decls, decl) - funcDecl, ok := decl.(*ast.FuncDecl) + funcDecl, ok := decl.(*dst.FuncDecl) if !ok { continue } @@ -272,7 +286,7 @@ func ImplementMethod(file *ast.File, methodDecl *ast.FuncDecl, reimplement bool) methodImplemented = true } if reimplement { - decls = decls[:1] + decls = decls[:len(decls)-1] } } } @@ -282,35 +296,21 @@ func ImplementMethod(file *ast.File, methodDecl *ast.FuncDecl, reimplement bool) if reimplement || !methodImplemented { file.Decls = append( decls, - &ast.FuncDecl{ - Doc: methodDecl.Doc, + &dst.FuncDecl{ Recv: methodDecl.Recv, Name: methodDecl.Name, Type: methodDecl.Type, - Body: &ast.BlockStmt{ - Lbrace: token.NoPos, - List: methodDecl.Body.List, - Rbrace: token.NoPos, + Body: &dst.BlockStmt{ + List: methodDecl.Body.List, }, }, ) } - // Reload AST: fix issues with syntax 'hallucinations' - // Write AST - var buf bytes.Buffer - if err := printer.Fprint(&buf, token.NewFileSet(), file); err != nil { + if err := ReloadAst(file); err != nil { return err } - // Load AST - formattedFile, err := parser.ParseFile(token.NewFileSet(), "", buf.String(), parser.ParseComments) - if err != nil { - return err - } - - *file = *formattedFile - return nil } @@ -345,13 +345,13 @@ func ImplementService(mainPkgPath string, modelName string, reimplement bool) er } fset := token.NewFileSet() - serviceFile, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + serviceFile, err := decorator.ParseFile(fset, filePath, nil, parser.ParseComments) if err != nil { log.Fatalf("Parsing error: %s: %v", mainPkgPath, err) return err } - err = MaintainImports(fset, serviceFile) + err = MaintainImports(serviceFile) if err != nil { return err } @@ -369,7 +369,7 @@ func ImplementService(mainPkgPath string, modelName string, reimplement bool) er return errors.New(fmt.Sprintf("Error occured to open `%s` service file: %v", modelName, err)) } - err = printer.Fprint(file, fset, serviceFile) + err = decorator.Fprint(file, serviceFile) if err != nil { return errors.New( fmt.Sprintf("Error occurred to writing changes in `%s` service file: %v", modelName, err), From 88225975c7ac6079abfe4b161ef508696e7f4071 Mon Sep 17 00:00:00 2001 From: gogacoder Date: Sun, 16 Feb 2025 14:40:35 +0700 Subject: [PATCH 2/2] refactor: delete unused code --- .gitignore | 3 ++- internal/writer.go | 18 ------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index bf72c8e..cbfc419 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -cmd/crudgen/main \ No newline at end of file +cmd/crudgen/main +cmd/crudgen/crudgen \ No newline at end of file diff --git a/internal/writer.go b/internal/writer.go index 80eebdf..fcded7b 100644 --- a/internal/writer.go +++ b/internal/writer.go @@ -69,10 +69,6 @@ func ImplementServiceStruct(modelName string, file *dst.File, reimplement bool) } file.Decls = append(decls[:insertPos], append([]dst.Decl{serviceStruct}, decls[insertPos:]...)...) - - if err := ReloadAst(file); err != nil { - log.Fatalf(err.Error()) - } } func ImplementModelAlias(modelName string, file *dst.File) { @@ -162,9 +158,6 @@ func ImplementModelAlias(modelName string, file *dst.File) { if !isAliasDefined { file.Decls = append(decls[:insertPos], append([]dst.Decl{&typeAlias}, decls[insertPos:]...)...) } - if err := ReloadAst(file); err != nil { - log.Fatalf(err.Error()) - } } func importExists(file *dst.File, importPath string) bool { @@ -246,18 +239,11 @@ func ImplementCrudMethods(modelName string, serviceName string, file *dst.File, if err != nil { return err } - if err := ReloadAst(file); err != nil { - return err - } } return nil } -func ReloadAst(_ *dst.File) error { - return nil -} - func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) error { var decls []dst.Decl methodImplemented := false @@ -307,10 +293,6 @@ func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) ) } - if err := ReloadAst(file); err != nil { - return err - } - return nil }