ruby-lsp 0.17.3 → 0.17.4

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +241 -91
  4. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +74 -102
  5. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +81 -19
  6. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +50 -2
  7. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  8. data/lib/ruby_indexer/test/index_test.rb +326 -27
  9. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  10. data/lib/ruby_indexer/test/method_test.rb +54 -24
  11. data/lib/ruby_indexer/test/rbs_indexer_test.rb +27 -2
  12. data/lib/ruby_lsp/document.rb +37 -8
  13. data/lib/ruby_lsp/global_state.rb +7 -3
  14. data/lib/ruby_lsp/internal.rb +1 -0
  15. data/lib/ruby_lsp/listeners/completion.rb +53 -14
  16. data/lib/ruby_lsp/listeners/definition.rb +11 -7
  17. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  18. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  19. data/lib/ruby_lsp/node_context.rb +6 -1
  20. data/lib/ruby_lsp/requests/completion.rb +5 -4
  21. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
  22. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  23. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  24. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  25. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
  26. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  27. data/lib/ruby_lsp/requests.rb +2 -0
  28. data/lib/ruby_lsp/server.rb +54 -15
  29. data/lib/ruby_lsp/test_helper.rb +1 -1
  30. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  31. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c02fa9ec776002a86f4eeee4037132d8b0f9403f101ccff5c7a31c46385a2818
4
- data.tar.gz: 3b788794763cee273e2ce677955c3750ebac90962c8d328946f91998a900eca5
3
+ metadata.gz: 71204369ec5201376f1af11875a2e1bf16df56a31719ea78932a7f5f6e9c7e5c
4
+ data.tar.gz: d83e299ade275415fe4cac25abe78f7ee7ec622c47cc1699d42b3b772816fdba
5
5
  SHA512:
6
- metadata.gz: e3e152e24d1dca4aca721537f64df9062ae48aeea65b361ffd80dc6710cd263b0e8d1181f970b6d06e6147882c1cad40ba91d25ef6f684e2f5374a9738e1fae9
7
- data.tar.gz: 79c3eb785ac55de19c4e9031ed6c8ca0a9a6df27156244ba7ee3ca2109659f8f2b9ce6efe7259cfcd65922a0b8cefc384fca2215dbdffac41735d9149db651a0
6
+ metadata.gz: f3ac517cb1c711dc1a5ddf2c4027f705e393e30028b9ae7063b18a5f51362fd4d7277607d19d8ca7a9a3c9146960bd612a02ed4a8933c8223a42daf3dfe5e63b
7
+ data.tar.gz: 0c4408865d1894547aeedfc0441fa558a8281f54e7e26c38b619e67fd67bfc3bed49aa27dc06d8b89924c7ee8db4452b5cd2f810c0dfb01157eef127910b2182
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.3
1
+ 0.17.4
@@ -5,6 +5,9 @@ module RubyIndexer
5
5
  class DeclarationListener
6
6
  extend T::Sig
7
7
 
8
+ OBJECT_NESTING = T.let(["Object"].freeze, T::Array[String])
9
+ BASIC_OBJECT_NESTING = T.let(["BasicObject"].freeze, T::Array[String])
10
+
8
11
  sig do
9
12
  params(index: Index, dispatcher: Prism::Dispatcher, parse_result: Prism::ParseResult, file_path: String).void
10
13
  end
@@ -33,6 +36,8 @@ module RubyIndexer
33
36
  :on_class_node_leave,
34
37
  :on_module_node_enter,
35
38
  :on_module_node_leave,
39
+ :on_singleton_class_node_enter,
40
+ :on_singleton_class_node_leave,
36
41
  :on_def_node_enter,
37
42
  :on_def_node_leave,
38
43
  :on_call_node_enter,
@@ -64,15 +69,26 @@ module RubyIndexer
64
69
  comments = collect_comments(node)
65
70
 
66
71
  superclass = node.superclass
72
+
73
+ nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
74
+
67
75
  parent_class = case superclass
68
76
  when Prism::ConstantReadNode, Prism::ConstantPathNode
69
77
  superclass.slice
70
78
  else
71
- "::Object"
79
+ case nesting
80
+ when OBJECT_NESTING
81
+ # When Object is reopened, its parent class should still be the top-level BasicObject
82
+ "::BasicObject"
83
+ when BASIC_OBJECT_NESTING
84
+ # When BasicObject is reopened, its parent class should still be nil
85
+ nil
86
+ else
87
+ # Otherwise, the parent class should be the top-level Object
88
+ "::Object"
89
+ end
72
90
  end
73
91
 
74
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
75
-
76
92
  entry = Entry::Class.new(
77
93
  nesting,
78
94
  @file_path,
@@ -82,7 +98,7 @@ module RubyIndexer
82
98
  )
83
99
 
