ruby-lsp 0.27.0.beta1 → 0.27.0.beta3

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +0 -46
  4. data/exe/ruby-lsp-check +0 -15
  5. data/lib/ruby_lsp/addon.rb +19 -19
  6. data/lib/ruby_lsp/global_state.rb +1 -6
  7. data/lib/ruby_lsp/internal.rb +3 -2
  8. data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
  9. data/lib/ruby_lsp/listeners/completion.rb +246 -382
  10. data/lib/ruby_lsp/listeners/definition.rb +7 -10
  11. data/lib/ruby_lsp/listeners/document_link.rb +4 -0
  12. data/lib/ruby_lsp/listeners/hover.rb +234 -82
  13. data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
  14. data/lib/ruby_lsp/listeners/spec_style.rb +6 -1
  15. data/lib/ruby_lsp/listeners/test_discovery.rb +38 -15
  16. data/lib/ruby_lsp/listeners/test_style.rb +21 -9
  17. data/lib/ruby_lsp/node_context.rb +31 -8
  18. data/lib/ruby_lsp/requests/completion_resolve.rb +55 -39
  19. data/lib/ruby_lsp/requests/discover_tests.rb +5 -41
  20. data/lib/ruby_lsp/requests/hover.rb +2 -5
  21. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +66 -22
  22. data/lib/ruby_lsp/requests/references.rb +180 -66
  23. data/lib/ruby_lsp/requests/rename.rb +1 -1
  24. data/lib/ruby_lsp/requests/request.rb +3 -33
  25. data/lib/ruby_lsp/requests/support/common.rb +82 -68
  26. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +82 -46
  27. data/lib/ruby_lsp/ruby_document.rb +0 -73
  28. data/lib/ruby_lsp/rubydex/declaration.rb +174 -0
  29. data/lib/ruby_lsp/rubydex/definition.rb +73 -0
  30. data/lib/ruby_lsp/rubydex/reference.rb +6 -1
  31. data/lib/ruby_lsp/rubydex/signature.rb +107 -0
  32. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  33. data/lib/ruby_lsp/server.rb +56 -171
  34. data/lib/ruby_lsp/test_helper.rb +0 -1
  35. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +89 -11
  37. metadata +12 -18
  38. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
  39. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
  40. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
  41. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
  42. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
  43. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
  44. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
  45. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
  46. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +0 -335
  47. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
  48. data/lib/ruby_indexer/ruby_indexer.rb +0 -20
  49. data/lib/ruby_lsp/static_docs.rb +0 -20
  50. data/static_docs/break.md +0 -103
  51. data/static_docs/yield.md +0 -81
  52. /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
@@ -106,7 +106,7 @@ module RubyLsp
106
106
 
107
107
  #: (Prism::ConstantPathNode node) -> void
108
108
  def on_constant_path_node_enter(node)
109
- name = RubyIndexer::Index.constant_name(node)
109
+ name = constant_name(node)
110
110
  return if name.nil?
111
111
 
112
112
  handle_constant_definition(name)
@@ -114,7 +114,7 @@ module RubyLsp
114
114
 
115
115
  #: (Prism::ConstantReadNode node) -> void
116
116
  def on_constant_read_node_enter(node)
117
- name = RubyIndexer::Index.constant_name(node)
117
+ name = constant_name(node)
118
118
  return if name.nil?
119
119
 
120
120
  handle_constant_definition(name)
@@ -249,7 +249,7 @@ module RubyLsp
249
249
  return unless surrounding_method
250
250
 
251
251
  handle_method_definition(
252
- surrounding_method,
252
+ surrounding_method.name,
253
253
  @type_inferrer.infer_receiver_type(@node_context),
254
254
  inherited_only: true,
255
255
  )
@@ -302,6 +302,9 @@ module RubyLsp
302
302
  return unless declaration
303
303
 
304
304
  Array(declaration).each do |decl|
305
+ next if decl.is_a?(Rubydex::Method) &&
306
+ !method_reachable_from_call_site?(decl, receiver_type, @graph, @node_context)
307
+
305
308
  decl.definitions.each do |definition|
306
309
  location = definition.location
