ruby-lsp 0.17.4 → 0.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +26 -1
  5. data/exe/ruby-lsp-check +1 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
  7. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
  10. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -8
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
  13. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  14. data/lib/ruby_indexer/test/constant_test.rb +17 -17
  15. data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
  16. data/lib/ruby_indexer/test/index_test.rb +367 -17
  17. data/lib/ruby_indexer/test/method_test.rb +58 -25
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
  19. data/lib/ruby_indexer/test/test_case.rb +1 -5
  20. data/lib/ruby_lsp/addon.rb +22 -5
  21. data/lib/ruby_lsp/base_server.rb +8 -3
  22. data/lib/ruby_lsp/document.rb +27 -46
  23. data/lib/ruby_lsp/erb_document.rb +125 -0
  24. data/lib/ruby_lsp/global_state.rb +47 -19
  25. data/lib/ruby_lsp/internal.rb +2 -0
  26. data/lib/ruby_lsp/listeners/completion.rb +161 -57
  27. data/lib/ruby_lsp/listeners/definition.rb +91 -27
  28. data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
  29. data/lib/ruby_lsp/listeners/hover.rb +61 -19
  30. data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
  31. data/lib/ruby_lsp/node_context.rb +65 -5
  32. data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
  33. data/lib/ruby_lsp/requests/code_actions.rb +11 -2
  34. data/lib/ruby_lsp/requests/completion.rb +4 -4
  35. data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
  36. data/lib/ruby_lsp/requests/definition.rb +18 -8
  37. data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
  38. data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
  39. data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
  40. data/lib/ruby_lsp/requests/formatting.rb +15 -0
  41. data/lib/ruby_lsp/requests/hover.rb +5 -5
  42. data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
  43. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  44. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  45. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  46. data/lib/ruby_lsp/requests/support/common.rb +11 -2
  47. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
  48. data/lib/ruby_lsp/ruby_document.rb +74 -0
  49. data/lib/ruby_lsp/server.rb +129 -54
  50. data/lib/ruby_lsp/store.rb +33 -9
  51. data/lib/ruby_lsp/test_helper.rb +3 -1
  52. data/lib/ruby_lsp/type_inferrer.rb +61 -25
  53. data/lib/ruby_lsp/utils.rb +13 -0
  54. metadata +9 -8
  55. data/exe/ruby-lsp-doctor +0 -23
@@ -7,12 +7,56 @@ module RubyLsp
7
7
  extend T::Sig
8
8
  include Requests::Support::Common
9
9
 
10
+ KEYWORDS = [
11
+ "alias",
12
+ "and",
13
+ "begin",
14
+ "BEGIN",
15
+ "break",
16
+ "case",
17
+ "class",
18
+ "def",
19
+ "defined?",
20
+ "do",
21
+ "else",
22
+ "elsif",
23
+ "end",
24
+ "END",
25
+ "ensure",
26
+ "false",
27
+ "for",
28
+ "if",
29
+ "in",
30
+ "module",
31
+ "next",
32
+ "nil",
33
+ "not",
34
+ "or",
35
+ "redo",
36
+ "rescue",
37
+ "retry",
38
+ "return",
39
+ "self",
40
+ "super",
41
+ "then",
42
+ "true",
43
+ "undef",
44
+ "unless",
45
+ "until",
46
+ "when",
47
+ "while",
48
+ "yield",
49
+ "__ENCODING__",
50
+ "__FILE__",
51
+ "__LINE__",
52
+ ].freeze
53
+
10
54
  sig do
