Racket Linter v0.2.0
1 Quick Start
2 Configuration
3 Command Options
4 Rule Inventory
5 Analysis Layers
6 Abstract Evaluation
7 Auto-Fix
8 Output Formats
9 Core API
10 Testing and Reliability
11 Known Limitations
12 Custom Rules
13 License
9.2

Racket Linter v0.2.0🔗ℹ

kimmy

 (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

    2 Configuration

    3 Command Options

    4 Rule Inventory

    5 Analysis Layers

    6 Abstract Evaluation

    7 Auto-Fix

    8 Output Formats

    9 Core API

    10 Testing and Reliability

    11 Known Limitations

    12 Custom Rules

    13 License

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