ruby-lsp 0.26.9 → 0.27.0.beta2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +2 -2
  4. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -8
  5. data/lib/ruby_indexer/ruby_indexer.rb +0 -1
  6. data/lib/ruby_lsp/addon.rb +19 -19
  7. data/lib/ruby_lsp/global_state.rb +11 -2
  8. data/lib/ruby_lsp/internal.rb +6 -1
  9. data/lib/ruby_lsp/listeners/definition.rb +65 -99
  10. data/lib/ruby_lsp/listeners/document_link.rb +4 -0
  11. data/lib/ruby_lsp/listeners/hover.rb +258 -123
  12. data/lib/ruby_lsp/listeners/spec_style.rb +6 -1
  13. data/lib/ruby_lsp/listeners/test_discovery.rb +21 -14
  14. data/lib/ruby_lsp/listeners/test_style.rb +20 -8
  15. data/lib/ruby_lsp/node_context.rb +32 -9
  16. data/lib/ruby_lsp/requests/completion_resolve.rb +9 -13
  17. data/lib/ruby_lsp/requests/discover_tests.rb +5 -41
  18. data/lib/ruby_lsp/requests/hover.rb +2 -5
  19. data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -0
  20. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +66 -22
  21. data/lib/ruby_lsp/requests/references.rb +170 -70
  22. data/lib/ruby_lsp/requests/rename.rb +64 -72
  23. data/lib/ruby_lsp/requests/request.rb +3 -33
  24. data/lib/ruby_lsp/requests/support/common.rb +53 -0
  25. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +82 -46
  26. data/lib/ruby_lsp/requests/workspace_symbol.rb +15 -37
  27. data/lib/ruby_lsp/rubydex/declaration.rb +48 -0
  28. data/lib/ruby_lsp/rubydex/definition.rb +257 -0
  29. data/lib/ruby_lsp/rubydex/reference.rb +21 -0
  30. data/lib/ruby_lsp/server.rb +82 -8
  31. data/lib/ruby_lsp/store.rb +0 -6
  32. data/lib/ruby_lsp/test_helper.rb +3 -0
  33. data/lib/ruby_lsp/type_inferrer.rb +111 -31
  34. metadata +18 -5
  35. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +0 -335
  36. data/lib/ruby_lsp/static_docs.rb +0 -20
  37. data/static_docs/break.md +0 -103
  38. data/static_docs/yield.md +0 -81
@@ -6,91 +6,95 @@ module RubyLsp
6
6
  class Hover
7
7
  include Requests::Support::Common
8
8
 
9
- ALLOWED_TARGETS = [
10
- Prism::BreakNode,
11
- Prism::CallNode,
12
- Prism::ConstantReadNode,
13
- Prism::ConstantWriteNode,
14
- Prism::ConstantPathNode,
15
- Prism::GlobalVariableAndWriteNode,
16
- Prism::GlobalVariableOperatorWriteNode,
17
- Prism::GlobalVariableOrWriteNode,
18
- Prism::GlobalVariableReadNode,
19
- Prism::GlobalVariableTargetNode,
20
- Prism::GlobalVariableWriteNode,
21
- Prism::InstanceVariableReadNode,
22
- Prism::InstanceVariableAndWriteNode,
23
- Prism::InstanceVariableOperatorWriteNode,
24
- Prism::InstanceVariableOrWriteNode,
25
- Prism::InstanceVariableTargetNode,
26
- Prism::InstanceVariableWriteNode,
27
- Prism::SymbolNode,
28
- Prism::StringNode,
29
- Prism::InterpolatedStringNode,
30
- Prism::SuperNode,
31
- Prism::ForwardingSuperNode,
32
- Prism::YieldNode,
33
- Prism::ClassVariableAndWriteNode,
34
- Prism::ClassVariableOperatorWriteNode,
35
- Prism::ClassVariableOrWriteNode,
36
- Prism::ClassVariableReadNode,
37
- Prism::ClassVariableTargetNode,
38
- Prism::ClassVariableWriteNode,
39
- ] #: Array[singleton(Prism::Node)]
40
-
41
9
  ALLOWED_REMOTE_PROVIDERS = [
42
10
  "https://github.com",
43
11
  "https://gitlab.com",
44
12
  ].freeze #: Array[String]
