Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ def initialize(target, index, dispatcher, uri, include_declarations: true)
:on_constant_path_or_write_node_enter,
:on_constant_path_operator_write_node_enter,
:on_constant_path_and_write_node_enter,
:on_constant_or_write_node_enter,
:on_constant_path_node_enter,
:on_constant_read_node_enter,
:on_constant_write_node_enter,
Expand Down
15 changes: 15 additions & 0 deletions lib/ruby_indexer/test/reference_finder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ class Bar
assert_equal(8, refs[2].location.start_line)
end

def test_does_not_duplicate_references_for_or_write_constants
refs = find_const_references("Foo::BAR", <<~RUBY)
module Foo
BAR ||= 1
BAR
end
RUBY

assert_equal(2, refs.size)
assert_equal("BAR", refs[0].name)
assert_equal(2, refs[0].location.start_line)
assert_equal("BAR", refs[1].name)
assert_equal(3, refs[1].location.start_line)
end

def test_finds_constant_references_inside_singleton_contexts
refs = find_const_references("Foo::<Class:Foo>::Bar", <<~RUBY)
class Foo
Expand Down
47 changes: 40 additions & 7 deletions lib/ruby_lsp/requests/references.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def perform
Prism::ConstantReadNode,
Prism::ConstantPathNode,
Prism::ConstantPathTargetNode,
Prism::ConstantWriteNode,
Prism::ConstantOrWriteNode,
Prism::ConstantAndWriteNode,
Prism::ConstantOperatorWriteNode,
Prism::ConstantTargetNode,
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Expand All @@ -53,9 +58,11 @@ def perform
parent,
position,
)
elsif position_outside_target?(position, target)
return @locations
end

target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode,
target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::ConstantWriteNode | Prism::ConstantOrWriteNode | Prism::ConstantAndWriteNode | Prism::ConstantOperatorWriteNode | Prism::ConstantTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode,

reference_target = create_reference_target(target, node_context)
return @locations unless reference_target
Expand All @@ -81,19 +88,21 @@ def perform

private

#: ((Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode) target_node, NodeContext node_context) -> RubyIndexer::ReferenceFinder::Target?
#: ((Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::ConstantWriteNode | Prism::ConstantOrWriteNode | Prism::ConstantAndWriteNode | Prism::ConstantOperatorWriteNode | Prism::ConstantTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode) target_node, NodeContext node_context) -> RubyIndexer::ReferenceFinder::Target?
def create_reference_target(target_node, node_context)
case target_node
when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode
name = RubyIndexer::Index.constant_name(target_node)
return unless name

entries = @global_state.index.resolve(name, node_context.nesting)
return unless entries
resolve_const_reference_target(name, node_context)
when Prism::ConstantWriteNode,
Prism::ConstantOrWriteNode,
Prism::ConstantAndWriteNode,
Prism::ConstantOperatorWriteNode,
Prism::ConstantTargetNode

fully_qualified_name = entries.first #: as !nil
.name
RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
resolve_const_reference_target(target_node.name.to_s, node_context)
when
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Expand All @@ -111,6 +120,30 @@ def create_reference_target(target_node, node_context)
end
end

#: (String name, NodeContext node_context) -> RubyIndexer::ReferenceFinder::ConstTarget?
def resolve_const_reference_target(name, node_context)
entries = @global_state.index.resolve(name, node_context.nesting)
return unless entries

fully_qualified_name = entries.first #: as !nil
.name
RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
end

#: (Hash[Symbol, untyped] position, Prism::Node? target) -> bool
def position_outside_target?(position, target)
case target
when Prism::ConstantWriteNode,
Prism::ConstantOrWriteNode,
Prism::ConstantAndWriteNode,
Prism::ConstantOperatorWriteNode

!covers_position?(target.name_loc, position)
else
false
end
end

#: (RubyIndexer::ReferenceFinder::Target target, Prism::LexResult parse_result, URI::Generic uri) -> void
def collect_references(target, parse_result, uri)
dispatcher = Prism::Dispatcher.new
Expand Down
82 changes: 82 additions & 0 deletions test/requests/references_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,93 @@ def test_finds_constant_references
assert_equal([0, 3], refs)
end

def test_finds_references_from_constant_write_definition
source = <<~RUBY
class Foo
BAR = 1
BAR
end

Foo::BAR
RUBY

refs = find_references_for_source(source, { line: 1, character: 2 }).map do |ref|
ref.range.start.line
end

assert_equal([1, 2, 5], refs)
end

def test_finds_references_from_constant_or_write_definition
source = <<~RUBY
class Foo
BAR ||= 1
BAR
end

Foo::BAR
RUBY

refs = find_references_for_source(source, { line: 1, character: 2 }).map do |ref|
ref.range.start.line
end

assert_equal([1, 2, 5], refs)
end

def test_finds_references_from_constant_operator_write_definition
source = <<~RUBY
class Foo
BAR = 1
BAR += 2
BAR
end
RUBY

refs = find_references_for_source(source, { line: 2, character: 2 }).map do |ref|
ref.range.start.line
end

assert_equal([1, 2, 3], refs)
end

def test_finds_references_from_constant_target_multi_write
source = <<~RUBY
CONST_A, CONST_B = 1, 2
CONST_A
CONST_B
RUBY

refs_a = find_references_for_source(source, { line: 0, character: 0 }).map do |ref|
ref.range.start.line
end
assert_equal([0, 1], refs_a)

refs_b = find_references_for_source(source, { line: 0, character: 9 }).map do |ref|
ref.range.start.line
end
assert_equal([0, 2], refs_b)
end

def test_returns_empty_when_cursor_is_on_constant_assignment_value
source = <<~RUBY
FOO = 123
FOO
RUBY

refs = find_references_for_source(source, { line: 0, character: 7 })
assert_empty(refs)
end

private

def find_references(fixture_path, position)
source = File.read(fixture_path)
path = File.expand_path(fixture_path)
find_references_for_source(source, position, path: path)
end

def find_references_for_source(source, position, path: File.expand_path("test/fixtures/fake.rb"))
global_state = RubyLsp::GlobalState.new
global_state.index.index_single(URI::Generic.from_path(path: path), source)

Expand Down
Loading