= Project Index

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 https://github.com/Shopify/rubydex[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

1. Add `rubydex` to your `Gemfile` and `bundle install`:
+
[source,ruby]
----
gem 'rubydex', require: false
----

2. Set the flag in your `.rubocop.yml`:
+
[source,yaml]
----
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, `AllCops/UseProjectIndex`
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 `AllCops/ProjectIndexIncludesGems: true` additionally indexes the sources of every
gem in the project's bundle:

[source,yaml]
----
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

`Lint/ConstantReassignment` reports reassignments whose previous definition lives in another file.
For example:

[source,ruby]
----
# a.rb
CROSS_FILE_CONST = :first

# b.rb
CROSS_FILE_CONST = :second
----

With `UseProjectIndex: true`, RuboCop reports a `Lint/ConstantReassignment` offense referencing the other file.

`Lint/DuplicateMethods` reports methods whose duplicate definition lives in another file.
For example:

[source,ruby]
----
# a.rb
class Foo
  def bar; end
end

# b.rb
class Foo
  def bar; end
end
----

With `UseProjectIndex: true`, RuboCop reports a `Lint/DuplicateMethods` 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.

`Lint/DeprecatedReference` (pending) is entirely powered by the index: it reports calls to methods
and references to constants documented with a YARD `@deprecated` tag anywhere in the project.

`Lint/UnusedPrivateMethod` (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.

`Style/MissingRespondToMissing` accepts a `respond_to_missing?` defined in another definition
of the same class or module (e.g. a reopening in another file).

`Lint/InheritException` also reports classes that inherit from `Exception` indirectly, through
a parent class defined elsewhere in the project.

`Style/StaticClass` does not report classes that are subclassed anywhere in the project.

`Naming/PredicatePrefix` and `Naming/AccessorMethodName` do not suggest renaming methods
that override a method defined by an ancestor elsewhere in the project.

`Style/ClassAndModuleChildren` 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).

`Lint/MissingSuper` 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`.

`Style/RedundantConstantBase` also reports a leading `::` inside a namespace when the
constant provably resolves identically without it.

`Lint/ConstantResolution` 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.

`Style/Documentation` 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.

`Lint/NameTypo` (pending) is entirely powered by the index: it reports qualified constant
references and constant-receiver method calls that do not resolve anywhere in the project
when a similarly named alternative exists, and suggests it. `CheckConstants` and
`CheckMethods` toggle the two checks independently.

Index-aware cops automatically pick up the index whenever it is built; no per-cop opt-in is required.

== Notes

- `rubydex` requires Ruby 3.2 or newer and ships native (Rust) extensions.
- Parallel inspection is currently disabled on Windows when `UseProjectIndex` is on;
  use serial inspection (omit `--parallel`).
- The index always covers the whole project (rooted at the directory containing `Gemfile`
  or `gems.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 `rubocop` invocation; 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.
