ruby-lsp 0.17.2 → 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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +280 -74
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +102 -102
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +234 -56
  7. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +147 -0
  8. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -2
  10. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  11. data/lib/ruby_indexer/test/constant_test.rb +1 -1
  12. data/lib/ruby_indexer/test/index_test.rb +702 -71
  13. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  14. data/lib/ruby_indexer/test/method_test.rb +74 -24
  15. data/lib/ruby_indexer/test/rbs_indexer_test.rb +67 -0
  16. data/lib/ruby_indexer/test/test_case.rb +7 -0
  17. data/lib/ruby_lsp/document.rb +37 -8
  18. data/lib/ruby_lsp/global_state.rb +43 -18
  19. data/lib/ruby_lsp/internal.rb +2 -0
  20. data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
  21. data/lib/ruby_lsp/listeners/completion.rb +53 -14
  22. data/lib/ruby_lsp/listeners/definition.rb +11 -7
  23. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  24. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  25. data/lib/ruby_lsp/node_context.rb +6 -1
  26. data/lib/ruby_lsp/requests/completion.rb +5 -4
  27. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
  28. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  29. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  31. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
  32. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  33. data/lib/ruby_lsp/requests.rb +2 -0
  34. data/lib/ruby_lsp/server.rb +54 -4
  35. data/lib/ruby_lsp/test_helper.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  37. metadata +29 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 595a062d747f467d33b204523cfc6173b450f1b570616888b292109d0390a980
4
- data.tar.gz: 97d1ed5a7a69521c427207a971684054eb7d35ddb6cd9e315e5fdb470c01d4d8
3
+ metadata.gz: 71204369ec5201376f1af11875a2e1bf16df56a31719ea78932a7f5f6e9c7e5c
4
+ data.tar.gz: d83e299ade275415fe4cac25abe78f7ee7ec622c47cc1699d42b3b772816fdba
5
5
  SHA512:
6
- metadata.gz: 445852ced0242742544611c8ee39574eb09ca33f1a2b9b27ce53c8c33514256716313db4723ce7ce0a8e2ad6bd29ee47c75f68afc65150c243cbf1a0254dad2e
7
- data.tar.gz: cb462f49c28c52ff92fa392790e030f3bdac3efeacfab865bf5892cb0b62494f7ee7c46c61a8e667867ed06f9abcd037786054e49567f982c974cd777da82551
6
+ metadata.gz: f3ac517cb1c711dc1a5ddf2c4027f705e393e30028b9ae7063b18a5f51362fd4d7277607d19d8ca7a9a3c9146960bd612a02ed4a8933c8223a42daf3dfe5e63b
7
+ data.tar.gz: 0c4408865d1894547aeedfc0441fa558a8281f54e7e26c38b619e67fd67bfc3bed49aa27dc06d8b89924c7ee8db4452b5cd2f810c0dfb01157eef127910b2182
data/README.md CHANGED
@@ -48,6 +48,8 @@ If using VS Code, all you have to do is install the [Ruby LSP
48
48
  extension](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp) to get the extra features in the
49
49
  editor. Do not install the `ruby-lsp` gem manually.
50
50
 
51
+ For more information on using and configuring the extension, see [vscode/README.md](vscode/README.md).
52
+
51
53
  ### With other editors
52
54
 
53
55
  See [editors](EDITORS.md) for community instructions on setting up the Ruby LSP, which current includes Emacs, Neovim, Sublime Text, and Zed.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.2
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,
@@ -52,6 +57,7 @@ module RubyIndexer
52
57
  :on_instance_variable_operator_write_node_enter,
53
58
  :on_instance_variable_or_write_node_enter,
54
59
  :on_instance_variable_target_node_enter,
60
+ :on_alias_method_node_enter,
55
61
  )
56
62
  end
57
63
 
@@ -63,13 +69,26 @@ module RubyIndexer
63
69
  comments = collect_comments(node)
64
70
 
65
71
  superclass = node.superclass
72
+
73
+ nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
74
+
66
75
  parent_class = case superclass
67
76
  when Prism::ConstantReadNode, Prism::ConstantPathNode
68
77
  superclass.slice
78
+ else
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
69
90
  end
