oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package guards
2
3import (
4 "io/fs"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10// maxJSFileLines is AGENTS.md's frontend cap: "300-line JS module cap,
11// test-enforced, ratchet-down only. The vendored Jspreadsheet blob is
12// exempt and named as such; nothing we write is."
13const maxJSFileLines = 300
14
15// jsLineCapExempt lists the files this cap does not apply to, by exact
16// repo-relative path.
17//
18// By exact path, never by directory: an exemption for
19// "static/vendor/**" would silently swallow the next file anybody drops
20// in there, which is how a 300-line rule quietly becomes a rule about
21// where you put your code. Adding an entry here is a visible decision
22// in a diff.
23//
24// TestEveryJSExemptionIsLive asserts the walk below actually reaches
25// each of these, so an entry can never be a comforting line that
26// exempts nothing.
27var jsLineCapExempt = map[string]bool{
28 // AGENTS.md names this one explicitly. It does not currently bite:
29 // the published dist is minified to 17 lines, well under the cap.
30 // Kept anyway, so an upgrade to an unminified build is not a CI
31 // surprise.
32 "internal/sheets/static/vendor/jspreadsheet.js": true,
33 // This one bites: 13,601 lines.
34 "internal/sheets/static/vendor/jsuites.js": true,
35}
36
37// skipJSDir is deliberately NOT repo_test.go's skipDir.
38//
39// skipDir skips any directory named "vendor", which is right for Go —
40// it means a dependency tree nobody here wrote. It is exactly wrong
41// here: our vendored JavaScript lives in a directory called vendor, so
42// reusing skipDir made this test walk past the only files its
43// exemption list names, pass, and prove nothing. It did precisely that
44// when first written, and the exemptions read as load-bearing while
45// being dead. Hence this function, and hence
46// TestEveryJSExemptionIsLive.
47func skipJSDir(name string) bool {
48 return strings.HasPrefix(name, ".") || name == "node_modules"
49}
50
51// jsFiles returns the repo-relative path of every .js file.
52func jsFiles(t *testing.T, root string) []string {
53 t.Helper()
54 var out []string
55 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
56 if err != nil {
57 return err
58 }
59 if d.IsDir() {
60 if skipJSDir(d.Name()) {
61 return filepath.SkipDir
62 }
63 return nil
64 }
65 if strings.HasSuffix(d.Name(), ".js") {
66 out = append(out, relPath(t, root, path))
67 }
68 return nil
69 })
70 if err != nil {
71 t.Fatalf("guards: walk: %v", err)
72 }
73 return out
74}
75
76// TestJSFileLineCap enforces the cap on every .js file in the repo.
77//
78// It is the frontend twin of TestFileLineCap, kept separate rather than
79// folded in as a mode because the two exemption lists have nothing in
80// common: the Go list is empty and expected to stay that way, and this
81// one is entirely third-party code.
82func TestJSFileLineCap(t *testing.T) {
83 root := repoRoot(t)
84 files := jsFiles(t, root)
85
86 // A cap that walks nothing passes. Asserted rather than assumed,
87 // because this test shipped once already having walked past
88 // everything its exemptions named.
89 if len(files) == 0 {
90 t.Fatal("guards: found no .js files at all; this test is not checking anything")
91 }
92
93 for _, rel := range files {
94 if jsLineCapExempt[rel] {
95 continue
96 }
97 n, err := countLines(filepath.Join(root, rel))
98 if err != nil {
99 t.Fatalf("guards: count lines in %s: %v", rel, err)
100 }
101 if n > maxJSFileLines {
102 t.Errorf("%s: %d lines exceeds the %d-line cap (AGENTS.md Frontend: 300-line JS module cap)",
103 rel, n, maxJSFileLines)
104 }
105 }
106}
107
108// TestEveryJSExemptionIsLive proves each exemption names a file the cap
109// actually reaches.
110//
111// This is the check that would have caught the bug above. An exemption
112// for a file the walk never visits — deleted, moved, or behind a
113// skipped directory — is worse than no exemption: it reads as "this
114// file is allowed to be long" while granting nothing, and the next
115// person to add a file at that path inherits permission nobody meant to
116// give.
117func TestEveryJSExemptionIsLive(t *testing.T) {
118 root := repoRoot(t)
119 seen := map[string]bool{}
120 for _, rel := range jsFiles(t, root) {
121 seen[rel] = true
122 }
123 for rel := range jsLineCapExempt {
124 if !seen[rel] {
125 t.Errorf("%s is exempt from the JS line cap, but the cap's own walk never reaches it: "+
126 "the exemption grants nothing", rel)
127 }
128 }
129}
130