ruby-lsp 0.17.8 → 0.17.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3a8a120b4cf3045cdeec98d2fb417ffdd300947b6426788d827346c43812ffc
4
- data.tar.gz: b43a823e71fca99a9cf79b3a6ce19e7a0ec86e499b943aecae1666788859d055
3
+ metadata.gz: 7fafb2e9f1ef8ae394523d09256bce9016307821ab32ecd17fe645dd3f9d88f1
4
+ data.tar.gz: 74396754fdbf6dffb91ff4d6d237f67c26b92fd7ec0f187fc3e40627dc214de1
5
5
  SHA512:
6
- metadata.gz: 9662d88d3f37b4fd7b744a69f38f2a20cd4bf00291f63968578e50bef3400916870efdcf58b6e81dd78a6829c4f8a7dd174e6d212ddc3a265420b29eaa2b6d8a
7
- data.tar.gz: b788ef9185e02a732cd2dccef6273d8dd3fb471ebb8c2300b3ac78cc48d3952eee378a777cc4cf4ceb927590fff7bcdfc57c95f0924b21e589eb850ec9128641
6
+ metadata.gz: 8901ced915cabc4e749cc9451de7d7f9927c4025c6484002a682e65b1a20f7e52d38d3e5b52255b061998e8c114da8efeaaf605f0419fff8ed93c09cf00a2dae
7
+ data.tar.gz: 5679783fa6a828a7e6a3db9fb193ec9b4c1c094c6a67c187f7c2169f5f11fe53fe5024b0c7b0f08273eb46293639ff1d67f7787a851f4f1ac599be23a10ff7dd
data/README.md CHANGED
@@ -33,7 +33,7 @@ The Ruby LSP features include
33
33
  - Completion for classes, modules, constants and require paths
34
34
  - Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
35
35
 
