feat(lint): add 11 Racket, Emacs Lisp and Clojure depth rules - #105
Merged
Conversation
RULE_COUNT 334 -> 345. Four standalone commands (the Clojure rules); Racket and Emacs Lisp are registry-only. lint-racket-depth (5): racket-match-unreachable-clause (Error), racket-for-comprehension-value-discarded, racket-begin0-single-form (fixable), racket-case-lambda-single-clause (fixable), racket-parameterize-empty-bindings. lint-elisp-depth (2): elisp-process-filter-assumes-whole-output, elisp-repeating-timer-handle-discarded. lint-clojure-depth (4, all Error): go-block-blocking-channel-op, parking-op-outside-go-machinery, contains-on-non-associative, reference-type-operator-mismatch. All three batches dropped most of what they proposed, and the refutations are the useful part. **Racket** got both a corpus and a toolchain -- 4492 .rkt files and Racket v9.2 -- so every premise was executed. Typed Racket's `Any` does the **opposite** of what was proposed: `(needs-number (takes-any 5))` is a type error, `expected: Number given: Any`, so `Any` maximally *constrains* consumers and `(-> _ Any)` is idiomatic for side-effecting functions. That is the second time a Typed Racket shape-match premise has been refuted here. `module+` reads a *later* `define` fine, because it is `module*` with `#f`, declared after the body. `define/contract` on a non-exported function still guarantees every internal caller -- the boundary is definition-vs-rest-of-module, verified by a same-module call raising while the recursive call did not. `racket-contract-out-arity-mismatch` was built, tested, corpus-audited and then **deleted on measurement**: 1.25ms for one realistic file, ~40000x the control, super-linear -- and zero findings over 214 `contract-out` occurrences, so the cost bought nothing. **Emacs Lisp shipped 2 of 8, and that is the honest number.** A bulk writer proved the process-filter bug: 10000 lines in one write reached the filter as 8 invocations with **7 mid-line splits**, because `read-process-output-max` is 65536 -- so the defect passes every small-output test and corrupts data past 64 KiB. Refuted: `goto-char` *clamps* rather than signalling; `advice-remove` compares with `equal` so a lambda **is** removable; and `setq` on a buffer-local variable creates a buffer-local binding, which had already been refuted once in this project and was re-proposed anyway. Two more elisp rules were built, audited and deleted rather than shipped at 11% and 20% fire rates on GNU Emacs's own tree: narrowing (112/1044 -- rmail, gnus and ediff narrow as their *display model*) and display properties (500/2679 -- `font-lock-ensure` leaves `buffer-modified-p` nil, so `'face` writes during fontification are correct). **Clojure**'s `go`-block rule survived a check that could have killed it: core.async *does* ship a blocking-op detector, but `check-blocking-in-dispatch` fires only under a system property read once at namespace load, which its own docs call development-only and "covers only part of the problem". `go-impl` does no static inspection. Conversely `defrecord-implements-protocol-method-not-in-protocol` **is** a compile error -- `Compiler.java:9128` throws "Can't define method not in interfaces" -- so that rule died. And a no-init `reduce` rule died on measurement: 35 of 172 corpus occurrences pass a literal fn with no 0-arity, all working code. `dynamic-scope-returns-lazy-seq` was built, corpus-run at 978 candidates / 1 finding / **0 true positives**, and withdrawn. Wiring notes: - **`RULE_DOCS` had to become `static`.** The 345th rule crossed clippy's default 16 KiB `array-size-threshold` (345 x 48 = 16560 B; at 334 it was 16032, just under). Took clippy's own suggestion rather than an `#[allow]`: nothing reads it in a const context, so `static` is also the substantive fix, since a `const` is substituted into every use site. That threshold is now permanently crossed. - The two `Fixable` Racket rules needed a **Racket** fixture in `fixable_rules_match_the_fix_engine` -- the Scheme fixture does not reach them, because `lint-scheme-idiom` declares `[Scheme, Racket]` while these declare `[Racket]` alone. Proved by removing the line and watching exactly those two go missing. Third time this test has caught a `--fix` that would silently do nothing. - treefmt reformatted `lint-racket-depth`'s own fixtures: the `tests/fixtures/*` exclusion is root-anchored and does not cover `packages/feature/*/tests/fixtures/`. Same trap as PR #88. - `docs/src/reference/architecture.md` had drifted across PRs #97, #99, #101 and #102 -- it claimed 320 rules and 66 packages. Rewritten with measured values. Two parser gaps found and reported, not fixed. **Racket: 1777 of 4492 files (39.6%) fail to parse** on `#rx`/`#px`, `#%kernel`, `#'`/`` #` ``, `#<<` here-strings, `#hash`, `#"..."` -- which caps what any Racket rule in this workspace can see. **Emacs Lisp: 191 of 1674 (11.4%)**, 153 of them radix literals (`#x010101`, `#b01111110`, `#o777`, `#24r1k`) in ordinary files like `bookmark.el`, `ansi-color.el` and `calc.el`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RULE_COUNT334 → 345. Four standalone commands (the Clojure rules); Racket and Emacs Lisp are registry-only.lint-racket-depth(5)racket-match-unreachable-clause(Error),racket-for-comprehension-value-discarded,racket-begin0-single-form(fixable),racket-case-lambda-single-clause(fixable),racket-parameterize-empty-bindingslint-elisp-depth(2)elisp-process-filter-assumes-whole-output,elisp-repeating-timer-handle-discardedlint-clojure-depth(4, all Error)go-block-blocking-channel-op,parking-op-outside-go-machinery,contains-on-non-associative,reference-type-operator-mismatchAll three batches dropped most of what they proposed, and the refutations are the useful part.
Racket — corpus and toolchain obtained, so every premise was executed
4492
.rktfiles and Racket v9.2. The earlier "3 unique.rktfiles in the whole store" no longer applies, so nothing here is labelled unaudited.Anydoes the opposite of what was proposed.(needs-number (takes-any 5))is a type error —expected: Number given: Any.Anymaximally constrains consumers, and(-> _ Any)is idiomatic for side-effecting functions. That's the second time a Typed Racket shape-match premise has been refuted in this project.module+reads a laterdefinefine — it'smodule*with#f, declared after the body. Printed6.define/contracton a non-exported function still guarantees every internal caller — the boundary is definition-vs-rest-of-module. Verified: a same-module call raised while the recursive call passed-5unchecked.equal?is identity as claimed, butgen:equal+hashalso fixes it and 1460 of 1845 corpus structs are deliberately opaque — flagging them is noise.racket-contract-out-arity-mismatchwas built, tested, corpus-audited and then deleted on measurement: 1.25 ms for one realistic file, ~40,000× the control, super-linear — and zero findings over 214contract-outoccurrences, so the cost bought nothing. The evidence and aWholeTreerebuild path are preserved insrc/cost_tests.rs.Corpus: 8 findings, 0 false positives after one was found and fixed — Typed Racket's legacy
(case-lambda (Number * -> Number))type spelling. Two true positives sit in Racket's owncollects/setup/parallel-build.rkt.Emacs Lisp shipped 2 of 8, and that's the honest number
The process-filter bug is real and explains why it ships. A bulk writer emitting 10000 lines in one
writereached the filter as 8 invocations with 7 mid-line splits and 7 malformed lines.read-process-output-maxis 65536 — so the defect passes every small-output test and corrupts data past 64 KiB. (The first probe used 400 separateprintfs and showed 0 splits; the producer was line-aligned, which is why it was re-run.)Refuted:
goto-charclamps rather than signalling ((goto-char 999)in a 6-char buffer →point now 6);advice-removecompares withequal, so a lambda is removable; andsetqon a buffer-local variable creates a buffer-local binding — which had already been refuted once in this project and I re-proposed it anyway.Two more rules were built, audited and deleted rather than shipped at 11% and 20% fire rates on GNU Emacs's own tree: narrowing (112/1044 — rmail, gnus and ediff narrow as their display model) and display properties (500/2679 —
font-lock-ensureleavesbuffer-modified-pnil, so'facewrites during fontification are correct).Clojure — one rule survived a check that could have killed it
core.async does ship a blocking-op detector, but
check-blocking-in-dispatchfires only under a system property read once at namespace-load time, which its own docs call development-only and "covers only part of the problem".go-impldoes no static inspection. So unlike CLloop'scollect x sum x, this is not already-caught.Conversely
defrecord-implements-protocol-method-not-in-protocolis a compile error —Compiler.java:9128throws "Can't define method not in interfaces" — so it died. A no-initreducerule died on measurement: 35 of 172 corpus occurrences pass a literal fn with no 0-arity, all working code. Anddynamic-scope-returns-lazy-seqwas built, corpus-run at 978 candidates / 1 finding / 0 true positives, and withdrawn.The one true positive is
onyx/src/onyx/api.clj:723— a blocking<!!inside ago, re-entered per log entry.Wiring notes
RULE_DOCShad to becomestatic. The 345th rule crossed clippy's default 16 KiBarray-size-threshold(345 × 48 = 16560 B; at 334 it was 16032, just under). Took clippy's own suggestion rather than an#[allow]— nothing reads it in a const context, sostaticis also the substantive fix, since aconstis substituted into every use site. That threshold is now permanently crossed.FixableRacket rules needed a Racket fixture infixable_rules_match_the_fix_engine: the Scheme fixture doesn't reach them, becauselint-scheme-idiomdeclares[Scheme, Racket]while these declare[Racket]alone. Proved by removing the line and watching exactly those two go missing. Third time this test has caught a--fixthat would silently do nothing.lint-racket-depth's own fixtures — thetests/fixtures/*exclusion is root-anchored and doesn't coverpackages/feature/*/tests/fixtures/. Same trap as PR feat(lint): add 8 Clojure and Scheme lint rules #88.docs/src/reference/architecture.mdhad drifted across feat(lint): add 3 CL loop-facility rules #97/feat(lint): add 4 CLOS-dispatch and binding-analysis rules #99/fix(semantics): resolve #' function designators to their local binding #101/feat(lint): add 6 CL data-structure rules #102, claiming 320 rules and 66 packages. Rewritten with values measured fromcargo metadata --no-deps.Two parser gaps found, reported not fixed
#rx/#px(459),#%…(~290),#'/#`(267),#<<here-strings (195),#hash(105),#"…"(81). This caps what any Racket rule in this workspace can see.#x010101,#b01111110,#o777,#24r1k— in ordinary files likebookmark.el,ansi-color.el,bindings.elandcalc.el.Verification
cargo build --workspace,cargo test --workspace(163 suites),cargo test --test cli(3085 passed),cargo fmt --all --check,cargo clippy --all-targets --all-features -- -D warnings— all exit 0.All 11 fire through the real binary, each reporting only itself. Sweeping
tests/fixtures/**gives 0 findings; widened to all 40 repo Lisp files gives 5, all inlint-racket-depth's owndangerous.rktpositive-control fixture, exactly one per Racket rule, with itsclean.rktsilent.Two stale one-line
descriptionfields in the copiedCargo.tomls are flagged but not fixed — both READMEs document the dropped rules correctly; only the package blurbs drifted.