package guards import ( "io/fs" "path/filepath" "strings" "testing" ) // maxJSFileLines is AGENTS.md's frontend cap: "300-line JS module cap, // test-enforced, ratchet-down only. The vendored Jspreadsheet blob is // exempt and named as such; nothing we write is." const maxJSFileLines = 300 // jsLineCapExempt lists the files this cap does not apply to, by exact // repo-relative path. // // By exact path, never by directory: an exemption for // "static/vendor/**" would silently swallow the next file anybody drops // in there, which is how a 300-line rule quietly becomes a rule about // where you put your code. Adding an entry here is a visible decision // in a diff. // // TestEveryJSExemptionIsLive asserts the walk below actually reaches // each of these, so an entry can never be a comforting line that // exempts nothing. var jsLineCapExempt = map[string]bool{ // AGENTS.md names this one explicitly. It does not currently bite: // the published dist is minified to 17 lines, well under the cap. // Kept anyway, so an upgrade to an unminified build is not a CI // surprise. "internal/sheets/static/vendor/jspreadsheet.js": true, // This one bites: 13,601 lines. "internal/sheets/static/vendor/jsuites.js": true, } // skipJSDir is deliberately NOT repo_test.go's skipDir. // // skipDir skips any directory named "vendor", which is right for Go — // it means a dependency tree nobody here wrote. It is exactly wrong // here: our vendored JavaScript lives in a directory called vendor, so // reusing skipDir made this test walk past the only files its // exemption list names, pass, and prove nothing. It did precisely that // when first written, and the exemptions read as load-bearing while // being dead. Hence this function, and hence // TestEveryJSExemptionIsLive. func skipJSDir(name string) bool { return strings.HasPrefix(name, ".") || name == "node_modules" } // jsFiles returns the repo-relative path of every .js file. func jsFiles(t *testing.T, root string) []string { t.Helper() var out []string err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { if skipJSDir(d.Name()) { return filepath.SkipDir } return nil } if strings.HasSuffix(d.Name(), ".js") { out = append(out, relPath(t, root, path)) } return nil }) if err != nil { t.Fatalf("guards: walk: %v", err) } return out } // TestJSFileLineCap enforces the cap on every .js file in the repo. // // It is the frontend twin of TestFileLineCap, kept separate rather than // folded in as a mode because the two exemption lists have nothing in // common: the Go list is empty and expected to stay that way, and this // one is entirely third-party code. func TestJSFileLineCap(t *testing.T) { root := repoRoot(t) files := jsFiles(t, root) // A cap that walks nothing passes. Asserted rather than assumed, // because this test shipped once already having walked past // everything its exemptions named. if len(files) == 0 { t.Fatal("guards: found no .js files at all; this test is not checking anything") } for _, rel := range files { if jsLineCapExempt[rel] { continue } n, err := countLines(filepath.Join(root, rel)) if err != nil { t.Fatalf("guards: count lines in %s: %v", rel, err) } if n > maxJSFileLines { t.Errorf("%s: %d lines exceeds the %d-line cap (AGENTS.md Frontend: 300-line JS module cap)", rel, n, maxJSFileLines) } } } // TestEveryJSExemptionIsLive proves each exemption names a file the cap // actually reaches. // // This is the check that would have caught the bug above. An exemption // for a file the walk never visits — deleted, moved, or behind a // skipped directory — is worse than no exemption: it reads as "this // file is allowed to be long" while granting nothing, and the next // person to add a file at that path inherits permission nobody meant to // give. func TestEveryJSExemptionIsLive(t *testing.T) { root := repoRoot(t) seen := map[string]bool{} for _, rel := range jsFiles(t, root) { seen[rel] = true } for rel := range jsLineCapExempt { if !seen[rel] { t.Errorf("%s is exempt from the JS line cap, but the cap's own walk never reaches it: "+ "the exemption grants nothing", rel) } } }