Skip to content

Commit 932b60a

Browse files
authored
Merge pull request #4015 from Shopify/03-03-migrate_go_to_definition_to_use_rubydex
Partially migrate go to definition to use Rubydex
2 parents f83f6f5 + ffe9281 commit 932b60a

12 files changed

Lines changed: 257 additions & 237 deletions

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: 64 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Definition
1212
def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
1313
@response_builder = response_builder
1414
@global_state = global_state
15-
@index = global_state.index #: RubyIndexer::Index
15+
@graph = global_state.graph #: Rubydex::Graph
1616
@type_inferrer = global_state.type_inferrer #: TypeInferrer
1717
@language_id = language_id
1818
@uri = uri
@@ -109,15 +109,15 @@ def on_constant_path_node_enter(node)
109109
name = RubyIndexer::Index.constant_name(node)
110110
return if name.nil?
111111

112-
find_in_index(name)
112+
handle_constant_definition(name)
113113
end
114114

115115
#: (Prism::ConstantReadNode node) -> void
116116
def on_constant_read_node_enter(node)
117117
name = RubyIndexer::Index.constant_name(node)
118118
return if name.nil?
119119

120-
find_in_index(name)
120+
handle_constant_definition(name)
121121
end
122122

123123
#: (Prism::GlobalVariableAndWriteNode node) -> void
@@ -152,32 +152,32 @@ def on_global_variable_write_node_enter(node)
152152

153153
#: (Prism::InstanceVariableReadNode node) -> void
154154
def on_instance_variable_read_node_enter(node)
155-
handle_instance_variable_definition(node.name.to_s)
155+
handle_variable_definition(node.name.to_s)
156156
end
157157

158158
#: (Prism::InstanceVariableWriteNode node) -> void
159159
def on_instance_variable_write_node_enter(node)
160-
handle_instance_variable_definition(node.name.to_s)
160+
handle_variable_definition(node.name.to_s)
161161
end
162162

163163
#: (Prism::InstanceVariableAndWriteNode node) -> void
164164
def on_instance_variable_and_write_node_enter(node)
165-
handle_instance_variable_definition(node.name.to_s)
165+
handle_variable_definition(node.name.to_s)
166166
end
167167

168168
#: (Prism::InstanceVariableOperatorWriteNode node) -> void
169169
def on_instance_variable_operator_write_node_enter(node)
170-
handle_instance_variable_definition(node.name.to_s)
170+
handle_variable_definition(node.name.to_s)
171171
end
172172

173173
#: (Prism::InstanceVariableOrWriteNode node) -> void
174174
def on_instance_variable_or_write_node_enter(node)
175-
handle_instance_variable_definition(node.name.to_s)
175+
handle_variable_definition(node.name.to_s)
176176
end
177177

178178
#: (Prism::InstanceVariableTargetNode node) -> void
179179
def on_instance_variable_target_node_enter(node)
180-
handle_instance_variable_definition(node.name.to_s)
180+
handle_variable_definition(node.name.to_s)
181181
end
182182

183183
#: (Prism::SuperNode node) -> void
@@ -192,32 +192,32 @@ def on_forwarding_super_node_enter(node)
192192

193193
#: (Prism::ClassVariableAndWriteNode node) -> void
194194
def on_class_variable_and_write_node_enter(node)
195-
handle_class_variable_definition(node.name.to_s)
195+
handle_variable_definition(node.name.to_s)
196196
end
197197

198198
#: (Prism::ClassVariableOperatorWriteNode node) -> void
199199
def on_class_variable_operator_write_node_enter(node)
200-
handle_class_variable_definition(node.name.to_s)
200+
handle_variable_definition(node.name.to_s)
201201
end
202202

203203
#: (Prism::ClassVariableOrWriteNode node) -> void
204204
def on_class_variable_or_write_node_enter(node)
205-
handle_class_variable_definition(node.name.to_s)
205+
handle_variable_definition(node.name.to_s)
206206
end
207207

208208
#: (Prism::ClassVariableTargetNode node) -> void
209209
def on_class_variable_target_node_enter(node)
210-
handle_class_variable_definition(node.name.to_s)
210+
handle_variable_definition(node.name.to_s)
211211
end
212212

213213
#: (Prism::ClassVariableReadNode node) -> void
214214
def on_class_variable_read_node_enter(node)
215-
handle_class_variable_definition(node.name.to_s)
215+
handle_variable_definition(node.name.to_s)
216216
end
217217

218218
#: (Prism::ClassVariableWriteNode node) -> void
219219
def on_class_variable_write_node_enter(node)
220-
handle_class_variable_definition(node.name.to_s)
220+
handle_variable_definition(node.name.to_s)
221221
end
222222

223223
private
@@ -257,106 +257,74 @@ def handle_super_node_definition
257257

258258
#: (String name) -> void
259259
def handle_global_variable_definition(name)
260-
entries = @index[name]
260+
declaration = @graph[name]
261+
return unless declaration
261262

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
275-
end
276-
277-
#: (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
263+
declaration.definitions.each { |definition| @response_builder << definition.to_lsp_selection_location }
293264
end
294265

266+
# Handle class or instance variables. We collect all definitions across the ancestors of the type
267+
#
295268
#: (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
269+
def handle_variable_definition(name)
270+
# Sorbet enforces that all variables be declared on typed strict or higher, which means it will be able to
271+
# provide all features for them
299272
return if @sorbet_level.strict?
300273

301274
type = @type_inferrer.infer_receiver_type(@node_context)
302275
return unless type
303276

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

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

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-
)
284+
member.definitions.each { |definition| @response_builder << definition.to_lsp_selection_location }
317285
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
320286
end
321287

