ruby-lsp 0.22.1 → 0.23.10
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 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +12 -11
- data/exe/ruby-lsp-check +5 -5
- data/exe/ruby-lsp-launcher +41 -15
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +191 -100
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +174 -61
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +12 -0
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +82 -61
- data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +2 -1
- data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +30 -6
- data/lib/ruby_indexer/test/configuration_test.rb +116 -51
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
- data/lib/ruby_indexer/test/index_test.rb +143 -44
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +86 -8
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_indexer/test/reference_finder_test.rb +90 -2
- data/lib/ruby_indexer/test/test_case.rb +2 -2
- data/lib/ruby_indexer/test/uri_test.rb +72 -0
- data/lib/ruby_lsp/addon.rb +9 -0
- data/lib/ruby_lsp/base_server.rb +17 -18
- data/lib/ruby_lsp/client_capabilities.rb +7 -1
- data/lib/ruby_lsp/document.rb +72 -10
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +42 -3
- data/lib/ruby_lsp/internal.rb +3 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +9 -5
- data/lib/ruby_lsp/listeners/completion.rb +78 -6
- data/lib/ruby_lsp/listeners/definition.rb +80 -19
- data/lib/ruby_lsp/listeners/document_highlight.rb +3 -2
- data/lib/ruby_lsp/listeners/document_link.rb +21 -3
- data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +59 -2
- data/lib/ruby_lsp/load_sorbet.rb +3 -3
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +90 -6
- data/lib/ruby_lsp/requests/code_actions.rb +57 -1
- data/lib/ruby_lsp/requests/completion.rb +8 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +7 -1
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/folding_ranges.rb +2 -6
- data/lib/ruby_lsp/requests/formatting.rb +2 -6
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +2 -2
- data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +29 -2
- data/lib/ruby_lsp/requests/rename.rb +17 -7
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +2 -9
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -3
- data/lib/ruby_lsp/ruby_document.rb +80 -6
- data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
- data/lib/ruby_lsp/server.rb +205 -61
- data/lib/ruby_lsp/setup_bundler.rb +50 -43
- data/lib/ruby_lsp/store.rb +7 -7
- data/lib/ruby_lsp/test_helper.rb +45 -11
- data/lib/ruby_lsp/type_inferrer.rb +60 -31
- data/lib/ruby_lsp/utils.rb +63 -3
- metadata +8 -8
- data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -16,8 +16,8 @@ module RubyIndexer
|
|
16
16
|
sig { returns(String) }
|
17
17
|
attr_reader :name
|
18
18
|
|
19
|
-
sig { returns(
|
20
|
-
attr_reader :
|
19
|
+
sig { returns(URI::Generic) }
|
20
|
+
attr_reader :uri
|
21
21
|
|
22
22
|
sig { returns(RubyIndexer::Location) }
|
23
23
|
attr_reader :location
|
@@ -30,14 +30,14 @@ module RubyIndexer
|
|
30
30
|
sig do
|
31
31
|
params(
|
32
32
|
name: String,
|
33
|
-
|
33
|
+
uri: URI::Generic,
|
34
34
|
location: Location,
|
35
35
|
comments: T.nilable(String),
|
36
36
|
).void
|
37
37
|
end
|
38
|
-
def initialize(name,
|
38
|
+
def initialize(name, uri, location, comments)
|
39
39
|
@name = name
|
40
|
-
@
|
40
|
+
@uri = uri
|
41
41
|
@comments = comments
|
42
42
|
@visibility = T.let(Visibility::PUBLIC, Visibility)
|
43
43
|
@location = location
|
@@ -60,14 +60,24 @@ module RubyIndexer
|
|
60
60
|
|
61
61
|
sig { returns(String) }
|
62
62
|
def file_name
|
63
|
-
|
63
|
+
if @uri.scheme == "untitled"
|
64
|
+
T.must(@uri.opaque)
|
65
|
+
else
|
66
|
+
File.basename(T.must(file_path))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
sig { returns(T.nilable(String)) }
|
71
|
+
def file_path
|
72
|
+
@uri.full_path
|
64
73
|
end
|
65
74
|
|
66
75
|
sig { returns(String) }
|
67
76
|
def comments
|
68
77
|
@comments ||= begin
|
69
78
|
# Parse only the comments based on the file path, which is much faster than parsing the entire file
|
70
|
-
|
79
|
+
path = file_path
|
80
|
+
parsed_comments = path ? Prism.parse_file_comments(path) : []
|
71
81
|
|
72
82
|
# Group comments based on whether they belong to a single block of comments
|
73
83
|
grouped = parsed_comments.slice_when do |left, right|
|
@@ -137,18 +147,18 @@ module RubyIndexer
|
|
137
147
|
sig do
|
138
148
|
params(
|
139
149
|
nesting: T::Array[String],
|
140
|
-
|
150
|
+
uri: URI::Generic,
|
141
151
|
location: Location,
|
142
152
|
name_location: Location,
|
143
153
|
comments: T.nilable(String),
|
144
154
|
).void
|
145
155
|
end
|
146
|
-
def initialize(nesting,
|
156
|
+
def initialize(nesting, uri, location, name_location, comments)
|
147
157
|
@name = T.let(nesting.join("::"), String)
|
148
158
|
# The original nesting where this namespace was discovered
|
149
159
|
@nesting = nesting
|
150
160
|
|
151
|
-
super(@name,
|
161
|
+
super(@name, uri, location, comments)
|
152
162
|
|
153
163
|
@name_location = name_location
|
154
164
|
end
|
@@ -186,15 +196,15 @@ module RubyIndexer
|
|
186
196
|
sig do
|
187
197
|
params(
|
188
198
|
nesting: T::Array[String],
|
189
|
-
|
199
|
+
uri: URI::Generic,
|
190
200
|
location: Location,
|
191
201
|
name_location: Location,
|
192
202
|
comments: T.nilable(String),
|
193
203
|
parent_class: T.nilable(String),
|
194
204
|
).void
|
195
205
|
end
|
196
|
-
def initialize(nesting,
|
197
|
-
super(nesting,
|
206
|
+
def initialize(nesting, uri, location, name_location, comments, parent_class) # rubocop:disable Metrics/ParameterLists
|
207
|
+
super(nesting, uri, location, name_location, comments)
|
198
208
|
@parent_class = parent_class
|
199
209
|
end
|
200
210
|
|
@@ -332,15 +342,15 @@ module RubyIndexer
|
|
332
342
|
sig do
|
333
343
|
params(
|
334
344
|
name: String,
|
335
|
-
|
345
|
+
uri: URI::Generic,
|
336
346
|
location: Location,
|
337
347
|
comments: T.nilable(String),
|
338
348
|
visibility: Visibility,
|
339
349
|
owner: T.nilable(Entry::Namespace),
|
340
350
|
).void
|
341
351
|
end
|
342
|
-
def initialize(name,
|
343
|
-
super(name,
|
352
|
+
def initialize(name, uri, location, comments, visibility, owner) # rubocop:disable Metrics/ParameterLists
|
353
|
+
super(name, uri, location, comments)
|
344
354
|
@visibility = visibility
|
345
355
|
@owner = owner
|
346
356
|
end
|
@@ -399,7 +409,7 @@ module RubyIndexer
|
|
399
409
|
sig do
|
400
410
|
params(
|
401
411
|
name: String,
|
402
|
-
|
412
|
+
uri: URI::Generic,
|
403
413
|
location: Location,
|
404
414
|
name_location: Location,
|
405
415
|
comments: T.nilable(String),
|
@@ -408,8 +418,8 @@ module RubyIndexer
|
|
408
418
|
owner: T.nilable(Entry::Namespace),
|
409
419
|
).void
|
410
420
|
end
|
411
|
-
def initialize(name,
|
412
|
-
super(name,
|
421
|
+
def initialize(name, uri, location, name_location, comments, signatures, visibility, owner) # rubocop:disable Metrics/ParameterLists
|
422
|
+
super(name, uri, location, comments, visibility, owner)
|
413
423
|
@signatures = signatures
|
414
424
|
@name_location = name_location
|
415
425
|
end
|
@@ -439,13 +449,13 @@ module RubyIndexer
|
|
439
449
|
target: String,
|
440
450
|
nesting: T::Array[String],
|
441
451
|
name: String,
|
442
|
-
|
452
|
+
uri: URI::Generic,
|
443
453
|
location: Location,
|
444
454
|
comments: T.nilable(String),
|
445
455
|
).void
|
446
456
|
end
|
447
|
-
def initialize(target, nesting, name,
|
448
|
-
super(name,
|
457
|
+
def initialize(target, nesting, name, uri, location, comments) # rubocop:disable Metrics/ParameterLists
|
458
|
+
super(name, uri, location, comments)
|
449
459
|
|
450
460
|
@target = target
|
451
461
|
@nesting = nesting
|
@@ -463,7 +473,7 @@ module RubyIndexer
|
|
463
473
|
def initialize(target, unresolved_alias)
|
464
474
|
super(
|
465
475
|
unresolved_alias.name,
|
466
|
-
unresolved_alias.
|
476
|
+
unresolved_alias.uri,
|
467
477
|
unresolved_alias.location,
|
468
478
|
unresolved_alias.comments,
|
469
479
|
)
|
@@ -476,6 +486,26 @@ module RubyIndexer
|
|
476
486
|
# Represents a global variable e.g.: $DEBUG
|
477
487
|
class GlobalVariable < Entry; end
|
478
488
|
|
489
|
+
# Represents a class variable e.g.: @@a = 1
|
490
|
+
class ClassVariable < Entry
|
491
|
+
sig { returns(T.nilable(Entry::Namespace)) }
|
492
|
+
attr_reader :owner
|
493
|
+
|
494
|
+
sig do
|
495
|
+
params(
|
496
|
+
name: String,
|
497
|
+
uri: URI::Generic,
|
498
|
+
location: Location,
|
499
|
+
comments: T.nilable(String),
|
500
|
+
owner: T.nilable(Entry::Namespace),
|
501
|
+
).void
|
502
|
+
end
|
503
|
+
def initialize(name, uri, location, comments, owner)
|
504
|
+
super(name, uri, location, comments)
|
505
|
+
@owner = owner
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
479
509
|
# Represents an instance variable e.g.: @a = 1
|
480
510
|
class InstanceVariable < Entry
|
481
511
|
sig { returns(T.nilable(Entry::Namespace)) }
|
@@ -484,14 +514,14 @@ module RubyIndexer
|
|
484
514
|
sig do
|
485
515
|
params(
|
486
516
|
name: String,
|
487
|
-
|
517
|
+
uri: URI::Generic,
|
488
518
|
location: Location,
|
489
519
|
comments: T.nilable(String),
|
490
520
|
owner: T.nilable(Entry::Namespace),
|
491
521
|
).void
|
492
522
|
end
|
493
|
-
def initialize(name,
|
494
|
-
super(name,
|
523
|
+
def initialize(name, uri, location, comments, owner)
|
524
|
+
super(name, uri, location, comments)
|
495
525
|
@owner = owner
|
496
526
|
end
|
497
527
|
end
|
@@ -513,13 +543,13 @@ module RubyIndexer
|
|
513
543
|
new_name: String,
|
514
544
|
old_name: String,
|
515
545
|
owner: T.nilable(Entry::Namespace),
|
516
|
-
|
546
|
+
uri: URI::Generic,
|
517
547
|
location: Location,
|
518
548
|
comments: T.nilable(String),
|
519
549
|
).void
|
520
550
|
end
|
521
|
-
def initialize(new_name, old_name, owner,
|
522
|
-
super(new_name,
|
551
|
+
def initialize(new_name, old_name, owner, uri, location, comments) # rubocop:disable Metrics/ParameterLists
|
552
|
+
super(new_name, uri, location, comments)
|
523
553
|
|
524
554
|
@new_name = new_name
|
525
555
|
@old_name = old_name
|
@@ -547,7 +577,7 @@ module RubyIndexer
|
|
547
577
|
|
548
578
|
super(
|
549
579
|
unresolved_alias.new_name,
|
550
|
-
unresolved_alias.
|
580
|
+
unresolved_alias.uri,
|
551
581
|
unresolved_alias.location,
|
552
582
|
full_comments,
|
553
583
|
)
|
@@ -15,6 +15,45 @@ module RubyIndexer
|
|
15
15
|
sig { returns(Configuration) }
|
16
16
|
attr_reader :configuration
|
17
17
|
|
18
|
+
class << self
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
# Returns the real nesting of a constant name taking into account top level
|
22
|
+
# references that may be included anywhere in the name or nesting where that
|
23
|
+
# constant was found
|
24
|
+
sig { params(stack: T::Array[String], name: String).returns(T::Array[String]) }
|
25
|
+
def actual_nesting(stack, name)
|
26
|
+
nesting = stack + [name]
|
27
|
+
corrected_nesting = []
|
28
|
+
|
29
|
+
nesting.reverse_each do |name|
|
30
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
31
|
+
|
32
|
+
break if name.start_with?("::")
|
33
|
+
end
|
34
|
+
|
35
|
+
corrected_nesting
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the unresolved name for a constant reference including all parts of a constant path, or `nil` if the
|
39
|
+
# constant contains dynamic or incomplete parts
|
40
|
+
sig do
|
41
|
+
params(
|
42
|
+
node: T.any(
|
43
|
+
Prism::ConstantPathNode,
|
44
|
+
Prism::ConstantReadNode,
|
45
|
+
Prism::ConstantPathTargetNode,
|
46
|
+
),
|
47
|
+
).returns(T.nilable(String))
|
48
|
+
end
|
49
|
+
def constant_name(node)
|
50
|
+
node.full_name
|
51
|
+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
52
|
+
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
18
57
|
sig { void }
|
19
58
|
def initialize
|
20
59
|
# Holds all entries in the index using the following format:
|
@@ -29,13 +68,14 @@ module RubyIndexer
|
|
29
68
|
|
30
69
|
# Holds references to where entries where discovered so that we can easily delete them
|
31
70
|
# {
|
32
|
-
# "
|
33
|
-
# "
|
71
|
+
# "file:///my/project/foo.rb" => [#<Entry::Class>, #<Entry::Class>],
|
72
|
+
# "file:///my/project/bar.rb" => [#<Entry::Class>],
|
73
|
+
# "untitled:Untitled-1" => [#<Entry::Class>],
|
34
74
|
# }
|
35
|
-
@
|
75
|
+
@uris_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
|
36
76
|
|
37
77
|
# Holds all require paths for every indexed item so that we can provide autocomplete for requires
|
38
|
-
@require_paths_tree = T.let(PrefixTree[
|
78
|
+
@require_paths_tree = T.let(PrefixTree[URI::Generic].new, PrefixTree[URI::Generic])
|
39
79
|
|
40
80
|
# Holds the linearized ancestors list for every namespace
|
41
81
|
@ancestors = T.let({}, T::Hash[String, T::Array[String]])
|
@@ -47,6 +87,8 @@ module RubyIndexer
|
|
47
87
|
)
|
48
88
|
|
49
89
|
@configuration = T.let(RubyIndexer::Configuration.new, Configuration)
|
90
|
+
|
91
|
+
@initial_indexing_completed = T.let(false, T::Boolean)
|
50
92
|
end
|
51
93
|
|
52
94
|
# Register an included `hook` that will be executed when `module_name` is included into any namespace
|
@@ -55,11 +97,12 @@ module RubyIndexer
|
|
55
97
|
(@included_hooks[module_name] ||= []) << hook
|
56
98
|
end
|
57
99
|
|
58
|
-
sig { params(
|
59
|
-
def delete(
|
100
|
+
sig { params(uri: URI::Generic, skip_require_paths_tree: T::Boolean).void }
|
101
|
+
def delete(uri, skip_require_paths_tree: false)
|
102
|
+
key = uri.to_s
|
60
103
|
# For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
|
61
104
|
# left, delete the constant from the index.
|
62
|
-
@
|
105
|
+
@uris_to_entries[key]&.each do |entry|
|
63
106
|
name = entry.name
|
64
107
|
entries = @entries[name]
|
65
108
|
next unless entries
|
@@ -77,9 +120,10 @@ module RubyIndexer
|
|
77
120
|
end
|
78
121
|
end
|
79
122
|
|
80
|
-
@
|
123
|
+
@uris_to_entries.delete(key)
|
124
|
+
return if skip_require_paths_tree
|
81
125
|
|
82
|
-
require_path =
|
126
|
+
require_path = uri.require_path
|
83
127
|
@require_paths_tree.delete(require_path) if require_path
|
84
128
|
end
|
85
129
|
|
@@ -88,7 +132,7 @@ module RubyIndexer
|
|
88
132
|
name = entry.name
|
89
133
|
|
90
134
|
(@entries[name] ||= []) << entry
|
91
|
-
(@
|
135
|
+
(@uris_to_entries[entry.uri.to_s] ||= []) << entry
|
92
136
|
@entries_tree.insert(name, T.must(@entries[name])) unless skip_prefix_tree
|
93
137
|
end
|
94
138
|
|
@@ -97,7 +141,7 @@ module RubyIndexer
|
|
97
141
|
@entries[fully_qualified_name.delete_prefix("::")]
|
98
142
|
end
|
99
143
|
|
100
|
-
sig { params(query: String).returns(T::Array[
|
144
|
+
sig { params(query: String).returns(T::Array[URI::Generic]) }
|
101
145
|
def search_require_paths(query)
|
102
146
|
@require_paths_tree.search(query)
|
103
147
|
end
|
@@ -115,8 +159,16 @@ module RubyIndexer
|
|
115
159
|
)]))
|
116
160
|
end
|
117
161
|
def first_unqualified_const(name)
|
162
|
+
# Look for an exact match first
|
118
163
|
_name, entries = @entries.find do |const_name, _entries|
|
119
|
-
const_name.end_with?(name)
|
164
|
+
const_name == name || const_name.end_with?("::#{name}")
|
165
|
+
end
|
166
|
+
|
167
|
+
# If an exact match is not found, then try to find a constant that ends with the name
|
168
|
+
unless entries
|
169
|
+
_name, entries = @entries.find do |const_name, _entries|
|
170
|
+
const_name.end_with?(name)
|
171
|
+
end
|
120
172
|
end
|
121
173
|
|
122
174
|
T.cast(
|
@@ -342,75 +394,70 @@ module RubyIndexer
|
|
342
394
|
nil
|
343
395
|
end
|
344
396
|
|
345
|
-
# Index all files for the given
|
346
|
-
#
|
347
|
-
#
|
397
|
+
# Index all files for the given URIs, which defaults to what is configured. A block can be used to track and control
|
398
|
+
# indexing progress. That block is invoked with the current progress percentage and should return `true` to continue
|
399
|
+
# indexing or `false` to stop indexing.
|
348
400
|
sig do
|
349
401
|
params(
|
350
|
-
|
402
|
+
uris: T::Array[URI::Generic],
|
351
403
|
block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
|
352
404
|
).void
|
353
405
|
end
|
354
|
-
def index_all(
|
406
|
+
def index_all(uris: @configuration.indexable_uris, &block)
|
355
407
|
# When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
|
356
408
|
# existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
|
357
409
|
# behavior and can take appropriate action.
|
358
|
-
|
359
|
-
if @entries.any?
|
410
|
+
if @initial_indexing_completed
|
360
411
|
raise IndexNotEmptyError,
|
361
412
|
"The index is not empty. To prevent invalid entries, `index_all` can only be called once."
|
362
413
|
end
|
363
414
|
|
415
|
+
@initial_indexing_completed = true
|
416
|
+
|
364
417
|
RBSIndexer.new(self).index_ruby_core
|
365
418
|
# Calculate how many paths are worth 1% of progress
|
366
|
-
progress_step = (
|
419
|
+
progress_step = (uris.length / 100.0).ceil
|
367
420
|
|
368
|
-
|
421
|
+
uris.each_with_index do |uri, index|
|
369
422
|
if block && index % progress_step == 0
|
370
423
|
progress = (index / progress_step) + 1
|
371
424
|
break unless block.call(progress)
|
372
425
|
end
|
373
426
|
|
374
|
-
|
427
|
+
index_file(uri, collect_comments: false)
|
375
428
|
end
|
376
429
|
end
|
377
430
|
|
378
|
-
sig { params(
|
379
|
-
def index_single(
|
380
|
-
content = source || File.read(indexable_path.full_path)
|
431
|
+
sig { params(uri: URI::Generic, source: String, collect_comments: T::Boolean).void }
|
432
|
+
def index_single(uri, source, collect_comments: true)
|
381
433
|
dispatcher = Prism::Dispatcher.new
|
382
434
|
|
383
|
-
result = Prism.parse(
|
384
|
-
listener = DeclarationListener.new(
|
385
|
-
self,
|
386
|
-
dispatcher,
|
387
|
-
result,
|
388
|
-
indexable_path.full_path,
|
389
|
-
collect_comments: collect_comments,
|
390
|
-
)
|
435
|
+
result = Prism.parse(source)
|
436
|
+
listener = DeclarationListener.new(self, dispatcher, result, uri, collect_comments: collect_comments)
|
391
437
|
dispatcher.dispatch(result.value)
|
392
438
|
|
393
|
-
|
394
|
-
|
395
|
-
require_path = indexable_path.require_path
|
396
|
-
@require_paths_tree.insert(require_path, indexable_path) if require_path
|
439
|
+
require_path = uri.require_path
|
440
|
+
@require_paths_tree.insert(require_path, uri) if require_path
|
397
441
|
|
398
|
-
|
399
|
-
|
400
|
-
$stderr.puts error
|
401
|
-
end
|
402
|
-
end
|
403
|
-
rescue Errno::EISDIR, Errno::ENOENT
|
404
|
-
# If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
|
405
|
-
# it
|
442
|
+
indexing_errors = listener.indexing_errors.uniq
|
443
|
+
indexing_errors.each { |error| $stderr.puts(error) } if indexing_errors.any?
|
406
444
|
rescue SystemStackError => e
|
407
445
|
if e.backtrace&.first&.include?("prism")
|
408
|
-
$stderr.puts "Prism error indexing #{
|
446
|
+
$stderr.puts "Prism error indexing #{uri}: #{e.message}"
|
409
447
|
else
|
410
448
|
raise
|
411
449
|
end
|
412
450
|
end
|
413
451
|
|
452
|
+
# Indexes a File URI by reading the contents from disk
|
453
|
+
sig { params(uri: URI::Generic, collect_comments: T::Boolean).void }
|
454
|
+
def index_file(uri, collect_comments: true)
|
455
|
+
index_single(uri, File.read(T.must(uri.full_path)), collect_comments: collect_comments)
|
456
|
+
rescue Errno::EISDIR, Errno::ENOENT
|
457
|
+
# If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
|
458
|
+
# it
|
459
|
+
end
|
460
|
+
|
414
461
|
# Follows aliases in a namespace. The algorithm keeps checking if the name is an alias and then recursively follows
|
415
462
|
# it. The idea is that we test the name in parts starting from the complete name to the first namespace. For
|
416
463
|
# `Foo::Bar::Baz`, we would test:
|
@@ -588,29 +635,81 @@ module RubyIndexer
|
|
588
635
|
entries.select { |e| ancestors.include?(e.owner&.name) }
|
589
636
|
end
|
590
637
|
|
638
|
+
sig { params(variable_name: String, owner_name: String).returns(T.nilable(T::Array[Entry::ClassVariable])) }
|
639
|
+
def resolve_class_variable(variable_name, owner_name)
|
640
|
+
entries = self[variable_name]&.grep(Entry::ClassVariable)
|
641
|
+
return unless entries&.any?
|
642
|
+
|
643
|
+
ancestors = linearized_attached_ancestors(owner_name)
|
644
|
+
return if ancestors.empty?
|
645
|
+
|
646
|
+
entries.select { |e| ancestors.include?(e.owner&.name) }
|
647
|
+
end
|
648
|
+
|
591
649
|
# Returns a list of possible candidates for completion of instance variables for a given owner name. The name must
|
592
650
|
# include the `@` prefix
|
593
|
-
sig
|
651
|
+
sig do
|
652
|
+
params(name: String, owner_name: String).returns(T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
|
653
|
+
end
|
594
654
|
def instance_variable_completion_candidates(name, owner_name)
|
595
|
-
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::InstanceVariable])
|
655
|
+
entries = T.cast(prefix_search(name).flatten, T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
|
656
|
+
# Avoid wasting time linearizing ancestors if we didn't find anything
|
657
|
+
return entries if entries.empty?
|
658
|
+
|
596
659
|
ancestors = linearized_ancestors_of(owner_name)
|
597
660
|
|
598
|
-
|
661
|
+
instance_variables, class_variables = entries.partition { |e| e.is_a?(Entry::InstanceVariable) }
|
662
|
+
variables = instance_variables.select { |e| ancestors.any?(e.owner&.name) }
|
663
|
+
|
664
|
+
# Class variables are only owned by the attached class in our representation. If the owner is in a singleton
|
665
|
+
# context, we have to search for ancestors of the attached class
|
666
|
+
if class_variables.any?
|
667
|
+
name_parts = owner_name.split("::")
|
668
|
+
|
669
|
+
if name_parts.last&.start_with?("<Class:")
|
670
|
+
attached_name = T.must(name_parts[0..-2]).join("::")
|
671
|
+
attached_ancestors = linearized_ancestors_of(attached_name)
|
672
|
+
variables.concat(class_variables.select { |e| attached_ancestors.any?(e.owner&.name) })
|
673
|
+
else
|
674
|
+
variables.concat(class_variables.select { |e| ancestors.any?(e.owner&.name) })
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
599
678
|
variables.uniq!(&:name)
|
600
679
|
variables
|
601
680
|
end
|
602
681
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
682
|
+
sig { params(name: String, owner_name: String).returns(T::Array[Entry::ClassVariable]) }
|
683
|
+
def class_variable_completion_candidates(name, owner_name)
|
684
|
+
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::ClassVariable])
|
685
|
+
# Avoid wasting time linearizing ancestors if we didn't find anything
|
686
|
+
return entries if entries.empty?
|
608
687
|
|
609
|
-
|
610
|
-
|
688
|
+
ancestors = linearized_attached_ancestors(owner_name)
|
689
|
+
variables = entries.select { |e| ancestors.any?(e.owner&.name) }
|
690
|
+
variables.uniq!(&:name)
|
691
|
+
variables
|
692
|
+
end
|
611
693
|
|
612
|
-
|
694
|
+
# Synchronizes a change made to the given URI. This method will ensure that new declarations are indexed, removed
|
695
|
+
# declarations removed and that the ancestor linearization cache is cleared if necessary. If a block is passed, the
|
696
|
+
# consumer of this API has to handle deleting and inserting/updating entries in the index instead of passing the
|
697
|
+
# document's source (used to handle unsaved changes to files)
|
698
|
+
sig do
|
699
|
+
params(uri: URI::Generic, source: T.nilable(String), block: T.nilable(T.proc.params(index: Index).void)).void
|
700
|
+
end
|
701
|
+
def handle_change(uri, source = nil, &block)
|
702
|
+
key = uri.to_s
|
703
|
+
original_entries = @uris_to_entries[key]
|
613
704
|
|
705
|
+
if block
|
706
|
+
block.call(self)
|
707
|
+
else
|
708
|
+
delete(uri)
|
709
|
+
index_single(uri, T.must(source))
|
710
|
+
end
|
711
|
+
|
712
|
+
updated_entries = @uris_to_entries[key]
|
614
713
|
return unless original_entries && updated_entries
|
615
714
|
|
616
715
|
# A change in one ancestor may impact several different others, which could be including that ancestor through
|
@@ -661,7 +760,7 @@ module RubyIndexer
|
|
661
760
|
|
662
761
|
singleton = Entry::SingletonClass.new(
|
663
762
|
[full_singleton_name],
|
664
|
-
attached_ancestor.
|
763
|
+
attached_ancestor.uri,
|
665
764
|
attached_ancestor.location,
|
666
765
|
attached_ancestor.name_location,
|
667
766
|
nil,
|
@@ -675,12 +774,12 @@ module RubyIndexer
|
|
675
774
|
|
676
775
|
sig do
|
677
776
|
type_parameters(:T).params(
|
678
|
-
|
777
|
+
uri: String,
|
679
778
|
type: T.nilable(T::Class[T.all(T.type_parameter(:T), Entry)]),
|
680
779
|
).returns(T.nilable(T.any(T::Array[Entry], T::Array[T.type_parameter(:T)])))
|
681
780
|
end
|
682
|
-
def entries_for(
|
683
|
-
entries = @
|
781
|
+
def entries_for(uri, type = nil)
|
782
|
+
entries = @uris_to_entries[uri.to_s]
|
684
783
|
return entries unless type
|
685
784
|
|
686
785
|
entries&.grep(type)
|
@@ -688,6 +787,20 @@ module RubyIndexer
|
|
688
787
|
|
689
788
|
private
|
690
789
|
|
790
|
+
# Always returns the linearized ancestors for the attached class, regardless of whether `name` refers to a singleton
|
791
|
+
# or attached namespace
|
792
|
+
sig { params(name: String).returns(T::Array[String]) }
|
793
|
+
def linearized_attached_ancestors(name)
|
794
|
+
name_parts = name.split("::")
|
795
|
+
|
796
|
+
if name_parts.last&.start_with?("<Class:")
|
797
|
+
attached_name = T.must(name_parts[0..-2]).join("::")
|
798
|
+
linearized_ancestors_of(attached_name)
|
799
|
+
else
|
800
|
+
linearized_ancestors_of(name)
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
691
804
|
# Runs the registered included hooks
|
692
805
|
sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
|
693
806
|
def run_included_hooks(fully_qualified_name, nesting)
|
@@ -44,5 +44,17 @@ module RubyIndexer
|
|
44
44
|
@start_column = start_column
|
45
45
|
@end_column = end_column
|
46
46
|
end
|
47
|
+
|
48
|
+
sig do
|
49
|
+
params(
|
50
|
+
other: T.any(Location, Prism::Location),
|
51
|
+
).returns(T::Boolean)
|
52
|
+
end
|
53
|
+
def ==(other)
|
54
|
+
start_line == other.start_line &&
|
55
|
+
end_line == other.end_line &&
|
56
|
+
start_column == other.start_column &&
|
57
|
+
end_column == other.end_column
|
58
|
+
end
|
47
59
|
end
|
48
60
|
end
|