45
13
 
46
- #: (ResponseBuilders::Hover response_builder, GlobalState global_state, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level) -> void
47
- def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
14
+ #: (ResponseBuilders::Hover response_builder, GlobalState global_state, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level, Hash[Symbol, untyped] position) -> void
15
+ def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level, position) # rubocop:disable Metrics/ParameterLists
48
16
  @response_builder = response_builder
49
17
  @global_state = global_state
50
18
  @index = global_state.index #: RubyIndexer::Index
19
+ @graph = global_state.graph #: Rubydex::Graph
51
20
  @type_inferrer = global_state.type_inferrer #: TypeInferrer
52
21
  @path = uri.to_standardized_path #: String?
53
22
  @node_context = node_context
54
23
  @sorbet_level = sorbet_level
24
+ @position = position
55
25
 
56
26
  dispatcher.register(
57
27
  self,
28
+ :on_alias_global_variable_node_enter,
29
+ :on_alias_method_node_enter,
30
+ :on_and_node_enter,
31
+ :on_begin_node_enter,
32
+ :on_block_node_enter,
58
33
  :on_break_node_enter,
34
+ :on_call_node_enter,
35
+ :on_case_match_node_enter,
36
+ :on_case_node_enter,
37
+ :on_class_node_enter,
38
+ :on_singleton_class_node_enter,
39
+ :on_lambda_node_enter,
40
+ :on_class_variable_and_write_node_enter,
41
+ :on_class_variable_operator_write_node_enter,
42
+ :on_class_variable_or_write_node_enter,
43
+ :on_class_variable_read_node_enter,
44
+ :on_class_variable_target_node_enter,
45
+ :on_class_variable_write_node_enter,
46
+ :on_constant_path_node_enter,
59
47
  :on_constant_read_node_enter,
60
48
  :on_constant_write_node_enter,
61
- :on_constant_path_node_enter,
62
- :on_call_node_enter,
49
+ :on_def_node_enter,
50
+ :on_defined_node_enter,
51
+ :on_else_node_enter,
52
+ :on_ensure_node_enter,
53
+ :on_false_node_enter,
54
+ :on_for_node_enter,
55
+ :on_forwarding_super_node_enter,
63
56
  :on_global_variable_and_write_node_enter,
64
57
  :on_global_variable_operator_write_node_enter,
65
58
  :on_global_variable_or_write_node_enter,
66
59
  :on_global_variable_read_node_enter,
67
60
  :on_global_variable_target_node_enter,
68
61
  :on_global_variable_write_node_enter,
69
- :on_instance_variable_read_node_enter,
70
- :on_instance_variable_write_node_enter,
62
+ :on_if_node_enter,
63
+ :on_in_node_enter,
71
64
  :on_instance_variable_and_write_node_enter,
72
65
  :on_instance_variable_operator_write_node_enter,
73
66
  :on_instance_variable_or_write_node_enter,
67
+ :on_instance_variable_read_node_enter,
74
68
  :on_instance_variable_target_node_enter,
75
- :on_super_node_enter,
76
- :on_forwarding_super_node_enter,
77
- :on_string_node_enter,
69
+ :on_instance_variable_write_node_enter,
78
70
  :on_interpolated_string_node_enter,
71
+ :on_module_node_enter,
72
+ :on_next_node_enter,
73
+ :on_nil_node_enter,
74
+ :on_or_node_enter,
75
+ :on_post_execution_node_enter,
76
+ :on_pre_execution_node_enter,
77
+ :on_redo_node_enter,
78
+ :on_rescue_modifier_node_enter,
79
+ :on_rescue_node_enter,
80
+ :on_retry_node_enter,
81
+ :on_return_node_enter,
82
+ :on_self_node_enter,
83
+ :on_source_encoding_node_enter,
84
+ :on_source_file_node_enter,
85
+ :on_source_line_node_enter,
86
+ :on_string_node_enter,
87
+ :on_super_node_enter,
88
+ :on_true_node_enter,
89
+ :on_undef_node_enter,
90
+ :on_unless_node_enter,
91
+ :on_until_node_enter,
92
+ :on_when_node_enter,
93
+ :on_while_node_enter,
79
94
  :on_yield_node_enter,
80
- :on_class_variable_and_write_node_enter,
81
- :on_class_variable_operator_write_node_enter,
82
- :on_class_variable_or_write_node_enter,
83
- :on_class_variable_read_node_enter,
84
- :on_class_variable_target_node_enter,
85
- :on_class_variable_write_node_enter,
86
95
  )