322288
#: (String message, TypeInferrer::Type? receiver_type, ?inherited_only: bool) -> void
323289
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)
290+
declaration = if receiver_type
291+
owner = @graph[receiver_type.name]
292+
owner.find_member("#{message}()", only_inherited: inherited_only) if owner.is_a?(Rubydex::Namespace)
326293
end
327294

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)
295+
# If the method doesn't have a receiver, or the guessed receiver doesn't have any matched candidates, then we
296+
# provide a few candidates to jump to. However, we don't want to provide too many candidates, as it can be
297+
# overwhelming
298+
if receiver_type.nil? || (receiver_type.is_a?(TypeInferrer::GuessedType) && declaration.nil?)
299+
declaration = @graph.search("##{message}()").take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
333300
end
334301

335-
return unless methods
302+
return unless declaration
336303

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))
304+
Array(declaration).each do |decl|
305+
decl.definitions.each do |definition|
306+
location = definition.location
307+
uri = URI(location.uri)
308+
full_path = uri.full_path
309+
next if @sorbet_level.true_or_higher? && (!full_path || not_in_dependencies?(full_path))
341310

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-
)
311+
@response_builder << Interface::LocationLink.new(
312+
target_uri: uri.to_s,
313+
target_range: definition.to_lsp_selection_range,
314+
target_selection_range: definition.to_lsp_name_range || definition.to_lsp_selection_range,
315+
)
316+
end
347317
end
348318
end
349319

350320
#: (Prism::StringNode node, Symbol message) -> void
351321
def handle_require_definition(node, message)
352322
case message
353323
when :require
354-
entry = @index.search_require_paths(node.content).find do |uri|
355-
uri.require_path == node.content
356-
end
324+
document = @graph.resolve_require_path(node.content, $LOAD_PATH)
357325

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

361329
if candidate
362330
@response_builder << Interface::Location.new(
@@ -392,35 +360,33 @@ def handle_autoload_definition(node)
392360
constant_name = argument.value
393361
return unless constant_name
394362

395-
find_in_index(constant_name)
363+
handle_constant_definition(constant_name)
396364
end
397365

398366
#: (String value) -> void
399-
def find_in_index(value)
400-
entries = @index.resolve(value, @node_context.nesting)
401-
return unless entries
367+
def handle_constant_definition(value)
368+
declaration = @graph.resolve_constant(value, @node_context.nesting)
369+
return unless declaration
402370

371+
# [RUBYDEX] TODO: temporarily commented out until we have the visibility API
372+
#
403373
# We should only allow jumping to the definition of private constants if the constant is defined in the same
404374
# namespace as the reference
405-
first_entry = entries.first #: as !nil
406-
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
375+
#
376+
# return if declaration.private? && declaration.name != "#{@node_context.fully_qualified_name}::#{value}"
407377

408-
entries.each do |entry|
378+
declaration.definitions.each do |definition|
409379
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
410380
# additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
411381
# ignore
412-
uri = entry.uri
382+
uri = URI(definition.location.uri)
413383
full_path = uri.full_path
414384

415385
if !@sorbet_level.ignore? && (!full_path || not_in_dependencies?(full_path))
416386
next
417387
end
418388

419-
@response_builder << Interface::LocationLink.new(
420-
target_uri: uri.to_s,
421-
target_range: range_from_location(entry.location),
422-
target_selection_range: range_from_location(entry.name_location),
423-
)
389+
@response_builder << definition.to_lsp_location_link
424390
end
425391
end
426392
end

lib/ruby_lsp/rubydex/definition.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module Rubydex
5+
class Definition
6+
#: () -> RubyLsp::Interface::LocationLink
7+
def to_lsp_location_link
8+
selection_range = to_lsp_selection_range
9+
10+
RubyLsp::Interface::LocationLink.new(
11+
target_uri: location.uri,
12+
target_range: selection_range,
13+
target_selection_range: to_lsp_name_range || selection_range,
14+
)
15+
end
16+
17+
#: () -> RubyLsp::Interface::Range
18+
def to_lsp_selection_range
19+
loc = location
20+
21+
RubyLsp::Interface::Range.new(
22+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
23+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
24+
)
25+
end
26+
27+
#: () -> RubyLsp::Interface::Location
28+
def to_lsp_selection_location
29+
location = self.location
30+
31+
RubyLsp::Interface::Location.new(
32+
uri: location.uri,
33+
range: RubyLsp::Interface::Range.new(
34+
start: RubyLsp::Interface::Position.new(line: location.start_line, character: location.start_column),
35+
end: RubyLsp::Interface::Position.new(line: location.end_line, character: location.end_column),
36+
),
37+
)
38+
end
39+
40+
#: () -> RubyLsp::Interface::Range?
41+
def to_lsp_name_range
42+
loc = name_location
43+
return unless loc
44+
45+
RubyLsp::Interface::Range.new(
46+
start: RubyLsp::Interface::Position.new(line: loc.start_line, character: loc.start_column),
47+
end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column),
48+
)
49+
end
50+
51+
#: () -> RubyLsp::Interface::Location?
52+
def to_lsp_name_location
53+
location = name_location
54+
return unless location
55+
56+
RubyLsp::Interface::Location.new(
57+
uri: location.uri,
58+
range: RubyLsp::Interface::Range.new(
59+
start: RubyLsp::Interface::Position.new(line: location.start_line, character: location.start_column),
60+
end: RubyLsp::Interface::Position.new(line: location.end_line, character: location.end_column),
61+
),
62+
)
63+
end
64+
end
65+
end

0 commit comments

Comments
 (0)