36
- Adding method support for definition, completion, hover and workspace symbol is partially supported, but not yet complete. Follow progress in https://github.com/Shopify/ruby-lsp/issues/899
36
+ As of July 2024, Ruby LSP has received significant enhancements to its code navigation features. For an in-depth look at these improvements, including video demonstrations, check out this [article](https://railsatscale.com/2024-07-18-mastering-ruby-code-navigation-major-enhancements-in-ruby-lsp-2024/). Despite these advancements, we plan to continue enhancing its code navigation support even further. You can follow our progress on this [GitHub issue](https://github.com/Shopify/ruby-lsp/issues/899).
37
37
 
38
38
  See complete information about features [here](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
39
39
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.8
1
+ 0.17.10
data/exe/ruby-lsp CHANGED
@@ -106,7 +106,7 @@ if options[:time_index]
106
106
 
107
107
  puts <<~MSG
108
108
  Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{result.round(5)} seconds and generated:
109
- - #{entries_by_entry_type.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
109
+ - #{entries_by_entry_type.sort_by { |k, _| k.to_s }.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
110
110
  MSG
111
111
  return
112
112
  end
@@ -504,17 +504,17 @@ module RubyIndexer
504
504
  @index.add(
505
505
  case value
506
506
  when Prism::ConstantReadNode, Prism::ConstantPathNode
507
- Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
507
+ Entry::UnresolvedConstantAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
508
508
  when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
509
509
  Prism::ConstantOperatorWriteNode
510
510
 
511
511
  # If the right hand side is another constant assignment, we need to visit it because that constant has to be
512
512
  # indexed too
513
- Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
513
+ Entry::UnresolvedConstantAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
514
514
  when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
515
515
  Prism::ConstantPathAndWriteNode
516
516
 
517
- Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
517
+ Entry::UnresolvedConstantAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
518
518
  else
519
519
  Entry::Constant.new(name, @file_path, node.location, comments)
520
520
  end,
@@ -411,7 +411,7 @@ module RubyIndexer
411
411
  # All aliases are inserted as UnresolvedAlias in the index first and then we lazily resolve them to the correct
412
412
  # target in [rdoc-ref:Index#resolve]. If the right hand side contains a constant that doesn't exist, then it's not
413
413
  # possible to resolve the alias and it will remain an UnresolvedAlias until the right hand side constant exists
414
- class UnresolvedAlias < Entry
414
+ class UnresolvedConstantAlias < Entry
415
415
  extend T::Sig
416
416
 
417
417
  sig { returns(String) }
@@ -439,13 +439,13 @@ module RubyIndexer
439
439
  end
440
440
 
441
441
  # Alias represents a resolved alias, which points to an existing constant target
442
- class Alias < Entry
442
+ class ConstantAlias < Entry
443
443
  extend T::Sig
444
444
 
445
445
  sig { returns(String) }
446
446
  attr_reader :target
447
447
 
448
- sig { params(target: String, unresolved_alias: UnresolvedAlias).void }
448
+ sig { params(target: String, unresolved_alias: UnresolvedConstantAlias).void }
449
449
  def initialize(target, unresolved_alias)
450
450
  super(unresolved_alias.name, unresolved_alias.file_path, unresolved_alias.location, unresolved_alias.comments)
451
451
 
@@ -492,7 +492,7 @@ module RubyIndexer
492
492
  old_name: String,
493
493
  owner: T.nilable(Entry::Namespace),
494
494
  file_path: String,
495
- location: Prism::Location,
495
+ location: T.any(Prism::Location, RubyIndexer::Location),
496
496
  comments: T::Array[String],
497
497
  ).void
498
498
  end
@@ -84,6 +84,34 @@ module RubyIndexer
84
84
  @require_paths_tree.search(query)
85
85
  end
86
86
 
87
+ # Searches for a constant based on an unqualified name and returns the first possible match regardless of whether
88
+ # there are more possible matching entries
89
+ sig do
90
+ params(
91
+ name: String,
92
+ ).returns(T.nilable(T::Array[T.any(
93
+ Entry::Namespace,
94
+ Entry::ConstantAlias,
95
+ Entry::UnresolvedConstantAlias,
96
+ Entry::Constant,
97
+ )]))
98
+ end
99
+ def first_unqualified_const(name)
100
+ _name, entries = @entries.find do |const_name, _entries|
101
+ const_name.end_with?(name)
102
+ end
103
+
104
+ T.cast(
105
+ entries,
106
+ T.nilable(T::Array[T.any(
107
+ Entry::Namespace,
108
+ Entry::ConstantAlias,
109
+ Entry::UnresolvedConstantAlias,
110
+ Entry::Constant,
111
+ )]),
112
+ )
113
+ end
114
+
87
115
  # Searches entries in the index based on an exact prefix, intended for providing autocomplete. All possible matches
88
116
  # to the prefix are returned. The return is an array of arrays, where each entry is the array of entries for a given
89
117
  # name match. For example:
@@ -202,8 +230,8 @@ module RubyIndexer
202
230
  seen_names: T::Array[String],
203
231
  ).returns(T.nilable(T::Array[T.any(
204
232
  Entry::Namespace,
205
- Entry::Alias,
206
- Entry::UnresolvedAlias,
233
+ Entry::ConstantAlias,
234
+ Entry::UnresolvedConstantAlias,
207
235
  )]))
208
236
  end
209
237
  def resolve(name, nesting, seen_names = [])
@@ -248,6 +276,7 @@ module RubyIndexer
248
276
  ).void
249
277
  end
250
278
  def index_all(indexable_paths: RubyIndexer.configuration.indexables, &block)
279
+ RBSIndexer.new(self).index_ruby_core
251
280
  # Calculate how many paths are worth 1% of progress
252
281
  progress_step = (indexable_paths.length / 100.0).ceil
253
282
 
@@ -305,13 +334,13 @@ module RubyIndexer
305
334
  entry = @entries[current_name]&.first
306
335
 
307
336
  case entry
