Skip to content

Commit bbe7546

Browse files
committed
Implement goto definition for send and public send
1 parent e0b26e1 commit bbe7546

File tree

2 files changed

+132
-5
lines changed

2 files changed

+132
-5
lines changed

lib/ruby_lsp/listeners/definition.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,35 @@ def on_string_node_enter(node)
7575
return unless enclosing_call
7676

7777
name = enclosing_call.name
78-
return unless name == :require || name == :require_relative
78+
case name
79+
when :require, :require_relative
80+
handle_require_definition(node, name)
81+
when :send, :public_send
82+
first_argument = enclosing_call.arguments.arguments.first
83+
method_name = first_argument.is_a?(Prism::StringNode) ? first_argument.content : first_argument.unescaped
7984

80-
handle_require_definition(node, name)
85+
return unless method_name == node.content
86+
87+
handle_method_definition(method_name, nil)
88+
end
8189
end
8290

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

88-
name = enclosing_call.name
89-
return unless name == :autoload
96+
case enclosing_call.name
97+
when :autoload
98+
handle_autoload_definition(enclosing_call)
99+
when :send, :public_send
100+
first_argument = enclosing_call.arguments.arguments.first
101+
method_name = first_argument.is_a?(Prism::StringNode) ? first_argument.content : first_argument.unescaped
90102

91-
handle_autoload_definition(enclosing_call)
103+
return unless method_name == node.unescaped
104+
105+
handle_method_definition(method_name, nil)
106+
end
92107
end
93108

94109
#: (Prism::BlockArgumentNode node) -> void

test/requests/definition_expectations_test.rb

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,118 @@ class Foo::Bar; end
408408
end
409409
end
410410

411+
def test_go_to_definition_for_send_symbol
412+
source = <<~RUBY
413+
class Foo
414+
def foo; end
415+
end
416+
417+
obj = Foo.new
418+
obj.send(:foo)
419+
RUBY
420+
421+
with_server(source) do |server, uri|
422+
server.process_message(
423+
id: 1,
424+
method: "textDocument/definition",
425+
params: { textDocument: { uri: uri }, position: { character: 11, line: 5 } }
426+
)
427+
response = server.pop_response.response.first
428+
assert_equal(1, response.target_range.start.line)
429+
assert_equal(1, response.target_range.end.line)
430+
end
431+
end
432+
433+
def test_go_to_definition_for_send_symbol_multiple_objects
434+
source = <<~RUBY
435+
class Foo
436+
def foo; end
437+
end
438+
439+
class Bar
440+
def foo; end
441+
end
442+
443+
f = Foo.new
444+
b = Bar.new
445+
446+
f.send(:foo)
447+
b.send(:foo)
448+
RUBY
449+
450+
with_server(source) do |server, uri|
451+
server.process_message(
452+
id: 1,
453+
method: "textDocument/definition",
454+
params: { textDocument: { uri: uri }, position: { character: 9, line: 11 } }
455+
)
456+
response = server.pop_response.response
457+
assert_equal(2, response.size)
458+
459+
assert_equal(1, response[0].target_range.start.line)
460+
assert_equal(1, response[0].target_range.end.line)
461+
462+
assert_equal(5, response[1].target_range.start.line)
463+
assert_equal(5, response[1].target_range.end.line)
464+
end
465+
end
466+
467+
def test_go_to_definition_for_send_string
468+
source = <<~RUBY
469+
class Foo
470+
def foo; end
471+
end
472+
473+
obj = Foo.new
474+
obj.send("foo")
475+
RUBY
476+
477+
with_server(source) do |server, uri|
478+
server.process_message(
479+
id: 2,
480+
method: "textDocument/definition",
481+
params: { textDocument: { uri: uri }, position: { character: 11, line: 5 } }
482+
)
483+
response = server.pop_response.response.first
484+
assert_equal(1, response.target_range.start.line)
485+
assert_equal(1, response.target_range.end.line)
486+
end
487+
end
488+
489+
def test_go_to_definition_for_send_string_multiple_objects
490+
source = <<~RUBY
491+
class Foo
492+
def foo; end
493+
end
494+
495+
class Bar
496+
def foo; end
497+
end
498+
499+
f = Foo.new
500+
b = Bar.new
501+
502+
f.send("foo")
503+
b.send("foo")
504+
RUBY
505+
506+
with_server(source) do |server, uri|
507+
server.process_message(
508+
id: 1,
509+
method: "textDocument/definition",
510+
params: { textDocument: { uri: uri }, position: { character: 9, line: 11 } }
511+
)
512+
response = server.pop_response.response
513+
assert_equal(2, response.size)
514+
515+
assert_equal(1, response[0].target_range.start.line)
516+
assert_equal(1, response[0].target_range.end.line)
517+
518+
assert_equal(5, response[1].target_range.start.line)
519+
assert_equal(5, response[1].target_range.end.line)
520+
end
521+
end
522+
411523
def test_does_nothing_when_autoload_declaration_does_not_exist
412524
source = <<~RUBY
413525
# typed: ignore

0 commit comments

Comments
 (0)