Appendix C - 8. Blocking semantic lints
The Structure of Blocking semantic lints — its shape at a glance:
Each lint scans the source for its invariant and reports findings. A blocking lint fails the build; a scoped, reason-bearing suppression comment escapes a legitimate exception.
flowchart LR Src[/Source tree/] --> Lint[Semantic lint] Lint -->|violation| Fail([build blocked]) Lint -->|clean| Pass([build proceeds]) Noqa[/Scoped suppression/] -.->|reason-bearing escape| Lint
Accessible description: a semantic lint scans the source tree for its invariant. A violation blocks the build; a clean scan lets it proceed. A scoped, reason-bearing suppression comment escapes a legitimate exception on a single line.
Projected from the catalogue entry validation-and-conformance / Blocking semantic lints.
On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns
Intent
Intent — A fleet of blocking semantic lints over the tool's own source (banned APIs, silent-catch bans, Console.WriteLine-in-prod, typed-seam violations) that fail the build on domain-invariant violations the compiler and review can't catch.
Motivation
The codebase carries hundreds of structural invariants: no silent catch, no banned API in prod, every cross-boundary call through its seam. Code review cannot hold hundreds of invariants in a reviewer's head, and the compiler enforces none of them (a silent catch, a banned API, a raw Console.WriteLine all compile fine). The failure is structural drift that quietly reintroduces a defect class, and it recurs continuously as code is written.
One of these lints once turned on its author. A regex meant to scan source for a banned pattern backtracked catastrophically on a real input and hung the deploy gate — the checker that guards the fleet became the thing that stalled it. The sting was personal: the author's doctoral research was on this precise failure, regular-expression denial of service, the class where a crafted string drives an innocent-looking pattern into exponential work. Knowing the failure cold did not stop shipping it. That is the lesson the entry carries. The cure was not a better regex or a lint that flags the bad one; it was to delete the surface — reach for the parser, and let the whole class go. The mechanism is the fix; this instance is why the fix reads the way it does.
Applicability
- A lint framework with per-lint scope + severity declaration.
- The invariants made mechanically detectable — a rule you can't express as a check can't join.
- A runner wired into the gates, and a scoped escape hatch for legitimate exceptions.
Structure
The Structure diagram appears at the top of this page.
Sample Code
A semantic lint encodes a domain rule the compiler can't and scans for it. This one bans a silent except — a catch that neither logs, re-raises, nor carries a justifying comment — because a silently swallowed error turns a fail-loud bug into a fail-quiet one. A reason-bearing suppression comment escapes a deliberate swallow.
import ast, sys
def lint(path: str, source: str, lines: list[str]) -> list[str]:
"""Ban the silent swallow: an `except` body that only passes, with no logging,
re-raise, or justifying comment. A swallowed error hides a real failure."""
findings = []
for node in ast.walk(ast.parse(source)):
if isinstance(node, ast.ExceptHandler):
body_is_pass = all(isinstance(s, ast.Pass) for s in node.body)
line = lines[node.lineno - 1]
if body_is_pass and "noqa: silent-catch" not in line: # scoped escape
findings.append(f"{path}:{node.lineno}: silent except — log, re-raise, or justify")
return findings
if __name__ == "__main__":
hits = []
for p in sys.argv[1:]:
text = open(p).read()
hits += lint(p, text, text.splitlines())
print("\n".join(hits)); sys.exit(1 if hits else 0)
Consequences
- A large maintenance surface. Hundreds of lints are hundreds of things to keep current: the "500+ lints" magnitude the support-apparatus ratio counts.
- False positives need escapes. A too-strict lint blocks legitimate code until a
noqais added, a small, audited hole. - Floor vs fleet. The custom fleet only earns its keep atop the commodity analysis floor; without that floor the grade rests on the wrong thing.
Example use within DocAble
lint-banned-apis,no-silent-catch,console-singleton-mutation, the typed-seam ban-lints.- The lint-declaration discipline (each lint declares its scope, severity, and a verb-of-checking docstring); the scoped
noqaescape convention.
Related Patterns
- See also (sibling) — coherence-lints: relational lints across sources; content-validator / standards-rule-engine: the other artifact validators.
- Counterpart — the ban-lints in canonical-models-and-seams are members of this fleet doing the "hold a construction seam in place" job.