308
- when Entry::Alias
337
+ when Entry::ConstantAlias
309
338
  target = entry.target
310
339
  return follow_aliased_namespace("#{target}::#{real_parts.join("::")}", seen_names)
311
- when Entry::UnresolvedAlias
340
+ when Entry::UnresolvedConstantAlias
312
341
  resolved = resolve_alias(entry, seen_names)
313
342
 
314
- if resolved.is_a?(Entry::UnresolvedAlias)
343
+ if resolved.is_a?(Entry::UnresolvedConstantAlias)
315
344
  raise UnresolvableAliasError, "The constant #{resolved.name} is an alias to a non existing constant"
316
345
  end
317
346
 
@@ -410,7 +439,7 @@ module RubyIndexer
410
439
  case entry
411
440
  when Entry::Namespace
412
441
  entry
413
- when Entry::Alias
442
+ when Entry::ConstantAlias
414
443
  self[entry.target]&.grep(Entry::Namespace)
415
444
  end
416
445
  end.flatten
@@ -420,7 +449,7 @@ module RubyIndexer
420
449
 
421
450
  # The original nesting where we discovered this namespace, so that we resolve the correct names of the
422
451
  # included/prepended/extended modules and parent classes
423
- nesting = T.must(namespaces.first).nesting
452
+ nesting = T.must(namespaces.first).nesting.flat_map { |n| n.split("::") }
424
453
 
425
454
  if nesting.any?
426
455
  singleton_levels.times do
@@ -629,7 +658,7 @@ module RubyIndexer
629
658
 
630
659
  if parent_class_name && fully_qualified_name != parent_class_name
631
660
 
632
- parent_name_parts = [parent_class_name]
661
+ parent_name_parts = parent_class_name.split("::")
633
662
  singleton_levels.times do
634
663
  parent_name_parts << "<Class:#{parent_name_parts.last}>"
635
664
  end
@@ -669,9 +698,9 @@ module RubyIndexer
669
698
  # that doesn't exist, then we return the same UnresolvedAlias
670
699
  sig do
671
700
  params(
672
- entry: Entry::UnresolvedAlias,
701
+ entry: Entry::UnresolvedConstantAlias,
673
702
  seen_names: T::Array[String],
674
- ).returns(T.any(Entry::Alias, Entry::UnresolvedAlias))
703
+ ).returns(T.any(Entry::ConstantAlias, Entry::UnresolvedConstantAlias))
675
704
  end
676
705
  def resolve_alias(entry, seen_names)
677
706
  alias_name = entry.name
@@ -683,7 +712,7 @@ module RubyIndexer
683
712
  return entry unless target
684
713
 
685
714
  target_name = T.must(target.first).name
686
- resolved_alias = Entry::Alias.new(target_name, entry)
715
+ resolved_alias = Entry::ConstantAlias.new(target_name, entry)
687
716
 
688
717
  # Replace the UnresolvedAlias by a resolved one so that we don't have to do this again later
689
718
  original_entries = T.must(@entries[alias_name])
@@ -702,8 +731,8 @@ module RubyIndexer
702
731
  seen_names: T::Array[String],
703
732
  ).returns(T.nilable(T::Array[T.any(
704
733
  Entry::Namespace,
705
- Entry::Alias,
706
- Entry::UnresolvedAlias,
734
+ Entry::ConstantAlias,
735
+ Entry::UnresolvedConstantAlias,
707
736
  )]))
708
737
  end
709
738
  def lookup_enclosing_scopes(name, nesting, seen_names)
@@ -731,8 +760,8 @@ module RubyIndexer
731
760
  seen_names: T::Array[String],
732
761
  ).returns(T.nilable(T::Array[T.any(
733
762
  Entry::Namespace,
734
- Entry::Alias,
735
- Entry::UnresolvedAlias,
763
+ Entry::ConstantAlias,
764
+ Entry::UnresolvedConstantAlias,
736
765
  )]))
