ruby-lsp 0.17.3 → 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 +251 -100
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +173 -114
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +337 -77
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +43 -14
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +79 -3
- data/lib/ruby_indexer/test/index_test.rb +563 -29
- data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
- data/lib/ruby_indexer/test/method_test.rb +75 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +38 -2
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +13 -1
- data/lib/ruby_lsp/document.rb +50 -23
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +11 -4
- data/lib/ruby_lsp/internal.rb +3 -0
- data/lib/ruby_lsp/listeners/completion.rb +69 -34
- data/lib/ruby_lsp/listeners/definition.rb +34 -23
- data/lib/ruby_lsp/listeners/hover.rb +14 -7
- data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
- data/lib/ruby_lsp/node_context.rb +6 -1
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
- data/lib/ruby_lsp/requests/completion.rb +6 -5
- data/lib/ruby_lsp/requests/completion_resolve.rb +7 -4
- data/lib/ruby_lsp/requests/definition.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +2 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -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/support/common.rb +19 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +87 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/ruby_document.rb +10 -0
- data/lib/ruby_lsp/server.rb +95 -26
- data/lib/ruby_lsp/store.rb +23 -8
- data/lib/ruby_lsp/test_helper.rb +3 -1
- data/lib/ruby_lsp/type_inferrer.rb +86 -0
- metadata +10 -6
@@ -65,13 +65,13 @@ module RubyIndexer
|
|
65
65
|
@require_paths_tree.delete(require_path) if require_path
|
66
66
|
end
|
67
67
|
|
68
|
-
sig { params(entry: Entry).void }
|
69
|
-
def
|
68
|
+
sig { params(entry: Entry, skip_prefix_tree: T::Boolean).void }
|
69
|
+
def add(entry, skip_prefix_tree: false)
|
70
70
|
name = entry.name
|
71
71
|
|
72
72
|
(@entries[name] ||= []) << entry
|
73
73
|
(@files_to_entries[entry.file_path] ||= []) << entry
|
74
|
-
@entries_tree.insert(name, T.must(@entries[name]))
|
74
|
+
@entries_tree.insert(name, T.must(@entries[name])) unless skip_prefix_tree
|
75
75
|
end
|
76
76
|
|
77
77
|
sig { params(fully_qualified_name: String).returns(T.nilable(T::Array[Entry])) }
|
@@ -118,11 +118,21 @@ module RubyIndexer
|
|
118
118
|
# Fuzzy searches index entries based on Jaro-Winkler similarity. If no query is provided, all entries are returned
|
119
119
|
sig { params(query: T.nilable(String)).returns(T::Array[Entry]) }
|
120
120
|
def fuzzy_search(query)
|
121
|
-
|
121
|
+
unless query
|
122
|
+
entries = @entries.filter_map do |_name, entries|
|
123
|
+
next if entries.first.is_a?(Entry::SingletonClass)
|
124
|
+
|
125
|
+
entries
|
126
|
+
end
|
127
|
+
|
128
|
+
return entries.flatten
|
129
|
+
end
|
122
130
|
|
123
131
|
normalized_query = query.gsub("::", "").downcase
|
124
132
|
|
125
133
|
results = @entries.filter_map do |name, entries|
|
134
|
+
next if entries.first.is_a?(Entry::SingletonClass)
|
135
|
+
|
126
136
|
similarity = DidYouMean::JaroWinkler.distance(name.gsub("::", "").downcase, normalized_query)
|
127
137
|
[entries, -similarity] if similarity > ENTRY_SIMILARITY_THRESHOLD
|
128
138
|
end
|
@@ -130,14 +140,52 @@ module RubyIndexer
|
|
130
140
|
results.flat_map(&:first)
|
131
141
|
end
|
132
142
|
|
133
|
-
sig
|
143
|
+
sig do
|
144
|
+
params(
|
145
|
+
name: T.nilable(String),
|
146
|
+
receiver_name: String,
|
147
|
+
).returns(T::Array[T.any(Entry::Member, Entry::MethodAlias)])
|
148
|
+
end
|
134
149
|
def method_completion_candidates(name, receiver_name)
|
135
150
|
ancestors = linearized_ancestors_of(receiver_name)
|
136
|
-
|
137
|
-
candidates.
|
138
|
-
|
151
|
+
|
152
|
+
candidates = name ? prefix_search(name).flatten : @entries.values.flatten
|
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]
|
185
|
+
end
|
139
186
|
end
|
140
|
-
|
187
|
+
|
188
|
+
completion_items.values.map!(&:first)
|
141
189
|
end
|
142
190
|
|
143
191
|
# Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
|
@@ -152,7 +200,11 @@ module RubyIndexer
|
|
152
200
|
name: String,
|
153
201
|
nesting: T::Array[String],
|
154
202
|
seen_names: T::Array[String],
|
155
|
-
).returns(T.nilable(T::Array[
|
203
|
+
).returns(T.nilable(T::Array[T.any(
|
204
|
+
Entry::Namespace,
|
205
|
+
Entry::Alias,
|
206
|
+
Entry::UnresolvedAlias,
|
207
|
+
)]))
|
156
208
|
end
|
157
209
|
def resolve(name, nesting, seen_names = [])
|
158
210
|
# If we have a top level reference, then we just search for it straight away ignoring the nesting
|
@@ -181,7 +233,7 @@ module RubyIndexer
|
|
181
233
|
return entries if entries
|
182
234
|
|
183
235
|
# Finally, as a fallback, Ruby will search for the constant in the top level namespace
|
184
|
-
|
236
|
+
direct_or_aliased_constant(name, seen_names)
|
185
237
|
rescue UnresolvableAliasError
|
186
238
|
nil
|
187
239
|
end
|
@@ -223,6 +275,12 @@ module RubyIndexer
|
|
223
275
|
rescue Errno::EISDIR, Errno::ENOENT
|
224
276
|
# If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
|
225
277
|
# it
|
278
|
+
rescue SystemStackError => e
|
279
|
+
if e.backtrace&.first&.include?("prism")
|
280
|
+
$stderr.puts "Prism error indexing #{indexable_path.full_path}: #{e.message}"
|
281
|
+
else
|
282
|
+
raise
|
283
|
+
end
|
226
284
|
end
|
227
285
|
|
228
286
|
# Follows aliases in a namespace. The algorithm keeps checking if the name is an alias and then recursively follows
|
@@ -269,20 +327,32 @@ module RubyIndexer
|
|
269
327
|
|
270
328
|
# Attempts to find methods for a resolved fully qualified receiver name.
|
271
329
|
# Returns `nil` if the method does not exist on that receiver
|
272
|
-
sig
|
330
|
+
sig do
|
331
|
+
params(
|
332
|
+
method_name: String,
|
333
|
+
receiver_name: String,
|
334
|
+
).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
|
335
|
+
end
|
273
336
|
def resolve_method(method_name, receiver_name)
|
274
337
|
method_entries = self[method_name]
|
275
|
-
ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
|
276
338
|
return unless method_entries
|
277
339
|
|
340
|
+
ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
|
278
341
|
ancestors.each do |ancestor|
|
279
|
-
found = method_entries.
|
280
|
-
|
281
|
-
|
282
|
-
|
342
|
+
found = method_entries.filter_map do |entry|
|
343
|
+
case entry
|
344
|
+
when Entry::Member, Entry::MethodAlias
|
345
|
+
entry if entry.owner&.name == ancestor
|
346
|
+
when Entry::UnresolvedMethodAlias
|
347
|
+
# Resolve aliases lazily as we find them
|
348
|
+
if entry.owner&.name == ancestor
|
349
|
+
resolved_alias = resolve_method_alias(entry, receiver_name)
|
350
|
+
resolved_alias if resolved_alias.is_a?(Entry::MethodAlias)
|
351
|
+
end
|
352
|
+
end
|
283
353
|
end
|
284
354
|
|
285
|
-
return
|
355
|
+
return found if found.any?
|
286
356
|
end
|
287
357
|
|
288
358
|
nil
|
@@ -304,8 +374,25 @@ module RubyIndexer
|
|
304
374
|
cached_ancestors = @ancestors[fully_qualified_name]
|
305
375
|
return cached_ancestors if cached_ancestors
|
306
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
|
+
|
307
389
|
# If we don't have an entry for `name`, raise
|
308
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
|
+
|
309
396
|
raise NonExistingNamespaceError, "No entry found for #{fully_qualified_name}" unless entries
|
310
397
|
|
311
398
|
ancestors = [fully_qualified_name]
|
@@ -328,62 +415,25 @@ module RubyIndexer
|
|
328
415
|
raise NonExistingNamespaceError,
|
329
416
|
"None of the entries for #{fully_qualified_name} are modules or classes" if namespaces.empty?
|
330
417
|
|
331
|
-
mixin_operations = namespaces.flat_map(&:mixin_operations)
|
332
|
-
main_namespace_index = 0
|
333
|
-
|
334
418
|
# The original nesting where we discovered this namespace, so that we resolve the correct names of the
|
335
419
|
# included/prepended/extended modules and parent classes
|
336
420
|
nesting = T.must(namespaces.first).nesting
|
337
421
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
module_fully_qualified_name = T.must(resolved_module.first).name
|
343
|
-
|
344
|
-
case operation
|
345
|
-
when Entry::Prepend
|
346
|
-
# When a module is prepended, Ruby checks if it hasn't been prepended already to prevent adding it in front of
|
347
|
-
# the actual namespace twice. However, it does not check if it has been included because you are allowed to
|
348
|
-
# prepend the same module after it has already been included
|
349
|
-
linearized_prepends = linearized_ancestors_of(module_fully_qualified_name)
|
350
|
-
|
351
|
-
# When there are duplicate prepended modules, we have to insert the new prepends after the existing ones. For
|
352
|
-
# example, if the current ancestors are `["A", "Foo"]` and we try to prepend `["A", "B"]`, then `"B"` has to
|
353
|
-
# be inserted after `"A`
|
354
|
-
uniq_prepends = linearized_prepends - T.must(ancestors[0...main_namespace_index])
|
355
|
-
insert_position = linearized_prepends.length - uniq_prepends.length
|
356
|
-
|
357
|
-
T.unsafe(ancestors).insert(
|
358
|
-
insert_position,
|
359
|
-
*(linearized_prepends - T.must(ancestors[0...main_namespace_index])),
|
360
|
-
)
|
361
|
-
|
362
|
-
main_namespace_index += linearized_prepends.length
|
363
|
-
when Entry::Include
|
364
|
-
# When including a module, Ruby will always prevent duplicate entries in case the module has already been
|
365
|
-
# prepended or included
|
366
|
-
linearized_includes = linearized_ancestors_of(module_fully_qualified_name)
|
367
|
-
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)}>"
|
368
425
|
end
|
369
426
|
end
|
370
427
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
resolved_parent_class = resolve(parent_class, nesting)
|
381
|
-
parent_class_name = resolved_parent_class&.first&.name
|
382
|
-
|
383
|
-
if parent_class_name && fully_qualified_name != parent_class_name
|
384
|
-
ancestors.concat(linearized_ancestors_of(parent_class_name))
|
385
|
-
end
|
386
|
-
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
|
+
)
|
387
437
|
|
388
438
|
ancestors
|
389
439
|
end
|
@@ -443,8 +493,175 @@ module RubyIndexer
|
|
443
493
|
@ancestors.clear if original_map.any? { |name, hash| updated_map[name] != hash }
|
444
494
|
end
|
445
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
|
+
|
446
539
|
private
|
447
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
|
+
|
448
665
|
# Attempts to resolve an UnresolvedAlias into a resolved Alias. If the unresolved alias is pointing to a constant
|
449
666
|
# that doesn't exist, then we return the same UnresolvedAlias
|
450
667
|
sig do
|
@@ -480,7 +697,11 @@ module RubyIndexer
|
|
480
697
|
name: String,
|
481
698
|
nesting: T::Array[String],
|
482
699
|
seen_names: T::Array[String],
|
483
|
-
).returns(T.nilable(T::Array[
|
700
|
+
).returns(T.nilable(T::Array[T.any(
|
701
|
+
Entry::Namespace,
|
702
|
+
Entry::Alias,
|
703
|
+
Entry::UnresolvedAlias,
|
704
|
+
)]))
|
484
705
|
end
|
485
706
|
def lookup_enclosing_scopes(name, nesting, seen_names)
|
486
707
|
nesting.length.downto(1).each do |i|
|
@@ -505,16 +726,20 @@ module RubyIndexer
|
|
505
726
|
name: String,
|
506
727
|
nesting: T::Array[String],
|
507
728
|
seen_names: T::Array[String],
|
508
|
-
).returns(T.nilable(T::Array[
|
729
|
+
).returns(T.nilable(T::Array[T.any(
|
730
|
+
Entry::Namespace,
|
731
|
+
Entry::Alias,
|
732
|
+
Entry::UnresolvedAlias,
|
733
|
+
)]))
|
509
734
|
end
|
510
735
|
def lookup_ancestor_chain(name, nesting, seen_names)
|
511
736
|
*nesting_parts, constant_name = build_non_redundant_full_name(name, nesting).split("::")
|
512
|
-
return if
|
737
|
+
return if nesting_parts.empty?
|
513
738
|
|
514
|
-
namespace_entries = resolve(
|
739
|
+
namespace_entries = resolve(nesting_parts.join("::"), [], seen_names)
|
515
740
|
return unless namespace_entries
|
516
741
|
|
517
|
-
ancestors =
|
742
|
+
ancestors = nesting_parts.empty? ? [] : linearized_ancestors_of(T.must(namespace_entries.first).name)
|
518
743
|
|
519
744
|
ancestors.each do |ancestor_name|
|
520
745
|
entries = direct_or_aliased_constant("#{ancestor_name}::#{constant_name}", seen_names)
|
@@ -557,15 +782,50 @@ module RubyIndexer
|
|
557
782
|
end
|
558
783
|
end
|
559
784
|
|
560
|
-
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
|
561
797
|
def direct_or_aliased_constant(full_name, seen_names)
|
562
798
|
entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
|
563
|
-
|
799
|
+
|
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
|
+
)
|
564
808
|
end
|
565
809
|
|
566
|
-
|
567
|
-
|
568
|
-
|
810
|
+
# Attempt to resolve a given unresolved method alias. This method returns the resolved alias if we managed to
|
811
|
+
# identify the target or the same unresolved alias entry if we couldn't
|
812
|
+
sig do
|
813
|
+
params(
|
814
|
+
entry: Entry::UnresolvedMethodAlias,
|
815
|
+
receiver_name: String,
|
816
|
+
).returns(T.any(Entry::MethodAlias, Entry::UnresolvedMethodAlias))
|
817
|
+
end
|
818
|
+
def resolve_method_alias(entry, receiver_name)
|
819
|
+
return entry if entry.new_name == entry.old_name
|
820
|
+
|
821
|
+
target_method_entries = resolve_method(entry.old_name, receiver_name)
|
822
|
+
return entry unless target_method_entries
|
823
|
+
|
824
|
+
resolved_alias = Entry::MethodAlias.new(T.must(target_method_entries.first), entry)
|
825
|
+
original_entries = T.must(@entries[entry.new_name])
|
826
|
+
original_entries.delete(entry)
|
827
|
+
original_entries << resolved_alias
|
828
|
+
resolved_alias
|
569
829
|
end
|
570
830
|
end
|
571
831
|
end
|
@@ -48,9 +48,14 @@ 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
|
-
@index
|
53
|
+
@index.add(class_entry)
|
54
|
+
declaration.members.each do |member|
|
55
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
56
|
+
|
57
|
+
handle_method(member, class_entry)
|
58
|
+
end
|
54
59
|
end
|
55
60
|
|
56
61
|
sig { params(declaration: RBS::AST::Declarations::Module, pathname: Pathname).void }
|
@@ -59,9 +64,14 @@ module RubyIndexer
|
|
59
64
|
file_path = pathname.to_s
|
60
65
|
location = to_ruby_indexer_location(declaration.location)
|
61
66
|
comments = Array(declaration.comment&.string)
|
62
|
-
module_entry = Entry::Module.new(nesting, file_path, location, comments)
|
67
|
+
module_entry = Entry::Module.new(nesting, file_path, location, location, comments)
|
63
68
|
add_declaration_mixins_to_entry(declaration, module_entry)
|
64
|
-
@index
|
69
|
+
@index.add(module_entry)
|
70
|
+
declaration.members.each do |member|
|
71
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
72
|
+
|
73
|
+
handle_method(member, module_entry)
|
74
|
+
end
|
65
75
|
end
|
66
76
|
|
67
77
|
sig { params(rbs_location: RBS::Location).returns(RubyIndexer::Location) }
|
@@ -83,17 +93,36 @@ module RubyIndexer
|
|
83
93
|
def add_declaration_mixins_to_entry(declaration, entry)
|
84
94
|
declaration.each_mixin do |mixin|
|
85
95
|
name = mixin.name.name.to_s
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
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
|
96
105
|
end
|
97
106
|
end
|
107
|
+
|
108
|
+
sig { params(member: RBS::AST::Members::MethodDefinition, owner: Entry::Namespace).void }
|
109
|
+
def handle_method(member, owner)
|
110
|
+
name = member.name.name
|
111
|
+
file_path = member.location.buffer.name
|
112
|
+
location = to_ruby_indexer_location(member.location)
|
113
|
+
comments = Array(member.comment&.string)
|
114
|
+
|
115
|
+
visibility = case member.visibility
|
116
|
+
when :private
|
117
|
+
Entry::Visibility::PRIVATE
|
118
|
+
when :protected
|
119
|
+
Entry::Visibility::PROTECTED
|
120
|
+
else
|
121
|
+
Entry::Visibility::PUBLIC
|
122
|
+
end
|
123
|
+
|
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))
|
126
|
+
end
|
98
127
|
end
|
99
128
|
end
|