ruby-lsp 0.27.0.beta1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e5c88483f280d13f2691e131a16e35df07df898e8f380071d077479cd24133e
4
- data.tar.gz: 7630ce8844fcb532ca63c77e299b692471b145530a623dcbc78ecbb50ce08596
3
+ metadata.gz: 11c508848c7c0db2c86807dd05b5de3a0cf33c8d69259cffd34865af8f50d93b
4
+ data.tar.gz: d030d7223e7714008fef42f5b8635b373cf42f88f60e3c22a878dd1e48ed2ee8
5
5
  SHA512:
6
- metadata.gz: 7757882e6e441d49705a389bad6b0b617b7f9a26fb1e5f34ebd3fc93eeb8921b31a703253cff79ed123ae411333fc7d91f0555ae162cafee1d10adfb13b76cf4
7
- data.tar.gz: c215743ab00113e4dd3186732faabd462fef6a3be425f1c76bb65b045846effd7db0673478244218f1087e3fa9ca96f8668c86970def65bf2e5fbb4ea0728a8d
6
+ metadata.gz: 23d4677fea98fe4723442e9162c9835ce9813eab354882c9b3cbd45deb4fc9cd81a91d1618cdb8e580c68eeaf840a45cc7271c031b2b4bf559d92a50d64755ec
7
+ data.tar.gz: 1e044d2758e1872a1a94df7f288e8e2ff4b1b4ca2a089db8958acf5830bacd8247086c1c0df167fe072a6586a8391332ceaaa1827add9280e9c42cd3ce96bdf2
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.27.0.beta1
1
+ 0.27.0.beta2
@@ -7,7 +7,6 @@ require "did_you_mean"
7
7
  require "ruby_indexer/lib/ruby_indexer/uri"
8
8
  require "ruby_indexer/lib/ruby_indexer/visibility_scope"
9
9
  require "ruby_indexer/lib/ruby_indexer/declaration_listener"
10
- require "ruby_indexer/lib/ruby_indexer/reference_finder"
11
10
  require "ruby_indexer/lib/ruby_indexer/enhancement"
12
11
  require "ruby_indexer/lib/ruby_indexer/index"
13
12
  require "ruby_indexer/lib/ruby_indexer/entry"
@@ -56,26 +56,9 @@ module RubyLsp
56
56
  addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
57
57
 
58
58
  if include_project_addons
59
- project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
60
59
  bundle_path = Bundler.bundle_path.to_s
61
- gems_dir = Bundler.bundle_path.join("gems")
62
-
63
- # Create an array of rejection glob patterns to ignore add-ons already discovered through Gem.find_files if
64
- # they are also copied inside the workspace for whatever reason. We received reports of projects having gems
65
- # installed in vendor/bundle despite BUNDLE_PATH pointing elsewhere. Without this mechanism, we will
66
- # double-require the same add-on, potentially for different versions of the same gem, which leads to incorrect
67
- # behavior
68
- reject_glob_patterns = addon_files.map do |path|
69
- relative_gem_path = Pathname.new(path).relative_path_from(gems_dir)
70
- first_part, *parts = relative_gem_path.to_s.split(File::SEPARATOR)
71
- first_part&.gsub!(/-([0-9.]+)$/, "*")
72
- "**/#{first_part}/#{parts.join("/")}"
73
- end
74
-
75
- project_addons.reject! do |path|
76
- path.start_with?(bundle_path) ||
77
- reject_glob_patterns.any? { |pattern| File.fnmatch?(pattern, path, File::Constants::FNM_PATHNAME) }
78
- end
60
+ project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
61
+ project_addons.reject! { |path| path.start_with?(bundle_path) || gem_installation_path?(path) }
79
62
 
80
63
  addon_files.concat(project_addons)
81
64
  end
@@ -162,6 +145,23 @@ module RubyLsp
162
145
  "Add-on is not compatible with this version of the Ruby LSP. Skipping its activation"
163
146
  end
164
147
  end
148
+
149
+ private
150
+
151
+ # Checks if a path appears to be inside a versioned gem installation directory (e.g., `rubocop-1.73.0/lib/...`) by
152
+ # looking for a directory segment matching `name-version` before the `lib` component
153
+ #
154
+ #: (String path) -> bool
155
+ def gem_installation_path?(path)
156
+ parts = path.split(%r{[/\\]})
157
+ lib_index = parts.rindex("lib")
158
+ return false unless lib_index
159
+
160
+ prefix = parts[0...lib_index] #: Array[String]?
161
+ return false unless prefix
162
+
163
+ prefix.any? { |part| part.match?(/-\d+(\.\d+)+$/) }
164
+ end
165
165
  end
166
166
 
167
167
  #: -> void
@@ -204,7 +204,7 @@ module RubyLsp
204
204
  Encoding::UTF_16LE
205
205
  else
206
206
  @graph.encoding = "utf32"
207
- Encoding::UTF_32
207
+ Encoding::UTF_32LE
208
208
  end
