ruby-lsp 0.17.3 → 0.17.4

Sign up to get free protection for your applications and to get access to all the features.
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