70
91
 
71
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
72
-
73
92
  entry = Entry::Class.new(
74
93
  nesting,
75
94
  @file_path,
@@ -79,7 +98,7 @@ module RubyIndexer
79
98
  )
80
99
 
81
100
  @owner_stack << entry
82
- @index << entry
101
+ @index.add(entry)
83
102
  @stack << name
84
103
  end
85
104
 
@@ -101,7 +120,7 @@ module RubyIndexer
101
120
  entry = Entry::Module.new(nesting, @file_path, node.location, comments)
102
121
 
103
122
  @owner_stack << entry
104
- @index << entry
123
+ @index.add(entry)
105
124
  @stack << name
106
125
  end
107
126
 
@@ -112,6 +131,37 @@ module RubyIndexer
112
131
  @visibility_stack.pop
113
132
  end
114
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
+
115
165
  sig { params(node: Prism::MultiWriteNode).void }
116
166
  def on_multi_write_node_enter(node)
117
167
  value = node.value
@@ -209,6 +259,8 @@ module RubyIndexer
209
259
  handle_attribute(node, reader: false, writer: true)
210
260
  when :attr_accessor
211
261
  handle_attribute(node, reader: true, writer: true)
262
+ when :alias_method
263
+ handle_alias_method(node)
212
264
  when :include, :prepend, :extend
213
265
  handle_module_operation(node, message)
214
266
  when :public
@@ -241,105 +293,110 @@ module RubyIndexer
241
293
 
242
294
  case node.receiver
243
295
  when nil
244
- @index << Entry::InstanceMethod.new(
296
+ @index.add(Entry::Method.new(
245
297
  method_name,
246
298
  @file_path,
247
299
  node.location,
248
300
  comments,
249
- node.parameters,
301
+ list_params(node.parameters),
250
302
  current_visibility,
251
303
  @owner_stack.last,
252
- )
304
+ ))
253
305
  when Prism::SelfNode
254
- @index << Entry::SingletonMethod.new(
306
+ singleton = singleton_klass
307
+
308
+ @index.add(Entry::Method.new(
255
309
  method_name,
256
310
  @file_path,
257
311
  node.location,
258
312
  comments,
259
- node.parameters,
313
+ list_params(node.parameters),
260
314
  current_visibility,
261
- @owner_stack.last,
262
- )
315
+ singleton,
316
+ ))
317
+
318
+ if singleton
319
+ @owner_stack << singleton
320
+ @stack << "<Class:#{@stack.last}>"
321
+ end
263
322
  end
264
323
  end
265
324
 
266
325
  sig { params(node: Prism::DefNode).void }
267
326
  def on_def_node_leave(node)
268
327
  @inside_def = false
328
+
329
+ if node.receiver.is_a?(Prism::SelfNode)
330
+ @owner_stack.pop
331
+ @stack.pop
332
+ end
269
333
  end
270
334
 
271
335
  sig { params(node: Prism::InstanceVariableWriteNode).void }
272
336
  def on_instance_variable_write_node_enter(node)
273
- name = node.name.to_s
274
- return if name == "@"
275
-
276
- @index << Entry::InstanceVariable.new(
277
- name,
278
- @file_path,
279
- node.name_loc,
280
- collect_comments(node),
281
- @owner_stack.last,
282
- )
337
+ handle_instance_variable(node, node.name_loc)
283
338
  end
284
339
 
285
340
  sig { params(node: Prism::InstanceVariableAndWriteNode).void }
286
341
  def on_instance_variable_and_write_node_enter(node)
287
- name = node.name.to_s
288
- return if name == "@"
289
-
290
- @index << Entry::InstanceVariable.new(
291
- name,
292
- @file_path,
293
- node.name_loc,
294
- collect_comments(node),
295
- @owner_stack.last,
296
- )
342
+ handle_instance_variable(node, node.name_loc)
297
343
  end
298
344
 
299
345
  sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
300
346
  def on_instance_variable_operator_write_node_enter(node)
301
- name = node.name.to_s
302
- return if name == "@"
303
-
304
- @index << Entry::InstanceVariable.new(
305
- name,
306
- @file_path,
307
- node.name_loc,
308
- collect_comments(node),
309
- @owner_stack.last,
310
- )
347
+ handle_instance_variable(node, node.name_loc)
311
348
  end
