Skip to content
143 changes: 143 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NitPick]

Duplicate tmp/ entry on lines 68 and 78. The Air live reload entry is redundant since temporary folders are already ignored above.

Context for Agents
[**NitPick**]

Duplicate `tmp/` entry on lines 68 and 78. The Air live reload entry is redundant since temporary folders are already ignored above.

File: .gitignore
Line: 78


# 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
5 changes: 5 additions & 0 deletions examples/models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ type Language struct {
Code string `gorm:"primarykey"`
Name string
}

type CreditCard struct {
*gorm.Model
Number string
}
14 changes: 14 additions & 0 deletions examples/output/models/user.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions examples/output/models_field_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

Expand Down
51 changes: 34 additions & 17 deletions internal/gen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type (
applicableConfigs []*genconfig.Config
inputPath string
relPath string
goModDir string // 缓存的 go mod 目录路径
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment contains Chinese characters. Please use English for consistency: // Cached go mod directory path

Suggested change
goModDir string // 缓存的 go mod 目录路径
goModDir string // Cached go mod directory path

Copilot uses AI. Check for mistakes.
Generator *Generator
}
Import struct {
Expand Down Expand Up @@ -243,6 +244,7 @@ func (g *Generator) processFile(inputFile, inputRoot string) error {
Package: f.Name.Name,
inputPath: inputFile,
relPath: relPath,
goModDir: findGoModDir(inputFile), // 初始化时缓存 go mod 目录
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment contains Chinese characters. Please use English for consistency: // Initialize and cache go mod directory

Suggested change
goModDir: findGoModDir(inputFile), // 初始化时缓存 go mod 目录
goModDir: findGoModDir(inputFile), // Initialize and cache go mod directory

Copilot uses AI. Check for mistakes.
Generator: g,
}

Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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
}
Comment on lines +735 to +740
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addEmbeddedFields helper function always returns true, making the return value meaningless. Consider either removing the return value or implementing proper error handling that could return false in failure cases.

Copilot uses AI. Check for mistakes.

// 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)
}
Comment on lines +742 to +749
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error from loadNamedStructType is silently ignored. Consider logging the error or propagating it to help with debugging when struct loading fails.

Copilot uses AI. Check for mistakes.

// 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
Expand Down
Loading