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,222 files), Apple Silicon:
| Scenario | nitrocop | RuboCop | Speedup |
|---|---|---|---|
| Local dev (50 files changed) | 64ms | 1.39s | 21.7x |
| CI (no cache) | 207ms | 18.21s | 87.8x |
Features
- 915 cops from 6 RuboCop gems (rubocop, rubocop-rails, rubocop-performance, rubocop-rspec, rubocop-rspec_rails, rubocop-factory_bot)
- 95.1% conformance against RuboCop across 1,000 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 915 cops from 6 RuboCop gems:
| Gem | Version | Cops | Coverage | Departments |
|---|---|---|---|---|
| rubocop | 1.84.2 | 593 | 100% | Layout, Lint, Style, Metrics, Naming, Security, Bundler, Gemspec, Migration |
| rubocop-rails | 2.34.3 | 138 | 100% | Rails |
| rubocop-performance | 1.26.1 | 52 | 100% | Performance |
| rubocop-rspec | 3.9.0 | 113 | 100% | RSpec |
| rubocop-rspec_rails | 2.32.0 | 8 | 100% | RSpecRails |
| rubocop-factory_bot | 2.28.0 | 11 | 100% | FactoryBot |
Every cop reads its RuboCop YAML config options and has fixture-based test coverage.
We diff nitrocop against RuboCop on 1,000 open-source repos (231k Ruby files) with all cops enabled. Every offense is compared by file, line, and cop name.
| Count | Rate | |
|---|---|---|
| Agreed | 11.4M | 95.2% |
| nitrocop extra (FP) | 63.2K | 0.5% |
| nitrocop missed (FN) | 514.5K | 4.3% |
Per-repo results (top 15 by GitHub stars):
| Repo | .rb files | RuboCop offenses | nitrocop extra (FP) | nitrocop missed (FN) | Agreement |
|---|---|---|---|---|---|
| rails | 3,498 | 314,853 | 967 | 17,139 | 94.2% |
| jekyll | 190 | 13,052 | 75 | 737 | 93.8% |
| mastodon | 3,123 | 76,303 | 108 | 2,550 | 96.5% |
| huginn | 451 | 34,402 | 154 | 961 | 96.7% |
| discourse | 9,172 | 619,551 | 2,186 | 13,691 | 97.4% |
| fastlane | 1,302 | 118,730 | 200 | 3,012 | 97.3% |
| devdocs | 833 | 19,903 | 118 | 1,283 | 93.0% |
| chatwoot | 2,262 | 64,941 | 59 | 1,496 | 97.6% |
| vagrant | 1,460 | 86,064 | 217 | 2,946 | 96.3% |
| devise | 206 | 5,800 | 20 | 411 | 92.5% |
| forem | 3,390 | 128,530 | 262 | 4,330 | 96.4% |
| postal | 294 | 13,948 | 24 | 715 | 94.7% |
| CocoaPods | 438 | 28,422 | 270 | 1,944 | 92.2% |
| openproject | 9,286 | 388,980 | 645 | 10,574 | 97.1% |
| gollum | 55 | 3,790 | 21 | 295 | 91.7% |
Remaining gaps are mostly in complex layout cops (indentation, alignment) and a few style cops. See docs/corpus.md for the full corpus breakdown.
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 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
cargo check # fast compile check
cargo test # run all tests (2,700+)
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