Fast Ruby linter in Rust targeting RuboCop compatibility.
Note
🚧 Early-stage: Detection is high-fidelity on most codebases but edge cases remain. Autocorrect is not yet complete. Expect bugs.
Benchmark on the rubygems.org repo (1,226 files, Ruby 4.0), Apple Silicon:
| Scenario | nitrocop | RuboCop | Speedup |
|---|---|---|---|
| Local dev (50 files changed) | 324ms | 1.14s | 3.5x |
| CI (no cache) | 318ms | 18.56s | 58.4x |
Features
- 916 cops from 6 RuboCop gems (rubocop, rubocop-rails, rubocop-performance, rubocop-rspec, rubocop-rspec_rails, rubocop-factory_bot)
- 98.7% conformance against RuboCop across 5,590 open-source repos
- Autocorrect (
-a/-A) is partial — work in progress - Reads your existing
.rubocop.yml— no migration needed - Uses Prism (Ruby's official parser) via
ruby-prismcrate - Parallel file processing with rayon
Requires Rust 1.85+ (edition 2024).
cargo install nitrocop # not yet published — build from source for nowThen run it in your Ruby project:
nitrocopnitrocop reads .rubocop.yml with full support for:
inherit_from— local files, recursiveinherit_gem— resolves gem paths viabundle infoinherit_mode— merge/override for arrays- Department-level config —
RSpec:,Rails:Include/Exclude/Enabled AllCops—NewCops,DisabledByDefault,Exclude,IncludeEnabled: pendingtri-state- Per-cop options —
EnforcedStyle,Max,AllowedMethods,AllowedPatterns, etc.
Config auto-discovery walks up from the target directory to find .rubocop.yml.
nitrocop supports 916 cops from 6 RuboCop gems.
Compared with RuboCop on 5,590 open-source repos (593k Ruby files).
98.7% of compared issue reports matched (28.3M of 28.6M). 502 of 916 cops matched exactly; 408 differed; 1 had no corpus data.
rubocop 1.84.2 (588 cops)
| Department | Cops | Matched exactly | Differed | Matched exactly % |
|---|---|---|---|---|
| Layout | 100 | 36 | 64 | 36.0% |
| Lint | 148 | 87 | 61 | 58.7% |
| Style | 287 | 106 | 181 | 36.9% |
| Metrics | 10 | 1 | 9 | 10.0% |
| Naming | 19 | 11 | 8 | 57.8% |
| Security | 6 | 3 | 3 | 50.0% |
| Bundler | 7 | 6 | 1 | 85.7% |
| Gemspec | 10 | 8 | 2 | 80.0% |
| Migration | 1 | 1 | 0 | ✓ 100.0% |
| Total | 588 | 259 | 329 | 44.0% |
rubocop-rails 2.34.3 (138 cops)
| Department | Cops | Matched exactly | Differed | Matched exactly % |
|---|---|---|---|---|
| Rails | 138 | 111 | 27 | 80.4% |
rubocop-performance 1.26.1 (52 cops)
| Department | Cops | Matched exactly | Differed | Matched exactly % |
|---|---|---|---|---|
| Performance | 52 | 49 | 3 | 94.2% |
rubocop-rspec 3.9.0 (113 cops)
| Department | Cops | Matched exactly | Differed | Matched exactly % |
|---|---|---|---|---|
| RSpec | 113 | 65 | 48 | 57.5% |
rubocop-rspec_rails 2.32.0 (8 cops)
| Department | Cops | Matched exactly | Differed | Matched exactly % |
|---|---|---|---|---|
| RSpecRails | 8 | 7 | 1 | 87.5% |
rubocop-factory_bot 2.28.0 (11 cops)
| Department | Cops | Matched exactly | Differed | Matched exactly % |
|---|---|---|---|---|
| FactoryBot | 11 | 11 | 0 | ✓ 100.0% |
"Matched exactly" means nitrocop produced no extra issues and missed no issues for that cop anywhere in the corpus. No corpus data means the cop never appeared in the corpus, so it has not been compared yet. See docs/corpus.md for the full corpus breakdown.
Every cop reads its RuboCop YAML config options and has fixture-based test coverage.
Use --rubocop-only to run nitrocop alongside RuboCop for cops it doesn't cover yet:
#!/usr/bin/env bash
# bin/lint — fast hybrid linter
nitrocop "$@"
REMAINING=$(nitrocop --rubocop-only)
if [ -n "$REMAINING" ]; then
bundle exec rubocop --only "$REMAINING" "$@"
finitrocop [OPTIONS] [PATHS]...
Arguments:
[PATHS]... Files or directories to lint [default: .]
Options:
-a, --autocorrect Autocorrect offenses (safe cops only)
-A, --autocorrect-all Autocorrect offenses (all cops, including unsafe)
-c, --config <PATH> Path to .rubocop.yml
-f, --format <FORMAT> Output format: text, json [default: text]
--only <COPS> Run only specified cops (comma-separated)
--except <COPS> Skip specified cops (comma-separated)
--rubocop-only Print cops NOT covered by nitrocop
--stdin <PATH> Read source from stdin, use PATH for display
--debug Print timing and debug info
--list-cops List all registered cops
--ignore-disable-comments Ignore all # rubocop:disable inline comments
--cache <true|false> Enable/disable file-level result caching [default: true]
--cache-clear Clear the result cache and exit
--init Pre-resolve gem paths and write lockfile to cache directory, then exit
--fail-level <SEV> Minimum severity for non-zero exit (convention/warning/error/fatal)
-F, --fail-fast Stop after first file with offenses
--force-exclusion Apply AllCops.Exclude to explicitly-passed files
-L, --list-target-files Print files that would be linted, then exit
--force-default-config Ignore all config files, use built-in defaults
-h, --help Print help
nitrocop now auto-refreshes its gem lockfile when it is missing or stale (for example after bundle update). --init remains available as an optional prewarm command.
cargo check # fast compile check
cargo test # run all tests (4,800+)
cargo run -- . # lint current directory
# Quality checks (must pass — zero tolerance)
cargo test config_audit # all YAML config keys implemented
cargo test prism_pitfalls # no missing node type handling
# Benchmarks
cargo run --release --bin bench_nitrocop # full: setup + bench + conform
cargo run --release --bin bench_nitrocop -- bench # timing only- Config resolution — Walks up from target to find
.rubocop.yml, resolvesinherit_from/inherit_gemchains, merges layers - File discovery — Uses the
ignorecrate for .gitignore-aware traversal, applies AllCops.Exclude/Include patterns - Parallel linting — Each rayon worker thread parses files with Prism (
ParseResultis!Send), runs all enabled cops per file - Cop execution — Three check phases per file:
check_lines(raw text),check_source(bytes + CodeMap),check_node(AST walk via batched dispatch table) - Output — RuboCop-compatible text format or JSON
These cops are registered but cannot be exercised under current Ruby versions:
Lint/ItWithoutArgumentsInBlock—itis a block parameter in Ruby 3.4+, making this cop obsoleteLint/NonDeterministicRequireOrder—Dirresults are sorted since Ruby 3.0Lint/NumberedParameterAssignment— assigning to_1is a syntax error in Ruby 3.4+Lint/UselessElseWithoutRescue— syntax error in Ruby 3.4+Security/YAMLLoad—YAML.loadis safe since Ruby 3.1 (cop has max Ruby 3.0)
These cops are excluded from corpus reporting counts.