307
310
  uri = URI(location.uri)
@@ -367,13 +370,7 @@ module RubyLsp
367
370
  def handle_constant_definition(value)
368
371
  declaration = @graph.resolve_constant(value, @node_context.nesting)
369
372
  return unless declaration
370
-
371
- # [RUBYDEX] TODO: temporarily commented out until we have the visibility API
372
- #
373
- # We should only allow jumping to the definition of private constants if the constant is defined in the same
374
- # namespace as the reference
375
- #
376
- # return if declaration.private? && declaration.name != "#{@node_context.fully_qualified_name}::#{value}"
373
+ return unless constant_reachable_from_call_site?(declaration, value, @node_context)
377
374
 
378
375
  declaration.definitions.each do |definition|
379
376
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
@@ -21,6 +21,8 @@ module RubyLsp
21
21
 
22
22
  Gem::Specification.stubs.each do |stub|
23
23
  spec = stub.to_spec
24
+ next unless spec
25
+
24
26
  lookup[spec.name] = {}
25
27
  lookup[spec.name][spec.version.to_s] = {}
26
28
 
@@ -31,6 +33,8 @@ module RubyLsp
31
33
 
32
34
  Gem::Specification.default_stubs.each do |stub|
33
35
  spec = stub.to_spec
36
+ next unless spec
37
+
34
38
  lookup[spec.name] = {}
35
39
  lookup[spec.name][spec.version.to_s] = {}
