Skip to content

buildrtech/nitrocop

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4,393 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NitroCop

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-prism crate
  • Parallel file processing with rayon

Quick Start (Work in progress 🚧)

Requires Rust 1.85+ (edition 2024).

cargo install nitrocop   # not yet published — build from source for now

Then run it in your Ruby project:

nitrocop

Configuration

nitrocop reads .rubocop.yml with full support for:

  • inherit_from — local files, recursive
  • inherit_gem — resolves gem paths via bundle info
  • inherit_mode — merge/override for arrays
  • Department-level configRSpec:, Rails: Include/Exclude/Enabled
  • AllCopsNewCops, DisabledByDefault, Exclude, Include
  • Enabled: pending tri-state
  • Per-cop optionsEnforcedStyle, Max, AllowedMethods, AllowedPatterns, etc.

Config auto-discovery walks up from the target directory to find .rubocop.yml.

Cops

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.

Hybrid Mode

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" "$@"
fi

CLI

nitrocop [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.

Local Development

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

How It Works

  1. Config resolution — Walks up from target to find .rubocop.yml, resolves inherit_from/inherit_gem chains, merges layers
  2. File discovery — Uses the ignore crate for .gitignore-aware traversal, applies AllCops.Exclude/Include patterns
  3. Parallel linting — Each rayon worker thread parses files with Prism (ParseResult is !Send), runs all enabled cops per file
  4. Cop execution — Three check phases per file: check_lines (raw text), check_source (bytes + CodeMap), check_node (AST walk via batched dispatch table)
  5. Output — RuboCop-compatible text format or JSON

Limitations

These cops are registered but cannot be exercised under current Ruby versions:

  • Lint/ItWithoutArgumentsInBlockit is a block parameter in Ruby 3.4+, making this cop obsolete
  • Lint/NonDeterministicRequireOrderDir results are sorted since Ruby 3.0
  • Lint/NumberedParameterAssignment — assigning to _1 is a syntax error in Ruby 3.4+
  • Lint/UselessElseWithoutRescue — syntax error in Ruby 3.4+
  • Security/YAMLLoadYAML.load is safe since Ruby 3.1 (cop has max Ruby 3.0)

These cops are excluded from corpus reporting counts.

About

Experimental RuboCop rewrite in Rust

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Rust 93.2%
  • Python 6.2%
  • Other 0.6%