737
766
  end
738
767
  def lookup_ancestor_chain(name, nesting, seen_names)
@@ -792,8 +821,8 @@ module RubyIndexer
792
821
  ).returns(
793
822
  T.nilable(T::Array[T.any(
794
823
  Entry::Namespace,
795
- Entry::Alias,
796
- Entry::UnresolvedAlias,
824
+ Entry::ConstantAlias,
825
+ Entry::UnresolvedConstantAlias,
797
826
  )]),
798
827
  )
799
828
  end
@@ -801,11 +830,11 @@ module RubyIndexer
801
830
  entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
802
831
 
803
832
  T.cast(
804
- entries&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e },
833
+ entries&.map { |e| e.is_a?(Entry::UnresolvedConstantAlias) ? resolve_alias(e, seen_names) : e },
805
834
  T.nilable(T::Array[T.any(
806
835
  Entry::Namespace,
807
- Entry::Alias,
808
- Entry::UnresolvedAlias,
836
+ Entry::ConstantAlias,
837
+ Entry::UnresolvedConstantAlias,
809
838
  )]),
810
839
  )
811
840
  end
@@ -37,45 +37,43 @@ module RubyIndexer
37
37
  sig { params(declaration: RBS::AST::Declarations::Base, pathname: Pathname).void }
38
38
  def process_declaration(declaration, pathname)
39
39
  case declaration
40
- when RBS::AST::Declarations::Class
41
- handle_class_declaration(declaration, pathname)
42
- when RBS::AST::Declarations::Module
43
- handle_module_declaration(declaration, pathname)
40
+ when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
41
+ handle_class_or_module_declaration(declaration, pathname)
42
+ when RBS::AST::Declarations::Constant
43
+ namespace_nesting = declaration.name.namespace.path.map(&:to_s)
44
+ handle_constant(declaration, namespace_nesting, pathname.to_s)
44
45
  else # rubocop:disable Style/EmptyElse
45
46
  # Other kinds not yet handled
46
47
  end
47
48
  end
48
49
 
49
- sig { params(declaration: RBS::AST::Declarations::Class, pathname: Pathname).void }
50
- def handle_class_declaration(declaration, pathname)
51
- nesting = [declaration.name.name.to_s]
52
- file_path = pathname.to_s
53
- location = to_ruby_indexer_location(declaration.location)
54
- comments = Array(declaration.comment&.string)
55
- parent_class = declaration.super_class&.name&.name&.to_s
56
- class_entry = Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
57
- add_declaration_mixins_to_entry(declaration, class_entry)
58
- @index.add(class_entry)
59
- declaration.members.each do |member|
60
- next unless member.is_a?(RBS::AST::Members::MethodDefinition)
61
-
62
- handle_method(member, class_entry)
63
- end
50
+ sig do
51
+ params(declaration: T.any(RBS::AST::Declarations::Class, RBS::AST::Declarations::Module), pathname: Pathname).void
64
52
  end
65
-
66
- sig { params(declaration: RBS::AST::Declarations::Module, pathname: Pathname).void }
67
- def handle_module_declaration(declaration, pathname)
53
+ def handle_class_or_module_declaration(declaration, pathname)
68
54
  nesting = [declaration.name.name.to_s]
69
55
  file_path = pathname.to_s
70
56
  location = to_ruby_indexer_location(declaration.location)
71
- comments = Array(declaration.comment&.string)
72
- module_entry = Entry::Module.new(nesting, file_path, location, location, comments)
73
- add_declaration_mixins_to_entry(declaration, module_entry)
74
- @index.add(module_entry)
57
+ comments = comments_to_string(declaration)
58
+ entry = if declaration.is_a?(RBS::AST::Declarations::Class)
59
+ parent_class = declaration.super_class&.name&.name&.to_s
60
+ Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
61
+ else
62
+ Entry::Module.new(nesting, file_path, location, location, comments)
63
+ end
64
+ add_declaration_mixins_to_entry(declaration, entry)
65
+ @index.add(entry)
75
66
  declaration.members.each do |member|