87
96
  end
88
97
 
89
- #: (Prism::BreakNode node) -> void
90
- def on_break_node_enter(node)
91
- handle_keyword_documentation(node.keyword)
92
- end
93
-
94
98
  #: (Prism::StringNode node) -> void
95
99
  def on_string_node_enter(node)
96
100
  if @path && File.basename(@path) == GEMFILE_NAME
@@ -143,6 +147,12 @@ module RubyLsp
143
147
  message = node.message
144
148
  return unless message
145
149
 
150
+ # `not x` is parsed as a call to `!` whose message_loc slices to "not"
151
+ if node.name == :! && message == "not"
152
+ handle_keyword_documentation("not")
153
+ return
154
+ end
155
+
146
156
  handle_method_hover(message)
147
157
  end
148
158
 
@@ -178,77 +188,208 @@ module RubyLsp
178
188
 
179
189
  #: (Prism::InstanceVariableReadNode node) -> void
180
190
  def on_instance_variable_read_node_enter(node)
181
- handle_instance_variable_hover(node.name.to_s)
191
+ handle_variable_hover(node.name.to_s)
182
192
  end
183
193
 
184
194
  #: (Prism::InstanceVariableWriteNode node) -> void
185
195
  def on_instance_variable_write_node_enter(node)
186
- handle_instance_variable_hover(node.name.to_s)
196
+ handle_variable_hover(node.name.to_s)
187
197
  end
188
198
 
189
199
  #: (Prism::InstanceVariableAndWriteNode node) -> void
190
200
  def on_instance_variable_and_write_node_enter(node)
191
- handle_instance_variable_hover(node.name.to_s)
201
+ handle_variable_hover(node.name.to_s)
192
202
  end
193
203
 
194
204
  #: (Prism::InstanceVariableOperatorWriteNode node) -> void
195
205
  def on_instance_variable_operator_write_node_enter(node)
196
- handle_instance_variable_hover(node.name.to_s)
206
+ handle_variable_hover(node.name.to_s)
197
207
  end
198
208
 
199
209
  #: (Prism::InstanceVariableOrWriteNode node) -> void
200
210
  def on_instance_variable_or_write_node_enter(node)
201
- handle_instance_variable_hover(node.name.to_s)
211
+ handle_variable_hover(node.name.to_s)
202
212
  end
203
213
 
204
214
  #: (Prism::InstanceVariableTargetNode node) -> void
205
215
  def on_instance_variable_target_node_enter(node)
206
- handle_instance_variable_hover(node.name.to_s)
216
+ handle_variable_hover(node.name.to_s)
207
217
  end
208
218
 
209
219
  #: (Prism::SuperNode node) -> void
210
220
  def on_super_node_enter(node)
211
- handle_super_node_hover
221
+ handle_super_node_hover(node.keyword_loc)
212
222
  end
213
223
 
214
224
  #: (Prism::ForwardingSuperNode node) -> void
215
225
  def on_forwarding_super_node_enter(node)