36
40
  prefix_matchers = Regexp.union(spec.require_paths.map do |rp|
@@ -6,92 +6,94 @@ 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
- @index = global_state.index #: RubyIndexer::Index
51
18
  @graph = global_state.graph #: Rubydex::Graph
52
19
  @type_inferrer = global_state.type_inferrer #: TypeInferrer
53
20
  @path = uri.to_standardized_path #: String?
54
21
  @node_context = node_context
55
22
  @sorbet_level = sorbet_level
23
+ @position = position
56
24
 
57
25
  dispatcher.register(
58
26
  self,
27
+ :on_alias_global_variable_node_enter,
28
+ :on_alias_method_node_enter,
29
+ :on_and_node_enter,
30
+ :on_begin_node_enter,
31
+ :on_block_node_enter,
59
32
  :on_break_node_enter,
33
+ :on_call_node_enter,
34
+ :on_case_match_node_enter,
35
+ :on_case_node_enter,
36
+ :on_class_node_enter,
37
+ :on_singleton_class_node_enter,
38
+ :on_lambda_node_enter,
39
+ :on_class_variable_and_write_node_enter,
40
+ :on_class_variable_operator_write_node_enter,
41
+ :on_class_variable_or_write_node_enter,
42
+ :on_class_variable_read_node_enter,
43
+ :on_class_variable_target_node_enter,
44
+ :on_class_variable_write_node_enter,
45
+ :on_constant_path_node_enter,
60
46
  :on_constant_read_node_enter,
61
47
  :on_constant_write_node_enter,
62
- :on_constant_path_node_enter,
63
- :on_call_node_enter,
48
+ :on_def_node_enter,
49
+ :on_defined_node_enter,
50
+ :on_else_node_enter,
51
+ :on_ensure_node_enter,
52
+ :on_false_node_enter,
53
+ :on_for_node_enter,
54
+ :on_forwarding_super_node_enter,
64
55
  :on_global_variable_and_write_node_enter,
65
56
  :on_global_variable_operator_write_node_enter,
66
57
  :on_global_variable_or_write_node_enter,
67
58
  :on_global_variable_read_node_enter,
68
59
  :on_global_variable_target_node_enter,
69
60
  :on_global_variable_write_node_enter,
70
- :on_instance_variable_read_node_enter,
71
- :on_instance_variable_write_node_enter,
61
+ :on_if_node_enter,
62
+ :on_in_node_enter,
72
63
  :on_instance_variable_and_write_node_enter,
73
64
  :on_instance_variable_operator_write_node_enter,
74
65
  :on_instance_variable_or_write_node_enter,
66
+ :on_instance_variable_read_node_enter,
75
67
  :on_instance_variable_target_node_enter,
76
- :on_super_node_enter,
77
- :on_forwarding_super_node_enter,
78
- :on_string_node_enter,
68
+ :on_instance_variable_write_node_enter,
79
69
  :on_interpolated_string_node_enter,
70
+ :on_module_node_enter,
71
+ :on_next_node_enter,
72
+ :on_nil_node_enter,
73
+ :on_or_node_enter,
74
+ :on_post_execution_node_enter,
75
+ :on_pre_execution_node_enter,
76
+ :on_redo_node_enter,
77
+ :on_rescue_modifier_node_enter,
78
+ :on_rescue_node_enter,
79
+ :on_retry_node_enter,
80
+ :on_return_node_enter,
81
+ :on_self_node_enter,
82
+ :on_source_encoding_node_enter,
83
+ :on_source_file_node_enter,
84
+ :on_source_line_node_enter,
85
+ :on_string_node_enter,
86
+ :on_super_node_enter,
87
+ :on_true_node_enter,
88
+ :on_undef_node_enter,
89
+ :on_unless_node_enter,
90
+ :on_until_node_enter,
91
+ :on_when_node_enter,
92
+ :on_while_node_enter,
80
93
  :on_yield_node_enter,
81
- :on_class_variable_and_write_node_enter,
82
- :on_class_variable_operator_write_node_enter,
83
- :on_class_variable_or_write_node_enter,
84
- :on_class_variable_read_node_enter,
85
- :on_class_variable_target_node_enter,
86
- :on_class_variable_write_node_enter,
87
94
  )
88
95
  end
89
96
 
90
- #: (Prism::BreakNode node) -> void
91
- def on_break_node_enter(node)
92
- handle_keyword_documentation(node.keyword)
93
- end
94
-
95
97
  #: (Prism::StringNode node) -> void
96
98
  def on_string_node_enter(node)
97
99
  if @path && File.basename(@path) == GEMFILE_NAME
@@ -114,7 +116,7 @@ module RubyLsp
114
116
  def on_constant_read_node_enter(node)
115
117
  return unless @sorbet_level.ignore?
116
118
 
117
- name = RubyIndexer::Index.constant_name(node)
119
+ name = constant_name(node)
118
120
  return if name.nil?
119
121
 
120
122
  generate_hover(name, node.location)
@@ -131,7 +133,7 @@ module RubyLsp
131
133
  def on_constant_path_node_enter(node)
132
134
  return unless @sorbet_level.ignore?
133
135
 
134
- name = RubyIndexer::Index.constant_name(node)
136
+ name = constant_name(node)
135
137
  return if name.nil?
136
138
 
137
139
  generate_hover(name, node.location)
@@ -144,6 +146,12 @@ module RubyLsp
144
146
  message = node.message
145
147
  return unless message
146
148
 
149
+ # `not x` is parsed as a call to `!` whose message_loc slices to "not"
150
+ if node.name == :! && message == "not"
151
+ handle_keyword_documentation("not")
152
+ return
153
+ end
154
+
147
155
  handle_method_hover(message)
148
156
  end
149
157
 
@@ -209,19 +217,150 @@ module RubyLsp
209
217
 
210
218
  #: (Prism::SuperNode node) -> void
211
219
  def on_super_node_enter(node)
212
- handle_super_node_hover
220
+ handle_super_node_hover(node.keyword_loc)
213
221
  end
214
222
 
215
223
  #: (Prism::ForwardingSuperNode node) -> void
216
224
  def on_forwarding_super_node_enter(node)
217
- handle_super_node_hover
225
+ handle_super_node_hover(node.location)
226
+ end
227
+
228
+ #: (Prism::AliasGlobalVariableNode) -> void
229
+ def on_alias_global_variable_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
230
+
231
+ #: (Prism::AliasMethodNode) -> void
232
+ def on_alias_method_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
233
+
234
+ #: (Prism::AndNode) -> void
235
+ def on_and_node_enter(node) = handle_keyword_at_location(node.operator_loc)
236
+
237
+ #: (Prism::BeginNode) -> void
238
+ def on_begin_node_enter(node) = handle_keyword_at_location(node.begin_keyword_loc, node.end_keyword_loc)
239
+
240
+ #: (Prism::BlockNode) -> void
241
+ def on_block_node_enter(node) = handle_keyword_at_location(node.opening_loc, node.closing_loc)
242
+
243
+ #: (Prism::BreakNode) -> void
244
+ def on_break_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
245
+
246
+ #: (Prism::CaseMatchNode) -> void
247
+ def on_case_match_node_enter(node) = handle_keyword_at_location(node.case_keyword_loc, node.end_keyword_loc)
248
+
249
+ #: (Prism::CaseNode) -> void
250
+ def on_case_node_enter(node) = handle_keyword_at_location(node.case_keyword_loc, node.end_keyword_loc)
251
+
252
+ #: (Prism::ClassNode) -> void
253
+ def on_class_node_enter(node) = handle_keyword_at_location(node.class_keyword_loc, node.end_keyword_loc)
254
+
255
+ #: (Prism::SingletonClassNode) -> void
256
+ def on_singleton_class_node_enter(node)
257
+ handle_keyword_at_location(node.class_keyword_loc, node.end_keyword_loc)
218
258
  end
219
259
 
220
- #: (Prism::YieldNode node) -> void
221
- def on_yield_node_enter(node)
222
- handle_keyword_documentation(node.keyword)
260
+ #: (Prism::LambdaNode) -> void
261
+ def on_lambda_node_enter(node) = handle_keyword_at_location(node.opening_loc, node.closing_loc)
262
+
263
+ #: (Prism::DefNode) -> void
264
+ def on_def_node_enter(node) = handle_keyword_at_location(node.def_keyword_loc, node.end_keyword_loc)
265
+
266
+ #: (Prism::DefinedNode) -> void
267
+ def on_defined_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
268
+
269
+ #: (Prism::ElseNode) -> void
270
+ def on_else_node_enter(node) = handle_keyword_at_location(node.else_keyword_loc, node.end_keyword_loc)
271
+
272
+ #: (Prism::EnsureNode) -> void
273
+ def on_ensure_node_enter(node) = handle_keyword_at_location(node.ensure_keyword_loc, node.end_keyword_loc)
274
+
275
+ #: (Prism::FalseNode) -> void
276
+ def on_false_node_enter(node) = handle_keyword_at_location(node.location)
277
+
278
+ #: (Prism::ForNode) -> void
279
+ def on_for_node_enter(node)
280
+ handle_keyword_at_location(
281
+ node.for_keyword_loc,
282
+ node.in_keyword_loc,
283
+ node.do_keyword_loc,
284
+ node.end_keyword_loc,
285
+ )
223
286
  end
224
287
 
288
+ #: (Prism::IfNode) -> void
289
+ def on_if_node_enter(node)
290
+ handle_keyword_at_location(node.if_keyword_loc, node.then_keyword_loc, node.end_keyword_loc)
291
+ end
292
+
293
+ #: (Prism::InNode) -> void
294
+ def on_in_node_enter(node) = handle_keyword_at_location(node.in_loc, node.then_loc)
295
+
296
+ #: (Prism::ModuleNode) -> void
297
+ def on_module_node_enter(node) = handle_keyword_at_location(node.module_keyword_loc, node.end_keyword_loc)
298
+
299
+ #: (Prism::NextNode) -> void
300
+ def on_next_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
301
+
302
+ #: (Prism::NilNode) -> void
303
+ def on_nil_node_enter(node) = handle_keyword_at_location(node.location)
304
+
305
+ #: (Prism::OrNode) -> void
306
+ def on_or_node_enter(node) = handle_keyword_at_location(node.operator_loc)
307
+
308
+ #: (Prism::PostExecutionNode) -> void
309
+ def on_post_execution_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
310
+
311
+ #: (Prism::PreExecutionNode) -> void
312
+ def on_pre_execution_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
313
+
314
+ #: (Prism::RedoNode) -> void
315
+ def on_redo_node_enter(node) = handle_keyword_at_location(node.location)
316
+
317
+ #: (Prism::RescueModifierNode) -> void
318
+ def on_rescue_modifier_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
319
+
320
+ #: (Prism::RescueNode) -> void
321
+ def on_rescue_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.then_keyword_loc)
322
+
323
+ #: (Prism::RetryNode) -> void
324
+ def on_retry_node_enter(node) = handle_keyword_at_location(node.location)
325
+
326
+ #: (Prism::ReturnNode) -> void
327
+ def on_return_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
328
+
329
+ #: (Prism::SelfNode) -> void
330
+ def on_self_node_enter(node) = handle_keyword_at_location(node.location)
331
+
332
+ #: (Prism::SourceEncodingNode) -> void
333
+ def on_source_encoding_node_enter(node) = handle_keyword_at_location(node.location)
334
+
335
+ #: (Prism::SourceFileNode) -> void
336
+ def on_source_file_node_enter(node) = handle_keyword_at_location(node.location)
337
+
338
+ #: (Prism::SourceLineNode) -> void
339
+ def on_source_line_node_enter(node) = handle_keyword_at_location(node.location)
340
+
341
+ #: (Prism::TrueNode) -> void
342
+ def on_true_node_enter(node) = handle_keyword_at_location(node.location)
343
+
344
+ #: (Prism::UndefNode) -> void
345
+ def on_undef_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
346
+
347
+ #: (Prism::UnlessNode) -> void
348
+ def on_unless_node_enter(node)
349
+ handle_keyword_at_location(node.keyword_loc, node.then_keyword_loc, node.end_keyword_loc)
350
+ end
351
+
352
+ #: (Prism::UntilNode) -> void
353
+ def on_until_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.do_keyword_loc, node.closing_loc)
354
+
355
+ #: (Prism::WhenNode) -> void
356
+ def on_when_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.then_keyword_loc)
357
+
358
+ #: (Prism::WhileNode) -> void
359
+ def on_while_node_enter(node) = handle_keyword_at_location(node.keyword_loc, node.do_keyword_loc, node.closing_loc)
360
+
361
+ #: (Prism::YieldNode) -> void
362
+ def on_yield_node_enter(node) = handle_keyword_at_location(node.keyword_loc)
363
+
225
364
  #: (Prism::ClassVariableAndWriteNode node) -> void