312
349
 
313
350
  sig { params(node: Prism::InstanceVariableOrWriteNode).void }
314
351
  def on_instance_variable_or_write_node_enter(node)
315
- name = node.name.to_s
316
- return if name == "@"
317
-
318
- @index << Entry::InstanceVariable.new(
319
- name,
320
- @file_path,
321
- node.name_loc,
322
- collect_comments(node),
323
- @owner_stack.last,
324
- )
352
+ handle_instance_variable(node, node.name_loc)
325
353
  end
326
354
 
327
355
  sig { params(node: Prism::InstanceVariableTargetNode).void }
328
356
  def on_instance_variable_target_node_enter(node)
329
- name = node.name.to_s
330
- return if name == "@"
357
+ handle_instance_variable(node, node.location)
358
+ end
331
359
 
332
- @index << Entry::InstanceVariable.new(
333
- name,
334
- @file_path,
335
- node.location,
336
- collect_comments(node),
337
- @owner_stack.last,
360
+ sig { params(node: Prism::AliasMethodNode).void }
361
+ def on_alias_method_node_enter(node)
362
+ method_name = node.new_name.slice
363
+ comments = collect_comments(node)
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
+ ),
338
373
  )
339
374
  end
340
375
 
341
376
  private
342
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
+
343
400
  sig { params(node: Prism::CallNode).void }
344
401
  def handle_private_constant(node)
345
402
  arguments = node.arguments&.arguments
@@ -365,6 +422,45 @@ module RubyIndexer
365
422
  entries&.each { |entry| entry.visibility = Entry::Visibility::PRIVATE }
366
423
  end
367
424
 
425
+ sig { params(node: Prism::CallNode).void }
426
+ def handle_alias_method(node)
427
+ arguments = node.arguments&.arguments
428
+ return unless arguments
429
+
430
+ new_name, old_name = arguments
431
+ return unless new_name && old_name
432
+
433
+ new_name_value = case new_name
434
+ when Prism::StringNode
435
+ new_name.content
436
+ when Prism::SymbolNode
437
+ new_name.value
438
+ end
439
+
440
+ return unless new_name_value
441
+
442
+ old_name_value = case old_name
443
+ when Prism::StringNode
444
+ old_name.content
445
+ when Prism::SymbolNode
446
+ old_name.value
447
+ end
448
+
449
+ return unless old_name_value
450
+
451
+ comments = collect_comments(node)
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
+ ),
461
+ )
462
+ end
463
+
368
464
  sig do
369
465
  params(
370
466
  node: T.any(
@@ -387,22 +483,24 @@ module RubyIndexer
387
483
  value = node.value unless node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
388
484
  comments = collect_comments(node)
389
485
 
390
- @index << case value
391
- when Prism::ConstantReadNode, Prism::ConstantPathNode
392
- Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
393
- 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,
394
491
  Prism::ConstantOperatorWriteNode
395
492
 
396
- # If the right hand side is another constant assignment, we need to visit it because that constant has to be
397
- # indexed too
398
- Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
399
- 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,
400
497
  Prism::ConstantPathAndWriteNode
401
498
 
402
- Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
403
- else
404
- Entry::Constant.new(name, @file_path, node.location, comments)
405
- 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
+ )
406
504
  end
407
505
 
408
506
  sig { params(node: Prism::Node).returns(T::Array[String]) }
@@ -459,15 +557,20 @@ module RubyIndexer
459
557
 
460
558
  next unless name && loc
461
559
 
462
- @index << Entry::Accessor.new(name, @file_path, loc, comments, current_visibility, @owner_stack.last) if reader
463
- @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(
464
567
  "#{name}=",
465
568
  @file_path,
466
569
  loc,
467
570
  comments,
468
571
  current_visibility,
469
572
  @owner_stack.last,
470
- ) if writer
573
+ ))
471
574
  end
472
575
  end
473
576
 
@@ -502,5 +605,108 @@ module RubyIndexer
502
605
  def current_visibility
503
606
  T.must(@visibility_stack.last)
504
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
505
711
  end
506
712
  end