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
```
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
node.go the node model and JSON -> node compilation
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
// `draw` carries what a read produced.
// - 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`.
// - render — one `Fake` call or one record, started by `renderOnce` where nothing
// outer started it. It owns one `drawSet`, so it is what a reference draw spans.
// expansion of its own.
// - render — what owns one `drawSet`, and so what one 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.
// `template.held` lists what an expansion holds, `draws` keeps them and
// `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.
// - family — a table and every table reaching it through a chain of
// `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
// column of it: `tableRead.whole`.
// - whole — a read that lands on a `row`, so the row renders through its table's
// 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
// disagree in. Every one runs in `New`, which is why `Fake` cannot fail on
// loaded data: `heldCheck` over an expansion's held names, `drawCheck` over a
// render's reference paths, and `checkFamilies` over the rows they pin.
// disagree in, which is why a render cannot fail: `heldCheck` over an
// expansion's held names, `drawCheck` over a render's reference paths, and
// `checkFamilies` over the rows they pin. `New` runs them over the data set,
// `NewTemplate` and `FakeStruct` over what those compile.
package fejkdata
+39 -32
View File
@@ -2,18 +2,20 @@ package fejkdata
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"regexp"
"strings"
"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
// names is declared here, so a renamed unit renames its entry with it.
// TestVocabularyNamesDeclaredSymbols proves the package doc's Vocabulary names
// symbols this package declares, so a renamed unit renames its entry with it.
func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
files := sourceFiles(t)
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 {
t.Helper()
entries, err := os.ReadDir(".")
pkg, err := build.ImportDir(".", 0)
if err != nil {
t.Fatalf("read the package directory: %v", err)
}
fset := token.NewFileSet()
var files []*ast.File
for _, e := range entries {
name := e.Name()
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
for _, name := range pkg.GoFiles {
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil {
t.Fatalf("parse %s: %v", name, err)
@@ -48,7 +44,6 @@ func sourceFiles(t *testing.T) []*ast.File {
return files
}
// vocabulary is the package doc from its Vocabulary heading on.
func vocabulary(t *testing.T, files []*ast.File) string {
t.Helper()
for _, f := range files {
@@ -63,45 +58,57 @@ func vocabulary(t *testing.T, files []*ast.File) string {
return ""
}
// declaredSymbols is every name the package declares, a method and a struct field
// under its type as well: fence, draws.pin, tableRead.whole.
// 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 name declared
// inside a function body is not one a Vocabulary entry may name.
func declaredSymbols(files []*ast.File) map[string]bool {
names := map[string]bool{}
for _, f := range files {
ast.Inspect(f, func(n ast.Node) bool {
switch d := n.(type) {
for _, decl := range f.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
names[d.Name.Name] = true
if d.Recv != nil {
names[receiverType(d.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
if recv := receiverType(d.Recv); recv != "" {
names[recv+"."+d.Name.Name] = true
}
case *ast.GenDecl:
addSpecs(names, d)
}
return true
})
}
}
return names
}
func addFields(names map[string]bool, d *ast.TypeSpec) {
s, isStruct := d.Type.(*ast.StructType)
func addSpecs(names map[string]bool, d *ast.GenDecl) {
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 {
return
}
for _, field := range s.Fields.List {
for _, field := range st.Fields.List {
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 {
if recv == nil || len(recv.List) == 0 {
return ""
}
e := recv.List[0].Type
if star, isPointer := e.(*ast.StarExpr); isPointer {
e = star.X