11
55
  params(
12
56
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
13
57
  global_state: GlobalState,
14
58
  node_context: NodeContext,
15
- typechecker_enabled: T::Boolean,
59
+ sorbet_level: RubyDocument::SorbetLevel,
16
60
  dispatcher: Prism::Dispatcher,
17
61
  uri: URI::Generic,
18
62
  trigger_character: T.nilable(String),
@@ -22,7 +66,7 @@ module RubyLsp
22
66
  response_builder,
23
67
  global_state,
24
68
  node_context,
25
- typechecker_enabled,
69
+ sorbet_level,
26
70
  dispatcher,
27
71
  uri,
28
72
  trigger_character
@@ -32,7 +76,7 @@ module RubyLsp
32
76
  @index = T.let(global_state.index, RubyIndexer::Index)
33
77
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
34
78
  @node_context = node_context
35
- @typechecker_enabled = typechecker_enabled
79
+ @sorbet_level = sorbet_level
36
80
  @uri = uri
37
81
  @trigger_character = trigger_character
38
82
 
@@ -53,7 +97,9 @@ module RubyLsp
53
97
  # Handle completion on regular constant references (e.g. `Bar`)
54
98
  sig { params(node: Prism::ConstantReadNode).void }
55
99
  def on_constant_read_node_enter(node)
56
- return if @global_state.has_type_checker
100
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
101
+ # no sigil, Sorbet will still provide completion for constants
102
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
57
103
 
58
104
  name = constant_name(node)
59
105
  return if name.nil?
@@ -74,7 +120,9 @@ module RubyLsp
74
120
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
75
121
  sig { params(node: Prism::ConstantPathNode).void }
76
122
  def on_constant_path_node_enter(node)
77
- return if @global_state.has_type_checker
123
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
124
+ # no sigil, Sorbet will still provide completion for constants
125
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
78
126
 
79
127
  name = constant_name(node)
80
128
  return if name.nil?
@@ -84,28 +132,32 @@ module RubyLsp
84
132
 
85
133
  sig { params(node: Prism::CallNode).void }
86
134
  def on_call_node_enter(node)
87
- receiver = node.receiver
88
-
89
- # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
90
- # methods). However, in addition to providing method completion, we also need to show possible constant
91
- # completions
92
- if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
93
- node.call_operator == "::"
94
-
95
- name = constant_name(receiver)
96
-
97
- if name
98
- start_loc = node.location
99
- end_loc = T.must(node.call_operator_loc)
100
-
101
- constant_path_completion(
102
- "#{name}::",
103
- Interface::Range.new(
104
- start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
105
- end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
106
- ),
107
- )
108
- return
135
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
136
+ # no sigil, Sorbet will still provide completion for constants
137
+ if @sorbet_level == RubyDocument::SorbetLevel::Ignore
138
+ receiver = node.receiver
139
+
140
+ # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
141
+ # singleton methods). However, in addition to providing method completion, we also need to show possible
142
+ # constant completions
143
+ if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
144
+ node.call_operator == "::"
145
+
146
+ name = constant_name(receiver)
147
+
148
+ if name
149
+ start_loc = node.location
150
+ end_loc = T.must(node.call_operator_loc)
151
+
152
+ constant_path_completion(
153
+ "#{name}::",
154
+ Interface::Range.new(
155
+ start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
156
+ end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
157
+ ),
158
+ )
159
+ return
160
+ end
109
161
  end
110
162
  end
111
163
 
@@ -118,7 +170,7 @@ module RubyLsp
118
170
  when "require_relative"
119
171
  complete_require_relative(node)
120
172
  else
121
- complete_methods(node, name) unless @typechecker_enabled
173
+ complete_methods(node, name)
122
174
  end
123
175
  end
124
176
 
@@ -203,14 +255,23 @@ module RubyLsp
203
255
 
204
256
  sig { params(name: String, location: Prism::Location).void }
205
257
  def handle_instance_variable_completion(name, location)
258
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
259
+ # to provide all features for them
260
+ return if @sorbet_level == RubyDocument::SorbetLevel::Strict
261
+
206
262
  type = @type_inferrer.infer_receiver_type(@node_context)
207
263
  return unless type
208
264
 
209
- @index.instance_variable_completion_candidates(name, type).each do |entry|
265
+ @index.instance_variable_completion_candidates(name, type.name).each do |entry|
210
266
  variable_name = entry.name
211
267
 
268
+ label_details = Interface::CompletionItemLabelDetails.new(
269
+ description: entry.file_name,
270
+ )
271
+
212
272
  @response_builder << Interface::CompletionItem.new(
213
273
  label: variable_name,
274
+ label_details: label_details,
214
275
  text_edit: Interface::TextEdit.new(
215
276
  range: range_from_location(location),
216
277
  new_text: variable_name,
@@ -272,6 +333,16 @@ module RubyLsp
272
333
 
273
334
  sig { params(node: Prism::CallNode, name: String).void }
274
335
  def complete_methods(node, name)
336
+ # If the node has a receiver, then we don't need to provide local nor keyword completions. Sorbet can provide
337
+ # local and keyword completion for any file with a Sorbet level of true or higher
338
+ if !sorbet_level_true_or_higher?(@sorbet_level) && !node.receiver
339
+ add_local_completions(node, name)
340
+ add_keyword_completions(node, name)
341
+ end
342
+
343
+ # Sorbet can provide completion for methods invoked on self on typed true or higher files
344
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
345
+
275
346
  type = @type_inferrer.infer_receiver_type(@node_context)
276
347
  return unless type
277
348
 
@@ -283,23 +354,37 @@ module RubyLsp
283
354
  range = if method_name
284
355
  range_from_location(T.must(node.message_loc))
285
356
  else
286
- loc = T.must(node.call_operator_loc)
287
- Interface::Range.new(
288
- start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
289
- end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
290
- )
357
+ loc = node.call_operator_loc
358
+
359
+ if loc
360
+ Interface::Range.new(
361
+ start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
362
+ end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
363
+ )
364
+ end
291
365
  end
292
366
 
293
- @index.method_completion_candidates(method_name, type).each do |entry|
367
+ return unless range
368
+
369
+ guessed_type = type.name
370
+
371
+ @index.method_completion_candidates(method_name, type.name).each do |entry|
294
372
  entry_name = entry.name
373
+ owner_name = entry.owner&.name
295
374
 
375
+ label_details = Interface::CompletionItemLabelDetails.new(
376
+ description: entry.file_name,
377
+ detail: entry.decorated_parameters,
378
+ )
296
379
  @response_builder << Interface::CompletionItem.new(
297
380
  label: entry_name,
298
381
  filter_text: entry_name,
382
+ label_details: label_details,
299
383
  text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
300
384
  kind: Constant::CompletionItemKind::METHOD,
301
385
  data: {
302
- owner_name: entry.owner&.name,
386
+ owner_name: owner_name,
387
+ guessed_type: guessed_type,
303
388
  },
304
389
  )
305
390
  end
@@ -307,29 +392,42 @@ module RubyLsp
307
392
  # We have not indexed this namespace, so we can't provide any completions
308
393
  end
309
394
 
310
- sig do
311
- params(
312
- entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
313
- node: Prism::CallNode,
314
- ).returns(Interface::CompletionItem)
395
+ sig { params(node: Prism::CallNode, name: String).void }
396
+ def add_local_completions(node, name)
397
+ range = range_from_location(T.must(node.message_loc))
398
+
399
+ @node_context.locals_for_scope.each do |local|
400
+ local_name = local.to_s
401
+ next unless local_name.start_with?(name)
402
+
403
+ @response_builder << Interface::CompletionItem.new(
404
+ label: local_name,
405
+ filter_text: local_name,
406
+ text_edit: Interface::TextEdit.new(range: range, new_text: local_name),
407
+ kind: Constant::CompletionItemKind::VARIABLE,
408
+ data: {
409
+ skip_resolve: true,
410
+ },
411
+ )
412
+ end
315
413
  end
316
- def build_method_completion(entry, node)
317
- name = entry.name
318
414
 
319
- Interface::CompletionItem.new(
320
- label: name,
321
- filter_text: name,
322
- text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
323
- kind: Constant::CompletionItemKind::METHOD,
324
- label_details: Interface::CompletionItemLabelDetails.new(
325
- detail: entry.decorated_parameters,
326
- description: entry.file_name,
327
- ),
328
- documentation: Interface::MarkupContent.new(
329
- kind: "markdown",
330
- value: markdown_from_index_entries(name, entry),
331
- ),
332
- )
415
+ sig { params(node: Prism::CallNode, name: String).void }
416
+ def add_keyword_completions(node, name)
417
+ range = range_from_location(T.must(node.message_loc))
418
+
419
+ KEYWORDS.each do |keyword|
420
+ next unless keyword.start_with?(name)
421
+
422
+ @response_builder << Interface::CompletionItem.new(
423
+ label: keyword,
424
+ text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
425
+ kind: Constant::CompletionItemKind::KEYWORD,
426
+ data: {
427
+ skip_resolve: true,
428
+ },
429
+ )
430
+ end
333
431
  end
334
432
 
335
433
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
@@ -413,8 +511,14 @@ module RubyLsp
413
511
  # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
414
512
  # For these top level references, we need to include the `::` as part of the filter text or else it won't match
415
513
  # the right entries in the index
514
+
515
+ label_details = Interface::CompletionItemLabelDetails.new(
516
+ description: entries.map(&:file_name).join(","),
517
+ )
518
+
416
519
  Interface::CompletionItem.new(
417
520
  label: real_name,
521
+ label_details: label_details,
418
522
  filter_text: filter_text,
419
523
  text_edit: Interface::TextEdit.new(
420
524
  range: range,
@@ -11,22 +11,27 @@ module RubyLsp
11
11
 
12
12
  sig do
13
13
  params(
14
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
14
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
15
+ Interface::Location,
16
+ Interface::LocationLink,
17
+ )],
15
18
  global_state: GlobalState,
19
+ language_id: Document::LanguageId,
16
20
  uri: URI::Generic,
17
21
  node_context: NodeContext,
18
22
  dispatcher: Prism::Dispatcher,
19
- typechecker_enabled: T::Boolean,
23
+ sorbet_level: RubyDocument::SorbetLevel,
20
24
  ).void
21
25
  end
22
- def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
26
+ def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
23
27
  @response_builder = response_builder
24
28
  @global_state = global_state
25
29
  @index = T.let(global_state.index, RubyIndexer::Index)
26
30
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
31
+ @language_id = language_id
27
32
  @uri = uri
28
33
  @node_context = node_context
29
- @typechecker_enabled = typechecker_enabled
34
+ @sorbet_level = sorbet_level
30
35
 
31
36
  dispatcher.register(
32
37
  self,
@@ -41,15 +46,29 @@ module RubyLsp
41
46
  :on_instance_variable_or_write_node_enter,
42
47
  :on_instance_variable_target_node_enter,
43
48
  :on_string_node_enter,
49
+ :on_symbol_node_enter,
50
+ :on_super_node_enter,
51
+ :on_forwarding_super_node_enter,
44
52
  )
45
53
  end
46
54
 
47
55
  sig { params(node: Prism::CallNode).void }
48
56
  def on_call_node_enter(node)
57
+ # Sorbet can handle go to definition for methods invoked on self on typed true or higher
58
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
59
+
49
60
  message = node.message
50
61
  return unless message
51
62
 
52
- handle_method_definition(message, @type_inferrer.infer_receiver_type(@node_context))
63
+ inferrer_receiver_type = @type_inferrer.infer_receiver_type(@node_context)
64
+
65
+ # Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
66
+ # treating method calls' type as `nil` will allow users to get some completion support first
67
+ if @language_id == Document::LanguageId::ERB && inferrer_receiver_type&.name == "Object"
68
+ inferrer_receiver_type = nil
69
+ end
70
+
71
+ handle_method_definition(message, inferrer_receiver_type)
53
72
  end
54
73
 
55
74
  sig { params(node: Prism::StringNode).void }
@@ -63,6 +82,17 @@ module RubyLsp
63
82
  handle_require_definition(node, name)
64
83
  end
65
84
 
85
+ sig { params(node: Prism::SymbolNode).void }
86
+ def on_symbol_node_enter(node)
87
+ enclosing_call = @node_context.call_node
88
+ return unless enclosing_call
89
+
90
+ name = enclosing_call.name
91
+ return unless name == :autoload
92
+
93
+ handle_autoload_definition(enclosing_call)
94
+ end
95
+
66
96
  sig { params(node: Prism::BlockArgumentNode).void }
67
97
  def on_block_argument_node_enter(node)
68
98
  expression = node.expression
@@ -120,14 +150,43 @@ module RubyLsp
120
150
  handle_instance_variable_definition(node.name.to_s)
121
151
  end
122
152
 
153
+ sig { params(node: Prism::SuperNode).void }
154
+ def on_super_node_enter(node)
155
+ handle_super_node_definition
156
+ end
157
+
158
+ sig { params(node: Prism::ForwardingSuperNode).void }
159
+ def on_forwarding_super_node_enter(node)
160
+ handle_super_node_definition
161
+ end
162
+
123
163
  private
124
164
 
165
+ sig { void }
166
+ def handle_super_node_definition
167
+ # Sorbet can handle super hover on typed true or higher
168
+ return if sorbet_level_true_or_higher?(@sorbet_level)
169
+
170
+ surrounding_method = @node_context.surrounding_method
171
+ return unless surrounding_method
172
+
173
+ handle_method_definition(
174
+ surrounding_method,
175
+ @type_inferrer.infer_receiver_type(@node_context),
176
+ inherited_only: true,
177
+ )
178
+ end
179
+
125
180
  sig { params(name: String).void }
126
181
  def handle_instance_variable_definition(name)
182
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
183
+ # to provide all features for them
184
+ return if @sorbet_level == RubyDocument::SorbetLevel::Strict
185
+
127
186
  type = @type_inferrer.infer_receiver_type(@node_context)
128
187
  return unless type
129
188
 
130
- entries = @index.resolve_instance_variable(name, type)
189
+ entries = @index.resolve_instance_variable(name, type.name)
131
190
  return unless entries
132
191
 
133
192
  entries.each do |entry|
@@ -145,10 +204,10 @@ module RubyLsp
145
204
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
146
205
  end
147
206
 
148
- sig { params(message: String, receiver_type: T.nilable(String)).void }
149
- def handle_method_definition(message, receiver_type)
207
+ sig { params(message: String, receiver_type: T.nilable(TypeInferrer::Type), inherited_only: T::Boolean).void }
208
+ def handle_method_definition(message, receiver_type, inherited_only: false)
150
209
  methods = if receiver_type
151
- @index.resolve_method(message, receiver_type)
210
+ @index.resolve_method(message, receiver_type.name, inherited_only: inherited_only)
152
211
  else
153
212
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
154
213
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -158,16 +217,13 @@ module RubyLsp
158
217
  return unless methods
159
218
 
160
219
  methods.each do |target_method|
161
- location = target_method.location
162
220
  file_path = target_method.file_path
163
- next if @typechecker_enabled && not_in_dependencies?(file_path)
221
+ next if sorbet_level_true_or_higher?(@sorbet_level) && not_in_dependencies?(file_path)
164
222
 
165
- @response_builder << Interface::Location.new(
166
- uri: URI::Generic.from_path(path: file_path).to_s,
167
- range: Interface::Range.new(
168
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
169
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
170
- ),
223
+ @response_builder << Interface::LocationLink.new(
224
+ target_uri: URI::Generic.from_path(path: file_path).to_s,
225
+ target_range: range_from_location(target_method.location),
226
+ target_selection_range: range_from_location(target_method.name_location),
171
227
  )
172
228
  end
173
229
  end
@@ -207,6 +263,17 @@ module RubyLsp
207
263
  end
208
264
  end
209
265
 
266
+ sig { params(node: Prism::CallNode).void }
267
+ def handle_autoload_definition(node)
268
+ argument = node.arguments&.arguments&.first
269
+ return unless argument.is_a?(Prism::SymbolNode)
270
+
271
+ constant_name = argument.value
272
+ return unless constant_name
273
+
274
+ find_in_index(constant_name)
275
+ end
276
+
210
277
  sig { params(value: String).void }
211
278
  def find_in_index(value)
212
279
  entries = @index.resolve(value, @node_context.nesting)
@@ -218,19 +285,16 @@ module RubyLsp
218
285
  return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
219
286
 
220
287
  entries.each do |entry|
221
- location = entry.location
222
288
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
223
- # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
224
- # in the project, even if the files are typed false
289
+ # additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
290
+ # ignore
225
291
  file_path = entry.file_path
226
- next if @typechecker_enabled && not_in_dependencies?(file_path)
292
+ next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(file_path)
227
293
 
228
- @response_builder << Interface::Location.new(
229
- uri: URI::Generic.from_path(path: file_path).to_s,
230
- range: Interface::Range.new(
231
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
232
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
233
- ),
294
+ @response_builder << Interface::LocationLink.new(
295
+ target_uri: URI::Generic.from_path(path: file_path).to_s,
296
+ target_range: range_from_location(entry.location),
297
+ target_selection_range: range_from_location(entry.name_location),
234
298
  )
235
299
  end
236
300
  end
@@ -180,7 +180,11 @@ module RubyLsp
180
180
  def on_call_node_enter(node)
181
181
  return unless matches?(node, [Prism::CallNode, Prism::DefNode])
182
182
 
183
- add_highlight(Constant::DocumentHighlightKind::READ, node.location)
183
+ loc = node.message_loc
184
+ # if we have `foo.` it's a call node but there is no message yet.
185
+ return unless loc
186
+
187
+ add_highlight(Constant::DocumentHighlightKind::READ, loc)
184
188
  end
185
189
 
186
190
  sig { params(node: Prism::DefNode).void }