Skip to content

Commit b80473a

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

File tree

2 files changed

+176
-5
lines changed

2 files changed

+176
-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+
return unless first_argument.eql?(node)
7984

80-
handle_require_definition(node, name)
85+
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+
return unless first_argument.eql?(node)
90102

91-
handle_autoload_definition(enclosing_call)
103+
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: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,162 @@ 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_ignores_non_method_send_symbol
468+
source = <<~RUBY
469+
class Foo
470+
def foo; end
471+
end
472+
473+
obj = Foo.new
474+
obj.send(:foo, :foo)
475+
RUBY
476+
477+
with_server(source) do |server, uri|
478+
server.process_message(
479+
id: 1,
480+
method: "textDocument/definition",
481+
params: { textDocument: { uri: uri }, position: { character: 17, line: 5 } },
482+
)
483+
response = server.pop_response.response
484+
485+
assert_empty(response)
486+
end
487+
end
488+
489+
def test_go_to_definition_for_send_string
490+
source = <<~RUBY
491+
class Foo
492+
def foo; end
493+
end
494+
495+
obj = Foo.new
496+
obj.send("foo")
497+
RUBY
498+
499+
with_server(source) do |server, uri|
500+
server.process_message(
501+
id: 2,
502+
method: "textDocument/definition",
503+
params: { textDocument: { uri: uri }, position: { character: 11, line: 5 } },
504+
)
505+
response = server.pop_response.response.first
506+
assert_equal(1, response.target_range.start.line)
507+
assert_equal(1, response.target_range.end.line)
508+
end
509+
end
510+
511+
def test_go_to_definition_for_send_string_multiple_objects
512+
source = <<~RUBY
513+
class Foo
514+
def foo; end
515+
end
516+
517+
class Bar
518+
def foo; end
519+
end
520+
521+
f = Foo.new
522+
b = Bar.new
523+
524+
f.send("foo")
525+
b.send("foo")
526+
RUBY
527+
528+
with_server(source) do |server, uri|
529+
server.process_message(
530+
id: 1,
531+
method: "textDocument/definition",
532+
params: { textDocument: { uri: uri }, position: { character: 9, line: 11 } },
533+
)
534+
response = server.pop_response.response
535+
assert_equal(2, response.size)
536+
537+
assert_equal(1, response[0].target_range.start.line)
538+
assert_equal(1, response[0].target_range.end.line)
539+
540+
assert_equal(5, response[1].target_range.start.line)
541+
assert_equal(5, response[1].target_range.end.line)
542+
end
543+
end
544+
545+
def test_go_to_definition_ignores_non_method_string_symbol
546+
source = <<~RUBY
547+
class Foo
548+
def foo; end
549+
end
550+
551+
obj = Foo.new
552+
obj.send("foo", "foo")
553+
RUBY
554+
555+
with_server(source) do |server, uri|
556+
server.process_message(
557+
id: 1,
558+
method: "textDocument/definition",
559+
params: { textDocument: { uri: uri }, position: { character: 17, line: 5 } },
560+
)
561+
response = server.pop_response.response
562+
563+
assert_empty(response)
564+
end
565+
end
566+
411567
def test_does_nothing_when_autoload_declaration_does_not_exist
412568
source = <<~RUBY
413569
# typed: ignore

0 commit comments

Comments
 (0)