package main import ( "go/ast" "go/parser" "go/token" "os" "path/filepath" "strings" ) type toolbarViolation struct { file string line int funcName string } // Functions that render full public pages but legitimately skip toolbar injection. var toolbarExemptFuncs = map[string]bool{ // Auto-generated SEO listing pages — not block-editable. "renderListingPage": true, // Historical version preview — has its own preview banner; toolbar edit/publish // buttons would be misleading in that context. "renderVersionPreview": true, } // checkToolbarInjection scans handler Go files for public page render functions // (identified by containing both getSiteSettings and doc["site_settings"]) and // verifies each one also injects siteSettings["toolbar"] for the admin toolbar. func checkToolbarInjection(root string) []toolbarViolation { var violations []toolbarViolation handlersDir := filepath.Join(root, "internal", "handlers") if _, err := os.Stat(handlersDir); err != nil { return nil } _ = filepath.Walk(handlersDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") { return nil } if strings.HasSuffix(path, "_test.go") { return nil } src, err := os.ReadFile(path) if err != nil { return nil } srcText := string(src) fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, src, 0) if err != nil { return nil } relPath, _ := filepath.Rel(root, path) for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok || fn.Body == nil { continue } if toolbarExemptFuncs[fn.Name.Name] { continue } bodyStart := fset.Position(fn.Body.Pos()).Offset bodyEnd := fset.Position(fn.Body.End()).Offset if bodyEnd > len(srcText) { bodyEnd = len(srcText) } body := srcText[bodyStart:bodyEnd] hasSiteSettings := strings.Contains(body, "getSiteSettings") hasDocAssign := strings.Contains(body, `doc["site_settings"]`) if hasSiteSettings && hasDocAssign { hasToolbar := strings.Contains(body, `siteSettings["toolbar"]`) if !hasToolbar { violations = append(violations, toolbarViolation{ file: relPath, line: fset.Position(fn.Pos()).Line, funcName: fn.Name.Name, }) } } } return nil }) return violations }