ruby-lsp 0.17.1 → 0.17.3
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 +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/document.rb +24 -1
- 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/listeners/definition.rb +20 -18
- data/lib/ruby_lsp/listeners/hover.rb +2 -0
- data/lib/ruby_lsp/node_context.rb +15 -4
- data/lib/ruby_lsp/requests/definition.rb +5 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +23 -6
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +5 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -3
- data/lib/ruby_lsp/server.rb +12 -1
- metadata +27 -5
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
|