Skip to content

Commit 97e72d3

Browse files
authored
Support go-to-definition for let(!) and subject declaration (#38)
1 parent fea0494 commit 97e72d3

File tree

5 files changed

+372
-3
lines changed

5 files changed

+372
-3
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ GEM
1414
diff-lcs (1.5.1)
1515
erubi (1.13.0)
1616
io-console (0.7.2)
17-
irb (1.14.0)
17+
irb (1.14.1)
1818
rdoc (>= 4.0.0)
1919
reline (>= 0.4.2)
2020
json (2.7.2)
@@ -126,4 +126,4 @@ DEPENDENCIES
126126
tapioca (~> 0.11)
127127

128128
BUNDLED WITH
129-
2.5.4
129+
2.5.11

lib/ruby_lsp/ruby_lsp_rspec/addon.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66

77
require_relative "code_lens"
88
require_relative "document_symbol"
9+
require_relative "definition"
10+
require_relative "indexing_enhancement"
911

1012
module RubyLsp
1113
module RSpec
1214
class Addon < ::RubyLsp::Addon
1315
extend T::Sig
1416

1517
sig { override.params(global_state: GlobalState, message_queue: Thread::Queue).void }
16-
def activate(global_state, message_queue); end
18+
def activate(global_state, message_queue)
19+
@index = T.let(global_state.index, T.nilable(RubyIndexer::Index))
20+
global_state.index.register_enhancement(IndexingEnhancement.new)
21+
end
1722

1823
sig { override.void }
1924
def deactivate; end
@@ -47,6 +52,21 @@ def create_document_symbol_listener(response_builder, dispatcher)
4752
DocumentSymbol.new(response_builder, dispatcher)
4853
end
4954

55+
sig do
56+
override.params(
57+
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
58+
Interface::Location,
59+
Interface::LocationLink,
60+
)],
61+
uri: URI::Generic,
62+
node_context: NodeContext,
63+
dispatcher: Prism::Dispatcher,
64+
).void
65+
end
66+
def create_definition_listener(response_builder, uri, node_context, dispatcher)
67+
Definition.new(response_builder, uri, node_context, T.must(@index), dispatcher)
68+
end
69+
5070
sig { override.returns(String) }
5171
def name
5272
"Ruby LSP RSpec"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module RSpec
6+
class Definition
7+
extend T::Sig
8+
9+
include ::RubyLsp::Requests::Support::Common
10+
11+
sig do
12+
params(
13+
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
14+
Interface::Location,
15+
Interface::LocationLink,
16+
)],
17+
uri: URI::Generic,
18+
node_context: NodeContext,
19+
index: RubyIndexer::Index,
20+
dispatcher: Prism::Dispatcher,
21+
).void
22+
end
23+
def initialize(response_builder, uri, node_context, index, dispatcher)
24+
@response_builder = response_builder
25+
@uri = uri
26+
@node_context = node_context
27+
@index = index
28+
dispatcher.register(self, :on_call_node_enter)
29+
end
30+
31+
sig { params(node: Prism::CallNode).void }
32+
def on_call_node_enter(node)
33+
message = node.message
34+
return unless message
35+
36+
return if @node_context.locals_for_scope.include?(message)
37+
38+
entries = @index[message]
39+
return unless entries
40+
return if entries.empty?
41+
42+
entries.each do |entry|
43+
# Technically, let can be defined in a different file, but we're not going to handle that case yet
44+
next unless entry.file_path == @uri.to_standardized_path
45+
46+
@response_builder << Interface::LocationLink.new(
47+
target_uri: URI::Generic.from_path(path: entry.file_path).to_s,
48+
target_range: range_from_location(entry.location),
49+
target_selection_range: range_from_location(entry.name_location),
50+
)
51+
end
52+
end
53+
end
54+
end
55+
end
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module RSpec
6+
class IndexingEnhancement
7+
extend T::Sig
8+
include RubyIndexer::Enhancement
9+
10+
sig do
11+
override.params(
12+
index: RubyIndexer::Index,
13+
owner: T.nilable(RubyIndexer::Entry::Namespace),
14+
node: Prism::CallNode,
15+
file_path: String,
16+
).void
17+
end
18+
def on_call_node(index, owner, node, file_path)
19+
return if node.receiver
20+
21+
name = node.name
22+
23+
case name
24+
when :let, :let!
25+
block_node = node.block
26+
return unless block_node
27+
28+
arguments = node.arguments
29+
return unless arguments
30+
31+
return if arguments.arguments.count != 1
32+
33+
method_name_node = T.must(arguments.arguments.first)
34+
35+
method_name = case method_name_node
36+
when Prism::StringNode
37+
method_name_node.slice
38+
when Prism::SymbolNode
39+
method_name_node.unescaped
40+
end
41+
42+
return unless method_name
43+
44+
index.add(RubyIndexer::Entry::Method.new(
45+
method_name,
46+
file_path,
47+
block_node.location,
48+
block_node.location,
49+
nil,
50+
index.configuration.encoding,
51+
[RubyIndexer::Entry::Signature.new([])],
52+
RubyIndexer::Entry::Visibility::PUBLIC,
53+
owner,
54+
))
55+
when :subject, :subject!
56+
block_node = node.block
57+
return unless block_node
58+
59+
arguments = node.arguments
60+
61+
if arguments && arguments.arguments.count == 1
62+
method_name_node = T.must(arguments.arguments.first)
63+
end
64+
65+
method_name = if method_name_node
66+
case method_name_node
67+
when Prism::StringNode
68+
method_name_node.slice
69+
when Prism::SymbolNode
70+
method_name_node.unescaped
71+
end
72+
else
73+
"subject"
74+
end
75+
76+
return unless method_name
77+
78+
index.add(RubyIndexer::Entry::Method.new(
79+
method_name,
80+
file_path,
81+
block_node.location,
82+
block_node.location,
83+
nil,
84+
index.configuration.encoding,
85+
[RubyIndexer::Entry::Signature.new([])],
86+
RubyIndexer::Entry::Visibility::PUBLIC,
87+
owner,
88+
))
89+
end
90+
end
91+
end
92+
end
93+
end

0 commit comments

Comments
 (0)