ruby-lsp 0.17.4 → 0.17.5
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 +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
|