Skip to content

Commit a6c13bd

Browse files
committed
Migrate go to definition to use Rubydex
1 parent cd9f1e7 commit a6c13bd

11 files changed

Lines changed: 212 additions & 207 deletions

File tree

lib/ruby_lsp/internal.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
require "shellwords"
3232
require "set"
3333

34+
# Rubydex LSP additions
35+
require "ruby_lsp/rubydex/definition"
36+
3437
require "ruby-lsp"
3538
require "ruby_lsp/base_server"
3639
require "ruby_indexer/ruby_indexer"

lib/ruby_lsp/listeners/definition.rb

Lines changed: 51 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def initialize(response_builder, global_state, language_id, uri, node_context, d
1313
@response_builder = response_builder
1414
@global_state = global_state
1515
@index = global_state.index #: RubyIndexer::Index
16+
@graph = global_state.graph #: Rubydex::Graph
1617
@type_inferrer = global_state.type_inferrer #: TypeInferrer
1718
@language_id = language_id
1819
@uri = uri
@@ -152,32 +153,32 @@ def on_global_variable_write_node_enter(node)
152153

153154
#: (Prism::InstanceVariableReadNode node) -> void
154155
def on_instance_variable_read_node_enter(node)
155-
handle_instance_variable_definition(node.name.to_s)
156+
handle_variable_definition(node.name.to_s)
156157
end
157158

158159
#: (Prism::InstanceVariableWriteNode node) -> void
159160
def on_instance_variable_write_node_enter(node)
160-
handle_instance_variable_definition(node.name.to_s)
161+
handle_variable_definition(node.name.to_s)
161162
end
162163

163164
#: (Prism::InstanceVariableAndWriteNode node) -> void
164165
def on_instance_variable_and_write_node_enter(node)
165-
handle_instance_variable_definition(node.name.to_s)
166+
handle_variable_definition(node.name.to_s)
166167
end
167168

168169
#: (Prism::InstanceVariableOperatorWriteNode node) -> void
169170
def on_instance_variable_operator_write_node_enter(node)
170-
handle_instance_variable_definition(node.name.to_s)
171+
handle_variable_definition(node.name.to_s)
171172
end
172173

173174
#: (Prism::InstanceVariableOrWriteNode node) -> void
174175
def on_instance_variable_or_write_node_enter(node)
175-
handle_instance_variable_definition(node.name.to_s)
176+
handle_variable_definition(node.name.to_s)
176177
end
177178

178179
#: (Prism::InstanceVariableTargetNode node) -> void
179180
def on_instance_variable_target_node_enter(node)
180-
handle_instance_variable_definition(node.name.to_s)
181+
handle_variable_definition(node.name.to_s)
181182
end
182183

183184
#: (Prism::SuperNode node) -> void
@@ -192,32 +193,32 @@ def on_forwarding_super_node_enter(node)
192193

193194
#: (Prism::ClassVariableAndWriteNode node) -> void
194195
def on_class_variable_and_write_node_enter(node)
195-
handle_class_variable_definition(node.name.to_s)
196+
handle_variable_definition(node.name.to_s)
196197
end
197198

198199
#: (Prism::ClassVariableOperatorWriteNode node) -> void
199200
def on_class_variable_operator_write_node_enter(node)
200-
handle_class_variable_definition(node.name.to_s)
201+
handle_variable_definition(node.name.to_s)
201202
end
202203

203204
#: (Prism::ClassVariableOrWriteNode node) -> void
204205
def on_class_variable_or_write_node_enter(node)
205-
handle_class_variable_definition(node.name.to_s)
206+
handle_variable_definition(node.name.to_s)
206207
end
207208

208209
#: (Prism::ClassVariableTargetNode node) -> void
209210
def on_class_variable_target_node_enter(node)
210-
handle_class_variable_definition(node.name.to_s)
211+
handle_variable_definition(node.name.to_s)
211212
end
212213

213214
#: (Prism::ClassVariableReadNode node) -> void
214215
def on_class_variable_read_node_enter(node)
215-
handle_class_variable_definition(node.name.to_s)
216+
handle_variable_definition(node.name.to_s)
216217
end
217218

218219
#: (Prism::ClassVariableWriteNode node) -> void
219220
def on_class_variable_write_node_enter(node)
220-
handle_class_variable_definition(node.name.to_s)
221+
handle_variable_definition(node.name.to_s)
221222
end
222223

223224
private
@@ -257,106 +258,74 @@ def handle_super_node_definition
257258

258259
#: (String name) -> void
259260
def handle_global_variable_definition(name)
260-
entries = @index[name]
261+
declaration = @graph[name]
262+
return unless declaration
261263

262-
return unless entries
263-
264-
entries.each do |entry|
265-
location = entry.location
266-
267-
@response_builder << Interface::Location.new(
268-
uri: entry.uri.to_s,
269-
range: Interface::Range.new(
270-
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
271-
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
272-
),
273-
)
274-
end
264+
declaration.definitions.each { |definition| @response_builder << definition.to_lsp_selection_location }
275265
end
276266

267+
# Handle class or instance variables. We collect all definitions across the ancestors of the type
268+
#
277269
#: (String name) -> void
278-
def handle_class_variable_definition(name)
279-
type = @type_inferrer.infer_receiver_type(@node_context)
280-
return unless type
281-
282-
entries = @index.resolve_class_variable(name, type.name)
283-
return unless entries
284-
285-
entries.each do |entry|
286-
@response_builder << Interface::Location.new(
287-
uri: entry.uri.to_s,
288-
range: range_from_location(entry.location),
289-
)
290-
end
291-
rescue RubyIndexer::Index::NonExistingNamespaceError
292-
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
293-
end
294-
295-
#: (String name) -> void
296-
def handle_instance_variable_definition(name)
297-
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
298-
# to provide all features for them
270+
def handle_variable_definition(name)
271+
# Sorbet enforces that all variables be declared on typed strict or higher, which means it will be able to
272+
# provide all features for them
299273
return if @sorbet_level.strict?
300274

301275
type = @type_inferrer.infer_receiver_type(@node_context)
302276
return unless type
303277

304-
entries = @index.resolve_instance_variable(name, type.name)
305-
return unless entries
278+
owner = @graph[type.name]
279+
return unless owner.is_a?(Rubydex::Namespace)
306280

307-
entries.each do |entry|
308-
location = entry.location
281+
owner.ancestors.each do |ancestor|
282+
member = ancestor.member(name)
283+
next unless member
309284

310-
@response_builder << Interface::Location.new(
311-
uri: entry.uri.to_s,
312-
range: Interface::Range.new(
313-
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
314-
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
315-
),
316-
)
285+
member.definitions.each { |definition| @response_builder << definition.to_lsp_selection_location }
317286
end
318-
rescue RubyIndexer::Index::NonExistingNamespaceError
319-
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
320287
end
321288

322289
#: (String message, TypeInferrer::Type? receiver_type, ?inherited_only: bool) -> void
323290
def handle_method_definition(message, receiver_type, inherited_only: false)
324-
methods = if receiver_type
325-
@index.resolve_method(message, receiver_type.name, inherited_only: inherited_only)
291+
declaration = if receiver_type
292+
owner = @graph[receiver_type.name]
293+
owner.find_member("#{message}()", only_inherited: inherited_only) if owner.is_a?(Rubydex::Namespace)
326294
end
327295

328-
# If the method doesn't have a receiver, or the guessed receiver doesn't have any matched candidates,
329-
# then we provide a few candidates to jump to
330-
# But we don't want to provide too many candidates, as it can be overwhelming
331-
if receiver_type.nil? || (receiver_type.is_a?(TypeInferrer::GuessedType) && methods.nil?)
332-
methods = @index[message]&.take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
296+
# If the method doesn't have a receiver, or the guessed receiver doesn't have any matched candidates, then we
297+
# provide a few candidates to jump to. However, we don't want to provide too many candidates, as it can be
298+
# overwhelming
299+
if receiver_type.nil? || (receiver_type.is_a?(TypeInferrer::GuessedType) && declaration.nil?)
300+
declaration = @graph.search("##{message}()").take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
333301
end
334302

335-
return unless methods
303+
return unless declaration
336304

337-
methods.each do |target_method|
338-
uri = target_method.uri
339-
full_path = uri.full_path
340-
next if @sorbet_level.true_or_higher? && (!full_path || not_in_dependencies?(full_path))
305+
Array(declaration).each do |decl|
306+
decl.definitions.each do |definition|
307+
location = definition.location
308+
uri = URI(location.uri)
309+
full_path = uri.full_path
310+
next if @sorbet_level.true_or_higher? && (!full_path || not_in_dependencies?(full_path))
341311

342-
@response_builder << Interface::LocationLink.new(
343-
target_uri: uri.to_s,
344-
target_range: range_from_location(target_method.location),
345-
target_selection_range: range_from_location(target_method.name_location),
346-
)
312+
@response_builder << Interface::LocationLink.new(
313+
target_uri: uri.to_s,
314+
target_range: definition.to_lsp_selection_range,
315+
target_selection_range: definition.to_lsp_name_range || definition.to_lsp_selection_range,
316+
)
317+
end
347318
end
348319
end
349320

350321
#: (Prism::StringNode node, Symbol message) -> void
351322
def handle_require_definition(node, message)
352323
case message
353324
when :require
354-
entry = @index.search_require_paths(node.content).find do |uri|
355-
uri.require_path == node.content
356-
end
325+
document = @graph.resolve_require_path(node.content, $LOAD_PATH)
357326

358-
if entry
359-
candidate = entry.full_path
327+
if document
328+
candidate = URI(document.uri).full_path
360329

361330
if candidate
362331
@response_builder << Interface::Location.new(

lib/ruby_lsp/rubydex/definition.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module Rubydex
5+
class Definition
6+
#: () -> RubyLsp::Interface::Range
7+
def to_lsp_selection_range
8+
loc = location
9+
10+
RubyLsp::Interface::Range.new(
11+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
12+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
13+
)
14+
end
15+
16+
#: () -> RubyLsp::Interface::Location
17+
def to_lsp_selection_location
18+
location = self.location
19+
20+
RubyLsp::Interface::Location.new(
21+
uri: location.uri,
22+
range: RubyLsp::Interface::Range.new(
23+
start: RubyLsp::Interface::Position.new(line: location.start_line, character: location.start_column),
24+
end: RubyLsp::Interface::Position.new(line: location.end_line, character: location.end_column),
25+
),
26+
)
27+
end
28+
29+
#: () -> RubyLsp::Interface::Range?
30+
def to_lsp_name_range
31+
loc = name_location
32+
return unless loc
33+
34+
RubyLsp::Interface::Range.new(
35+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
36+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
37+
)
38+
end
39+
40+
#: () -> RubyLsp::Interface::Location?
41+
def to_lsp_name_location
42+
location = name_location
43+
return unless location
44+
45+
RubyLsp::Interface::Location.new(
46+
uri: location.uri,
47+
range: RubyLsp::Interface::Range.new(
48+
start: RubyLsp::Interface::Position.new(line: location.start_line, character: location.start_column),
49+
end: RubyLsp::Interface::Position.new(line: location.end_line, character: location.end_column),
50+
),
51+
)
52+
end
53+
end
54+
end
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
{
2+
"params": [
3+
{
4+
"line": 3,
5+
"character": 10
6+
}
7+
],
28
"result": [
39
{
4-
"targetUri": "file:///fixtures/class_reference_target.rb",
10+
"targetUri": "file:////fake.rb",
511
"targetSelectionRange": {
612
"start": {
7-
"line": 4,
8-
"character": 8
13+
"line": 0,
14+
"character": 6
915
},
1016
"end": {
11-
"line": 4,
12-
"character": 20
17+
"line": 0,
18+
"character": 12
1319
}
1420
},
1521
"targetRange": {
1622
"start": {
17-
"line": 4,
18-
"character": 2
23+
"line": 0,
24+
"character": 0
1925
},
2026
"end": {
21-
"line": 7,
22-
"character": 5
27+
"line": 1,
28+
"character": 3
2329
}
2430
}
2531
}
26-
],
27-
"params": [
28-
{
29-
"line": 0,
30-
"character": 19
31-
}
3232
]
3333
}
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
{
2+
"params": [
3+
{
4+
"line": 2,
5+
"character": 10
6+
}
7+
],
28
"result": [
39
{
4-
"targetUri": "file:///fixtures/constant_reference_target.rb",
5-
"targetSelectionRange": {
10+
"targetUri": "file:////fake.rb",
11+
"targetRange": {
612
"start": {
7-
"line": 3,
8-
"character": 7
13+
"line": 0,
14+
"character": 0
915
},
1016
"end": {
11-
"line": 3,
17+
"line": 0,
1218
"character": 10
1319
}
1420
},
15-
"targetRange": {
21+
"targetSelectionRange": {
1622
"start": {
17-
"line": 3,
23+
"line": 0,
1824
"character": 0
1925
},
2026
"end": {
21-
"line": 4,
22-
"character": 3
27+
"line": 0,
28+
"character": 10
2329
}
2430
}
2531
}
26-
],
27-
"params": [
28-
{
29-
"line": 0,
30-
"character": 12
31-
}
3232
]
3333
}

test/fixtures/class_reference.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
example = RubyLsp::ExampleClass.new
1+
class Target
2+
end
3+
4+
example = Target.new

0 commit comments

Comments
 (0)