226
365
  def on_class_variable_and_write_node_enter(node)
227
366
  handle_variable_hover(node.name.to_s)
@@ -279,27 +418,37 @@ module RubyLsp
279
418
  end
280
419
  end
281
420
 
282
- #: (String keyword) -> void
283
- def handle_keyword_documentation(keyword)
284
- content = KEYWORD_DOCS[keyword]
285
- return unless content
421
+ #: (String) -> void
422
+ def handle_keyword_documentation(name)
423
+ keyword = @graph.keyword(name)
424
+ return unless keyword
286
425
 
287
- doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
288
-
289
- @response_builder.push("```ruby\n#{keyword}\n```", category: :title)
290
- @response_builder.push("[Read more](#{doc_uri})", category: :links)
291
- @response_builder.push(content, category: :documentation)
426
+ @response_builder.push("```ruby\n#{keyword.name}\n```", category: :title)
427
+ @response_builder.push(keyword.documentation, category: :documentation)
292
428
  end
293
429
 
294
- #: -> void
295
- def handle_super_node_hover
296
- # Sorbet can handle super hover on typed true or higher
297
- return if @sorbet_level.true_or_higher?
430
+ # Push keyword documentation when the cursor is on one of the provided locations. The keyword name is taken from
431
+ # the covering location's slice so that operator forms (`&&`, `||`, `{`, `}`, ternary `? :`) yield no hover —
432
+ # their slice is not a keyword in the Rubydex graph.
433
+ #
434
+ #: (*Prism::Location?) -> void
435
+ def handle_keyword_at_location(*locations)
436
+ loc = locations.find { |l| l && covers_position?(l, @position) }
437
+ return unless loc
438
+
439
+ handle_keyword_documentation(loc.slice)
440
+ end
298
441
 
