ruby-lsp 0.17.2 → 0.17.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +56 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +28 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +156 -40
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +99 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +3 -2
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +388 -56
- data/lib/ruby_indexer/test/method_test.rb +20 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +42 -0
- data/lib/ruby_indexer/test/test_case.rb +7 -0
- data/lib/ruby_lsp/global_state.rb +37 -16
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
- data/lib/ruby_lsp/server.rb +11 -0
- metadata +26 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c02fa9ec776002a86f4eeee4037132d8b0f9403f101ccff5c7a31c46385a2818
|
4
|
+
data.tar.gz: 3b788794763cee273e2ce677955c3750ebac90962c8d328946f91998a900eca5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3e152e24d1dca4aca721537f64df9062ae48aeea65b361ffd80dc6710cd263b0e8d1181f970b6d06e6147882c1cad40ba91d25ef6f684e2f5374a9738e1fae9
|
7
|
+
data.tar.gz: 79c3eb785ac55de19c4e9031ed6c8ca0a9a6df27156244ba7ee3ca2109659f8f2b9ce6efe7259cfcd65922a0b8cefc384fca2215dbdffac41735d9149db651a0
|
data/README.md
CHANGED
@@ -48,6 +48,8 @@ If using VS Code, all you have to do is install the [Ruby LSP
|
|
48
48
|
extension](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp) to get the extra features in the
|
49
49
|
editor. Do not install the `ruby-lsp` gem manually.
|
50
50
|
|
51
|
+
For more information on using and configuring the extension, see [vscode/README.md](vscode/README.md).
|
52
|
+
|
51
53
|
### With other editors
|
52
54
|
|
53
55
|
See [editors](EDITORS.md) for community instructions on setting up the Ruby LSP, which current includes Emacs, Neovim, Sublime Text, and Zed.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.3
|
@@ -52,6 +52,7 @@ module RubyIndexer
|
|
52
52
|
:on_instance_variable_operator_write_node_enter,
|
53
53
|
:on_instance_variable_or_write_node_enter,
|
54
54
|
:on_instance_variable_target_node_enter,
|
55
|
+
:on_alias_method_node_enter,
|
55
56
|
)
|
56
57
|
end
|
57
58
|
|
@@ -66,6 +67,8 @@ module RubyIndexer
|
|
66
67
|
parent_class = case superclass
|
67
68
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
68
69
|
superclass.slice
|
70
|
+
else
|
71
|
+
"::Object"
|
69
72
|
end
|
70
73
|
|
71
74
|
nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
|
@@ -209,6 +212,8 @@ module RubyIndexer
|
|
209
212
|
handle_attribute(node, reader: false, writer: true)
|
210
213
|
when :attr_accessor
|
211
214
|
handle_attribute(node, reader: true, writer: true)
|
215
|
+
when :alias_method
|
216
|
+
handle_alias_method(node)
|
212
217
|
when :include, :prepend, :extend
|
213
218
|
handle_module_operation(node, message)
|
214
219
|
when :public
|
@@ -338,6 +343,20 @@ module RubyIndexer
|
|
338
343
|
)
|
339
344
|
end
|
340
345
|
|
346
|
+
sig { params(node: Prism::AliasMethodNode).void }
|
347
|
+
def on_alias_method_node_enter(node)
|
348
|
+
method_name = node.new_name.slice
|
349
|
+
comments = collect_comments(node)
|
350
|
+
@index << Entry::UnresolvedMethodAlias.new(
|
351
|
+
method_name,
|
352
|
+
node.old_name.slice,
|
353
|
+
@owner_stack.last,
|
354
|
+
@file_path,
|
355
|
+
node.new_name.location,
|
356
|
+
comments,
|
357
|
+
)
|
358
|
+
end
|
359
|
+
|
341
360
|
private
|
342
361
|
|
343
362
|
sig { params(node: Prism::CallNode).void }
|
@@ -365,6 +384,43 @@ module RubyIndexer
|
|
365
384
|
entries&.each { |entry| entry.visibility = Entry::Visibility::PRIVATE }
|
366
385
|
end
|
367
386
|
|
387
|
+
sig { params(node: Prism::CallNode).void }
|
388
|
+
def handle_alias_method(node)
|
389
|
+
arguments = node.arguments&.arguments
|
390
|
+
return unless arguments
|
391
|
+
|
392
|
+
new_name, old_name = arguments
|
393
|
+
return unless new_name && old_name
|
394
|
+
|
395
|
+
new_name_value = case new_name
|
396
|
+
when Prism::StringNode
|
397
|
+
new_name.content
|
398
|
+
when Prism::SymbolNode
|
399
|
+
new_name.value
|
400
|
+
end
|
401
|
+
|
402
|
+
return unless new_name_value
|
403
|
+
|
404
|
+
old_name_value = case old_name
|
405
|
+
when Prism::StringNode
|
406
|
+
old_name.content
|
407
|
+
when Prism::SymbolNode
|
408
|
+
old_name.value
|
409
|
+
end
|
410
|
+
|
411
|
+
return unless old_name_value
|
412
|
+
|
413
|
+
comments = collect_comments(node)
|
414
|
+
@index << Entry::UnresolvedMethodAlias.new(
|
415
|
+
new_name_value,
|
416
|
+
old_name_value,
|
417
|
+
@owner_stack.last,
|
418
|
+
@file_path,
|
419
|
+
new_name.location,
|
420
|
+
comments,
|
421
|
+
)
|
422
|
+
end
|
423
|
+
|
368
424
|
sig do
|
369
425
|
params(
|
370
426
|
node: T.any(
|
@@ -469,5 +469,33 @@ module RubyIndexer
|
|
469
469
|
@owner = owner
|
470
470
|
end
|
471
471
|
end
|
472
|
+
|
473
|
+
class UnresolvedMethodAlias < Entry
|
474
|
+
extend T::Sig
|
475
|
+
|
476
|
+
sig { returns(String) }
|
477
|
+
attr_reader :new_name, :old_name
|
478
|
+
|
479
|
+
sig { returns(T.nilable(Entry::Namespace)) }
|
480
|
+
attr_reader :owner
|
481
|
+
|
482
|
+
sig do
|
483
|
+
params(
|
484
|
+
new_name: String,
|
485
|
+
old_name: String,
|
486
|
+
owner: T.nilable(Entry::Namespace),
|
487
|
+
file_path: String,
|
488
|
+
location: Prism::Location,
|
489
|
+
comments: T::Array[String],
|
490
|
+
).void
|
491
|
+
end
|
492
|
+
def initialize(new_name, old_name, owner, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
|
493
|
+
super(new_name, file_path, location, comments)
|
494
|
+
|
495
|
+
@new_name = new_name
|
496
|
+
@old_name = old_name
|
497
|
+
@owner = owner
|
498
|
+
end
|
499
|
+
end
|
472
500
|
end
|
473
501
|
end
|
@@ -140,35 +140,48 @@ module RubyIndexer
|
|
140
140
|
candidates
|
141
141
|
end
|
142
142
|
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
|
149
|
-
|
143
|
+
# Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
|
144
|
+
# documentation:
|
145
|
+
#
|
146
|
+
# name: the name of the reference how it was found in the source code (qualified or not)
|
147
|
+
# nesting: the nesting structure where the reference was found (e.g.: ["Foo", "Bar"])
|
148
|
+
# seen_names: this parameter should not be used by consumers of the api. It is used to avoid infinite recursion when
|
149
|
+
# resolving circular references
|
150
|
+
sig do
|
151
|
+
params(
|
152
|
+
name: String,
|
153
|
+
nesting: T::Array[String],
|
154
|
+
seen_names: T::Array[String],
|
155
|
+
).returns(T.nilable(T::Array[Entry]))
|
156
|
+
end
|
157
|
+
def resolve(name, nesting, seen_names = [])
|
158
|
+
# If we have a top level reference, then we just search for it straight away ignoring the nesting
|
150
159
|
if name.start_with?("::")
|
151
|
-
|
152
|
-
|
153
|
-
return results&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e) : e }
|
160
|
+
entries = direct_or_aliased_constant(name.delete_prefix("::"), seen_names)
|
161
|
+
return entries if entries
|
154
162
|
end
|
155
163
|
|
156
|
-
|
157
|
-
|
158
|
-
full_name = namespace.empty? ? name : "#{namespace}::#{name}"
|
164
|
+
# Non qualified reference path
|
165
|
+
full_name = nesting.any? ? "#{nesting.join("::")}::#{name}" : name
|
159
166
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
# the LSP itself we alias `RubyLsp::Interface` to `LanguageServer::Protocol::Interface`, which means doing
|
165
|
-
# `RubyLsp::Interface::Location` is allowed. For these cases, we need some way to realize that the
|
166
|
-
# `RubyLsp::Interface` part is an alias, that has to be resolved
|
167
|
-
entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
|
168
|
-
return entries.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e) : e } if entries
|
169
|
-
end
|
167
|
+
# When the name is not qualified with any namespaces, Ruby will take several steps to try to the resolve the
|
168
|
+
# constant. First, it will try to find the constant in the exact namespace where the reference was found
|
169
|
+
entries = direct_or_aliased_constant(full_name, seen_names)
|
170
|
+
return entries if entries
|
170
171
|
|
171
|
-
|
172
|
+
# If the constant is not found yet, then Ruby will try to find the constant in the enclosing lexical scopes,
|
173
|
+
# unwrapping each level one by one. Important note: the top level is not included because that's the fallback of
|
174
|
+
# the algorithm after every other possibility has been exhausted
|
175
|
+
entries = lookup_enclosing_scopes(name, nesting, seen_names)
|
176
|
+
return entries if entries
|
177
|
+
|
178
|
+
# If the constant does not exist in any enclosing scopes, then Ruby will search for it in the ancestors of the
|
179
|
+
# specific namespace where the reference was found
|
180
|
+
entries = lookup_ancestor_chain(name, nesting, seen_names)
|
181
|
+
return entries if entries
|
182
|
+
|
183
|
+
# Finally, as a fallback, Ruby will search for the constant in the top level namespace
|
184
|
+
search_top_level(name, seen_names)
|
172
185
|
rescue UnresolvableAliasError
|
173
186
|
nil
|
174
187
|
end
|
@@ -222,8 +235,8 @@ module RubyIndexer
|
|
222
235
|
# If we find an alias, then we want to follow its target. In the same example, if `Foo::Bar` is an alias to
|
223
236
|
# `Something::Else`, then we first discover `Something::Else::Baz`. But `Something::Else::Baz` might contain other
|
224
237
|
# aliases, so we have to invoke `follow_aliased_namespace` again to check until we only return a real name
|
225
|
-
sig { params(name: String).returns(String) }
|
226
|
-
def follow_aliased_namespace(name)
|
238
|
+
sig { params(name: String, seen_names: T::Array[String]).returns(String) }
|
239
|
+
def follow_aliased_namespace(name, seen_names = [])
|
227
240
|
return name if @entries[name]
|
228
241
|
|
229
242
|
parts = name.split("::")
|
@@ -236,16 +249,16 @@ module RubyIndexer
|
|
236
249
|
case entry
|
237
250
|
when Entry::Alias
|
238
251
|
target = entry.target
|
239
|
-
return follow_aliased_namespace("#{target}::#{real_parts.join("::")}")
|
252
|
+
return follow_aliased_namespace("#{target}::#{real_parts.join("::")}", seen_names)
|
240
253
|
when Entry::UnresolvedAlias
|
241
|
-
resolved = resolve_alias(entry)
|
254
|
+
resolved = resolve_alias(entry, seen_names)
|
242
255
|
|
243
256
|
if resolved.is_a?(Entry::UnresolvedAlias)
|
244
257
|
raise UnresolvableAliasError, "The constant #{resolved.name} is an alias to a non existing constant"
|
245
258
|
end
|
246
259
|
|
247
260
|
target = resolved.target
|
248
|
-
return follow_aliased_namespace("#{target}::#{real_parts.join("::")}")
|
261
|
+
return follow_aliased_namespace("#{target}::#{real_parts.join("::")}", seen_names)
|
249
262
|
else
|
250
263
|
real_parts.unshift(T.must(parts[i]))
|
251
264
|
end
|
@@ -291,6 +304,10 @@ module RubyIndexer
|
|
291
304
|
cached_ancestors = @ancestors[fully_qualified_name]
|
292
305
|
return cached_ancestors if cached_ancestors
|
293
306
|
|
307
|
+
# If we don't have an entry for `name`, raise
|
308
|
+
entries = self[fully_qualified_name]
|
309
|
+
raise NonExistingNamespaceError, "No entry found for #{fully_qualified_name}" unless entries
|
310
|
+
|
294
311
|
ancestors = [fully_qualified_name]
|
295
312
|
|
296
313
|
# Cache the linearized ancestors array eagerly. This is important because we might have circular dependencies and
|
@@ -298,10 +315,6 @@ module RubyIndexer
|
|
298
315
|
# the cache will reflect the final result
|
299
316
|
@ancestors[fully_qualified_name] = ancestors
|
300
317
|
|
301
|
-
# If we don't have an entry for `name`, raise
|
302
|
-
entries = resolve(fully_qualified_name, [])
|
303
|
-
raise NonExistingNamespaceError, "No entry found for #{fully_qualified_name}" unless entries
|
304
|
-
|
305
318
|
# If none of the entries for `name` are namespaces, raise
|
306
319
|
namespaces = entries.filter_map do |entry|
|
307
320
|
case entry
|
@@ -395,8 +408,8 @@ module RubyIndexer
|
|
395
408
|
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::InstanceVariable])
|
396
409
|
ancestors = linearized_ancestors_of(owner_name)
|
397
410
|
|
398
|
-
variables = entries.
|
399
|
-
variables.
|
411
|
+
variables = entries.select { |e| ancestors.any?(e.owner&.name) }
|
412
|
+
variables.uniq!(&:name)
|
400
413
|
variables
|
401
414
|
end
|
402
415
|
|
@@ -434,22 +447,125 @@ module RubyIndexer
|
|
434
447
|
|
435
448
|
# Attempts to resolve an UnresolvedAlias into a resolved Alias. If the unresolved alias is pointing to a constant
|
436
449
|
# that doesn't exist, then we return the same UnresolvedAlias
|
437
|
-
sig
|
438
|
-
|
439
|
-
|
450
|
+
sig do
|
451
|
+
params(
|
452
|
+
entry: Entry::UnresolvedAlias,
|
453
|
+
seen_names: T::Array[String],
|
454
|
+
).returns(T.any(Entry::Alias, Entry::UnresolvedAlias))
|
455
|
+
end
|
456
|
+
def resolve_alias(entry, seen_names)
|
457
|
+
alias_name = entry.name
|
458
|
+
return entry if seen_names.include?(alias_name)
|
459
|
+
|
460
|
+
seen_names << alias_name
|
461
|
+
|
462
|
+
target = resolve(entry.target, entry.nesting, seen_names)
|
440
463
|
return entry unless target
|
441
464
|
|
442
465
|
target_name = T.must(target.first).name
|
443
466
|
resolved_alias = Entry::Alias.new(target_name, entry)
|
444
467
|
|
445
468
|
# Replace the UnresolvedAlias by a resolved one so that we don't have to do this again later
|
446
|
-
original_entries = T.must(@entries[
|
469
|
+
original_entries = T.must(@entries[alias_name])
|
447
470
|
original_entries.delete(entry)
|
448
471
|
original_entries << resolved_alias
|
449
472
|
|
450
|
-
@entries_tree.insert(
|
473
|
+
@entries_tree.insert(alias_name, original_entries)
|
451
474
|
|
452
475
|
resolved_alias
|
453
476
|
end
|
477
|
+
|
478
|
+
sig do
|
479
|
+
params(
|
480
|
+
name: String,
|
481
|
+
nesting: T::Array[String],
|
482
|
+
seen_names: T::Array[String],
|
483
|
+
).returns(T.nilable(T::Array[Entry]))
|
484
|
+
end
|
485
|
+
def lookup_enclosing_scopes(name, nesting, seen_names)
|
486
|
+
nesting.length.downto(1).each do |i|
|
487
|
+
namespace = T.must(nesting[0...i]).join("::")
|
488
|
+
|
489
|
+
# If we find an entry with `full_name` directly, then we can already return it, even if it contains aliases -
|
490
|
+
# because the user might be trying to jump to the alias definition.
|
491
|
+
#
|
492
|
+
# However, if we don't find it, then we need to search for possible aliases in the namespace. For example, in
|
493
|
+
# the LSP itself we alias `RubyLsp::Interface` to `LanguageServer::Protocol::Interface`, which means doing
|
494
|
+
# `RubyLsp::Interface::Location` is allowed. For these cases, we need some way to realize that the
|
495
|
+
# `RubyLsp::Interface` part is an alias, that has to be resolved
|
496
|
+
entries = direct_or_aliased_constant("#{namespace}::#{name}", seen_names)
|
497
|
+
return entries if entries
|
498
|
+
end
|
499
|
+
|
500
|
+
nil
|
501
|
+
end
|
502
|
+
|
503
|
+
sig do
|
504
|
+
params(
|
505
|
+
name: String,
|
506
|
+
nesting: T::Array[String],
|
507
|
+
seen_names: T::Array[String],
|
508
|
+
).returns(T.nilable(T::Array[Entry]))
|
509
|
+
end
|
510
|
+
def lookup_ancestor_chain(name, nesting, seen_names)
|
511
|
+
*nesting_parts, constant_name = build_non_redundant_full_name(name, nesting).split("::")
|
512
|
+
return if T.must(nesting_parts).empty?
|
513
|
+
|
514
|
+
namespace_entries = resolve(T.must(nesting_parts).join("::"), [], seen_names)
|
515
|
+
return unless namespace_entries
|
516
|
+
|
517
|
+
ancestors = T.must(nesting_parts).empty? ? [] : linearized_ancestors_of(T.must(namespace_entries.first).name)
|
518
|
+
|
519
|
+
ancestors.each do |ancestor_name|
|
520
|
+
entries = direct_or_aliased_constant("#{ancestor_name}::#{constant_name}", seen_names)
|
521
|
+
return entries if entries
|
522
|
+
end
|
523
|
+
|
524
|
+
nil
|
525
|
+
rescue NonExistingNamespaceError
|
526
|
+
nil
|
527
|
+
end
|
528
|
+
|
529
|
+
# Removes redudancy from a constant reference's full name. For example, if we find a reference to `A::B::Foo` inside
|
530
|
+
# of the ["A", "B"] nesting, then we should not concatenate the nesting with the name or else we'll end up with
|
531
|
+
# `A::B::A::B::Foo`. This method will remove any redundant parts from the final name based on the reference and the
|
532
|
+
# nesting
|
533
|
+
sig { params(name: String, nesting: T::Array[String]).returns(String) }
|
534
|
+
def build_non_redundant_full_name(name, nesting)
|
535
|
+
return name if nesting.empty?
|
536
|
+
|
537
|
+
namespace = nesting.join("::")
|
538
|
+
|
539
|
+
# If the name is not qualified, we can just concatenate the nesting and the name
|
540
|
+
return "#{namespace}::#{name}" unless name.include?("::")
|
541
|
+
|
542
|
+
name_parts = name.split("::")
|
543
|
+
|
544
|
+
# Find the first part of the name that is not in the nesting
|
545
|
+
index = name_parts.index { |part| !nesting.include?(part) }
|
546
|
+
|
547
|
+
if index.nil?
|
548
|
+
# All parts of the nesting are redundant because they are already present in the name. We can return the name
|
549
|
+
# directly
|
550
|
+
name
|
551
|
+
elsif index == 0
|
552
|
+
# No parts of the nesting are in the name, we can concatenate the namespace and the name
|
553
|
+
"#{namespace}::#{name}"
|
554
|
+
else
|
555
|
+
# The name includes some parts of the nesting. We need to remove the redundant parts
|
556
|
+
"#{namespace}::#{T.must(name_parts[index..-1]).join("::")}"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
sig { params(full_name: String, seen_names: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
|
561
|
+
def direct_or_aliased_constant(full_name, seen_names)
|
562
|
+
entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
|
563
|
+
entries&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e }
|
564
|
+
end
|
565
|
+
|
566
|
+
sig { params(name: String, seen_names: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
|
567
|
+
def search_top_level(name, seen_names)
|
568
|
+
@entries[name]&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e }
|
569
|
+
end
|
454
570
|
end
|
455
571
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class RBSIndexer
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(index: Index).void }
|
9
|
+
def initialize(index)
|
10
|
+
@index = index
|
11
|
+
end
|
12
|
+
|
13
|
+
sig { void }
|
14
|
+
def index_ruby_core
|
15
|
+
loader = RBS::EnvironmentLoader.new
|
16
|
+
RBS::Environment.from_loader(loader).resolve_type_names
|
17
|
+
|
18
|
+
loader.each_signature do |source, pathname, _buffer, declarations, _directives|
|
19
|
+
process_signature(source, pathname, declarations)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
sig { params(source: T.untyped, pathname: Pathname, declarations: T::Array[RBS::AST::Declarations::Base]).void }
|
26
|
+
def process_signature(source, pathname, declarations)
|
27
|
+
declarations.each do |declaration|
|
28
|
+
process_declaration(declaration, pathname)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(declaration: RBS::AST::Declarations::Base, pathname: Pathname).void }
|
33
|
+
def process_declaration(declaration, pathname)
|
34
|
+
case declaration
|
35
|
+
when RBS::AST::Declarations::Class
|
36
|
+
handle_class_declaration(declaration, pathname)
|
37
|
+
when RBS::AST::Declarations::Module
|
38
|
+
handle_module_declaration(declaration, pathname)
|
39
|
+
else # rubocop:disable Style/EmptyElse
|
40
|
+
# Other kinds not yet handled
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(declaration: RBS::AST::Declarations::Class, pathname: Pathname).void }
|
45
|
+
def handle_class_declaration(declaration, pathname)
|
46
|
+
nesting = [declaration.name.name.to_s]
|
47
|
+
file_path = pathname.to_s
|
48
|
+
location = to_ruby_indexer_location(declaration.location)
|
49
|
+
comments = Array(declaration.comment&.string)
|
50
|
+
parent_class = declaration.super_class&.name&.name&.to_s
|
51
|
+
class_entry = Entry::Class.new(nesting, file_path, location, comments, parent_class)
|
52
|
+
add_declaration_mixins_to_entry(declaration, class_entry)
|
53
|
+
@index << class_entry
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { params(declaration: RBS::AST::Declarations::Module, pathname: Pathname).void }
|
57
|
+
def handle_module_declaration(declaration, pathname)
|
58
|
+
nesting = [declaration.name.name.to_s]
|
59
|
+
file_path = pathname.to_s
|
60
|
+
location = to_ruby_indexer_location(declaration.location)
|
61
|
+
comments = Array(declaration.comment&.string)
|
62
|
+
module_entry = Entry::Module.new(nesting, file_path, location, comments)
|
63
|
+
add_declaration_mixins_to_entry(declaration, module_entry)
|
64
|
+
@index << module_entry
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { params(rbs_location: RBS::Location).returns(RubyIndexer::Location) }
|
68
|
+
def to_ruby_indexer_location(rbs_location)
|
69
|
+
RubyIndexer::Location.new(
|
70
|
+
rbs_location.start_line,
|
71
|
+
rbs_location.end_line,
|
72
|
+
rbs_location.start_column,
|
73
|
+
rbs_location.end_column,
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
sig do
|
78
|
+
params(
|
79
|
+
declaration: T.any(RBS::AST::Declarations::Class, RBS::AST::Declarations::Module),
|
80
|
+
entry: Entry::Namespace,
|
81
|
+
).void
|
82
|
+
end
|
83
|
+
def add_declaration_mixins_to_entry(declaration, entry)
|
84
|
+
declaration.each_mixin do |mixin|
|
85
|
+
name = mixin.name.name.to_s
|
86
|
+
mixin_operation =
|
87
|
+
case mixin
|
88
|
+
when RBS::AST::Members::Include
|
89
|
+
Entry::Include.new(name)
|
90
|
+
when RBS::AST::Members::Extend
|
91
|
+
Entry::Extend.new(name)
|
92
|
+
when RBS::AST::Members::Prepend
|
93
|
+
Entry::Prepend.new(name)
|
94
|
+
end
|
95
|
+
entry.mixin_operations << mixin_operation if mixin_operation
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -11,6 +11,7 @@ require "ruby_indexer/lib/ruby_indexer/entry"
|
|
11
11
|
require "ruby_indexer/lib/ruby_indexer/configuration"
|
12
12
|
require "ruby_indexer/lib/ruby_indexer/prefix_tree"
|
13
13
|
require "ruby_indexer/lib/ruby_indexer/location"
|
14
|
+
require "ruby_indexer/lib/ruby_indexer/rbs_indexer"
|
14
15
|
|
15
16
|
module RubyIndexer
|
16
17
|
@configuration = T.let(Configuration.new, Configuration)
|
@@ -191,7 +191,8 @@ module RubyIndexer
|
|
191
191
|
|
192
192
|
@index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
|
193
193
|
refute_entry("Foo")
|
194
|
-
|
194
|
+
|
195
|
+
assert_no_indexed_entries
|
195
196
|
end
|
196
197
|
|
197
198
|
def test_comments_can_be_attached_to_a_class
|
@@ -323,7 +324,7 @@ module RubyIndexer
|
|
323
324
|
assert_equal("Bar", foo.parent_class)
|
324
325
|
|
325
326
|
baz = T.must(@index["Baz"].first)
|
326
|
-
|
327
|
+
assert_equal("::Object", baz.parent_class)
|
327
328
|
|
328
329
|
qux = T.must(@index["Something::Qux"].first)
|
329
330
|
assert_equal("::Baz", qux.parent_class)
|
@@ -54,7 +54,7 @@ module RubyIndexer
|
|
54
54
|
|
55
55
|
assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
|
56
56
|
assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
|
57
|
-
assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/
|
57
|
+
assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/erb.rb")
|
58
58
|
end
|
59
59
|
|
60
60
|
def test_indexables_includes_project_files
|