|
Note
|
The project index feature was introduced in RuboCop 1.87. |
|
Warning
|
This feature is experimental and should not be considered stable. Changes to its behavior or interface may occur. |
RuboCop can optionally use Rubydex to build a project-wide index of declarations and references. When enabled, cops that opt in can consult the index to detect issues that span multiple files.
This integration is opt-in and experimental. The default behavior of RuboCop is unchanged.
Enabling
-
Add
rubydexto yourGemfileandbundle install:gem 'rubydex', require: false -
Set the flag in your
.rubocop.yml:AllCops: UseProjectIndex: true
If UseProjectIndex is true but the rubydex gem is not installed, or the running Ruby
is older than the version rubydex supports, RuboCop prints a warning and falls back to
its standard file-local behavior.
The integration requires Ruby 3.2 or later. On Ruby 3.1 and older, <code>AllCops/UseProjectIndex</code>
has no effect even if set to true.
Indexing gem sources
By default the index covers only the project’s own files, so ancestry chains and members that live in gems are unresolvable, and index-aware cops fall back to their conservative behavior whenever one is involved (e.g. a class inheriting from a framework base class). Setting <code>AllCops/ProjectIndexIncludesGems: true</code> additionally indexes the sources of every gem in the project’s bundle:
AllCops:
UseProjectIndex: true
ProjectIndexIncludesGems: true
This makes ancestry-based reasoning conclusive for most real-world classes (on RuboCop’s own repository it raises the share of classes with a fully resolvable ancestry from about 20% to about 97%) at the cost of extra memory proportional to the bundle’s size and a slightly longer index build. The option requires RuboCop to run inside Bundler; outside a bundle it silently degrades to project-only indexing.
What it enables
<code>Lint/ConstantReassignment</code> reports reassignments whose previous definition lives in another file. For example:
# a.rb
CROSS_FILE_CONST = :first
# b.rb
CROSS_FILE_CONST = :second
With <code>UseProjectIndex: true</code>, RuboCop reports a <code>Lint/ConstantReassignment</code> offense referencing the other file.
<code>Lint/DuplicateMethods</code> reports methods whose duplicate definition lives in another file. For example:
# a.rb
class Foo
def ; end
end
# b.rb
class Foo
def ; end
end
With <code>UseProjectIndex: true</code>, RuboCop reports a <code>Lint/DuplicateMethods</code> offense in each file,
referencing the definition in the other one. Redefining a method from another file on purpose
(e.g. a monkey patch) can be signaled with the self-alias trick (alias bar bar right before
the redefinition), which also suppresses Ruby’s method redefinition warning.
<code>Lint/DeprecatedReference</code> (pending) is entirely powered by the index: it reports calls to methods and references to constants documented with a YARD <code>@deprecated</code> tag anywhere in the project.
<code>Lint/UnusedPrivateMethod</code> (disabled by default) uses the index for project-wide dead-code detection: it reports private instance methods whose names are never referenced anywhere in the indexed project. Since symbol-based references from other files (e.g. Rails callbacks declared in a concern) cannot be detected, it is best suited for occasional dead-code sweeps.
<code>Style/MissingRespondToMissing</code> accepts a respond_to_missing? defined in another definition
of the same class or module (e.g. a reopening in another file).
<code>Lint/InheritException</code> also reports classes that inherit from Exception indirectly, through
a parent class defined elsewhere in the project.
<code>Style/StaticClass</code> does not report classes that are subclassed anywhere in the project.
<code>Naming/PredicatePrefix</code> and <code>Naming/AccessorMethodName</code> do not suggest renaming methods that override a method defined by an ancestor elsewhere in the project.
<code>Style/ClassAndModuleChildren</code> uses the index to make its (unsafe) autocorrection more reliable:
when nesting a compact definition (class Foo::Bar), the namespace wrapper’s keyword (class or
module) is resolved from the actual definition of Foo instead of guessed, and compacting a
nested definition is skipped when the outer namespace is not defined elsewhere (the compact form
would raise NameError at load time).
<code>Lint/MissingSuper</code> skips the constructor offense when the class' entire ancestry is resolvable
in the index and no ancestor defines initialize - in that case super would only reach the
no-op Object#initialize. This removes the need to list project-local abstract base classes in
AllowedParentClasses.
<code>Style/RedundantConstantBase</code> also reports a leading <code>::</code> inside a namespace when the constant provably resolves identically without it.
<code>Lint/ConstantResolution</code> reports only genuinely ambiguous constants - those that resolve to a
different declaration through the surrounding nesting than they would fully qualified - which
makes the cop practical to enable without Only/Ignore lists.
<code>Style/Documentation</code> accepts a reopened class or module when any of its other definition sites carries a documentation comment, instead of requiring a comment at every reopening.
Index-aware cops automatically pick up the index whenever it is built; no per-cop opt-in is required.
Notes
-
rubydexrequires Ruby 3.2 or newer and ships native (Rust) extensions. -
Parallel inspection is currently disabled on Windows when
UseProjectIndexis on; use serial inspection (omit--parallel). -
The index always covers the whole project (rooted at the directory containing
Gemfileorgems.rb, falling back to the current directory), regardless of which files a particular run inspects. Inspecting a single file therefore reports the same cross-file offenses as a full run. -
The index is rebuilt once per
rubocopinvocation; no on-disk index is shared between runs. Indexing is fast (roughly 100ms for a couple thousand files), but very large monorepos will notice the per-run cost on single-file runs.