216
- handle_super_node_hover
226
+ handle_super_node_hover(node.location)
227
+ end
228
+
229
+ #: (Prism::AliasGlobalVariableNode) -> void
230
+ def on_alias_global_variable_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
231
+
232
+ #: (Prism::AliasMethodNode) -> void
233
+ def on_alias_method_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
234
+
235
+ #: (Prism::AndNode) -> void
236
+ def on_and_node_enter(node) = handle_keyword_at_location(node.operator_loc)
237
+
238
+ #: (Prism::BeginNode) -> void
239
+ def on_begin_node_enter(node) = handle_keyword_at_location(node.begin_keyword_loc, node.end_keyword_loc)
240
+
241
+ #: (Prism::BlockNode) -> void
242
+ def on_block_node_enter(node) = handle_keyword_at_location(node.opening_loc, node.closing_loc)
243
+
244
+ #: (Prism::BreakNode) -> void
245
+ def on_break_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
246
+
247
+ #: (Prism::CaseMatchNode) -> void
248
+ def on_case_match_node_enter(node) = handle_keyword_at_location(node.case_keyword_loc, node.end_keyword_loc)
249
+
250
+ #: (Prism::CaseNode) -> void
251
+ def on_case_node_enter(node) = handle_keyword_at_location(node.case_keyword_loc, node.end_keyword_loc)
252
+
253
+ #: (Prism::ClassNode) -> void
254
+ def on_class_node_enter(node) = handle_keyword_at_location(node.class_keyword_loc, node.end_keyword_loc)
255
+
256
+ #: (Prism::SingletonClassNode) -> void
257
+ def on_singleton_class_node_enter(node)
258
+ handle_keyword_at_location(node.class_keyword_loc, node.end_keyword_loc)
259
+ end
260
+
261
+ #: (Prism::LambdaNode) -> void
262
+ def on_lambda_node_enter(node) = handle_keyword_at_location(node.opening_loc, node.closing_loc)
263
+
264
+ #: (Prism::DefNode) -> void
265
+ def on_def_node_enter(node) = handle_keyword_at_location(node.def_keyword_loc, node.end_keyword_loc)
266
+
267
+ #: (Prism::DefinedNode) -> void
268
+ def on_defined_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
269
+
270
+ #: (Prism::ElseNode) -> void
271
+ def on_else_node_enter(node) = handle_keyword_at_location(node.else_keyword_loc, node.end_keyword_loc)
272
+
273
+ #: (Prism::EnsureNode) -> void
274
+ def on_ensure_node_enter(node) = handle_keyword_at_location(node.ensure_keyword_loc, node.end_keyword_loc)
275
+
276
+ #: (Prism::FalseNode) -> void
277
+ def on_false_node_enter(node) = handle_keyword_at_location(node.location)
278
+
279
+ #: (Prism::ForNode) -> void
280
+ def on_for_node_enter(node)
281
+ handle_keyword_at_location(
282
+ node.for_keyword_loc,
283
+ node.in_keyword_loc,
284
+ node.do_keyword_loc,
285
+ node.end_keyword_loc,
286
+ )
217
287
  end
218
288
 
