diff --git a/.gitignore b/.gitignore index f14e057..853fe85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,145 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# Build output g gorm +/bin/ +/build/ +/dist/ + +# IDE and Editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Log files +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node_modules (if using Node.js tools) +node_modules/ + +# Temporary folders +tmp/ +temp/ + +# Go module download cache +$GOPATH/pkg/mod/ + +# Go build cache +$GOCACHE/ + +# Air live reload +tmp/ + +# Local environment variables +.env +.env.local +.env.*.local + +# Database files +*.db +*.sqlite +*.sqlite3 + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +# cgo generated files +_cgo_gotypes.go +_cgo_export.* + +# test generated files +*_test_gen.go + +# Ignore generated documentation +*.html + +# Ignore backup files +*.bak +*.backup +*.orig + +# Ignore profiling files +*.prof +cpu.out +mem.out +profile.out + +# Ignore security sensitive files +*.pem +*.key +*.crt +*.p12 + +# Docker files (if not needed in repo) +# Dockerfile +# docker-compose.yml +# .dockerignore + +# Kubernetes files (if not needed in repo) +# *.yaml +# *.yml + +# Generated code (if applicable) +# *_gen.go +# *.pb.go + +# Config files with sensitive data +config.json +secrets.yaml +secrets.yml diff --git a/examples/models/user.go b/examples/models/user.go index 49bb617..825740a 100644 --- a/examples/models/user.go +++ b/examples/models/user.go @@ -64,3 +64,8 @@ type Language struct { Code string `gorm:"primarykey"` Name string } + +type CreditCard struct { + *gorm.Model + Number string +} diff --git a/examples/output/models/user.go b/examples/output/models/user.go index 7bf2397..ffd9ad4 100644 --- a/examples/output/models/user.go +++ b/examples/output/models/user.go @@ -130,3 +130,17 @@ var Language = struct { Code: field.String{}.WithColumn("code"), Name: field.String{}.WithColumn("name"), } + +var CreditCard = struct { + ID field.Number[uint] + CreatedAt field.Time + UpdatedAt field.Time + DeletedAt field.Field[gorm.DeletedAt] + Number field.String +}{ + ID: field.Number[uint]{}.WithColumn("id"), + CreatedAt: field.Time{}.WithColumn("created_at"), + UpdatedAt: field.Time{}.WithColumn("updated_at"), + DeletedAt: field.Field[gorm.DeletedAt]{}.WithColumn("deleted_at"), + Number: field.String{}.WithColumn("number"), +} diff --git a/examples/output/models_field_helpers_test.go b/examples/output/models_field_helpers_test.go index d223ca0..ffcced4 100644 --- a/examples/output/models_field_helpers_test.go +++ b/examples/output/models_field_helpers_test.go @@ -396,6 +396,13 @@ func TestGeneratedModels_FieldTypes(t *testing.T) { // Language _ field.String = generated.Language.Code _ field.String = generated.Language.Name + + // CreditCard + _ field.Number[uint] = generated.CreditCard.ID + _ field.Time = generated.CreditCard.CreatedAt + _ field.Time = generated.CreditCard.UpdatedAt + _ field.Field[gorm.DeletedAt] = generated.CreditCard.DeletedAt + _ field.String = generated.CreditCard.Number ) } diff --git a/internal/gen/generator.go b/internal/gen/generator.go index b256205..9ed55aa 100644 --- a/internal/gen/generator.go +++ b/internal/gen/generator.go @@ -37,6 +37,7 @@ type ( applicableConfigs []*genconfig.Config inputPath string relPath string + goModDir string Generator *Generator } Import struct { @@ -243,6 +244,7 @@ func (g *Generator) processFile(inputFile, inputRoot string) error { Package: f.Name.Name, inputPath: inputFile, relPath: relPath, + goModDir: findGoModDir(inputFile), Generator: g, } @@ -426,7 +428,7 @@ func (f Field) Type() string { return fmt.Sprintf("field.Number[%s]", goType) } - if typ := loadNamedType(findGoModDir(f.file.inputPath), f.file.getFullImportPath(pkgName), typName); typ != nil { + if typ := loadNamedType(f.file.goModDir, f.file.getFullImportPath(pkgName), typName); typ != nil { if ImplementsAllowedInterfaces(typ) { // For interface-implementing types, use generic Field return fmt.Sprintf("field.Field[%s]", filepath.Base(goType)) } @@ -730,33 +732,48 @@ func (p *File) getFullImportPath(shortName string) string { // handleAnonymousEmbedding processes anonymous embedded fields and returns true if handled func (p *File) handleAnonymousEmbedding(field *ast.Field, pkgName string, s *Struct) bool { - switch t := field.Type.(type) { + // Helper function to add fields from embedded struct + addEmbeddedFields := func(structType *ast.StructType, typeName, embeddedPkgName string) bool { + sub := p.processStructType(&ast.TypeSpec{Name: &ast.Ident{Name: typeName}}, structType, embeddedPkgName) + s.Fields = append(s.Fields, sub.Fields...) + return true + } + + // Helper function to load and process external struct type + loadAndProcessExternalStruct := func(pkgName, typeName string) bool { + st, err := loadNamedStructType(p.goModDir, p.getFullImportPath(pkgName), typeName) + if err != nil || st == nil { + return false + } + return addEmbeddedFields(st, typeName, pkgName) + } + + // Unwrap pointer types to get the underlying type + fieldType := field.Type + if starExpr, ok := fieldType.(*ast.StarExpr); ok { + fieldType = starExpr.X + } + + switch t := fieldType.(type) { case *ast.Ident: - // Local type embedding + // Local type embedding (e.g., BaseStruct or *BaseStruct) if t.Obj != nil { if ts, ok := t.Obj.Decl.(*ast.TypeSpec); ok { if st, ok := ts.Type.(*ast.StructType); ok { - sub := p.processStructType(ts, st, pkgName) - s.Fields = append(s.Fields, sub.Fields...) - return true + return addEmbeddedFields(st, t.Name, pkgName) } } } + case *ast.SelectorExpr: - // External package type embedding + // External package type embedding (e.g., pkg.BaseStruct or *pkg.BaseStruct) if pkgIdent, ok := t.X.(*ast.Ident); ok { - st, err := loadNamedStructType(findGoModDir(p.inputPath), p.getFullImportPath(pkgIdent.Name), t.Sel.Name) - if err == nil && st != nil { - sub := p.processStructType(&ast.TypeSpec{Name: &ast.Ident{Name: t.Sel.Name}}, st, pkgIdent.Name) - s.Fields = append(s.Fields, sub.Fields...) - return true - } + return loadAndProcessExternalStruct(pkgIdent.Name, t.Sel.Name) } + case *ast.StructType: - // Anonymous inline struct embedding - sub := p.processStructType(&ast.TypeSpec{Name: &ast.Ident{Name: "AnonymousStruct"}}, t, pkgName) - s.Fields = append(s.Fields, sub.Fields...) - return true + // Anonymous inline struct embedding (e.g., struct{...}) + return addEmbeddedFields(t, "AnonymousStruct", pkgName) } return false