209
209
  @index.configuration.encoding = @encoding
210
210
 
@@ -32,6 +32,7 @@ require "shellwords"
32
32
  require "set"
33
33
 
34
34
  # Rubydex LSP additions
35
+ require "ruby_lsp/rubydex/declaration"
35
36
  require "ruby_lsp/rubydex/definition"
36
37
  require "ruby_lsp/rubydex/reference"
37
38
 
@@ -39,7 +40,6 @@ require "ruby-lsp"
39
40
  require "ruby_lsp/base_server"
40
41
  require "ruby_indexer/ruby_indexer"
41
42
  require "ruby_lsp/utils"
42
- require "ruby_lsp/static_docs"
43
43
  require "ruby_lsp/scope"
44
44
  require "ruby_lsp/client_capabilities"
45
45
  require "ruby_lsp/global_state"
@@ -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
  )
@@ -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,45 +6,13 @@ 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
@@ -53,45 +21,80 @@ module RubyLsp
53
21
  @path = uri.to_standardized_path #: String?
54
22
  @node_context = node_context
55
23
  @sorbet_level = sorbet_level
24
+ @position = position
56
25
 
57
26
  dispatcher.register(
58
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,
59
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,
60
47
  :on_constant_read_node_enter,
61
48
  :on_constant_write_node_enter,
62
- :on_constant_path_node_enter,
63
- :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,
64
56
  :on_global_variable_and_write_node_enter,
65
57
  :on_global_variable_operator_write_node_enter,
66
58
  :on_global_variable_or_write_node_enter,
67
59
  :on_global_variable_read_node_enter,
68
60
  :on_global_variable_target_node_enter,
69
61
  :on_global_variable_write_node_enter,
70
- :on_instance_variable_read_node_enter,
71
- :on_instance_variable_write_node_enter,
62
+ :on_if_node_enter,
63
+ :on_in_node_enter,
72
64
  :on_instance_variable_and_write_node_enter,
73
65
  :on_instance_variable_operator_write_node_enter,
74
66
  :on_instance_variable_or_write_node_enter,
67
+ :on_instance_variable_read_node_enter,
75
68
  :on_instance_variable_target_node_enter,
76
- :on_super_node_enter,
77
- :on_forwarding_super_node_enter,
78
- :on_string_node_enter,
69
+ :on_instance_variable_write_node_enter,
79
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,
80
94
  :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
95
  )
88
96
  end
89
97
 
90
- #: (Prism::BreakNode node) -> void
91
- def on_break_node_enter(node)
92
- handle_keyword_documentation(node.keyword)
93
- end
94
-
95
98
  #: (Prism::StringNode node) -> void
96
99
  def on_string_node_enter(node)
97
100
  if @path && File.basename(@path) == GEMFILE_NAME
@@ -144,6 +147,12 @@ module RubyLsp
144
147
  message = node.message
145
148
  return unless message
146
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
+
147
156
  handle_method_hover(message)
148
157
  end
149
158
 
@@ -209,19 +218,150 @@ module RubyLsp
209
218
 
210
219
  #: (Prism::SuperNode node) -> void
211
220
  def on_super_node_enter(node)
212
- handle_super_node_hover
221
+ handle_super_node_hover(node.keyword_loc)
213
222
  end
214
223
 
215
224
  #: (Prism::ForwardingSuperNode node) -> void
216
225
  def on_forwarding_super_node_enter(node)
217
- 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
+ )
287
+ end
288
+
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)
218
292
  end
219
293
 
220
- #: (Prism::YieldNode node) -> void
221
- def on_yield_node_enter(node)
222
- handle_keyword_documentation(node.keyword)
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)
223
351
  end
224
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
+
225
365
  #: (Prism::ClassVariableAndWriteNode node) -> void
226
366
  def on_class_variable_and_write_node_enter(node)
227
367
  handle_variable_hover(node.name.to_s)
@@ -279,27 +419,37 @@ module RubyLsp
279
419
  end
280
420
  end
281
421
 
282
- #: (String keyword) -> void
283
- def handle_keyword_documentation(keyword)
284
- content = KEYWORD_DOCS[keyword]
285
- return unless content
422
+ #: (String) -> void
423
+ def handle_keyword_documentation(name)
424
+ keyword = @graph.keyword(name)
425
+ return unless keyword
286
426
 
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)
427
+ @response_builder.push("```ruby\n#{keyword.name}\n```", category: :title)
428
+ @response_builder.push(keyword.documentation, category: :documentation)
292
429
  end
293
430
 
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?
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
298
439
 
299
- surrounding_method = @node_context.surrounding_method
300
- return unless surrounding_method
440
+ handle_keyword_documentation(loc.slice)
441
+ end
442
+
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
301
451
 
302
- handle_method_hover(surrounding_method, inherited_only: true)
452
+ handle_keyword_at_location(keyword_location)
303
453
  end
304
454
 
305
455
  #: (String message, ?inherited_only: bool) -> void
@@ -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("::")}::<#{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