Require a fill header to name a function, not just a symbol

This commit is contained in:
2026-09-21 11:36:01 +02:00
committed by Lillem4n
parent 67ff2b258e
commit 8f74968371
+43 -22
View File
@@ -6,6 +6,7 @@ import (
"go/parser" "go/parser"
"go/token" "go/token"
"regexp" "regexp"
"slices"
"strings" "strings"
"testing" "testing"
) )
@@ -17,22 +18,33 @@ var backticked = regexp.MustCompile("`([^`\n]+)`")
func TestVocabularyNamesDeclaredSymbols(t *testing.T) { func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
_, files := sourceFiles(t) _, files := sourceFiles(t)
declared := declaredSymbols(files) declared := declaredSymbols(files)
if namesDeclared(t, "the Vocabulary", vocabulary(t, files), declared) == 0 { if len(namesDeclared(t, "the Vocabulary", vocabulary(t, files), declared)) == 0 {
t.Fatal("the Vocabulary names no symbol, so nothing holds a renamed unit to its entry") t.Fatal("the Vocabulary names no symbol, so nothing holds a renamed unit to its entry")
} }
} }
// namesDeclared reports how many symbols text backticks, failing for each one the // namesDeclared is every symbol text backticks, failing for each one the package
// package declares nowhere. // declares nowhere.
func namesDeclared(t *testing.T, where, text string, declared map[string]bool) int { func namesDeclared(t *testing.T, where, text string, declared map[string]bool) []string {
t.Helper() t.Helper()
named := backticked.FindAllStringSubmatch(text, -1) var named []string
for _, m := range named { for _, m := range backticked.FindAllStringSubmatch(text, -1) {
if !declared[m[1]] { if !declared[m[1]] {
t.Errorf("%s names %s, which the package declares nowhere", where, m[1]) t.Errorf("%s names %s, which the package declares nowhere", where, m[1])
} }
named = append(named, m[1])
}
return named
}
// namesAPass fails unless one symbol text backticks is a function: a field names
// what a pass reads or writes, never the pass.
func namesAPass(t *testing.T, where, text string, declared, funcs map[string]bool) {
t.Helper()
named := namesDeclared(t, where, text, declared)
if !slices.ContainsFunc(named, func(n string) bool { return funcs[n] }) {
t.Errorf("%s names no function, so it names no pass", where)
} }
return len(named)
} }
func sourceFiles(t *testing.T) (*token.FileSet, []*ast.File) { func sourceFiles(t *testing.T) (*token.FileSet, []*ast.File) {
@@ -73,17 +85,30 @@ func vocabulary(t *testing.T, files []*ast.File) string {
// declaredSymbols is every name the package declares at the top level, a method and // declaredSymbols is every name the package declares at the top level, a method and
// a struct field keyed under its type: draws.pin, tableRead.whole. // a struct field keyed under its type: draws.pin, tableRead.whole.
func declaredSymbols(files []*ast.File) map[string]bool { func declaredSymbols(files []*ast.File) map[string]bool {
names := declaredFuncs(files)
for _, f := range files {
for _, decl := range f.Decls {
if d, isGen := decl.(*ast.GenDecl); isGen {
addSpecs(names, d)
}
}
}
return names
}
// declaredFuncs is every function the package declares, a method keyed under its
// type: template.compileFormat.
func declaredFuncs(files []*ast.File) map[string]bool {
names := map[string]bool{} names := map[string]bool{}
for _, f := range files { for _, f := range files {
for _, decl := range f.Decls { for _, decl := range f.Decls {
switch d := decl.(type) { d, isFunc := decl.(*ast.FuncDecl)
case *ast.FuncDecl: if !isFunc {
names[d.Name.Name] = true continue
if recv := receiverType(d.Recv); recv != "" { }
names[recv+"."+d.Name.Name] = true names[d.Name.Name] = true
} if recv := receiverType(d.Recv); recv != "" {
case *ast.GenDecl: names[recv+"."+d.Name.Name] = true
addSpecs(names, d)
} }
} }
} }
@@ -137,11 +162,9 @@ const fillHeader = "Filled by "
// fills, and to the template doc. // fills, and to the template doc.
func TestTemplateFieldGroupsNameADeclaredPass(t *testing.T) { func TestTemplateFieldGroupsNameADeclaredPass(t *testing.T) {
fset, files := sourceFiles(t) fset, files := sourceFiles(t)
declared := declaredSymbols(files) declared, funcs := declaredSymbols(files), declaredFuncs(files)
doc, st, headers := templateStruct(t, fset, files) doc, st, headers := templateStruct(t, fset, files)
if namesDeclared(t, "the template doc", doc, declared) == 0 { namesAPass(t, "the template doc", doc, declared, funcs)
t.Error("the template doc names no pass, so nothing says when a field is final")
}
for _, field := range st.Fields.List { for _, field := range st.Fields.List {
if len(field.Names) == 0 { if len(field.Names) == 0 {
t.Error("an embedded field of template sits under no header, so nothing says which pass fills it") t.Error("an embedded field of template sits under no header, so nothing says which pass fills it")
@@ -152,9 +175,7 @@ func TestTemplateFieldGroupsNameADeclaredPass(t *testing.T) {
t.Errorf("template.%s sits under no %q header, so nothing says which pass fills it", id.Name, fillHeader) t.Errorf("template.%s sits under no %q header, so nothing says which pass fills it", id.Name, fillHeader)
continue continue
} }
if namesDeclared(t, "the header above template."+id.Name, header, declared) == 0 { namesAPass(t, "the header above template."+id.Name, header, declared, funcs)
t.Errorf("the header above template.%s names no symbol, so it names no pass", id.Name)
}
} }
} }
} }