Files
fejkdata/doc_test.go
T

203 lines
5.2 KiB
Go

package fejkdata
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"regexp"
"strings"
"testing"
)
// Every backtick in the Vocabulary is a symbol.
var backticked = regexp.MustCompile("`([^`\n]+)`")
// TestVocabularyNamesDeclaredSymbols holds a renamed unit to its Vocabulary entry.
func TestVocabularyNamesDeclaredSymbols(t *testing.T) {
files := sourceFiles(t)
declared := declaredSymbols(files)
named := backticked.FindAllStringSubmatch(vocabulary(t, files), -1)
if len(named) == 0 {
t.Fatal("the Vocabulary names no symbol, so nothing holds a renamed unit to its entry")
}
for _, m := range named {
if !declared[m[1]] {
t.Errorf("the Vocabulary names %s, which the package declares nowhere", m[1])
}
}
}
func sourceFiles(t *testing.T) []*ast.File {
t.Helper()
pkg, err := build.ImportDir(".", 0)
if err != nil {
t.Fatalf("read the package directory: %v", err)
}
fset := token.NewFileSet()
var files []*ast.File
for _, name := range pkg.GoFiles {
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil {
t.Fatalf("parse %s: %v", name, err)
}
files = append(files, f)
}
return files
}
func vocabulary(t *testing.T, files []*ast.File) string {
t.Helper()
var sections strings.Builder
for _, f := range files {
for _, c := range f.Comments {
section, found := strings.CutPrefix(c.Text(), "Vocabulary\n\n")
if found && c.Pos() > f.Package {
sections.WriteString(section)
}
}
}
if sections.Len() == 0 {
t.Fatal("no comment below a package clause opens with a Vocabulary heading, so the words the package is written in are defined nowhere a reader of the code meets them")
}
return sections.String()
}
// 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.
func declaredSymbols(files []*ast.File) map[string]bool {
names := map[string]bool{}
for _, f := range files {
for _, decl := range f.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
names[d.Name.Name] = true
if recv := receiverType(d.Recv); recv != "" {
names[recv+"."+d.Name.Name] = true
}
case *ast.GenDecl:
addSpecs(names, d)
}
}
}
return names
}
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 st.Fields.List {
for _, id := range field.Names {
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
}
id, isIdent := e.(*ast.Ident)
if !isIdent {
return ""
}
return id.Name
}
// fillHeader opens the comment naming the pass that fills the fields below it.
const fillHeader = "Filled by "
// TestTemplateFieldsNameTheirPass holds every template field to a header naming the
// pass that writes it.
func TestTemplateFieldsNameTheirPass(t *testing.T) {
files := sourceFiles(t)
declared := declaredSymbols(files)
st, headers := templateStruct(t, files)
for _, field := range st.Fields.List {
for _, id := range field.Names {
header := headerAbove(headers, id.Pos())
if header == "" {
t.Errorf("template.%s sits under no %q header, so nothing says which pass fills it", id.Name, fillHeader)
continue
}
named := backticked.FindAllStringSubmatch(header, -1)
if len(named) == 0 {
t.Errorf("the header above template.%s names no function, so it names no pass", id.Name)
}
for _, m := range named {
if !declared[m[1]] {
t.Errorf("the header above template.%s names %s, which the package declares nowhere", id.Name, m[1])
}
}
}
}
}
// templateStruct is the template struct and the fill headers standing between its
// fields, in source order.
func templateStruct(t *testing.T, files []*ast.File) (*ast.StructType, []*ast.CommentGroup) {
t.Helper()
for _, f := range files {
for _, decl := range f.Decls {
d, isGen := decl.(*ast.GenDecl)
if !isGen {
continue
}
for _, spec := range d.Specs {
s, isType := spec.(*ast.TypeSpec)
if !isType || s.Name.Name != "template" {
continue
}
st, isStruct := s.Type.(*ast.StructType)
if !isStruct {
t.Fatal("template is not a struct")
}
return st, fillHeaders(f, st)
}
}
}
t.Fatal("the package declares no template struct")
return nil, nil
}
func fillHeaders(f *ast.File, st *ast.StructType) []*ast.CommentGroup {
var found []*ast.CommentGroup
for _, c := range f.Comments {
if c.Pos() > st.Pos() && c.End() < st.End() && strings.HasPrefix(c.Text(), fillHeader) {
found = append(found, c)
}
}
return found
}
// headerAbove is the last fill header standing above pos.
func headerAbove(headers []*ast.CommentGroup, pos token.Pos) string {
text := ""
for _, c := range headers {
if c.Pos() < pos {
text = c.Text()
}
}
return text
}