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
25 changes: 20 additions & 5 deletions lib/ruby_lsp/listeners/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,35 @@ def on_string_node_enter(node)
return unless enclosing_call

name = enclosing_call.name
return unless name == :require || name == :require_relative
case name
when :require, :require_relative
handle_require_definition(node, name)
when :send, :public_send
first_argument = enclosing_call.arguments.arguments.first
return unless first_argument.eql?(node)

handle_require_definition(node, name)
method_name = node.content

handle_method_definition(method_name, nil)
end
end

#: (Prism::SymbolNode node) -> void
def on_symbol_node_enter(node)
enclosing_call = @node_context.call_node
return unless enclosing_call

name = enclosing_call.name
return unless name == :autoload
case enclosing_call.name
when :autoload
handle_autoload_definition(enclosing_call)
when :send, :public_send
first_argument = enclosing_call.arguments.arguments.first
return unless first_argument.eql?(node)

handle_autoload_definition(enclosing_call)
method_name = node.unescaped

handle_method_definition(method_name, nil)
end
end

#: (Prism::BlockArgumentNode node) -> void
Expand Down
156 changes: 156 additions & 0 deletions test/requests/definition_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,162 @@ class Foo::Bar; end
end
end

def test_go_to_definition_for_send_symbol
source = <<~RUBY
class Foo
def foo; end
end

obj = Foo.new
obj.send(:foo)
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 11, line: 5 } },
)
response = server.pop_response.response.first
assert_equal(1, response.target_range.start.line)
assert_equal(1, response.target_range.end.line)
end
end

def test_go_to_definition_for_send_symbol_multiple_objects
source = <<~RUBY
class Foo
def foo; end
end

class Bar
def foo; end
end

f = Foo.new
b = Bar.new

f.send(:foo)
b.send(:foo)
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 9, line: 11 } },
)
response = server.pop_response.response
assert_equal(2, response.size)

assert_equal(1, response[0].target_range.start.line)
assert_equal(1, response[0].target_range.end.line)

assert_equal(5, response[1].target_range.start.line)
assert_equal(5, response[1].target_range.end.line)
end
end

def test_go_to_definition_ignores_non_method_send_symbol
source = <<~RUBY
class Foo
def foo; end
end

obj = Foo.new
obj.send(:foo, :foo)
RUBY

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

assert_empty(response)
end
end

def test_go_to_definition_for_send_string
source = <<~RUBY
class Foo
def foo; end
end

obj = Foo.new
obj.send("foo")
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 2,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 11, line: 5 } },
)
response = server.pop_response.response.first
assert_equal(1, response.target_range.start.line)
assert_equal(1, response.target_range.end.line)
end
end

def test_go_to_definition_for_send_string_multiple_objects
source = <<~RUBY
class Foo
def foo; end
end

class Bar
def foo; end
end

f = Foo.new
b = Bar.new

f.send("foo")
b.send("foo")
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 9, line: 11 } },
)
response = server.pop_response.response
assert_equal(2, response.size)

assert_equal(1, response[0].target_range.start.line)
assert_equal(1, response[0].target_range.end.line)

assert_equal(5, response[1].target_range.start.line)
assert_equal(5, response[1].target_range.end.line)
end
end

def test_go_to_definition_ignores_non_method_string_symbol
source = <<~RUBY
class Foo
def foo; end
end

obj = Foo.new
obj.send("foo", "foo")
RUBY

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

assert_empty(response)
end
end

def test_does_nothing_when_autoload_declaration_does_not_exist
source = <<~RUBY
# typed: ignore
Expand Down