Skip to content

Commit 040b237

Browse files
authored
Use Rubydex for method hover (#4099)
1 parent 6fa9320 commit 040b237

4 files changed

Lines changed: 63 additions & 21 deletions

File tree

lib/ruby_lsp/listeners/hover.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class Hover
1515
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level, position) # rubocop:disable Metrics/ParameterLists
1616
@response_builder = response_builder
1717
@global_state = global_state
18-
@index = global_state.index #: RubyIndexer::Index
1918
@graph = global_state.graph #: Rubydex::Graph
2019
@type_inferrer = global_state.type_inferrer #: TypeInferrer
2120
@path = uri.to_standardized_path #: String?
@@ -457,21 +456,22 @@ def handle_method_hover(message, inherited_only: false)
457456
type = @type_inferrer.infer_receiver_type(@node_context)
458457
return unless type
459458

460-
methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
461-
return unless methods
459+
owner = @graph[type.name]
460+
return unless owner.is_a?(Rubydex::Namespace)
462461

463-
first_method = methods.first #: as !nil
464-
return unless method_reachable_from_call_site?(first_method, type, @graph, @node_context)
462+
method = owner.find_member("#{message}()", only_inherited: inherited_only)
463+
return unless method.is_a?(Rubydex::Method)
464+
return unless method_reachable_from_call_site?(method, type, @graph, @node_context)
465465

466-
title = "#{message}#{first_method.decorated_parameters}"
467-
title << first_method.formatted_signatures
466+
title = +"#{message}#{method.decorated_parameters}"
467+
title << method.formatted_signatures
468468

469469
if type.is_a?(TypeInferrer::GuessedType)
470470
title << "\n\nGuessed receiver: #{type.name}"
471471
@response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
472472
end
473473

474-
categorized_markdown_from_index_entries(title, methods).each do |category, content|
474+
categorized_markdown_from_definitions(title, method.definitions).each do |category, content|
475475
@response_builder.push(content, category: category)
476476
end
477477
end

lib/ruby_lsp/listeners/signature_help.rb

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,7 @@ def on_call_node_enter(node)
3333
target_method = owner.find_member("#{message}()")
3434
return unless target_method.is_a?(Rubydex::Method)
3535

36-
signatures = target_method.definitions.flat_map do |defn|
37-
case defn
38-
when Rubydex::MethodDefinition, Rubydex::MethodAliasDefinition
39-
defn.signatures
40-
else
41-
[]
42-
end
43-
end
36+
signatures = target_method.signatures
4437

4538
# If the method doesn't have any signatures, there's nothing to show
4639
return if signatures.empty?

lib/ruby_lsp/rubydex/declaration.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,44 @@ class Method
108108
def to_lsp_completion_kind
109109
RubyLsp::Constant::CompletionItemKind::METHOD
110110
end
111+
112+
# All signatures collected across every definition (re-opens, RBS overloads, alias targets) of this method.
113+
#: () -> Array[Rubydex::Signature]
114+
def signatures
115+
definitions.flat_map do |defn|
116+
case defn
117+
when Rubydex::MethodDefinition, Rubydex::MethodAliasDefinition
118+
defn.signatures
119+
else
120+
[]
121+
end
122+
end
123+
end
124+
125+
# Decorated parameter list of the first signature, e.g. `(a, b = <default>, &block)`. Returns `()` when there are
126+
# no signatures (e.g. an unresolved alias).
127+
#: () -> String
128+
def decorated_parameters
129+
first = signatures.first
130+
return "()" unless first
131+
132+
"(#{first.format})"
133+
end
134+
135+
# Suffix line that hints at additional overloads beyond the first signature, matching the legacy index entry
136+
# rendering used in hover.
137+
#: () -> String
138+
def formatted_signatures
139+
count = signatures.size
140+
case count
141+
when 0, 1
142+
""
143+
when 2
144+
"\n(+1 overload)"
145+
else
146+
"\n(+#{count - 1} overloads)"
147+
end
148+
end
111149
end
112150

113151
class InstanceVariable

test/requests/hover_expectations_test.rb

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -806,19 +806,28 @@ def baz
806806
end
807807

808808
def test_hover_for_methods_shows_overload_count
809-
skip("[RUBYDEX] Temporarily skipped because we don't yet index RBS methods")
809+
rbs = <<~RBS
810+
class Foo
811+
def try_convert: (Object object) -> String?
812+
| (String s) -> String
813+
| (Symbol s) -> String
814+
end
815+
RBS
816+
rbs_uri = URI::Generic.from_path(path: "/fake/path/foo.rbs").to_s
810817

811818
source = <<~RUBY
812-
String.try_convert
819+
Foo.new.try_convert
813820
RUBY
814821

815822
with_server(source) do |server, uri|
816-
index = server.instance_variable_get(:@global_state).index
817-
RubyIndexer::RBSIndexer.new(index).index_ruby_core
823+
graph = server.global_state.graph
824+
graph.index_source(rbs_uri, rbs, "rbs")
825+
graph.resolve
826+
818827
server.process_message(
819828
id: 1,
820829
method: "textDocument/hover",
821-
params: { textDocument: { uri: uri }, position: { character: 8, line: 0 } },
830+
params: { textDocument: { uri: uri }, position: { character: 12, line: 0 } },
822831
)
823832

824833
contents = server.pop_response.response.contents.value
@@ -905,6 +914,8 @@ def baz
905914
end
906915

907916
def test_hover_for_aliased_methods
917+
skip("[RUBYDEX] need to expose method alias targets in the Ruby API")
918+
908919
source = <<~RUBY
909920
class Parent
910921
# Original

0 commit comments

Comments
 (0)