// 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) } }