Skip to content
Merged
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
16 changes: 8 additions & 8 deletions lib/ruby_lsp/listeners/hover.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class Hover
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level, position) # rubocop:disable Metrics/ParameterLists
@response_builder = response_builder
@global_state = global_state
@index = global_state.index #: RubyIndexer::Index
@graph = global_state.graph #: Rubydex::Graph
@type_inferrer = global_state.type_inferrer #: TypeInferrer
@path = uri.to_standardized_path #: String?
Expand Down Expand Up @@ -457,21 +456,22 @@ def handle_method_hover(message, inherited_only: false)
type = @type_inferrer.infer_receiver_type(@node_context)
return unless type

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

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

title = "#{message}#{first_method.decorated_parameters}"
title << first_method.formatted_signatures
title = +"#{message}#{method.decorated_parameters}"
title << method.formatted_signatures

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

categorized_markdown_from_index_entries(title, methods).each do |category, content|
categorized_markdown_from_definitions(title, method.definitions).each do |category, content|
@response_builder.push(content, category: category)
end
end
Expand Down
9 changes: 1 addition & 8 deletions lib/ruby_lsp/listeners/signature_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,7 @@ def on_call_node_enter(node)
target_method = owner.find_member("#{message}()")
return unless target_method.is_a?(Rubydex::Method)

signatures = target_method.definitions.flat_map do |defn|
case defn
when Rubydex::MethodDefinition, Rubydex::MethodAliasDefinition
defn.signatures
else
[]
end
end
signatures = target_method.signatures

# If the method doesn't have any signatures, there's nothing to show
return if signatures.empty?
Expand Down
38 changes: 38 additions & 0 deletions lib/ruby_lsp/rubydex/declaration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,44 @@ class Method
def to_lsp_completion_kind
RubyLsp::Constant::CompletionItemKind::METHOD
end

# All signatures collected across every definition (re-opens, RBS overloads, alias targets) of this method.
#: () -> Array[Rubydex::Signature]
def signatures
definitions.flat_map do |defn|
case defn
when Rubydex::MethodDefinition, Rubydex::MethodAliasDefinition
defn.signatures
else
[]
end
end
end

# Decorated parameter list of the first signature, e.g. `(a, b = <default>, &block)`. Returns `()` when there are
# no signatures (e.g. an unresolved alias).
#: () -> String
def decorated_parameters
first = signatures.first
return "()" unless first

"(#{first.format})"
end

# Suffix line that hints at additional overloads beyond the first signature, matching the legacy index entry
# rendering used in hover.
#: () -> String
def formatted_signatures
count = signatures.size
case count
when 0, 1
""
when 2
"\n(+1 overload)"
else
"\n(+#{count - 1} overloads)"
end
end
end

class InstanceVariable
Expand Down
21 changes: 16 additions & 5 deletions test/requests/hover_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -806,19 +806,28 @@ def baz
end

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

source = <<~RUBY
String.try_convert
Foo.new.try_convert
RUBY

with_server(source) do |server, uri|
index = server.instance_variable_get(:@global_state).index
RubyIndexer::RBSIndexer.new(index).index_ruby_core
graph = server.global_state.graph
graph.index_source(rbs_uri, rbs, "rbs")
graph.resolve

server.process_message(
id: 1,
method: "textDocument/hover",
params: { textDocument: { uri: uri }, position: { character: 8, line: 0 } },
params: { textDocument: { uri: uri }, position: { character: 12, line: 0 } },
)

contents = server.pop_response.response.contents.value
Expand Down Expand Up @@ -905,6 +914,8 @@ def baz
end

def test_hover_for_aliased_methods
skip("[RUBYDEX] need to expose method alias targets in the Ruby API")

source = <<~RUBY
class Parent
# Original
Expand Down
Loading