299
- surrounding_method = @node_context.surrounding_method
300
- return unless surrounding_method
442
+ #: (Prism::Location keyword_location) -> void
443
+ def handle_super_node_hover(keyword_location)
444
+ # Sorbet can handle the inherited-method hover on typed true or higher, but it does not surface keyword docs, so
445
+ # we still push those
446
+ unless @sorbet_level.true_or_higher?
447
+ surrounding_method = @node_context.surrounding_method
448
+ handle_method_hover(surrounding_method.name, inherited_only: true) if surrounding_method
449
+ end
301
450
 
302
- handle_method_hover(surrounding_method, inherited_only: true)
451
+ handle_keyword_at_location(keyword_location)
303
452
  end
304
453
 
305
454
  #: (String message, ?inherited_only: bool) -> void
@@ -307,20 +456,22 @@ module RubyLsp
307
456
  type = @type_inferrer.infer_receiver_type(@node_context)
308
457
  return unless type
309
458
 
310
- methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
311
- return unless methods
459
+ owner = @graph[type.name]
460
+ return unless owner.is_a?(Rubydex::Namespace)
312
461
 
313
- first_method = methods.first #: as !nil
462
+ method = owner.find_member("#{message}()", only_inherited: inherited_only)
463
+ return unless method.is_a?(Rubydex::Method)
464
+ return unless method_reachable_from_call_site?(method, type, @graph, @node_context)
314
465
 