219
- #: (Prism::YieldNode node) -> void
220
- def on_yield_node_enter(node)
221
- handle_keyword_documentation(node.keyword)
289
+ #: (Prism::IfNode) -> void
290
+ def on_if_node_enter(node)
291
+ handle_keyword_at_location(node.if_keyword_loc, node.then_keyword_loc, node.end_keyword_loc)
292
+ end
293
+
294
+ #: (Prism::InNode) -> void
295
+ def on_in_node_enter(node) = handle_keyword_at_location(node.in_loc, node.then_loc)
296
+
297
+ #: (Prism::ModuleNode) -> void
298
+ def on_module_node_enter(node) = handle_keyword_at_location(node.module_keyword_loc, node.end_keyword_loc)
299
+
300
+ #: (Prism::NextNode) -> void
301
+ def on_next_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
302
+
303
+ #: (Prism::NilNode) -> void
304
+ def on_nil_node_enter(node) = handle_keyword_at_location(node.location)
305
+
306
+ #: (Prism::OrNode) -> void
307
+ def on_or_node_enter(node) = handle_keyword_at_location(node.operator_loc)
308
+
309
+ #: (Prism::PostExecutionNode) -> void
310
+ def on_post_execution_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
311
+
312
+ #: (Prism::PreExecutionNode) -> void
313
+ def on_pre_execution_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
314
+
315
+ #: (Prism::RedoNode) -> void
316
+ def on_redo_node_enter(node) = handle_keyword_at_location(node.location)
317
+
318
+ #: (Prism::RescueModifierNode) -> void
319
+ def on_rescue_modifier_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
320
+
321
+ #: (Prism::RescueNode) -> void
322
+ def on_rescue_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.then_keyword_loc)
323
+
324
+ #: (Prism::RetryNode) -> void
325
+ def on_retry_node_enter(node) = handle_keyword_at_location(node.location)
326
+
327
+ #: (Prism::ReturnNode) -> void
328
+ def on_return_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
329
+
330
+ #: (Prism::SelfNode) -> void
331
+ def on_self_node_enter(node) = handle_keyword_at_location(node.location)
332
+
333
+ #: (Prism::SourceEncodingNode) -> void
334
+ def on_source_encoding_node_enter(node) = handle_keyword_at_location(node.location)
335
+
336
+ #: (Prism::SourceFileNode) -> void
337
+ def on_source_file_node_enter(node) = handle_keyword_at_location(node.location)
338
+
339
+ #: (Prism::SourceLineNode) -> void
340
+ def on_source_line_node_enter(node) = handle_keyword_at_location(node.location)
341
+
342
+ #: (Prism::TrueNode) -> void
343
+ def on_true_node_enter(node) = handle_keyword_at_location(node.location)
344
+
345
+ #: (Prism::UndefNode) -> void
346
+ def on_undef_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
347
+
348
+ #: (Prism::UnlessNode) -> void
349
+ def on_unless_node_enter(node)
350
+ handle_keyword_at_location(node.keyword_loc, node.then_keyword_loc, node.end_keyword_loc)
222
351
  end
223
352
 
353
+ #: (Prism::UntilNode) -> void
354
+ def on_until_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.do_keyword_loc, node.closing_loc)
355
+
356
+ #: (Prism::WhenNode) -> void
357
+ def on_when_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.then_keyword_loc)
358
+
359
+ #: (Prism::WhileNode) -> void
360
+ def on_while_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.do_keyword_loc, node.closing_loc)
361
+
362
+ #: (Prism::YieldNode) -> void
363
+ def on_yield_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
364
+
224
365
  #: (Prism::ClassVariableAndWriteNode node) -> void
225
366
  def on_class_variable_and_write_node_enter(node)
226
- handle_class_variable_hover(node.name.to_s)
367
+ handle_variable_hover(node.name.to_s)
227
368
  end
228
369
 
229
370
  #: (Prism::ClassVariableOperatorWriteNode node) -> void
230
371
  def on_class_variable_operator_write_node_enter(node)
231
- handle_class_variable_hover(node.name.to_s)
372
+ handle_variable_hover(node.name.to_s)
232
373
  end
233
374
 
234
375
  #: (Prism::ClassVariableOrWriteNode node) -> void
235
376
  def on_class_variable_or_write_node_enter(node)
236
- handle_class_variable_hover(node.name.to_s)
377
+ handle_variable_hover(node.name.to_s)
237
378
  end
238
379
 
239
380
  #: (Prism::ClassVariableTargetNode node) -> void
240
381
  def on_class_variable_target_node_enter(node)
241
- handle_class_variable_hover(node.name.to_s)
382
+ handle_variable_hover(node.name.to_s)
242
383
  end
243
384
 
244
385
  #: (Prism::ClassVariableReadNode node) -> void
245
386
  def on_class_variable_read_node_enter(node)
246
- handle_class_variable_hover(node.name.to_s)
387
+ handle_variable_hover(node.name.to_s)
247
388
  end
248
389
 
249
390
  #: (Prism::ClassVariableWriteNode node) -> void
250
391
  def on_class_variable_write_node_enter(node)
