ruby-lsp 0.17.8 → 0.17.10

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.
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