diff --git a/lib/ruby_indexer/lib/ruby_indexer/index.rb b/lib/ruby_indexer/lib/ruby_indexer/index.rb index 9703661432..aa03279c67 100644 --- a/lib/ruby_indexer/lib/ruby_indexer/index.rb +++ b/lib/ruby_indexer/lib/ruby_indexer/index.rb @@ -249,16 +249,19 @@ def follow_aliased_namespace(name) def resolve_method(method_name, receiver_name) method_entries = self[method_name] ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::")) - return unless ancestors && method_entries + return unless method_entries && ancestors.any? - T.cast( - method_entries.select do |entry| + ancestors.each do |ancestor| + found = method_entries.select do |entry| next unless entry.is_a?(Entry::Member) - ancestors.any?(entry.owner&.name) - end, - T::Array[Entry::Member], - ) + entry.owner&.name == ancestor + end + + return T.cast(found, T::Array[Entry::Member]) unless found.empty? + end + + nil end # Linearizes the ancestors for a given name, returning the order of namespace in which Ruby will search for method diff --git a/lib/ruby_indexer/test/index_test.rb b/lib/ruby_indexer/test/index_test.rb index d2fe916727..a1e80ae134 100644 --- a/lib/ruby_indexer/test/index_test.rb +++ b/lib/ruby_indexer/test/index_test.rb @@ -635,5 +635,30 @@ class Wow < Bar assert_equal("qux", entry.name) assert_equal("Bar", T.must(entry.owner).name) end + + def test_resolving_an_inherited_method_lands_on_first_match + index(<<~RUBY) + module Foo + def qux; end + end + + class Bar + def qux; end + end + + class Wow < Bar + prepend Foo + + def qux; end + end + RUBY + + entries = T.must(@index.resolve_method("qux", "Wow")) + assert_equal(1, entries.length) + + entry = T.must(entries.first) + assert_equal("qux", entry.name) + assert_equal("Foo", T.must(entry.owner).name) + end end end diff --git a/lib/ruby_lsp/listeners/completion.rb b/lib/ruby_lsp/listeners/completion.rb index e7aff969a4..ade9465581 100644 --- a/lib/ruby_lsp/listeners/completion.rb +++ b/lib/ruby_lsp/listeners/completion.rb @@ -170,9 +170,10 @@ def complete_self_receiver_method(node, name) return unless receiver_entries receiver = T.must(receiver_entries.first) + ancestors = @index.linearized_ancestors_of(receiver.name) @index.prefix_search(name).each do |entries| - entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name } + entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && ancestors.any?(e.owner&.name) } next unless entry @response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node) diff --git a/test/requests/completion_test.rb b/test/requests/completion_test.rb index 703aa7e727..fe42a58668 100644 --- a/test/requests/completion_test.rb +++ b/test/requests/completion_test.rb @@ -588,6 +588,40 @@ def you end end + def test_completion_for_inherited_methods + source = <<~RUBY + module Foo + module First + def method1; end + end + + class Bar + def method2; end + end + + class Baz < Bar + include First + + def do_it + m + end + end + end + RUBY + + with_server(source) do |server, uri| + with_file_structure(server) do + server.process_message(id: 1, method: "textDocument/completion", params: { + textDocument: { uri: uri }, + position: { line: 13, character: 7 }, + }) + + result = server.pop_response.response + assert_equal(["method1", "method2"], result.map(&:label)) + end + end + end + def test_relative_completion_command prefix = "support/" source = <<~RUBY diff --git a/test/requests/definition_expectations_test.rb b/test/requests/definition_expectations_test.rb index 244ed3c17d..8c04d673e9 100644 --- a/test/requests/definition_expectations_test.rb +++ b/test/requests/definition_expectations_test.rb @@ -486,6 +486,47 @@ def baz end end + def test_definition_for_inherited_methods + source = <<~RUBY + module Foo + module First + def method1; end + end + + class Bar + def method2; end + end + + class Baz < Bar + include First + + def method3 + method1 + method2 + end + end + end + RUBY + + with_server(source) do |server, uri| + server.process_message( + id: 1, + method: "textDocument/definition", + params: { textDocument: { uri: uri }, position: { character: 6, line: 13 } }, + ) + response = server.pop_response.response.first + assert_equal(2, response.range.start.line) + + server.process_message( + id: 1, + method: "textDocument/definition", + params: { textDocument: { uri: uri }, position: { character: 6, line: 14 } }, + ) + response = server.pop_response.response.first + assert_equal(6, response.range.start.line) + end + end + private def create_definition_addon diff --git a/test/requests/hover_expectations_test.rb b/test/requests/hover_expectations_test.rb index c0baf1050a..93246ece89 100644 --- a/test/requests/hover_expectations_test.rb +++ b/test/requests/hover_expectations_test.rb @@ -311,6 +311,48 @@ def bar end end + def test_hovering_over_inherited_methods + source = <<~RUBY + module Foo + module First + # Method 1 + def method1; end + end + + class Bar + # Method 2 + def method2; end + end + + class Baz < Bar + include First + + def method3 + method1 + method2 + end + end + end + RUBY + + # Going to definition on `argument` should not take you to the `foo` method definition + with_server(source) do |server, uri| + server.process_message( + id: 1, + method: "textDocument/hover", + params: { textDocument: { uri: uri }, position: { character: 6, line: 15 } }, + ) + assert_match("Method 1", server.pop_response.response.contents.value) + + server.process_message( + id: 1, + method: "textDocument/hover", + params: { textDocument: { uri: uri }, position: { character: 6, line: 16 } }, + ) + assert_match("Method 2", server.pop_response.response.contents.value) + end + end + private def create_hover_addon