Skip to content

Support finding references from constant definition sites - #4264

Open
reginaldalfret wants to merge 1 commit into
Shopify:mainfrom
reginaldalfret:fix/shopify-ruby-lsp-constant-definition-references
Open

reginaldalfret wants to merge 1 commit into
Shopify:mainfrom
reginaldalfret:fix/shopify-ruby-lsp-constant-definition-references

Conversation

@reginaldalfret

Copy link
Copy Markdown

Motivation

Currently, triggering textDocument/references ("Find All References" or Shift+F12 in VS Code) while the cursor is placed on a constant definition site (such as FOO = 1, BAR ||= 2, or A, B = 1, 2) yields "No references found".

This happens because RubyLsp::Requests::References#perform delegates node selection to RubyDocument#locate, which was configured to only accept:

  • Prism::ConstantReadNode
  • Prism::ConstantPathNode
  • Prism::ConstantPathTargetNode

Consequently, constant writes (ConstantWriteNode, ConstantOrWriteNode, ConstantAndWriteNode, ConstantOperatorWriteNode, and ConstantTargetNode) were excluded during node location, making it impossible to search for references directly from the constant's definition or assignment site.

Additionally, in RubyIndexer::ReferenceFinder#initialize, :on_constant_or_write_node_enter was accidentally registered twice with the dispatcher, causing conditional assignments like CONST ||= 1 to emit duplicated references.

Implementation

  1. Include Constant Write and Target Nodes in References Locate:

    • Added Prism::ConstantWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, and Prism::ConstantTargetNode to the node selection in RubyDocument#locate.
    • Updated the Sorbet type signatures to include these nodes.
  2. Handle Definition Site Resolution:

    • Extracted helper resolve_const_reference_target to look up index entries using index.resolve with node_context.nesting.
    • For constant writes and targets, resolve target_node.name.to_s into a RubyIndexer::ReferenceFinder::ConstTarget.
  3. Guard Against Value-Position Cursoring:

    • Added position_outside_target? ensuring that references are only triggered when the cursor actually covers the constant's name location (target.name_loc), returning empty locations if the cursor is resting inside the RHS expression/value.
  4. Deduplicate Dispatcher Registration in ReferenceFinder:

    • Removed duplicate :on_constant_or_write_node_enter entry from dispatcher.register in RubyIndexer::ReferenceFinder.

Automated Tests

  • Added 5 new regression tests in test/requests/references_test.rb:
    • test_finds_references_from_constant_write_definition
    • test_finds_references_from_constant_or_write_definition
    • test_finds_references_from_constant_operator_write_definition
    • test_finds_references_from_constant_target_multi_write
    • test_returns_empty_when_cursor_is_on_constant_assignment_value
  • Added 1 new test in lib/ruby_indexer/test/reference_finder_test.rb:
    • test_does_not_duplicate_references_for_or_write_constants
  • Verified Sorbet type check passes cleanly with spoom srb tc.
  • Verified RuboCop passes with 0 offenses across modified files.

Manual Tests

  1. In VS Code with Ruby LSP active, open a Ruby file containing:
    class Foo
      BAR = 123
      BAR
    end
    
    Foo::BAR
  2. Place the cursor on BAR in BAR = 123 (line 2).
  3. Execute "Find All References" (Shift+F12 / References: Find All References).
  4. Verify that all 3 occurrences (line 2 definition, line 3 read, and line 6 constant path read) are returned.
  5. Place the cursor on 123 on line 2 and execute "Find All References".
  6. Verify no constant references are returned.

When invoking Find References on a constant definition (such as ConstantWriteNode, ConstantOrWriteNode, ConstantAndWriteNode, ConstantOperatorWriteNode, or ConstantTargetNode), Ruby LSP previously returned empty results because locate was only matching read nodes and constant path nodes.

This commit expands RubyDocument.locate to locate constant write/target nodes, resolves their fully qualified name using index.resolve, and guards against invoking references when the cursor is in the value position of the assignment.

Additionally, this removes a duplicate :on_constant_or_write_node_enter dispatcher listener in RubyIndexer::ReferenceFinder that caused or-write constants (CONST ||= 1) to be emitted twice.

Signed-off-by: Reginald Alfret <reginaldalfret@gmail.com>
@reginaldalfret
reginaldalfret requested a review from a team as a code owner November 7, 2026 09:11

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant