Racket Linter v0.2.0
| (require racket-linter) | package: racket-linter |
Racket Linter is a configurable, extensible static analysis tool for Racket projects. It scans *.rkt files, runs text, syntax, and expansion rules, and reports file-level and project-level diagnostics.
The Scribble manual is the source of truth for the command and rule contract. The repository README contains only a short development quick start.
1 Quick Start
Install the package or link a checkout:
raco pkg install /path/to/racket-linter |
raco pkg install --link /path/to/racket-linter |
Re-index a linked checkout after changing info.rkt or command metadata:
raco setup --pkgs racket-linter |
Run the command:
raco lint /path/to/project |
raco lint --help |
raco lint --output json /path/to/project |
The command exits with status 0 when no diagnostics are produced and status 1 when at least one diagnostic is produced. Invalid command-line arguments and internal rule failures also return a non-zero status.
2 Configuration
Create .racket-linter.rkt in the project root. The file may be a normal Racket module with a #lang line and must evaluate to a hash:
#lang racket/base |
(hash |
'style/line-length (hash 'max-length 120) |
'reachability/unused-require (hash 'enabled #t) |
'export/unused-project (hash 'enabled #f)) |
For compatibility, a configuration file containing only the hash expression is also accepted. User configuration is merged with each rule’s defaults. Project-level diagnostics use the same rule IDs and configuration hash.
Configuration is evaluated as trusted Racket code. Do not load an untrusted project configuration without sandboxing or reviewing it first.
3 Command Options
--help prints usage and exits successfully.
--fix applies the supported text fixes.
--format runs raco fmt on discovered files.
--no-config ignores the project configuration.
--config <file> selects a configuration file.
--exclude <directory> excludes matching paths; it can be repeated.
--parallel analyzes files concurrently and collects results in file order.
--output <text|json|sarif|junit> selects the output format.
Only one project directory argument is accepted. JSON, SARIF, and JUnit output are machine-readable; all strings are escaped by their respective serializers.
4 Rule Inventory
The following table describes the rules registered by the CLI. Rules marked enabled run unless disabled by configuration. Rules marked disabled are available but opt-in.
Rule ID | Layer | Default | Contract |
style/line-length | text | enabled | Reports lines over configurable max-length; default 102 |
style/trailing-whitespace | text | enabled | Reports trailing spaces or tabs |
style/newline-at-eof | text | enabled | Requires a final newline |
style/sexpr-depth | syntax | disabled | Reports syntax nesting over configurable max-depth; default 10 |
style/definition-length | text | enabled | Reports definitions over 66 lines |
style/file-length | text | enabled | Reports files over 1000 lines |
style/naming-convention | text | disabled | Reports underscores and camelCase |
style/require-sort | text | disabled | Reports unsorted require forms |
style/provide-sort | text | disabled | Reports unsorted provide forms |
style/extract-let | text | disabled | Suggests extracting repeated expressions |
style/simplify-cond | text | disabled | Suggests else instead of a final #t clause |
definition/unused | text | disabled | Regex-based top-level unused definition heuristic |
reachability/undefined | syntax | disabled | Reports references not resolved by the local scanner |
reachability/unused-require | syntax | disabled | Reports unused required bindings using syntax scanning |
reachability/unused-require-expand | expand | disabled | Reports unused requires after expansion |
export/unused | syntax | disabled | Reports exports not used within one module |
module/require-provide | syntax | disabled | Reports provided names without local definitions |
abstract/type-error | expand | disabled | Conservative definite non-procedure application checks |
abstract/unreachable-code | text | disabled | Heuristic scan for code after exit, raise, or error |
check-syntax/unused | syntax | disabled | Uses DrRacket check-syntax callbacks when available |
module/circular-dependency | project | enabled | Reports cycles in the simplified require graph |
export/unused-project | project | disabled | Reports exports unused by files in this project |
The project-level export rule cannot know about consumers outside the scanned project. Library projects should normally disable it or use a project-specific entry-point policy.
5 Analysis Layers
Rules declare one of these layers:
text receives raw file text and runs for every file.
syntax receives syntax only for languages in the safe-language whitelist.
expand receives expanded syntax only for safe languages. Expansion errors become diagnostics.
both runs in both the text and syntax phases.
project is implemented by the project analysis pass and receives the discovered file set.
Non-whitelisted languages are analyzed by text rules only. This is intentional: expansion can load modules and execute compile-time code.
6 Abstract Evaluation
analyze-abstract accepts expanded syntax and a source path, and returns a list of diagnostics.
The current domain includes top, bottom, numbers, strings, symbols, booleans, procedures, lists, and pairs.
It detects applications of values proven to be non-procedures and simple known procedure arity errors.
The interpreter has a bounded fixpoint loop for recursive bindings, but it is not a Racket type checker and does not prove general program properties. Unknown values are represented by top and should not produce a definite type diagnostic. The separate abstract/unreachable-code rule is currently a text heuristic; it is not a proof generated by the abstract interpreter.
7 Auto-Fix
Supported fixes are intentionally limited:
trailing whitespace removal
missing final newline insertion
simple require sorting
simple provide sorting
final #t to else replacement in cond text
the current simplified extract-let transformation
Use --fix only after reviewing the proposed diagnostics. The text-based fixers do not provide a semantic proof of the rewritten program.
8 Output Formats
raco lint --output json /path/to/project |
raco lint --output sarif /path/to/project |
raco lint --output junit /path/to/project |
JSON is an object containing a diagnostics array. SARIF uses version 2.1.0 with one run. JUnit emits one testcase per diagnostic. All three formats are serialized structurally rather than assembled from unescaped strings.
9 Core API
The public core modules provide these values:
diagnostic, diagnostic?, and diagnostic accessors for locations and messages.
rule, rule?, define-rule, and rule accessors for rule registration.
run-file for file-level rule execution.
merge-configs for recursive default/user configuration merging.
analyze-project, build-dependency-graph, and project diagnostics.
analyze-abstract for conservative expanded-syntax analysis.
A rule check receives syntax or #f, a path string, and its merged configuration hash, and returns a list of diagnostics. The CLI adds an linter/internal-error diagnostic when a rule raises an exception.
10 Testing and Reliability
Run the package tests after changing rules or the engine:
raco test tests |
raco setup --pkgs racket-linter |
raco lint --no-config --output json /path/to/a/fixture-project |
Rule tests should assert the diagnostic rule ID, severity, location, and message for positive cases, and explicitly assert zero diagnostics for valid cases. Tests that only assert that a result is a list do not establish rule correctness. Expansion tests must distinguish a valid no-diagnostic result from an expansion failure.
11 Known Limitations
The undefined-identifier rule is a local syntax scanner, not binding-identity analysis. It can require project-specific exclusions.
Project export analysis cannot observe library consumers outside the scanned directory.
The abstract interpreter is conservative and incomplete; it is not a full type system or theorem prover.
The unreachable-code rule is text-based and should be treated as a heuristic.
The check-syntax adapter depends on DrRacket APIs and may not expose every binding diagnostic.
The simplified require/provide graph does not fully model phases, submodules, collection resolution, or dynamic requires.
Configuration evaluation is trusted-code execution.
Auto-fixes are syntax/text transformations and require review.
12 Custom Rules
A custom rule module can export a custom-rules list:
#lang racket/base |
(require racket-linter/core/rule |
racket-linter/core/diagnostic) |
|
(define-rule my/custom-rule |
#:id 'my/custom-rule |
#:severity 'warning |
#:config-keys (hash 'enabled #t) |
#:layer 'text |
(lambda (stx path config) |
'())) |
|
(provide custom-rules) |
(define custom-rules (list my/custom-rule)) |
13 License
MIT