76
- next unless member.is_a?(RBS::AST::Members::MethodDefinition)
77
-
78
- handle_method(member, module_entry)
67
+ case member
68
+ when RBS::AST::Members::MethodDefinition
69
+ handle_method(member, entry)
70
+ when RBS::AST::Declarations::Constant
71
+ handle_constant(member, nesting, file_path)
72
+ when RBS::AST::Members::Alias
73
+ # In RBS, an alias means that two methods have the same signature.
74
+ # It does not mean the same thing as a Ruby alias.
75
+ handle_signature_alias(member, entry)
76
+ end
79
77
  end
80
78
  end
81
79
 
@@ -115,7 +113,7 @@ module RubyIndexer
115
113
  name = member.name.name
116
114
  file_path = member.location.buffer.name
117
115
  location = to_ruby_indexer_location(member.location)
118
- comments = Array(member.comment&.string)
116
+ comments = comments_to_string(member)
119
117
 
120
118
  visibility = case member.visibility
121
119
  when :private
@@ -224,5 +222,60 @@ module RubyIndexer
224
222
 
225
223
  Entry::KeywordRestParameter.new(name: name)
226
224
  end
225
+
226
+ # RBS treats constant definitions differently depend on where they are defined.
227
+ # When constants' rbs are defined inside a class/module block, they are treated as
228
+ # members of the class/module.
229
+ #
230
+ # module Encoding
231
+ # US_ASCII = ... # US_ASCII is a member of Encoding
232
+ # end
233
+ #
234
+ # When constants' rbs are defined outside a class/module block, they are treated as
235
+ # top-level constants.
236
+ #
237
+ # Complex::I = ... # Complex::I is a top-level constant
238
+ #
239
+ # And we need to handle their nesting differently.
240
+ sig { params(declaration: RBS::AST::Declarations::Constant, nesting: T::Array[String], file_path: String).void }
241
+ def handle_constant(declaration, nesting, file_path)
242
+ fully_qualified_name = [*nesting, declaration.name.name.to_s].join("::")
243
+ @index.add(Entry::Constant.new(
244
+ fully_qualified_name,
245
+ file_path,
246
+ to_ruby_indexer_location(declaration.location),
247
+ comments_to_string(declaration),
248
+ ))
249
+ end
250
+
251
+ sig { params(member: RBS::AST::Members::Alias, owner_entry: Entry::Namespace).void }
252
+ def handle_signature_alias(member, owner_entry)
253
+ file_path = member.location.buffer.name
254
+ comments = comments_to_string(member)
255
+
256
+ entry = Entry::UnresolvedMethodAlias.new(
257
+ member.new_name.to_s,
258
+ member.old_name.to_s,
259
+ owner_entry,
260
+ file_path,
261
+ to_ruby_indexer_location(member.location),
262
+ comments,
263
+ )
264
+
265
+ @index.add(entry)
266
+ end
267
+
268
+ sig do
269
+ params(declaration: T.any(
270
+ RBS::AST::Declarations::Class,
271
+ RBS::AST::Declarations::Module,
272
+ RBS::AST::Declarations::Constant,
273
+ RBS::AST::Members::MethodDefinition,
274
+ RBS::AST::Members::Alias,
275
+ )).returns(T::Array[String])
276
+ end
277
+ def comments_to_string(declaration)
278
+ Array(declaration.comment&.string)
279
+ end
227
280
  end
228
281
  end
@@ -200,12 +200,12 @@ module RubyIndexer
200
200
  RUBY
201
201
 
202
202
  unresolve_entry = @index["A::FIRST"].first
203
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
203
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
204
204
  assert_equal(["A"], unresolve_entry.nesting)
205
205
  assert_equal("B::C", unresolve_entry.target)
206
206
 