251
- handle_class_variable_hover(node.name.to_s)
392
+ handle_variable_hover(node.name.to_s)
252
393
  end
253
394
 
254
395
  private
@@ -278,27 +419,37 @@ module RubyLsp
278
419
  end
279
420
  end
280
421
 
281
- #: (String keyword) -> void
282
- def handle_keyword_documentation(keyword)
283
- content = KEYWORD_DOCS[keyword]
284
- return unless content
422
+ #: (String) -> void
423
+ def handle_keyword_documentation(name)
424
+ keyword = @graph.keyword(name)
425
+ return unless keyword
285
426
 
286
- doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
287
-
288
- @response_builder.push("```ruby\n#{keyword}\n```", category: :title)
289
- @response_builder.push("[Read more](#{doc_uri})", category: :links)
290
- @response_builder.push(content, category: :documentation)
427
+ @response_builder.push("```ruby\n#{keyword.name}\n```", category: :title)
428
+ @response_builder.push(keyword.documentation, category: :documentation)
291
429
  end
292
430
 
293
- #: -> void
294
- def handle_super_node_hover
295
- # Sorbet can handle super hover on typed true or higher
296
- return if @sorbet_level.true_or_higher?
431
+ # Push keyword documentation when the cursor is on one of the provided locations. The keyword name is taken from
432
+ # the covering location's slice so that operator forms (`&&`, `||`, `{`, `}`, ternary `? :`) yield no hover —
433
+ # their slice is not a keyword in the Rubydex graph.
434
+ #
435
+ #: (*Prism::Location?) -> void
436
+ def handle_keyword_at_location(*locations)
437
+ loc = locations.find { |l| l && covers_position?(l, @position) }
438
+ return unless loc
439
+
440
+ handle_keyword_documentation(loc.slice)
441
+ end
297
442
 
298
- surrounding_method = @node_context.surrounding_method
299
- return unless surrounding_method
443
+ #: (Prism::Location keyword_location) -> void
444
+ def handle_super_node_hover(keyword_location)
445
+ # Sorbet can handle the inherited-method hover on typed true or higher, but it does not surface keyword docs, so
446
+ # we still push those
447
+ unless @sorbet_level.true_or_higher?
448
+ surrounding_method = @node_context.surrounding_method
449
+ handle_method_hover(surrounding_method.name, inherited_only: true) if surrounding_method
450
+ end
300
451
 
301
- handle_method_hover(surrounding_method, inherited_only: true)
452
+ handle_keyword_at_location(keyword_location)
302
453
  end
303
454
 
304
455
  #: (String message, ?inherited_only: bool) -> void
@@ -324,62 +475,46 @@ module RubyLsp
324
475
  end
325
476
  end
326
477
 
327
- #: (String name) -> void
328
- def handle_instance_variable_hover(name)
329
- # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
330
- # to provide all features for them
331
- return if @sorbet_level.strict?
332
-
333
- type = @type_inferrer.infer_receiver_type(@node_context)
334
- return unless type
335
-
336
- entries = @index.resolve_instance_variable(name, type.name)
337
- return unless entries
338
-
339
- categorized_markdown_from_index_entries(name, entries).each do |category, content|
340
- @response_builder.push(content, category: category)
341
- end
342
- rescue RubyIndexer::Index::NonExistingNamespaceError
343
- # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
344
- end
345
-
346
478
  #: (String name) -> void
347
479
  def handle_global_variable_hover(name)
348
- entries = @index[name]
349
- return unless entries
480
+ declaration = @graph[name]
481
+ return unless declaration
350
482
 
351
- categorized_markdown_from_index_entries(name, entries).each do |category, content|
483
+ categorized_markdown_from_definitions(name, declaration.definitions).each do |category, content|
352
484
  @response_builder.push(content, category: category)
353
485
  end
354
486
  end
355
487
 
488
+ # Handle class or instance variables. We collect all definitions across the ancestors of the type
489
+ #
356
490
  #: (String name) -> void
