| 1 | package guards |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "os" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | // maxFileLines is AGENTS.md's per-file cap ("Mechanical rules" and the |
| 10 | // frontend section both cite 300 lines; this test enforces the Go side). |
| 11 | const maxFileLines = 300 |
| 12 | |
| 13 | // lineCapExempt lists files exempt from the cap, by exact repo-relative |
| 14 | // path — never by pattern or directory, so an exemption is a visible, |
| 15 | // reviewable decision rather than a rule that silently swallows |
| 16 | // whatever lands nearby later. Empty today: nothing vendored or |
| 17 | // generated in this repo's .go tree exceeds the cap yet. Add an entry |
| 18 | // here, dated and justified, the day one does. |
| 19 | var lineCapExempt = map[string]bool{} |
| 20 | |
| 21 | // TestFileLineCap enforces the 300-line cap per .go file. It is a floor, |
| 22 | // not a ratchet: it stops a file from ever crossing 300 lines, but does |
| 23 | // not push any file already under the cap any lower. |
| 24 | func TestFileLineCap(t *testing.T) { |
| 25 | root := repoRoot(t) |
| 26 | for _, path := range goFiles(t, root) { |
| 27 | rel := relPath(t, root, path) |
| 28 | if lineCapExempt[rel] { |
| 29 | continue |
| 30 | } |
| 31 | n, err := countLines(path) |
| 32 | if err != nil { |
| 33 | t.Fatalf("guards: count lines in %s: %v", rel, err) |
| 34 | } |
| 35 | if n > maxFileLines { |
| 36 | t.Errorf("%s: %d lines exceeds the %d-line cap (AGENTS.md Mechanical rules: 300-line file cap)", rel, n, maxFileLines) |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | // countLines counts lines the way bufio.Scanner's ScanLines does: each |
| 42 | // newline-terminated line, plus a final line even if it has no trailing |
| 43 | // newline. That is NOT what `wc -l` reports — wc -l counts newline |
| 44 | // characters, so a file whose last line lacks a trailing newline gets |
| 45 | // one fewer from wc than from this. This function's count is the |
| 46 | // stricter of the two (it can only report equal or one higher), and |
| 47 | // gofmt already guarantees a trailing newline on every .go file this |
| 48 | // test walks, so the difference never shows up here in practice. |
| 49 | func countLines(path string) (int, error) { |
| 50 | f, err := os.Open(path) |
| 51 | if err != nil { |
| 52 | return 0, err |
| 53 | } |
| 54 | defer f.Close() |
| 55 | sc := bufio.NewScanner(f) |
| 56 | sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) |
| 57 | n := 0 |
| 58 | for sc.Scan() { |
| 59 | n++ |
| 60 | } |
| 61 | return n, sc.Err() |
| 62 | } |
| 63 | |