207
207
  resolved_entry = @index.resolve("A::FIRST", []).first
208
- assert_instance_of(Entry::Alias, resolved_entry)
208
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
209
209
  assert_equal("A::B::C", resolved_entry.target)
210
210
  end
211
211
 
@@ -226,12 +226,12 @@ module RubyIndexer
226
226
  RUBY
227
227
 
228
228
  unresolve_entry = @index["A::ALIAS"].first
229
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
229
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
230
230
  assert_equal(["A"], unresolve_entry.nesting)
231
231
  assert_equal("B", unresolve_entry.target)
232
232
 
233
233
  resolved_entry = @index.resolve("ALIAS", ["A"]).first
234
- assert_instance_of(Entry::Alias, resolved_entry)
234
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
235
235
  assert_equal("A::B", resolved_entry.target)
236
236
 
237
237
  resolved_entry = @index.resolve("ALIAS::C", ["A"]).first
@@ -239,7 +239,7 @@ module RubyIndexer
239
239
  assert_equal("A::B::C", resolved_entry.name)
240
240
 
241
241
  unresolve_entry = @index["Other::ONE_MORE"].first
242
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
242
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
243
243
  assert_equal(["Other"], unresolve_entry.nesting)
244
244
  assert_equal("A::ALIAS", unresolve_entry.target)
245
245
 
@@ -259,12 +259,12 @@ module RubyIndexer
259
259
 
260
260
  # B and C
261
261
  unresolve_entry = @index["A::B"].first
262
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
262
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
263
263
  assert_equal(["A"], unresolve_entry.nesting)
264
264
  assert_equal("C", unresolve_entry.target)
265
265
 
266
266
  resolved_entry = @index.resolve("A::B", []).first
267
- assert_instance_of(Entry::Alias, resolved_entry)
267
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
268
268
  assert_equal("A::C", resolved_entry.target)
269
269
 
270
270
  constant = @index["A::C"].first
@@ -272,38 +272,38 @@ module RubyIndexer
272
272
 
273
273
  # D and E
274
274
  unresolve_entry = @index["A::D"].first
275
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
275
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
276
276
  assert_equal(["A"], unresolve_entry.nesting)
277
277
  assert_equal("E", unresolve_entry.target)
278
278
 
279
279
  resolved_entry = @index.resolve("A::D", []).first
280
- assert_instance_of(Entry::Alias, resolved_entry)
280
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
281
281
  assert_equal("A::E", resolved_entry.target)
282
282
 
283
283
  # F and G::H
284
284
  unresolve_entry = @index["A::F"].first
285
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
285
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
286
286
  assert_equal(["A"], unresolve_entry.nesting)
287
287
  assert_equal("G::H", unresolve_entry.target)
288
288
 
289
289
  resolved_entry = @index.resolve("A::F", []).first
290
- assert_instance_of(Entry::Alias, resolved_entry)
290
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
291
291
  assert_equal("A::G::H", resolved_entry.target)
292
292
 
293
293
  # I::J, K::L and M
294
294
  unresolve_entry = @index["A::I::J"].first
295
- assert_instance_of(Entry::UnresolvedAlias, unresolve_entry)
295
+ assert_instance_of(Entry::UnresolvedConstantAlias, unresolve_entry)
296
296
  assert_equal(["A"], unresolve_entry.nesting)
297
297
  assert_equal("K::L", unresolve_entry.target)
298
298
 
299
299
  resolved_entry = @index.resolve("A::I::J", []).first
300
- assert_instance_of(Entry::Alias, resolved_entry)
300
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
301
301
  assert_equal("A::K::L", resolved_entry.target)
302
302
 
303
303
  # When we are resolving A::I::J, we invoke `resolve("K::L", ["A"])`, which recursively resolves A::K::L too.
304
304
  # Therefore, both A::I::J and A::K::L point to A::M by the end of the previous resolve invocation
305
305
  resolved_entry = @index["A::K::L"].first