84
100
  @owner_stack << entry
85
- @index << entry
101
+ @index.add(entry)
86
102
  @stack << name
87
103
  end
88
104
 
@@ -104,7 +120,7 @@ module RubyIndexer
104
120
  entry = Entry::Module.new(nesting, @file_path, node.location, comments)
105
121
 
106
122
  @owner_stack << entry
107
- @index << entry
123
+ @index.add(entry)
108
124
  @stack << name
109
125
  end
110
126
 
@@ -115,6 +131,37 @@ module RubyIndexer
115
131
  @visibility_stack.pop
116
132
  end
117
133
 
134
+ sig { params(node: Prism::SingletonClassNode).void }
135
+ def on_singleton_class_node_enter(node)
136
+ @visibility_stack.push(Entry::Visibility::PUBLIC)
137
+
138
+ current_owner = @owner_stack.last
139
+
140
+ if current_owner
141
+ expression = node.expression
142
+ @stack << (expression.is_a?(Prism::SelfNode) ? "<Class:#{@stack.last}>" : "<Class:#{expression.slice}>")
143
+
144
+ existing_entries = T.cast(@index[@stack.join("::")], T.nilable(T::Array[Entry::SingletonClass]))
145
+
146
+ if existing_entries
147
+ entry = T.must(existing_entries.first)
148
+ entry.update_singleton_information(node.location, collect_comments(node))
149
+ else
150
+ entry = Entry::SingletonClass.new(@stack, @file_path, node.location, collect_comments(node), nil)
151
+ @index.add(entry, skip_prefix_tree: true)
152
+ end
153
+
154
+ @owner_stack << entry
155
+ end
156
+ end
157
+
158
+ sig { params(node: Prism::SingletonClassNode).void }
159
+ def on_singleton_class_node_leave(node)
160
+ @stack.pop
161
+ @owner_stack.pop
162
+ @visibility_stack.pop
163
+ end
164
+
118
165
  sig { params(node: Prism::MultiWriteNode).void }
119
166
  def on_multi_write_node_enter(node)
120
167
  value = node.value
@@ -246,119 +293,110 @@ module RubyIndexer
246
293
 
247
294
  case node.receiver
248
295
  when nil
249
- @index << Entry::InstanceMethod.new(
296
+ @index.add(Entry::Method.new(
250
297
  method_name,
251
298
  @file_path,
252
299
  node.location,
253
300
  comments,
254
- node.parameters,
301
+ list_params(node.parameters),
255
302
  current_visibility,
256
303
  @owner_stack.last,
257
- )
304
+ ))
258
305
  when Prism::SelfNode
259
- @index << Entry::SingletonMethod.new(
306
+ singleton = singleton_klass
307
+
308
+ @index.add(Entry::Method.new(
260
309
  method_name,
261
310
  @file_path,
262
311
  node.location,
263
312
  comments,
264
- node.parameters,
313
+ list_params(node.parameters),
265
314
  current_visibility,
266
- @owner_stack.last,
267
- )
315
+ singleton,
316
+ ))
317
+
318
+ if singleton
319
+ @owner_stack << singleton
320
+ @stack << "<Class:#{@stack.last}>"
321
+ end
268
322
  end
269
323
  end
270
324
 
271
325
  sig { params(node: Prism::DefNode).void }
272
326
  def on_def_node_leave(node)
273
327
  @inside_def = false
328
+
329
+ if node.receiver.is_a?(Prism::SelfNode)
330
+ @owner_stack.pop
331
+ @stack.pop
332
+ end
274
333
  end
275
334
 
276
335
  sig { params(node: Prism::InstanceVariableWriteNode).void }
277
336
  def on_instance_variable_write_node_enter(node)
278
- name = node.name.to_s
279
- return if name == "@"
280
-
281
- @index << Entry::InstanceVariable.new(
282
- name,
283
- @file_path,
284
- node.name_loc,
285
- collect_comments(node),
286
- @owner_stack.last,
287
- )
337
+ handle_instance_variable(node, node.name_loc)
288
338
  end
289
339
 
290
340
  sig { params(node: Prism::InstanceVariableAndWriteNode).void }
291
341
  def on_instance_variable_and_write_node_enter(node)
292
- name = node.name.to_s
293
- return if name == "@"
294
-
295
- @index << Entry::InstanceVariable.new(
296
- name,
297
- @file_path,
298
- node.name_loc,
299
- collect_comments(node),
300
- @owner_stack.last,
301
- )
342
+ handle_instance_variable(node, node.name_loc)
302
343
  end
303
344
 
304
345
  sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
305
346
  def on_instance_variable_operator_write_node_enter(node)
306
- name = node.name.to_s
307
- return if name == "@"
308
-
309
- @index << Entry::InstanceVariable.new(
310
- name,
311
- @file_path,
312
- node.name_loc,
313
- collect_comments(node),
314
- @owner_stack.last,
315
- )
347
+ handle_instance_variable(node, node.name_loc)
316
348
  end