357
- def handle_class_variable_hover(name)
491
+ def handle_variable_hover(name)
492
+ # Sorbet enforces that all variables be declared on typed strict or higher, which means it will be able to
493
+ # provide all features for them
494
+ return if @sorbet_level.strict?
495
+
358
496
  type = @type_inferrer.infer_receiver_type(@node_context)
359
497
  return unless type
360
498
 
361
- entries = @index.resolve_class_variable(name, type.name)
362
- return unless entries
499
+ owner = @graph[type.name]
500
+ return unless owner.is_a?(Rubydex::Namespace)
363
501
 
364
- categorized_markdown_from_index_entries(name, entries).each do |category, content|
365
- @response_builder.push(content, category: category)
502
+ owner.ancestors.each do |ancestor|
503
+ member = ancestor.member(name)
504
+ next unless member
505
+
506
+ categorized_markdown_from_definitions(member.name, member.definitions).each do |category, content|
507
+ @response_builder.push(content, category: category)
508
+ end
366
509
  end
367
- rescue RubyIndexer::Index::NonExistingNamespaceError
368
- # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
369
510
  end
370
511
 
371
512
  #: (String name, Prism::Location location) -> void
372
513
  def generate_hover(name, location)
373
- entries = @index.resolve(name, @node_context.nesting)
374
- return unless entries
375
-
376
- # We should only show hover for private constants if the constant is defined in the same namespace as the
377
- # reference
378
- first_entry = entries.first #: as !nil
379
- full_name = first_entry.name
380
- return if first_entry.private? && full_name != "#{@node_context.fully_qualified_name}::#{name}"
514
+ declaration = @graph.resolve_constant(name, @node_context.nesting)
515
+ return unless declaration
381
516
 
382
- categorized_markdown_from_index_entries(full_name, entries).each do |category, content|
517
+ categorized_markdown_from_definitions(declaration.name, declaration.definitions).each do |category, content|
383
518
  @response_builder.push(content, category: category)
384
519
  end
385
520
  end
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  #: (Prism::ClassNode) -> void
35
35
  def on_class_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
36
36
  with_test_ancestor_tracking(node) do |name, ancestors|
37
- @spec_group_id_stack << (ancestors.include?("Minitest::Spec") ? ClassGroup.new(name) : nil)
37
+ @spec_group_id_stack << (spec_group?(ancestors, name) ? ClassGroup.new(name) : nil)
38
38
  end
39
39
  end
40
40
 
@@ -81,6 +81,11 @@ module RubyLsp
81
81
 
82
82
  private
83
83
 
84
+ #: (Array[String], String) -> bool
85
+ def spec_group?(ancestors, fully_qualified_name)
86
+ fully_qualified_name != "Minitest::Spec" && ancestors.include?("Minitest::Spec")
87
+ end
88
+
84
89
  #: (Prism::CallNode) -> void
85
90
  def handle_describe(node)
86
91
  # Describes will include the nesting of all classes and all outer describes as part of its ID, unlike classes
@@ -13,7 +13,7 @@ module RubyLsp
13
13
  def initialize(response_builder, global_state, uri)
14
14
  @response_builder = response_builder
15
15
  @uri = uri
16
- @index = global_state.index #: RubyIndexer::Index
16
+ @graph = global_state.graph #: Rubydex::Graph
17
17
  @visibility_stack = [:public] #: Array[Symbol]
18
18
  @nesting = [] #: Array[String]
19
19
  end
@@ -64,22 +64,29 @@ module RubyLsp
64
64
  superclass = node.superclass
65
65
 
66
66
  begin
