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
15 changes: 15 additions & 0 deletions lib/ruby_lsp/listeners/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def initialize(response_builder, global_state, language_id, uri, node_context, d
dispatcher.register(
self,
:on_call_node_enter,
:on_def_node_enter,
:on_block_argument_node_enter,
:on_constant_read_node_enter,
:on_constant_path_node_enter,
Expand Down Expand Up @@ -69,6 +70,20 @@ def on_call_node_enter(node)
handle_method_definition(message, inferrer_receiver_type)
end

# On the name of a method declaration, the declaration itself is the definition. Returning it lets editors fall
# back to their alternative action, such as showing references in VS Code
#: (Prism::DefNode node) -> void
def on_def_node_enter(node)
# Sorbet can handle go to definition for methods declared on typed true or higher
return if @sorbet_level.true_or_higher?

@response_builder << Interface::LocationLink.new(
target_uri: @uri.to_s,
target_range: range_from_location(node.location),
target_selection_range: range_from_location(node.name_loc),
)
end

#: (Prism::StringNode node) -> void
def on_string_node_enter(node)
with_enclosing_call(node) do |enclosing_call, name|
Expand Down
4 changes: 3 additions & 1 deletion lib/ruby_lsp/requests/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def initialize(document, global_state, position, dispatcher, sorbet_level)
char_position,
node_types: [
Prism::CallNode,
Prism::DefNode,
Prism::ConstantReadNode,
Prism::ConstantPathNode,
Prism::BlockArgumentNode,
Expand Down Expand Up @@ -109,7 +110,8 @@ def position_outside_target?(position, target)
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableWriteNode
Prism::InstanceVariableWriteNode,
Prism::DefNode

!covers_position?(target.name_loc, position)
when Prism::CallNode
Expand Down
93 changes: 93 additions & 0 deletions test/requests/definition_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,99 @@ def baz
end
end

def test_definition_on_method_name_returns_the_method_itself
source = <<~RUBY
class Foo
def bar(baz)
baz
end
end
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 7, line: 1 } },
)

response = server.pop_response.response
assert_equal(1, response.length)

link = response.first
assert_equal(uri.to_s, link.target_uri)

selection_range = link.target_selection_range
assert_equal([[1, 6], [1, 9]], [selection_range.start, selection_range.end].map { |p| [p.line, p.character] })
assert_equal([1, 3], [link.target_range.start.line, link.target_range.end.line])
end
end

def test_definition_on_singleton_method_name_returns_the_method_itself
source = <<~RUBY
class Foo
def self.bar; end
end
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 12, line: 1 } },
)

response = server.pop_response.response
assert_equal(1, response.length)

selection_range = response.first.target_selection_range
assert_equal([[1, 11], [1, 14]], [selection_range.start, selection_range.end].map { |p| [p.line, p.character] })
end
end

def test_definition_inside_method_declaration_outside_its_name_returns_nothing
source = <<~RUBY
class Foo
def bar(baz)
baz
end
end
RUBY

with_server(source) do |server, uri|
# The `def` keyword, the parameter and the local variable in the body
[{ character: 3, line: 1 }, { character: 11, line: 1 }, { character: 5, line: 2 }].each do |position|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: position },
)

assert_empty(server.pop_response.response)
end
end
end

def test_definition_on_method_name_is_disabled_for_typed_true
source = <<~RUBY
# typed: true
class Foo
def bar
end
end
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 7, line: 2 } },
)

assert_empty(server.pop_response.response)
end
end

def test_definition_for_instance_variables_is_disabled_on_typed_strict
source = <<~RUBY
# typed: strict
Expand Down
Loading