Skip to content
Open
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
5 changes: 5 additions & 0 deletions internal/gen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,11 @@ func (p *File) processStructType(typeSpec *ast.TypeSpec, data *ast.StructType, p
fieldTag, _ = strconv.Unquote(field.Tag.Value)
}

// GORM maps no column for these, so a helper would reference nothing.
if isIgnoredByGORM(fieldTag) {
continue
}

s.Fields = append(s.Fields, Field{
Name: n.Name,
DBName: generateDBName(n.Name, fieldTag),
Expand Down
80 changes: 80 additions & 0 deletions internal/gen/generator_support_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package gen

import (
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)

// generateFromSources writes files into a throwaway module, runs the generator over it and
// returns the concatenated generated code. Keys are file names relative to the module root.
func generateFromSources(t *testing.T, files map[string]string) string {
t.Helper()

dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/sample\n\ngo 1.21\n"), 0o644); err != nil {
t.Fatalf("write go.mod: %v", err)
}
for name, content := range files {
path := filepath.Join(dir, name)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("create directory for %s: %v", name, err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}

out := filepath.Join(dir, "out")
g := &Generator{Files: map[string]*File{}, outPath: out}
if err := g.Process(dir); err != nil {
t.Fatalf("Process: %v", err)
}
if err := g.Gen(); err != nil {
t.Fatalf("Gen: %v", err)
}
return readGeneratedTree(t, out)
}

// readGeneratedTree concatenates every generated file under dir, the nested packages included.
// readAllGeneratedGoFiles reads the top level only, which is enough for a fixture of one package
// but silently drops the output of one that spans several.
func readGeneratedTree(t *testing.T, dir string) string {
t.Helper()

var b strings.Builder
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".go") {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return err
}
b.WriteString(string(content))
b.WriteString("\n\n")

return nil
})
if err != nil {
t.Fatalf("read generated tree %s: %v", dir, err)
}
if b.Len() == 0 {
t.Fatalf("no .go files under %s", dir)
}

return b.String()
}

// containsField reports whether the generated code declares a helper named name with the given
// type, tolerating the column alignment gofmt applies to struct fields.
func containsField(content, name, typ string) bool {
re := regexp.MustCompile(`(?m)^\s*` + regexp.QuoteMeta(name) + `\s+` + regexp.QuoteMeta(typ) + `\s*$`)
return re.MatchString(content)
}
26 changes: 26 additions & 0 deletions internal/gen/ignored_fields_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gen

import (
"strings"
"testing"
)

func TestIgnoredFieldsGetNoHelpers(t *testing.T) {
content := generateFromSources(t, map[string]string{
"model.go": "package sample\n\ntype Article struct {\n" +
"\tID uint\n" +
"\tDraft string `gorm:\"-\"`\n" +
"\tCache string `gorm:\"-:all\"`\n" +
"\tLegacy string `gorm:\"-:migration\"`\n" +
"}\n",
})

for _, absent := range []string{"Draft", "Cache"} {
if strings.Contains(content, absent) {
t.Errorf("field %s is ignored by GORM and must not get a helper, got:\n%s", absent, content)
}
}
if !containsField(content, "Legacy", "field.String") {
t.Errorf("field Legacy only skips migrations and must keep its helper, got:\n%s", content)
}
}
12 changes: 12 additions & 0 deletions internal/gen/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ func loadNamedStructType(modRoot, pkgPath, name string) (*ast.StructType, error)
return nil, fmt.Errorf("struct %s not found in package %s", name, pkgPath)
}

// isIgnoredByGORM reports whether the gorm tag removes the field from the schema. "-" and
// "-:all" map no column; "-:migration" only skips migrations and still maps one.
func isIgnoredByGORM(fieldTag string) bool {
settings := schema.ParseTagSetting(reflect.StructTag(fieldTag).Get("gorm"), ";")
v, ok := settings["-"]
if !ok {
return false
}
v = strings.ToLower(strings.TrimSpace(v))
return v == "-" || v == "all"
}

// generateDBName generates database column name using GORM's NamingStrategy and COLUMN tag.
func generateDBName(fieldName, gormTag string) string {
tagSettings := schema.ParseTagSetting(reflect.StructTag(gormTag).Get("gorm"), ";")
Expand Down
Loading