306
- assert_instance_of(Entry::Alias, resolved_entry)
306
+ assert_instance_of(Entry::ConstantAlias, resolved_entry)
307
307
  assert_equal("A::M", resolved_entry.target)
308
308
 
309
309
  constant = @index["A::M"].first
@@ -344,11 +344,11 @@ module RubyIndexer
344
344
  RUBY
345
345
 
346
346
  assert_entry("A::B", Entry::Constant, "/fake/path/foo.rb:1-2:1-3")
347
- assert_entry("A::C", Entry::UnresolvedAlias, "/fake/path/foo.rb:1-5:1-6")
348
- assert_entry("A::D::E", Entry::UnresolvedAlias, "/fake/path/foo.rb:2-2:2-6")
347
+ assert_entry("A::C", Entry::UnresolvedConstantAlias, "/fake/path/foo.rb:1-5:1-6")
348
+ assert_entry("A::D::E", Entry::UnresolvedConstantAlias, "/fake/path/foo.rb:2-2:2-6")
349
349
  assert_entry("A::F::G", Entry::Constant, "/fake/path/foo.rb:2-8:2-12")
350
350
  assert_entry("A::H", Entry::Constant, "/fake/path/foo.rb:3-2:3-3")
351
- assert_entry("A::I::J", Entry::UnresolvedAlias, "/fake/path/foo.rb:3-5:3-9")
351
+ assert_entry("A::I::J", Entry::UnresolvedConstantAlias, "/fake/path/foo.rb:3-5:3-9")
352
352
  assert_entry("A::K", Entry::Constant, "/fake/path/foo.rb:4-2:4-3")
353
353
  assert_entry("A::L", Entry::Constant, "/fake/path/foo.rb:4-5:4-6")
354
354
  end
@@ -181,20 +181,20 @@ module RubyIndexer
181
181
 
182
182
  def test_resolving_aliases_to_non_existing_constants_with_conflicting_names
183
183
  @index.index_single(IndexablePath.new("/fake", "/fake/path/foo.rb"), <<~RUBY)
184
- class Float
184
+ class Bar
185
185
  end
186
186
 
187
187
  module Foo
188
- class Float < self
189
- INFINITY = ::Float::INFINITY
188
+ class Bar < self
189
+ BAZ = ::Bar::BAZ
190
190
  end
191
191
  end
192
192
  RUBY
193
193
 
194
- entry = @index.resolve("INFINITY", ["Foo", "Float"]).first
194
+ entry = @index.resolve("BAZ", ["Foo", "Bar"]).first
195
195
  refute_nil(entry)
196
196
 
197
- assert_instance_of(Entry::UnresolvedAlias, entry)
197
+ assert_instance_of(Entry::UnresolvedConstantAlias, entry)
198
198
  end
199
199
 
200
200
  def test_visitor_does_not_visit_unnecessary_nodes
@@ -1015,11 +1015,11 @@ module RubyIndexer
1015
1015
 
1016
1016
  foo_entry = T.must(@index.resolve("FOO", ["Namespace"])&.first)
1017
1017
  assert_equal(2, foo_entry.location.start_line)
1018
- assert_instance_of(Entry::Alias, foo_entry)
1018
+ assert_instance_of(Entry::ConstantAlias, foo_entry)
1019
1019
 
1020
1020
  bar_entry = T.must(@index.resolve("BAR", ["Namespace"])&.first)
1021
1021
  assert_equal(3, bar_entry.location.start_line)
1022
- assert_instance_of(Entry::Alias, bar_entry)
1022
+ assert_instance_of(Entry::ConstantAlias, bar_entry)
1023
1023
  end
1024
1024
 
1025
1025
  def test_resolving_circular_alias_three_levels
@@ -1033,15 +1033,15 @@ module RubyIndexer
1033
1033
 
1034
1034
  foo_entry = T.must(@index.resolve("FOO", ["Namespace"])&.first)