315
- title = "#{message}#{first_method.decorated_parameters}"
316
- title << first_method.formatted_signatures
466
+ title = +"#{message}#{method.decorated_parameters}"
467
+ title << method.formatted_signatures
317
468
 
318
469
  if type.is_a?(TypeInferrer::GuessedType)
319
470
  title << "\n\nGuessed receiver: #{type.name}"
320
471
  @response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
321
472
  end
322
473
 
323
- categorized_markdown_from_index_entries(title, methods).each do |category, content|
474
+ categorized_markdown_from_definitions(title, method.definitions).each do |category, content|
324
475
  @response_builder.push(content, category: category)
325
476
  end
326
477
  end
@@ -363,6 +514,7 @@ module RubyLsp
363
514
  def generate_hover(name, location)
364
515
  declaration = @graph.resolve_constant(name, @node_context.nesting)
365
516
  return unless declaration
517
+ return unless constant_reachable_from_call_site?(declaration, name, @node_context)
366
518
 
367
519
  categorized_markdown_from_definitions(declaration.name, declaration.definitions).each do |category, content|
368
520
  @response_builder.push(content, category: category)
@@ -11,7 +11,7 @@ module RubyLsp
11
11
  @sorbet_level = sorbet_level
12
12
  @response_builder = response_builder
13
13
  @global_state = global_state
14
- @index = global_state.index #: RubyIndexer::Index
14
+ @graph = global_state.graph #: Rubydex::Graph
15
15
  @type_inferrer = global_state.type_inferrer #: TypeInferrer
16
16
  @node_context = node_context
17
17
  dispatcher.register(self, :on_call_node_enter)
@@ -27,18 +27,17 @@ module RubyLsp
27
27
  type = @type_inferrer.infer_receiver_type(@node_context)
28
28
  return unless type
29
29
 
30
- methods = @index.resolve_method(message, type.name)
31
- return unless methods
30
+ owner = @graph[type.name]
31
+ return unless owner.is_a?(Rubydex::Namespace)
32
32
 
33
- target_method = methods.first
34
- return unless target_method
33
+ target_method = owner.find_member("#{message}()")
34
+ return unless target_method.is_a?(Rubydex::Method)
35
35
 
36
36
  signatures = target_method.signatures
37
37
 
38
- # If the method doesn't have any parameters, there's no need to show signature help
38
+ # If the method doesn't have any signatures, there's nothing to show
39
39
  return if signatures.empty?
40
40
 
41
- name = target_method.name
42
41
  title = +""
43
42
 
44
43
  extra_links = if type.is_a?(TypeInferrer::GuessedType)
@@ -49,7 +48,7 @@ module RubyLsp
49
48
  active_signature, active_parameter = determine_active_signature_and_parameter(node, signatures)
50
49
 
51
50
  signature_help = Interface::SignatureHelp.new(
52
- signatures: generate_signatures(signatures, name, methods, title, extra_links),
51
+ signatures: generate_signatures(signatures, message, target_method, title, extra_links),
53
52
  active_signature: active_signature,
54
53
  active_parameter: active_parameter,
55
54
  )