317
349
 
318
350
  sig { params(node: Prism::InstanceVariableOrWriteNode).void }
319
351
  def on_instance_variable_or_write_node_enter(node)
320
- name = node.name.to_s
321
- return if name == "@"
322
-
323
- @index << Entry::InstanceVariable.new(
324
- name,
325
- @file_path,
326
- node.name_loc,
327
- collect_comments(node),
328
- @owner_stack.last,
329
- )
352
+ handle_instance_variable(node, node.name_loc)
330
353
  end
331
354
 
332
355
  sig { params(node: Prism::InstanceVariableTargetNode).void }
333
356
  def on_instance_variable_target_node_enter(node)
334
- name = node.name.to_s
335
- return if name == "@"
336
-
337
- @index << Entry::InstanceVariable.new(
338
- name,
339
- @file_path,
340
- node.location,
341
- collect_comments(node),
342
- @owner_stack.last,
343
- )
357
+ handle_instance_variable(node, node.location)
344
358
  end
345
359
 
346
360
  sig { params(node: Prism::AliasMethodNode).void }
347
361
  def on_alias_method_node_enter(node)
348
362
  method_name = node.new_name.slice
349
363
  comments = collect_comments(node)
350
- @index << Entry::UnresolvedMethodAlias.new(
351
- method_name,
352
- node.old_name.slice,
353
- @owner_stack.last,
354
- @file_path,
355
- node.new_name.location,
356
- comments,
364
+ @index.add(
365
+ Entry::UnresolvedMethodAlias.new(
366
+ method_name,
367
+ node.old_name.slice,
368
+ @owner_stack.last,
369
+ @file_path,
370
+ node.new_name.location,
371
+ comments,
372
+ ),
357
373
  )
358
374
  end
359
375
 
360
376
  private
361
377
 
378
+ sig do
379
+ params(
380
+ node: T.any(
381
+ Prism::InstanceVariableAndWriteNode,
382
+ Prism::InstanceVariableOperatorWriteNode,
383
+ Prism::InstanceVariableOrWriteNode,
384
+ Prism::InstanceVariableTargetNode,
385
+ Prism::InstanceVariableWriteNode,
386
+ ),
387
+ loc: Prism::Location,
388
+ ).void
389
+ end
390
+ def handle_instance_variable(node, loc)
391
+ name = node.name.to_s
392
+ return if name == "@"
393
+
394
+ # When instance variables are declared inside the class body, they turn into class instance variables rather than
395
+ # regular instance variables
396
+ owner = @inside_def ? @owner_stack.last : singleton_klass
397
+ @index.add(Entry::InstanceVariable.new(name, @file_path, loc, collect_comments(node), owner))
398
+ end
399
+
362
400
  sig { params(node: Prism::CallNode).void }
363
401
  def handle_private_constant(node)
364
402
  arguments = node.arguments&.arguments
@@ -411,13 +449,15 @@ module RubyIndexer
411
449
  return unless old_name_value
412
450
 
413
451
  comments = collect_comments(node)
414
- @index << Entry::UnresolvedMethodAlias.new(
415
- new_name_value,
416
- old_name_value,
417
- @owner_stack.last,
418
- @file_path,
419
- new_name.location,
420
- comments,
452
+ @index.add(
453
+ Entry::UnresolvedMethodAlias.new(
454
+ new_name_value,
455
+ old_name_value,
456
+ @owner_stack.last,
457
+ @file_path,
458
+ new_name.location,
459
+ comments,
460
+ ),
421
461
  )
422
462
  end
423
463
 
@@ -443,22 +483,24 @@ module RubyIndexer
443
483
  value = node.value unless node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
444
484
  comments = collect_comments(node)
445
485
 
446
- @index << case value
447
- when Prism::ConstantReadNode, Prism::ConstantPathNode
448
- Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
449
- when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
486
+ @index.add(
487
+ case value
488
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
489
+ Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
490
+ when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
450
491
  Prism::ConstantOperatorWriteNode
451
492
 
452
- # If the right hand side is another constant assignment, we need to visit it because that constant has to be
453
- # indexed too
454
- Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
455
- when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
493
+ # If the right hand side is another constant assignment, we need to visit it because that constant has to be
494
+ # indexed too
495
+ Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
496
+ when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
456
497
  Prism::ConstantPathAndWriteNode
457
498
 
458
- Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
459
- else
460
- Entry::Constant.new(name, @file_path, node.location, comments)
461
- end
499
+ Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
500
+ else
501
+ Entry::Constant.new(name, @file_path, node.location, comments)
502
+ end,
503
+ )
462
504
  end
463
505
 
464
506
  sig { params(node: Prism::Node).returns(T::Array[String]) }
@@ -515,15 +557,20 @@ module RubyIndexer
515
557
 
516
558
  next unless name && loc
517
559
 
518
- @index << Entry::Accessor.new(name, @file_path, loc, comments, current_visibility, @owner_stack.last) if reader
519
- @index << Entry::Accessor.new(
560
+ if reader
561
+ @index.add(Entry::Accessor.new(name, @file_path, loc, comments, current_visibility, @owner_stack.last))
562
+ end
563
+
564
+ next unless writer
565
+
566
+ @index.add(Entry::Accessor.new(
520
567
  "#{name}=",
521
568
  @file_path,
522
569
  loc,
523
570
  comments,
524
571
  current_visibility,
525
572
  @owner_stack.last,
526
- ) if writer
573
+ ))
527
574
  end
528
575
  end
529
576
 
@@ -558,5 +605,108 @@ module RubyIndexer
558
605
  def current_visibility
559
606
  T.must(@visibility_stack.last)
560
607
  end
608
+
609
+ sig { params(parameters_node: T.nilable(Prism::ParametersNode)).returns(T::Array[Entry::Parameter]) }
610
+ def list_params(parameters_node)
611
+ return [] unless parameters_node
612
+
613
+ parameters = []
614
+
615
+ parameters_node.requireds.each do |required|
616
+ name = parameter_name(required)
617
+ next unless name
618
+
619
+ parameters << Entry::RequiredParameter.new(name: name)
620
+ end
621
+
622
+ parameters_node.optionals.each do |optional|
623
+ name = parameter_name(optional)
624
+ next unless name
625
+
626
+ parameters << Entry::OptionalParameter.new(name: name)
627
+ end
628
+
629
+ rest = parameters_node.rest
630
+
631
+ if rest.is_a?(Prism::RestParameterNode)
632
+ rest_name = rest.name || Entry::RestParameter::DEFAULT_NAME
633
+ parameters << Entry::RestParameter.new(name: rest_name)
634
+ end
635
+
636
+ parameters_node.keywords.each do |keyword|
637
+ name = parameter_name(keyword)
638
+ next unless name
639
+
640
+ case keyword
641
+ when Prism::RequiredKeywordParameterNode
642
+ parameters << Entry::KeywordParameter.new(name: name)
643
+ when Prism::OptionalKeywordParameterNode
644
+ parameters << Entry::OptionalKeywordParameter.new(name: name)
645
+ end
646
+ end
647
+
648
+ keyword_rest = parameters_node.keyword_rest
649
+
650
+ if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
651
+ keyword_rest_name = parameter_name(keyword_rest) || Entry::KeywordRestParameter::DEFAULT_NAME
652
+ parameters << Entry::KeywordRestParameter.new(name: keyword_rest_name)
653
+ end
654
+
655
+ parameters_node.posts.each do |post|
656
+ name = parameter_name(post)
657
+ next unless name
658
+
659
+ parameters << Entry::RequiredParameter.new(name: name)
660
+ end
661
+
662
+ block = parameters_node.block
663
+ parameters << Entry::BlockParameter.new(name: block.name || Entry::BlockParameter::DEFAULT_NAME) if block
664
+
665
+ parameters
666
+ end
667
+
668
+ sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(Symbol)) }
669
+ def parameter_name(node)
670
+ case node
671
+ when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
672
+ Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
673
+ Prism::RestParameterNode, Prism::KeywordRestParameterNode
674
+ node.name
675
+ when Prism::MultiTargetNode
676
+ names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }
677
+
678
+ rest = node.rest
679
+ if rest.is_a?(Prism::SplatNode)
680
+ name = rest.expression&.slice
681
+ names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
682
+ end
683
+
684
+ names << nil if rest.is_a?(Prism::ImplicitRestNode)
685
+
686
+ names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })
687
+
688
+ names_with_commas = names.join(", ")
689
+ :"(#{names_with_commas})"
690
+ end
691
+ end
692
+
693
+ sig { returns(T.nilable(Entry::Class)) }
694
+ def singleton_klass
695
+ attached_class = @owner_stack.last
696
+ return unless attached_class
697
+
698
+ # Return the existing singleton class if available
699
+ owner = T.cast(
700
+ @index["#{attached_class.name}::<Class:#{attached_class.name}>"],
701
+ T.nilable(T::Array[Entry::SingletonClass]),
702
+ )
703
+ return owner.first if owner
704
+
705
+ # If not available, create the singleton class lazily
706
+ nesting = @stack + ["<Class:#{@stack.last}>"]
707
+ entry = Entry::SingletonClass.new(nesting, @file_path, attached_class.location, [], nil)
708
+ @index.add(entry, skip_prefix_tree: true)
709
+ entry
710
+ end
561
711
  end
562
712
  end