mesh/delivery-group group feat/judge-once rejected: composed together: the checker's verdict at the members' heads (8a8ede2fb2c8): unit: go.gofmt
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: go.gofmt: not as gofmt writes them (run gofmt -w): go/catalogue/streams.go | 62cddf4efc62 on ff5e00f1f894 | unit 0 run, 78 ke…
Review round 1 (issue 1585): L1, DataItemsHeld holds a module by the culprit rule PlansHeld uses, a held module's data left out, so an item its composition refuses holds that module alone; L2, mesh-sdk's own tests scan its go/ tree as mesh-catalog's checks/sdkjudge does, with the same list of composition's refusals. Two tests merged into one. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
176 lines
5.9 KiB
Go
176 lines
5.9 KiB
Go
// The same scan as mesh-catalog's checks/sdkjudge, run by mesh-sdk's own check (novox/hq ADR 0578 §4: the change that
|
|
// adds a manifest judge to mesh-sdk is refused), so a change to mesh-sdk alone is refused too. Copied from mesh-catalog
|
|
// feat/judge-once checks/sdkjudge/main.go; the two lists of composition's refusals are kept the same.
|
|
package stdio
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// composition is every function or table the check lets stay in mesh-sdk, as "path-from-go/:name", and why. It names no
|
|
// manifest judgement: each is the composer's refusal of composition itself (ADR 0578 clause 5).
|
|
var composition = map[string]string{
|
|
"catalogue/consumers.go:consumersPlaceholderProblems": "composition: rendering a consumer's template",
|
|
"catalogue/collections.go:collectedPlaceholderProblems": "composition: placeholders a contribution renders",
|
|
"catalogue/environment_into.go:placeholderProblems": "composition: placeholders a contribution renders",
|
|
"catalogue/seat_contributions.go:seatPlaceholderProblems": "composition: placeholders a contribution renders",
|
|
"catalogue/collections.go:itemTemplateProblem": "composition: filling an item's template",
|
|
"catalogue/setting_takes.go:takenSettingsProblems": "composition: whether stored settings still compose",
|
|
"catalogue/template.go:HeldTemplateProblem": "composition: the take-in refusal of a resolved build",
|
|
}
|
|
|
|
// judgeProblems is what mesh-sdk's Go tree under root (its go/ directory) holds of a manifest judge, one line each, but for
|
|
// what composition lets stay (the map the caller gives; the test of the real tree gives the package's), and the
|
|
// entries of composition it no longer holds.
|
|
func judgeProblems(root string, composition map[string]string) ([]string, error) {
|
|
var out []string
|
|
seen := map[string]bool{}
|
|
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
if n := d.Name(); n == "testdata" || n == "vendor" || strings.HasPrefix(n, ".") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
rel, _ := filepath.Rel(root, path)
|
|
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.SkipObjectResolution)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", rel, err)
|
|
}
|
|
inCatalogue := filepath.Base(filepath.Dir(path)) == "catalogue"
|
|
for _, d := range file.Decls {
|
|
switch d := d.(type) {
|
|
case *ast.FuncDecl:
|
|
if why := judgeFunc(d); why != "" {
|
|
out = append(out, note(composition, seen, rel, d.Name.Name, why))
|
|
}
|
|
case *ast.GenDecl:
|
|
if !inCatalogue || d.Tok != token.VAR {
|
|
continue
|
|
}
|
|
for _, spec := range d.Specs {
|
|
v, _ := spec.(*ast.ValueSpec)
|
|
if v == nil {
|
|
continue
|
|
}
|
|
for i, name := range v.Names {
|
|
if i < len(v.Values) && allowName.MatchString(name.Name) && isTable(v.Values[i]) {
|
|
out = append(out, note(composition, seen, rel, name.Name,
|
|
"a table of what a manifest may declare (an allow-list)"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var kept []string
|
|
for _, line := range out {
|
|
if line != "" {
|
|
kept = append(kept, line)
|
|
}
|
|
}
|
|
for key, why := range composition {
|
|
if !seen[key] {
|
|
kept = append(kept, fmt.Sprintf("composition lists %s (%s), which mesh-sdk no longer holds: take it off the list", key, why))
|
|
}
|
|
}
|
|
sort.Strings(kept)
|
|
return kept, nil
|
|
}
|
|
|
|
// note is the line refusing name in rel for why, or "" when composition lets it stay.
|
|
func note(composition map[string]string, seen map[string]bool, rel, name, why string) string {
|
|
key := rel + ":" + name
|
|
if _, ok := composition[key]; ok {
|
|
seen[key] = true
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%s: %s is %s; mesh-sdk carries the contract only, the judge is the indexer's and the checker's (ADR 0578 §4)", rel, name, why)
|
|
}
|
|
|
|
// allowName is what names a table of what is allowed.
|
|
var allowName = regexp.MustCompile(`(?i)allow|permit|removable|reserved|forbid|denied`)
|
|
|
|
// isTable says whether an expression is a map or a list literal of more than one entry.
|
|
func isTable(e ast.Expr) bool {
|
|
c, ok := e.(*ast.CompositeLit)
|
|
if !ok || len(c.Elts) < 2 {
|
|
return false
|
|
}
|
|
switch c.Type.(type) {
|
|
case *ast.MapType, *ast.ArrayType:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// judgeFunc says why a function is a manifest judge, or "".
|
|
func judgeFunc(f *ast.FuncDecl) string {
|
|
name := f.Name.Name
|
|
if judgeEntry.MatchString(name) {
|
|
return "a function that judges a manifest"
|
|
}
|
|
if !strings.HasSuffix(name, "Problems") && !strings.HasSuffix(name, "Problem") {
|
|
return ""
|
|
}
|
|
if takes(f.Recv) || takes(f.Type.Params) {
|
|
return "a function that gathers a manifest's problems"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// judgeEntry is the names of the judge's entry points: the strict parsers and the validators of a manifest or a shelf.
|
|
var judgeEntry = regexp.MustCompile(`^(ParseManifest(With|ForInputs)?|Validate(Manifest|Shelf)\w*)$`)
|
|
|
|
// takes says whether a list of parameters names a Manifest or a Shelf anywhere in a type.
|
|
func takes(fields *ast.FieldList) bool {
|
|
if fields == nil {
|
|
return false
|
|
}
|
|
found := false
|
|
for _, f := range fields.List {
|
|
ast.Inspect(f.Type, func(n ast.Node) bool {
|
|
if id, ok := n.(*ast.Ident); ok && (id.Name == "Manifest" || id.Name == "Shelf") {
|
|
found = true
|
|
}
|
|
return !found
|
|
})
|
|
}
|
|
return found
|
|
}
|
|
|
|
// mesh-sdk holds no manifest judge: no function gathering a manifest's problems, no judge entry point and no allow-list
|
|
// table in the catalogue, but for composition's own refusals, each listed with why (ADR 0578 §4, §5).
|
|
func TestMeshSDKHoldsNoManifestJudge(t *testing.T) {
|
|
root, err := filepath.Abs(".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
problems, err := judgeProblems(root, composition)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, p := range problems {
|
|
t.Error(p)
|
|
}
|
|
}
|