1035
1035
  assert_equal(2, foo_entry.location.start_line)
1036
- assert_instance_of(Entry::Alias, foo_entry)
1036
+ assert_instance_of(Entry::ConstantAlias, foo_entry)
1037
1037
 
1038
1038
  bar_entry = T.must(@index.resolve("BAR", ["Namespace"])&.first)
1039
1039
  assert_equal(3, bar_entry.location.start_line)
1040
- assert_instance_of(Entry::Alias, bar_entry)
1040
+ assert_instance_of(Entry::ConstantAlias, bar_entry)
1041
1041
 
1042
1042
  baz_entry = T.must(@index.resolve("BAZ", ["Namespace"])&.first)
1043
1043
  assert_equal(4, baz_entry.location.start_line)
1044
- assert_instance_of(Entry::Alias, baz_entry)
1044
+ assert_instance_of(Entry::ConstantAlias, baz_entry)
1045
1045
  end
1046
1046
 
1047
1047
  def test_resolving_constants_in_aliased_namespace
@@ -1542,6 +1542,21 @@ module RubyIndexer
1542
1542
  assert_empty(@index.method_completion_candidates("bar", "Foo"))
1543
1543
  end
1544
1544
 
1545
+ def test_first_unqualified_const
1546
+ index(<<~RUBY)
1547
+ module Foo
1548
+ class Bar; end
1549
+ end
1550
+
1551
+ module Baz
1552
+ class Bar; end
1553
+ end
1554
+ RUBY
1555
+
1556
+ entry = T.must(@index.first_unqualified_const("Bar")&.first)
1557
+ assert_equal("Foo::Bar", entry.name)
1558
+ end
1559
+
1545
1560
  def test_completion_does_not_duplicate_overridden_methods
1546
1561
  index(<<~RUBY)
1547
1562
  class Foo
@@ -1750,5 +1765,62 @@ module RubyIndexer
1750
1765
  @index.linearized_ancestors_of("A::<Class:A>")
1751
1766
  end
1752
1767
  end
1768
+
1769
+ def test_linearizing_singleton_parent_class_with_namespace
1770
+ index(<<~RUBY)
1771
+ class ActiveRecord::Base; end
1772
+
1773
+ class User < ActiveRecord::Base
1774
+ end
1775
+ RUBY
1776
+
1777
+ assert_equal(
1778
+ [
1779
+ "User::<Class:User>",
1780
+ "ActiveRecord::Base::<Class:Base>",
1781
+ "Object::<Class:Object>",
1782
+ "BasicObject::<Class:BasicObject>",
1783
+ "Class",
1784
+ "Module",
1785
+ "Object",
1786
+ "Kernel",
1787
+ "BasicObject",
1788
+ ],
1789
+ @index.linearized_ancestors_of("User::<Class:User>"),
1790
+ )
1791
+ end
1792
+
1793
+ def test_singleton_nesting_is_correctly_split_during_linearization
1794
+ index(<<~RUBY)
1795
+ module Bar; end
1796
+
1797
+ module Foo
1798
+ class Namespace::Parent
1799
+ extend Bar
1800
+ end
1801
+ end
1802
+
1803
+ module Foo
1804
+ class Child < Namespace::Parent
1805
+ end
1806
+ end
1807
+ RUBY
1808
+
1809
+ assert_equal(
1810
+ [
1811
+ "Foo::Child::<Class:Child>",
1812
+ "Foo::Namespace::Parent::<Class:Parent>",
1813
+ "Bar",
1814
+ "Object::<Class:Object>",
1815
+ "BasicObject::<Class:BasicObject>",
1816
+ "Class",
1817
+ "Module",
1818
+ "Object",
1819
+ "Kernel",
1820
+ "BasicObject",
1821
+ ],
1822
+ @index.linearized_ancestors_of("Foo::Child::<Class:Child>"),
1823
+ )
1824
+ end
1753
1825
  end
1754
1826
  end