Skip to content

feat: enforce max lines per file by glob - #29

Merged
machado144 merged 2 commits into
mainfrom
feat/file-size-limits
Oct 16, 2026
Merged

machado144 merged 2 commits into
mainfrom
feat/file-size-limits

Conversation

@machado144

@machado144 machado144 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Closes #28 (spec 014, approved).

What

Adds file_size, which sets the maximum number of lines a file may have, matched by glob. It works the same for Go, TypeScript and Python repositories, so it can replace ad-hoc check-file-size.sh scripts. Function- and class-length limits stay in language linters.

# requires structlint >= v0.7.0
file_size:
  rules:                    # ordered — FIRST matching rule wins
    - pattern: "**/*.py"
      maxLines: 300
    - pattern: "**/*.go"
      maxLines: 500
      severity: warning     # optional
  exclude: ["**/*_test.py", "**/migrations/versions/*.py", "**/*.lock"]

Before: no size checks; projects relied on shell scripts.
After:

✗ File too long: app/service.py has 412 lines (max 300)
::error file=app/service.py,title=file_too_long::File too long: app/service.py has 412 lines (max 300)

Behavior

  • Precedence: the first rule whose pattern matches decides the limit. Files that match no rule are not checked. A leading **/ also matches files at the repo root.
  • Counting: same as wc -l (the number of \n characters). A last line without a trailing newline is not counted. CRLF counts once per line. An empty file has 0 lines. I checked these counts against real wc -l output.
  • Skipped: the global ignore list, exclude matches, binary files (a NUL byte in the first 8000 bytes, the heuristic git uses) and non-regular files. --changed-only and --staged are respected.
  • New frozen code: file_too_long (the code count goes from 13 to 14). It shows up in text, json, sarif and github output, and exits non-zero like every other violation.
  • Invalid config fails at load time: maxLines <= 0, an empty or invalid glob, or an unknown severity. Example message: file_size.rules[0] (*.py): maxLines must be greater than 0, got 0.
  • extends: the child's rules go first, so a child can override a preset's limit for the same pattern. exclude is merged with a dedup append.
  • Opt-in: without a file_size block, behavior is unchanged. The presets and init --infer only ship a commented example, so extends: users see no new violations.

Also in this PR

  • fix(config): allowlists web/ in the repo's own .structlint.yaml. Self-validation, and the tests that run it, were already failing on main since feat: add dedicated marketing site #25.
  • This repo now uses the feature on itself: a 500-line limit on non-test .go files.

Deferred (see spec non-goals)

Max files per directory and max directory depth. Depth can already be limited with allowedPaths globs. Files-per-directory has unsettled semantics and no consumer yet.

Tests

TDD: I wrote the integration tests first; they build and run the binary. They cover: exactly maxLines passes, maxLines+1 fails, an empty file, no trailing newline, CRLF, a lone CR, excluded files, ignored directories, first-match precedence in both orders, **/ matching root files, binary files, the invalid-config table, all output formats, warning severity, --staged, extends overrides, and that the init/infer examples stay valid once uncommented. Unit tests cover the counter (including multi-chunk reads), the matcher, validation and merge.

gofumpt, golangci-lint (0 issues) and go test -race ./... all pass.

Note: the docs use the pragma >= v0.7.0, assuming this ships as the next minor release. Adjust it if the release tag differs.

🤖 Generated with Claude Code

machado144 and others added 2 commits October 16, 2026 12:52
* web/ (marketing site, #25) was never added to allowedPaths, so
  `structlint validate` on this repo and the self-validation tests failed on main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* new file_size.rules (pattern, maxLines, severity) + exclude; first match wins
* lines counted like wc -l; binary, ignored and excluded files skipped
* new frozen code file_too_long across text/json/sarif/github outputs
* extends: child rules first, same-pattern parent rules dropped
* schema, docs, SKILL.md, commented examples in init templates and --infer
* dogfood a 500-line limit on non-test Go files

Refs #28

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

✅ PR title follows the required format

Current title: feat: enforce max lines per file by glob

@machado144
machado144 marked this pull request as ready for review October 16, 2026 11:22
@machado144
machado144 merged commit aeb2b52 into main Oct 16, 2026
12 checks passed
@machado144
machado144 deleted the feat/file-size-limits branch October 16, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(014): file size limits (max lines per file by glob)

1 participant