ruby-lsp 0.17.2 → 0.17.4

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