67
- ancestors = @index.linearized_ancestors_of(fully_qualified_name)
68
- # If the project has no bundle and a test class inherits from `Minitest::Test`, the linearized ancestors will
69
- # not include the parent class because we never indexed it in the first place. Here we add the superclass
70
- # directly, so that we can support running tests in projects without a bundle
71
- return ancestors if ancestors.length > 1
72
-
73
- # If all we found is the class itself, then manually include the parent class
74
- if ancestors.first == fully_qualified_name && superclass
75
- return [*ancestors, superclass.slice]
67
+ declaration = @graph[fully_qualified_name]
68
+
69
+ unless declaration.is_a?(Rubydex::Namespace)
70
+ # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
71
+ # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
72
+ return [superclass&.slice].compact
73
+ end
74
+
75
+ ancestors = declaration.ancestors.map(&:name)
76
+ superclass_ref = declaration.definitions
77
+ .filter_map { |d| d.superclass if d.is_a?(Rubydex::ClassDefinition) }
78
+ .find { |ref| !ref.is_a?(Rubydex::ResolvedConstantReference) || ref.declaration.name != "Object" }
79
+
80
+ # If we couldn't resolve the parent class, then artificially inject it into the ancestors
81
+ if superclass_ref.is_a?(Rubydex::UnresolvedConstantReference) && superclass
82
+ insert_index = ancestors.index(fully_qualified_name) #: as !nil
83
+ insert_index += 1
84
+ ancestors.insert(insert_index, superclass.slice)
85
+ return ancestors
76
86
  end
77
87
 
88
+ # If the parent class is properly resolved or if there isn't one, then just use the ancestors
78
89
  ancestors
79
- rescue RubyIndexer::Index::NonExistingNamespaceError
80
- # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
81
- # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
82
- [superclass&.slice].compact
83
90
  end
84
91
  end
85
92
 
@@ -174,9 +174,10 @@ module RubyLsp
174
174
  #: (Prism::ClassNode node) -> void
175
175
  def on_class_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
176
176
  with_test_ancestor_tracking(node) do |name, ancestors|
177
- @framework = :test_unit if ancestors.include?("Test::Unit::TestCase")
177
+ is_test_unit = test_unit_group?(ancestors, name)
178
+ @framework = :test_unit if is_test_unit
178
179
 
179
- if @framework == :test_unit || non_declarative_minitest?(ancestors, name)
180
+ if is_test_unit || non_declarative_minitest?(ancestors, name)
180
181
  test_item = Requests::Support::TestItem.new(
181
182
  name,
182
183
  name,
@@ -259,17 +260,28 @@ module RubyLsp
259
260
  @parent_stack[index] #: as Requests::Support::TestItem | ResponseBuilders::TestCollection
260
261
  end
261
262
 
262
- #: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
263
+ #: (Array[String], String) -> bool
264
+ def test_unit_group?(ancestors, fully_qualified_name)
265
+ fully_qualified_name != "Test::Unit::TestCase" && ancestors.include?("Test::Unit::TestCase")
266
+ end
267
+
268
+ #: (Array[String], String) -> bool
263
269
  def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
270
+ return false if ["Minitest::Spec", "Minitest::Test", "ActiveSupport::TestCase"].include?(fully_qualified_name)
264
271
  return false unless attached_ancestors.include?("Minitest::Test")
265
272
 
266
273
  # We only support regular Minitest tests. The declarative syntax provided by ActiveSupport is handled by the
267
274
  # Rails add-on
268
- name_parts = fully_qualified_name.split("::")
269
- singleton_name = "#{name_parts.join("::")}::<Class:#{name_parts.last}>"
270
- !@index.linearized_ancestors_of(singleton_name).include?("ActiveSupport::Testing::Declarative")
271
- rescue RubyIndexer::Index::NonExistingNamespaceError
272
- true
275
+
276
+ declaration = @graph[fully_qualified_name]
277
+ # If we don't find the fully qualified name in the graph, it means there's a dynamic portion in the test class
278
+ # definition. In that case, if the ancestors did include `Minitest::Test`, we always assume it's a test
279
+ return true unless declaration.is_a?(Rubydex::Namespace)
280
+
281
+ singleton = declaration.singleton_class
282
+ return !singleton.ancestors.map(&:name).include?("ActiveSupport::Testing::Declarative") if singleton
283
+
284
+ !attached_ancestors.include?("ActiveSupport::TestCase")
273
285
  end
274
286
  end
275
287
  end