Name every render start, and let no backticked spelling go unchecked

This commit is contained in:
2026-09-21 10:22:42 +02:00
committed by Lillem4n
parent f569580077
commit aed74498e6
3 changed files with 51 additions and 41 deletions
+1 -1
View File
@@ -1309,7 +1309,7 @@ the Development section below, and who ships a register the four above then draw
## Layout ## Layout
``` ```
doc.go the package doc, and the vocabulary the rest of it uses for a render and its fences doc.go the package doc, and the vocabulary the package is written in
fejkdata.go Generator, New, options, the embedded data set, List fejkdata.go Generator, New, options, the embedded data set, List
node.go the node model and JSON -> node compilation node.go the node model and JSON -> node compilation
table.go tables: the rows TSV, its options and links, row selection and draws table.go tables: the rows TSV, its options and links, row selection and draws
+11 -8
View File
@@ -19,9 +19,11 @@
// the pick produced: `pick` and `drawn` settle a choice, `table.draw` a row, and // the pick produced: `pick` and `drawn` settle a choice, `table.draw` a row, and
// `draw` carries what a read produced. // `draw` carries what a read produced.
// - expansion — one render of one format, by `expand`. A nested template is an // - expansion — one render of one format, by `expand`. A nested template is an
// expansion of its own, and so is each iteration of a `template.repeat`. // expansion of its own.
// - render — one `Fake` call or one record, started by `renderOnce` where nothing // - render — what owns one `drawSet`, and so what one reference draw spans:
// outer started it. It owns one `drawSet`, so it is what a reference draw spans. // `Generator.Fake`, `FakeRecord`, `FakeStruct`, `Template.Fake` and
// `RecordTemplate.Fake` each start one, as does `expandAnew` per iteration of a
// `template.repeat` — an iteration is an expansion and a render of its own.
// - hold — keeping one draw of a name, so every route to it reads that value. // - hold — keeping one draw of a name, so every route to it reads that value.
// `template.held` lists what an expansion holds, `draws` keeps them and // `template.held` lists what an expansion holds, `draws` keeps them and
// `readField` reads through it; a reference path is held for the whole render. // `readField` reads through it; a reference path is held for the whole render.
@@ -32,10 +34,11 @@
// rows of its ancestors, and `draws.rowOf` draws one where none is pinned. // rows of its ancestors, and `draws.rowOf` draws one where none is pinned.
// - family — a table and every table reaching it through a chain of // - family — a table and every table reaching it through a chain of
// `table.parentT`, named by the root that chain ends at: `table.family`. // `table.parentT`, named by the root that chain ends at: `table.family`.
// - whole — a read that lands on a `row`, so the table's format renders every // - whole — a read that lands on a `row`, so the row renders through its table's
// column of it: `tableRead.whole`. // format: `tableRead.whole` says a read did, `table.whole` is the node.
// - fence — a load-time check refusing data that two routes to one draw would // - fence — a load-time check refusing data that two routes to one draw would
// disagree in. Every one runs in `New`, which is why `Fake` cannot fail on // disagree in, which is why a render cannot fail: `heldCheck` over an
// loaded data: `heldCheck` over an expansion's held names, `drawCheck` over a // expansion's held names, `drawCheck` over a render's reference paths, and
// render's reference paths, and `checkFamilies` over the rows they pin. // `checkFamilies` over the rows they pin. `New` runs them over the data set,
// `NewTemplate` and `FakeStruct` over what those compile.
package fejkdata package fejkdata
+39 -32
View File
@@ -2,18 +2,20 @@ package fejkdata
import ( import (
"go/ast" "go/ast"
"go/build"
"go/parser" "go/parser"
"go/token" "go/token"
"os"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
) )
var backticked = regexp.MustCompile("`([A-Za-z][A-Za-z0-9]*(?:\\.[A-Za-z][A-Za-z0-9]*)?)`") // Every backtick in the Vocabulary is a symbol, so an unrecognised spelling fails
// rather than going unchecked.
var backticked = regexp.MustCompile("`([^`]+)`")
// TestVocabularyNamesDeclaredSymbols proves every symbol the package doc's Vocabulary // TestVocabularyNamesDeclaredSymbols proves the package doc's Vocabulary names
// names is declared here, so a renamed unit renames its entry with it. // symbols this package declares, so a renamed unit renames its entry with it.
func TestVocabularyNamesDeclaredSymbols(t *testing.T) { func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
files := sourceFiles(t) files := sourceFiles(t)
declared := declaredSymbols(files) declared := declaredSymbols(files)
@@ -24,21 +26,15 @@ func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
} }
} }
// sourceFiles parses the package's own files, the tests left out: the Vocabulary
// indexes what ships, not what proves it.
func sourceFiles(t *testing.T) []*ast.File { func sourceFiles(t *testing.T) []*ast.File {
t.Helper() t.Helper()
entries, err := os.ReadDir(".") pkg, err := build.ImportDir(".", 0)
if err != nil { if err != nil {
t.Fatalf("read the package directory: %v", err) t.Fatalf("read the package directory: %v", err)
} }
fset := token.NewFileSet() fset := token.NewFileSet()
var files []*ast.File var files []*ast.File
for _, e := range entries { for _, name := range pkg.GoFiles {
name := e.Name()
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments) f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil { if err != nil {
t.Fatalf("parse %s: %v", name, err) t.Fatalf("parse %s: %v", name, err)
@@ -48,7 +44,6 @@ func sourceFiles(t *testing.T) []*ast.File {
return files return files
} }
// vocabulary is the package doc from its Vocabulary heading on.
func vocabulary(t *testing.T, files []*ast.File) string { func vocabulary(t *testing.T, files []*ast.File) string {
t.Helper() t.Helper()
for _, f := range files { for _, f := range files {
@@ -63,45 +58,57 @@ func vocabulary(t *testing.T, files []*ast.File) string {
return "" return ""
} }
// declaredSymbols is every name the package declares, a method and a struct field // declaredSymbols is every name the package declares at the top level, a method and
// under its type as well: fence, draws.pin, tableRead.whole. // a struct field keyed under its type: draws.pin, tableRead.whole. A name declared
// inside a function body is not one a Vocabulary entry may name.
func declaredSymbols(files []*ast.File) map[string]bool { func declaredSymbols(files []*ast.File) map[string]bool {
names := map[string]bool{} names := map[string]bool{}
for _, f := range files { for _, f := range files {
ast.Inspect(f, func(n ast.Node) bool { for _, decl := range f.Decls {
switch d := n.(type) { switch d := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
names[d.Name.Name] = true names[d.Name.Name] = true
if d.Recv != nil { if recv := receiverType(d.Recv); recv != "" {
names[receiverType(d.Recv)+"."+d.Name.Name] = true names[recv+"."+d.Name.Name] = true
}
case *ast.TypeSpec:
names[d.Name.Name] = true
addFields(names, d)
case *ast.ValueSpec:
for _, id := range d.Names {
names[id.Name] = true
} }
case *ast.GenDecl:
addSpecs(names, d)
} }
return true }
})
} }
return names return names
} }
func addFields(names map[string]bool, d *ast.TypeSpec) { func addSpecs(names map[string]bool, d *ast.GenDecl) {
s, isStruct := d.Type.(*ast.StructType) for _, spec := range d.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
names[s.Name.Name] = true
addFields(names, s)
case *ast.ValueSpec:
for _, id := range s.Names {
names[id.Name] = true
}
}
}
}
func addFields(names map[string]bool, s *ast.TypeSpec) {
st, isStruct := s.Type.(*ast.StructType)
if !isStruct { if !isStruct {
return return
} }
for _, field := range s.Fields.List { for _, field := range st.Fields.List {
for _, id := range field.Names { for _, id := range field.Names {
names[d.Name.Name+"."+id.Name] = true names[s.Name.Name+"."+id.Name] = true
} }
} }
} }
func receiverType(recv *ast.FieldList) string { func receiverType(recv *ast.FieldList) string {
if recv == nil || len(recv.List) == 0 {
return ""
}
e := recv.List[0].Type e := recv.List[0].Type
if star, isPointer := e.(*ast.StarExpr); isPointer { if star, isPointer := e.(*ast.StarExpr); isPointer {
e = star.X e = star.X