Stylistic Benchmark
This benchmark compares the native corsa stylistic engine (Rust, single source
scan) against the real upstream @stylistic ESLint
plugin (JavaScript, ESLint + a TypeScript parser) on the same corpus and the
same rule set.
Run it with:
node bench/stylistic/compare.mjs
On first run it bootstraps @stylistic/eslint-plugin, eslint, and
@typescript-eslint/parser into a git-ignored .cache/bench_stylistic/
prefix (the same approach as the conformance oracle), then writes
.cache/bench_stylistic.json.
What is measured
- Corpus — the package's own TypeScript sources
(
src/bindings/nodejs/corsa_oxlint/ts): 63 files, ~358 KB, ~11.7 k lines of real-world TS. - Rule set — the intersection of the rules corsa implements and the rules
@stylisticships: 41 rules, each aterrorwith default options. - Workload — both engines lint every file from source on each iteration
(corsa re-lexes;
@stylisticre-parses via ESLint'sLinter), so the comparison is apples-to-apples on identical inputs. - The corsa side is timed by the
bench_stylistic_compareRust binary; the@stylisticside is timed in-process through ESLint'sLinter.verify.
Results
Median milliseconds to lint the whole corpus once (120 iterations, warm):
| engine | median ms | p95 ms | MB/s | diagnostics |
|---|---|---|---|---|
| corsa | 12.66 | 13.14 | 27.60 | 3 662 |
@stylistic |
259.95 | 288.98 | 1.34 | 3 710 |
corsa is ≈20× faster than @stylistic on this workload (the ratio runs
20–25× across machines; corsa's time is steady while ESLint's varies with V8
warm-up and GC).
The two engines report nearly the same number of diagnostics — 3 662 vs
3 710 (98.7 %) — consistent with the
conformance harness: the small gap
is the handful of cases that genuinely need a full AST or type information (for
example a ?: ternary in space-infix-ops), which corsa tracks explicitly
rather than guessing at.
Why the gap
The speed comes from the architecture, not from doing less work per rule:
- One scan, all rules. corsa lexes and bracket-matches each source once
and shares that scan across every enabled rule.
@stylisticruns each rule as a separate ESLint AST visitor over a full parse tree. - No AST round-trip. corsa's rules reason about a flat token stream plus
light structural context;
@stylisticneeds a complete ESTree/TS AST and the ESLint rule runtime (scope analysis, source-code service, per-rule listeners). - Native hot path. the scan and all rule logic are zero-cost Rust; the JS side only batches the request.
This mirrors the single-native-scan model described in the stylistic rules reference.
Caveats
@stylisticoperates on a real AST, so it is correct on constructs corsa's heuristics approximate; the diagnostic-count parity above quantifies how close the two are in practice on real code.- Numbers depend on the corpus and hardware. Re-run
bench/stylistic/compare.mjs(optionally--corpus <dir>/--iterations <n>) to measure your own.
See also
- Stylistic rules — the native scan engine and parity methodology.
- Performance — the broader benchmark suite (CLI, transport, bindings).