@@ -58,7 +57,7 @@ module RubyLsp
58
57
 
59
58
  private
60
59
 
61
- #: (Prism::CallNode node, Array[RubyIndexer::Entry::Signature] signatures) -> [Integer, Integer]
60
+ #: (Prism::CallNode node, Array[Rubydex::Signature] signatures) -> [Integer, Integer]
62
61
  def determine_active_signature_and_parameter(node, signatures)
63
62
  arguments_node = node.arguments
64
63
  arguments = arguments_node&.arguments || []
@@ -86,15 +85,15 @@ module RubyLsp
86
85
  [active_sig_index, active_parameter]
87
86
  end
88
87
 
89
- #: (Array[RubyIndexer::Entry::Signature] signatures, String method_name, Array[RubyIndexer::Entry] methods, String title, String? extra_links) -> Array[Interface::SignatureInformation]
90
- def generate_signatures(signatures, method_name, methods, title, extra_links)
88
+ #: (Array[Rubydex::Signature] signatures, String method_name, Rubydex::Method method, String title, String? extra_links) -> Array[Interface::SignatureInformation]
89
+ def generate_signatures(signatures, method_name, method, title, extra_links)
91
90
  signatures.map do |signature|
92
91
  Interface::SignatureInformation.new(
93
92
  label: "#{method_name}(#{signature.format})",
94
93
  parameters: signature.parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
95
94
  documentation: Interface::MarkupContent.new(
96
95
  kind: "markdown",
97
- value: markdown_from_index_entries(title, methods, extra_links: extra_links),
96
+ value: markdown_from_definitions(title, method.definitions, extra_links: extra_links),
98
97
  ),
99
98
  )
100
99
  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
@@ -56,7 +56,23 @@ module RubyLsp
56
56
 
57
57
  #: (String? name) -> String
58
58
  def calc_fully_qualified_name(name)
59
- RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
59
+ parts = name ? @nesting + [name] : @nesting
60
+ return "" if parts.empty?
61
+
62
+ last = parts.last #: as !nil
63
+ rest = parts[0...-1] #: as !nil
64
+
65
+ resolved = @graph.resolve_constant(last, rest)
66
+ return resolved.name if resolved
67
+
68
+ # Fallback for unresolved constants (e.g. dynamic references): preserve top-level reset semantics by
69
+ # truncating at the first `::`-prefixed part when scanning from the innermost out.
70
+ corrected = []
71
+ parts.reverse_each do |part|
72
+ corrected.prepend(part.delete_prefix("::"))
73
+ break if part.start_with?("::")
74
+ end
75
+ corrected.join("::")
60
76
  end
61
77
 
62
78
  #: (Prism::ClassNode node, String fully_qualified_name) -> Array[String]
@@ -64,22 +80,29 @@ module RubyLsp
64
80
  superclass = node.superclass
65
81
 
66
82
  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]
83
+ declaration = @graph[fully_qualified_name]
84
+
85
+ unless declaration.is_a?(Rubydex::Namespace)
86
+ # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
87
+ # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
88
+ return [superclass&.slice].compact
89
+ end
90
+
91
+ ancestors = declaration.ancestors.map(&:name)
92
+ superclass_ref = declaration.definitions
93
+ .filter_map { |d| d.superclass if d.is_a?(Rubydex::ClassDefinition) }
94
+ .find { |ref| !ref.is_a?(Rubydex::ResolvedConstantReference) || ref.declaration.name != "Object" }
95
+
96
+ # If we couldn't resolve the parent class, then artificially inject it into the ancestors
97
+ if superclass_ref.is_a?(Rubydex::UnresolvedConstantReference) && superclass
98
+ insert_index = ancestors.index(fully_qualified_name) #: as !nil
99
+ insert_index += 1
100
+ ancestors.insert(insert_index, superclass.slice)
101
+ return ancestors
76
102
  end
77
103
 
104
+ # If the parent class is properly resolved or if there isn't one, then just use the ancestors
78
105
  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
106
  end
84
107
  end
85
108