ruby-lsp 0.17.4 → 0.17.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +40 -39
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +112 -25
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +266 -68
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +13 -32
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +33 -3
- data/lib/ruby_indexer/test/index_test.rb +242 -7
- data/lib/ruby_indexer/test/method_test.rb +21 -1
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +11 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +13 -1
- data/lib/ruby_lsp/document.rb +13 -15
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +4 -1
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +26 -30
- data/lib/ruby_lsp/listeners/definition.rb +24 -17
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -7
- data/lib/ruby_lsp/requests/definition.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +2 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +10 -0
- data/lib/ruby_lsp/server.rb +41 -11
- data/lib/ruby_lsp/store.rb +23 -8
- data/lib/ruby_lsp/test_helper.rb +2 -0
- metadata +7 -6
@@ -150,17 +150,42 @@ module RubyIndexer
|
|
150
150
|
ancestors = linearized_ancestors_of(receiver_name)
|
151
151
|
|
152
152
|
candidates = name ? prefix_search(name).flatten : @entries.values.flatten
|
153
|
-
candidates.
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
153
|
+
completion_items = candidates.each_with_object({}) do |entry, hash|
|
154
|
+
unless entry.is_a?(Entry::Member) || entry.is_a?(Entry::MethodAlias) ||
|
155
|
+
entry.is_a?(Entry::UnresolvedMethodAlias)
|
156
|
+
next
|
157
|
+
end
|
158
|
+
|
159
|
+
entry_name = entry.name
|
160
|
+
ancestor_index = ancestors.index(entry.owner&.name)
|
161
|
+
existing_entry, existing_entry_index = hash[entry_name]
|
162
|
+
|
163
|
+
# Conditions for matching a method completion candidate:
|
164
|
+
# 1. If an ancestor_index was found, it means that this method is owned by the receiver. The exact index is
|
165
|
+
# where in the ancestor chain the method was found. For example, if the ancestors are ["A", "B", "C"] and we
|
166
|
+
# found the method declared in `B`, then the ancestors index is 1
|
167
|
+
#
|
168
|
+
# 2. We already established that this method is owned by the receiver. Now, check if we already added a
|
169
|
+
# completion candidate for this method name. If not, then we just go and add it (the left hand side of the or)
|
170
|
+
#
|
171
|
+
# 3. If we had already found a method entry for the same name, then we need to check if the current entry that
|
172
|
+
# we are comparing appears first in the hierarchy or not. For example, imagine we have the method `open` defined
|
173
|
+
# in both `File` and its parent `IO`. If we first find the method `open` in `IO`, it will be inserted into the
|
174
|
+
# hash. Then, when we find the entry for `open` owned by `File`, we need to replace `IO.open` by `File.open`,
|
175
|
+
# since `File.open` appears first in the hierarchy chain and is therefore the correct method being invoked. The
|
176
|
+
# last part of the conditional checks if the current entry was found earlier in the hierarchy chain, in which
|
177
|
+
# case we must update the existing entry to avoid showing the wrong method declaration for overridden methods
|
178
|
+
next unless ancestor_index && (!existing_entry || ancestor_index < existing_entry_index)
|
179
|
+
|
180
|
+
if entry.is_a?(Entry::UnresolvedMethodAlias)
|
181
|
+
resolved_alias = resolve_method_alias(entry, receiver_name)
|
182
|
+
hash[entry_name] = [resolved_alias, ancestor_index] if resolved_alias.is_a?(Entry::MethodAlias)
|
183
|
+
else
|
184
|
+
hash[entry_name] = [entry, ancestor_index]
|
162
185
|
end
|
163
186
|
end
|
187
|
+
|
188
|
+
completion_items.values.map!(&:first)
|
164
189
|
end
|
165
190
|
|
166
191
|
# Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
|
@@ -175,7 +200,11 @@ module RubyIndexer
|
|
175
200
|
name: String,
|
176
201
|
nesting: T::Array[String],
|
177
202
|
seen_names: T::Array[String],
|
178
|
-
).returns(T.nilable(T::Array[
|
203
|
+
).returns(T.nilable(T::Array[T.any(
|
204
|
+
Entry::Namespace,
|
205
|
+
Entry::Alias,
|
206
|
+
Entry::UnresolvedAlias,
|
207
|
+
)]))
|
179
208
|
end
|
180
209
|
def resolve(name, nesting, seen_names = [])
|
181
210
|
# If we have a top level reference, then we just search for it straight away ignoring the nesting
|
@@ -204,7 +233,7 @@ module RubyIndexer
|
|
204
233
|
return entries if entries
|
205
234
|
|
206
235
|
# Finally, as a fallback, Ruby will search for the constant in the top level namespace
|
207
|
-
|
236
|
+
direct_or_aliased_constant(name, seen_names)
|
208
237
|
rescue UnresolvableAliasError
|
209
238
|
nil
|
210
239
|
end
|
@@ -345,8 +374,25 @@ module RubyIndexer
|
|
345
374
|
cached_ancestors = @ancestors[fully_qualified_name]
|
346
375
|
return cached_ancestors if cached_ancestors
|
347
376
|
|
377
|
+
parts = fully_qualified_name.split("::")
|
378
|
+
singleton_levels = 0
|
379
|
+
|
380
|
+
parts.reverse_each do |part|
|
381
|
+
break unless part.include?("<Class:")
|
382
|
+
|
383
|
+
singleton_levels += 1
|
384
|
+
parts.pop
|
385
|
+
end
|
386
|
+
|
387
|
+
attached_class_name = parts.join("::")
|
388
|
+
|
348
389
|
# If we don't have an entry for `name`, raise
|
349
390
|
entries = self[fully_qualified_name]
|
391
|
+
|
392
|
+
if singleton_levels > 0 && !entries
|
393
|
+
entries = [existing_or_new_singleton_class(attached_class_name)]
|
394
|
+
end
|
395
|
+
|
350
396
|
raise NonExistingNamespaceError, "No entry found for #{fully_qualified_name}" unless entries
|
351
397
|
|
352
398
|
ancestors = [fully_qualified_name]
|
@@ -369,62 +415,25 @@ module RubyIndexer
|
|
369
415
|
raise NonExistingNamespaceError,
|
370
416
|
"None of the entries for #{fully_qualified_name} are modules or classes" if namespaces.empty?
|
371
417
|
|
372
|
-
mixin_operations = namespaces.flat_map(&:mixin_operations)
|
373
|
-
main_namespace_index = 0
|
374
|
-
|
375
418
|
# The original nesting where we discovered this namespace, so that we resolve the correct names of the
|
376
419
|
# included/prepended/extended modules and parent classes
|
377
420
|
nesting = T.must(namespaces.first).nesting
|
378
421
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
module_fully_qualified_name = T.must(resolved_module.first).name
|
384
|
-
|
385
|
-
case operation
|
386
|
-
when Entry::Prepend
|
387
|
-
# When a module is prepended, Ruby checks if it hasn't been prepended already to prevent adding it in front of
|
388
|
-
# the actual namespace twice. However, it does not check if it has been included because you are allowed to
|
389
|
-
# prepend the same module after it has already been included
|
390
|
-
linearized_prepends = linearized_ancestors_of(module_fully_qualified_name)
|
391
|
-
|
392
|
-
# When there are duplicate prepended modules, we have to insert the new prepends after the existing ones. For
|
393
|
-
# example, if the current ancestors are `["A", "Foo"]` and we try to prepend `["A", "B"]`, then `"B"` has to
|
394
|
-
# be inserted after `"A`
|
395
|
-
uniq_prepends = linearized_prepends - T.must(ancestors[0...main_namespace_index])
|
396
|
-
insert_position = linearized_prepends.length - uniq_prepends.length
|
397
|
-
|
398
|
-
T.unsafe(ancestors).insert(
|
399
|
-
insert_position,
|
400
|
-
*(linearized_prepends - T.must(ancestors[0...main_namespace_index])),
|
401
|
-
)
|
402
|
-
|
403
|
-
main_namespace_index += linearized_prepends.length
|
404
|
-
when Entry::Include
|
405
|
-
# When including a module, Ruby will always prevent duplicate entries in case the module has already been
|
406
|
-
# prepended or included
|
407
|
-
linearized_includes = linearized_ancestors_of(module_fully_qualified_name)
|
408
|
-
T.unsafe(ancestors).insert(main_namespace_index + 1, *(linearized_includes - ancestors))
|
422
|
+
if nesting.any?
|
423
|
+
singleton_levels.times do
|
424
|
+
nesting << "<Class:#{T.must(nesting.last)}>"
|
409
425
|
end
|
410
426
|
end
|
411
427
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
resolved_parent_class = resolve(parent_class, nesting)
|
422
|
-
parent_class_name = resolved_parent_class&.first&.name
|
423
|
-
|
424
|
-
if parent_class_name && fully_qualified_name != parent_class_name
|
425
|
-
ancestors.concat(linearized_ancestors_of(parent_class_name))
|
426
|
-
end
|
427
|
-
end
|
428
|
+
linearize_mixins(ancestors, namespaces, nesting)
|
429
|
+
linearize_superclass(
|
430
|
+
ancestors,
|
431
|
+
attached_class_name,
|
432
|
+
fully_qualified_name,
|
433
|
+
namespaces,
|
434
|
+
nesting,
|
435
|
+
singleton_levels,
|
436
|
+
)
|
428
437
|
|
429
438
|
ancestors
|
430
439
|
end
|
@@ -484,8 +493,175 @@ module RubyIndexer
|
|
484
493
|
@ancestors.clear if original_map.any? { |name, hash| updated_map[name] != hash }
|
485
494
|
end
|
486
495
|
|
496
|
+
sig { returns(T::Boolean) }
|
497
|
+
def empty?
|
498
|
+
@entries.empty?
|
499
|
+
end
|
500
|
+
|
501
|
+
sig { returns(T::Array[String]) }
|
502
|
+
def names
|
503
|
+
@entries.keys
|
504
|
+
end
|
505
|
+
|
506
|
+
sig { params(name: String).returns(T::Boolean) }
|
507
|
+
def indexed?(name)
|
508
|
+
@entries.key?(name)
|
509
|
+
end
|
510
|
+
|
511
|
+
sig { returns(Integer) }
|
512
|
+
def length
|
513
|
+
@entries.count
|
514
|
+
end
|
515
|
+
|
516
|
+
sig { params(name: String).returns(Entry::SingletonClass) }
|
517
|
+
def existing_or_new_singleton_class(name)
|
518
|
+
*_namespace, unqualified_name = name.split("::")
|
519
|
+
full_singleton_name = "#{name}::<Class:#{unqualified_name}>"
|
520
|
+
singleton = T.cast(self[full_singleton_name]&.first, T.nilable(Entry::SingletonClass))
|
521
|
+
|
522
|
+
unless singleton
|
523
|
+
attached_ancestor = T.must(self[name]&.first)
|
524
|
+
|
525
|
+
singleton = Entry::SingletonClass.new(
|
526
|
+
[full_singleton_name],
|
527
|
+
attached_ancestor.file_path,
|
528
|
+
attached_ancestor.location,
|
529
|
+
attached_ancestor.name_location,
|
530
|
+
[],
|
531
|
+
nil,
|
532
|
+
)
|
533
|
+
add(singleton, skip_prefix_tree: true)
|
534
|
+
end
|
535
|
+
|
536
|
+
singleton
|
537
|
+
end
|
538
|
+
|
487
539
|
private
|
488
540
|
|
541
|
+
# Linearize mixins for an array of namespace entries. This method will mutate the `ancestors` array with the
|
542
|
+
# linearized ancestors of the mixins
|
543
|
+
sig do
|
544
|
+
params(
|
545
|
+
ancestors: T::Array[String],
|
546
|
+
namespace_entries: T::Array[Entry::Namespace],
|
547
|
+
nesting: T::Array[String],
|
548
|
+
).void
|
549
|
+
end
|
550
|
+
def linearize_mixins(ancestors, namespace_entries, nesting)
|
551
|
+
mixin_operations = namespace_entries.flat_map(&:mixin_operations)
|
552
|
+
main_namespace_index = 0
|
553
|
+
|
554
|
+
mixin_operations.each do |operation|
|
555
|
+
resolved_module = resolve(operation.module_name, nesting)
|
556
|
+
next unless resolved_module
|
557
|
+
|
558
|
+
module_fully_qualified_name = T.must(resolved_module.first).name
|
559
|
+
|
560
|
+
case operation
|
561
|
+
when Entry::Prepend
|
562
|
+
# When a module is prepended, Ruby checks if it hasn't been prepended already to prevent adding it in front of
|
563
|
+
# the actual namespace twice. However, it does not check if it has been included because you are allowed to
|
564
|
+
# prepend the same module after it has already been included
|
565
|
+
linearized_prepends = linearized_ancestors_of(module_fully_qualified_name)
|
566
|
+
|
567
|
+
# When there are duplicate prepended modules, we have to insert the new prepends after the existing ones. For
|
568
|
+
# example, if the current ancestors are `["A", "Foo"]` and we try to prepend `["A", "B"]`, then `"B"` has to
|
569
|
+
# be inserted after `"A`
|
570
|
+
uniq_prepends = linearized_prepends - T.must(ancestors[0...main_namespace_index])
|
571
|
+
insert_position = linearized_prepends.length - uniq_prepends.length
|
572
|
+
|
573
|
+
T.unsafe(ancestors).insert(
|
574
|
+
insert_position,
|
575
|
+
*(linearized_prepends - T.must(ancestors[0...main_namespace_index])),
|
576
|
+
)
|
577
|
+
|
578
|
+
main_namespace_index += linearized_prepends.length
|
579
|
+
when Entry::Include
|
580
|
+
# When including a module, Ruby will always prevent duplicate entries in case the module has already been
|
581
|
+
# prepended or included
|
582
|
+
linearized_includes = linearized_ancestors_of(module_fully_qualified_name)
|
583
|
+
T.unsafe(ancestors).insert(main_namespace_index + 1, *(linearized_includes - ancestors))
|
584
|
+
end
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
# Linearize the superclass of a given namespace (including modules with the implicit `Module` superclass). This
|
589
|
+
# method will mutate the `ancestors` array with the linearized ancestors of the superclass
|
590
|
+
sig do
|
591
|
+
params(
|
592
|
+
ancestors: T::Array[String],
|
593
|
+
attached_class_name: String,
|
594
|
+
fully_qualified_name: String,
|
595
|
+
namespace_entries: T::Array[Entry::Namespace],
|
596
|
+
nesting: T::Array[String],
|
597
|
+
singleton_levels: Integer,
|
598
|
+
).void
|
599
|
+
end
|
600
|
+
def linearize_superclass( # rubocop:disable Metrics/ParameterLists
|
601
|
+
ancestors,
|
602
|
+
attached_class_name,
|
603
|
+
fully_qualified_name,
|
604
|
+
namespace_entries,
|
605
|
+
nesting,
|
606
|
+
singleton_levels
|
607
|
+
)
|
608
|
+
# Find the first class entry that has a parent class. Notice that if the developer makes a mistake and inherits
|
609
|
+
# from two diffent classes in different files, we simply ignore it
|
610
|
+
superclass = T.cast(
|
611
|
+
if singleton_levels > 0
|
612
|
+
self[attached_class_name]&.find { |n| n.is_a?(Entry::Class) && n.parent_class }
|
613
|
+
else
|
614
|
+
namespace_entries.find { |n| n.is_a?(Entry::Class) && n.parent_class }
|
615
|
+
end,
|
616
|
+
T.nilable(Entry::Class),
|
617
|
+
)
|
618
|
+
|
619
|
+
if superclass
|
620
|
+
# If the user makes a mistake and creates a class that inherits from itself, this method would throw a stack
|
621
|
+
# error. We need to ensure that this isn't the case
|
622
|
+
parent_class = T.must(superclass.parent_class)
|
623
|
+
|
624
|
+
resolved_parent_class = resolve(parent_class, nesting)
|
625
|
+
parent_class_name = resolved_parent_class&.first&.name
|
626
|
+
|
627
|
+
if parent_class_name && fully_qualified_name != parent_class_name
|
628
|
+
|
629
|
+
parent_name_parts = [parent_class_name]
|
630
|
+
singleton_levels.times do
|
631
|
+
parent_name_parts << "<Class:#{parent_name_parts.last}>"
|
632
|
+
end
|
633
|
+
|
634
|
+
ancestors.concat(linearized_ancestors_of(parent_name_parts.join("::")))
|
635
|
+
end
|
636
|
+
|
637
|
+
# When computing the linearization for a class's singleton class, it inherits from the linearized ancestors of
|
638
|
+
# the `Class` class
|
639
|
+
if parent_class_name&.start_with?("BasicObject") && singleton_levels > 0
|
640
|
+
class_class_name_parts = ["Class"]
|
641
|
+
|
642
|
+
(singleton_levels - 1).times do
|
643
|
+
class_class_name_parts << "<Class:#{class_class_name_parts.last}>"
|
644
|
+
end
|
645
|
+
|
646
|
+
ancestors.concat(linearized_ancestors_of(class_class_name_parts.join("::")))
|
647
|
+
end
|
648
|
+
elsif singleton_levels > 0
|
649
|
+
# When computing the linearization for a module's singleton class, it inherits from the linearized ancestors of
|
650
|
+
# the `Module` class
|
651
|
+
mod = T.cast(self[attached_class_name]&.find { |n| n.is_a?(Entry::Module) }, T.nilable(Entry::Module))
|
652
|
+
|
653
|
+
if mod
|
654
|
+
module_class_name_parts = ["Module"]
|
655
|
+
|
656
|
+
(singleton_levels - 1).times do
|
657
|
+
module_class_name_parts << "<Class:#{module_class_name_parts.last}>"
|
658
|
+
end
|
659
|
+
|
660
|
+
ancestors.concat(linearized_ancestors_of(module_class_name_parts.join("::")))
|
661
|
+
end
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
489
665
|
# Attempts to resolve an UnresolvedAlias into a resolved Alias. If the unresolved alias is pointing to a constant
|
490
666
|
# that doesn't exist, then we return the same UnresolvedAlias
|
491
667
|
sig do
|
@@ -521,7 +697,11 @@ module RubyIndexer
|
|
521
697
|
name: String,
|
522
698
|
nesting: T::Array[String],
|
523
699
|
seen_names: T::Array[String],
|
524
|
-
).returns(T.nilable(T::Array[
|
700
|
+
).returns(T.nilable(T::Array[T.any(
|
701
|
+
Entry::Namespace,
|
702
|
+
Entry::Alias,
|
703
|
+
Entry::UnresolvedAlias,
|
704
|
+
)]))
|
525
705
|
end
|
526
706
|
def lookup_enclosing_scopes(name, nesting, seen_names)
|
527
707
|
nesting.length.downto(1).each do |i|
|
@@ -546,7 +726,11 @@ module RubyIndexer
|
|
546
726
|
name: String,
|
547
727
|
nesting: T::Array[String],
|
548
728
|
seen_names: T::Array[String],
|
549
|
-
).returns(T.nilable(T::Array[
|
729
|
+
).returns(T.nilable(T::Array[T.any(
|
730
|
+
Entry::Namespace,
|
731
|
+
Entry::Alias,
|
732
|
+
Entry::UnresolvedAlias,
|
733
|
+
)]))
|
550
734
|
end
|
551
735
|
def lookup_ancestor_chain(name, nesting, seen_names)
|
552
736
|
*nesting_parts, constant_name = build_non_redundant_full_name(name, nesting).split("::")
|
@@ -598,15 +782,29 @@ module RubyIndexer
|
|
598
782
|
end
|
599
783
|
end
|
600
784
|
|
601
|
-
sig
|
785
|
+
sig do
|
786
|
+
params(
|
787
|
+
full_name: String,
|
788
|
+
seen_names: T::Array[String],
|
789
|
+
).returns(
|
790
|
+
T.nilable(T::Array[T.any(
|
791
|
+
Entry::Namespace,
|
792
|
+
Entry::Alias,
|
793
|
+
Entry::UnresolvedAlias,
|
794
|
+
)]),
|
795
|
+
)
|
796
|
+
end
|
602
797
|
def direct_or_aliased_constant(full_name, seen_names)
|
603
798
|
entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
|
604
|
-
entries&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e }
|
605
|
-
end
|
606
799
|
|
607
|
-
|
608
|
-
|
609
|
-
|
800
|
+
T.cast(
|
801
|
+
entries&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e },
|
802
|
+
T.nilable(T::Array[T.any(
|
803
|
+
Entry::Namespace,
|
804
|
+
Entry::Alias,
|
805
|
+
Entry::UnresolvedAlias,
|
806
|
+
)]),
|
807
|
+
)
|
610
808
|
end
|
611
809
|
|
612
810
|
# Attempt to resolve a given unresolved method alias. This method returns the resolved alias if we managed to
|
@@ -48,7 +48,7 @@ module RubyIndexer
|
|
48
48
|
location = to_ruby_indexer_location(declaration.location)
|
49
49
|
comments = Array(declaration.comment&.string)
|
50
50
|
parent_class = declaration.super_class&.name&.name&.to_s
|
51
|
-
class_entry = Entry::Class.new(nesting, file_path, location, comments, parent_class)
|
51
|
+
class_entry = Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
|
52
52
|
add_declaration_mixins_to_entry(declaration, class_entry)
|
53
53
|
@index.add(class_entry)
|
54
54
|
declaration.members.each do |member|
|
@@ -64,7 +64,7 @@ module RubyIndexer
|
|
64
64
|
file_path = pathname.to_s
|
65
65
|
location = to_ruby_indexer_location(declaration.location)
|
66
66
|
comments = Array(declaration.comment&.string)
|
67
|
-
module_entry = Entry::Module.new(nesting, file_path, location, comments)
|
67
|
+
module_entry = Entry::Module.new(nesting, file_path, location, location, comments)
|
68
68
|
add_declaration_mixins_to_entry(declaration, module_entry)
|
69
69
|
@index.add(module_entry)
|
70
70
|
declaration.members.each do |member|
|
@@ -93,16 +93,15 @@ module RubyIndexer
|
|
93
93
|
def add_declaration_mixins_to_entry(declaration, entry)
|
94
94
|
declaration.each_mixin do |mixin|
|
95
95
|
name = mixin.name.name.to_s
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
entry.mixin_operations << mixin_operation if mixin_operation
|
96
|
+
case mixin
|
97
|
+
when RBS::AST::Members::Include
|
98
|
+
entry.mixin_operations << Entry::Include.new(name)
|
99
|
+
when RBS::AST::Members::Prepend
|
100
|
+
entry.mixin_operations << Entry::Prepend.new(name)
|
101
|
+
when RBS::AST::Members::Extend
|
102
|
+
singleton = @index.existing_or_new_singleton_class(entry.name)
|
103
|
+
singleton.mixin_operations << Entry::Include.new(name)
|
104
|
+
end
|
106
105
|
end
|
107
106
|
end
|
108
107
|
|
@@ -122,26 +121,8 @@ module RubyIndexer
|
|
122
121
|
Entry::Visibility::PUBLIC
|
123
122
|
end
|
124
123
|
|
125
|
-
real_owner = member.singleton? ?
|
126
|
-
@index.add(Entry::Method.new(name, file_path, location, comments, [], visibility, real_owner))
|
127
|
-
end
|
128
|
-
|
129
|
-
sig { params(owner: Entry::Namespace).returns(T.nilable(Entry::Class)) }
|
130
|
-
def existing_or_new_singleton_klass(owner)
|
131
|
-
*_parts, name = owner.name.split("::")
|
132
|
-
|
133
|
-
# Return the existing singleton class if available
|
134
|
-
singleton_entries = T.cast(
|
135
|
-
@index["#{owner.name}::<Class:#{name}>"],
|
136
|
-
T.nilable(T::Array[Entry::SingletonClass]),
|
137
|
-
)
|
138
|
-
return singleton_entries.first if singleton_entries
|
139
|
-
|
140
|
-
# If not available, create the singleton class lazily
|
141
|
-
nesting = owner.nesting + ["<Class:#{name}>"]
|
142
|
-
entry = Entry::SingletonClass.new(nesting, owner.file_path, owner.location, [], nil)
|
143
|
-
@index.add(entry, skip_prefix_tree: true)
|
144
|
-
entry
|
124
|
+
real_owner = member.singleton? ? @index.existing_or_new_singleton_class(owner.name) : owner
|
125
|
+
@index.add(Entry::Method.new(name, file_path, location, location, comments, [], visibility, real_owner))
|
145
126
|
end
|
146
127
|
end
|
147
128
|
end
|
@@ -461,13 +461,13 @@ module RubyIndexer
|
|
461
461
|
end
|
462
462
|
RUBY
|
463
463
|
|
464
|
-
foo = T.must(@index["Foo"][0])
|
464
|
+
foo = T.must(@index["Foo::<Class:Foo>"][0])
|
465
465
|
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.mixin_operation_module_names)
|
466
466
|
|
467
|
-
qux = T.must(@index["Foo::Qux"][0])
|
467
|
+
qux = T.must(@index["Foo::Qux::<Class:Qux>"][0])
|
468
468
|
assert_equal(["Corge", "Corge", "Baz"], qux.mixin_operation_module_names)
|
469
469
|
|
470
|
-
constant_path_references = T.must(@index["ConstantPathReferences"][0])
|
470
|
+
constant_path_references = T.must(@index["ConstantPathReferences::<Class:ConstantPathReferences>"][0])
|
471
471
|
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.mixin_operation_module_names)
|
472
472
|
end
|
473
473
|
|
@@ -516,5 +516,35 @@ module RubyIndexer
|
|
516
516
|
|
517
517
|
assert_entry("Foo::<Class:Foo>::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
|
518
518
|
end
|
519
|
+
|
520
|
+
def test_name_location_points_to_constant_path_location
|
521
|
+
index(<<~RUBY)
|
522
|
+
class Foo
|
523
|
+
def foo; end
|
524
|
+
end
|
525
|
+
|
526
|
+
module Bar
|
527
|
+
def bar; end
|
528
|
+
end
|
529
|
+
RUBY
|
530
|
+
|
531
|
+
foo = T.must(@index["Foo"].first)
|
532
|
+
refute_equal(foo.location, foo.name_location)
|
533
|
+
|
534
|
+
name_location = foo.name_location
|
535
|
+
assert_equal(1, name_location.start_line)
|
536
|
+
assert_equal(1, name_location.end_line)
|
537
|
+
assert_equal(6, name_location.start_column)
|
538
|
+
assert_equal(9, name_location.end_column)
|
539
|
+
|
540
|
+
bar = T.must(@index["Bar"].first)
|
541
|
+
refute_equal(bar.location, bar.name_location)
|
542
|
+
|
543
|
+
name_location = bar.name_location
|
544
|
+
assert_equal(5, name_location.start_line)
|
545
|
+
assert_equal(5, name_location.end_line)
|
546
|
+
assert_equal(7, name_location.start_column)
|
547
|
+
assert_equal(10, name_location.end_column)
|
548
|
+
end
|
519
549
|
end
|
520
550
|
end
|