ruby-lsp 0.16.6 → 0.17.0
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 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +21 -4
- data/exe/ruby-lsp-check +1 -3
- data/exe/ruby-lsp-doctor +1 -4
- data/lib/core_ext/uri.rb +3 -0
- data/lib/ruby_indexer/lib/ruby_indexer/{collector.rb → declaration_listener.rb} +258 -140
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +187 -12
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +106 -10
- data/lib/ruby_indexer/test/configuration_test.rb +4 -5
- data/lib/ruby_indexer/test/constant_test.rb +11 -8
- data/lib/ruby_indexer/test/index_test.rb +528 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
- data/lib/ruby_indexer/test/method_test.rb +93 -0
- data/lib/ruby_indexer/test/test_case.rb +3 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +3 -3
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +11 -0
- data/lib/ruby_lsp/listeners/completion.rb +144 -51
- data/lib/ruby_lsp/listeners/definition.rb +77 -12
- data/lib/ruby_lsp/listeners/document_highlight.rb +1 -1
- data/lib/ruby_lsp/listeners/document_link.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +60 -6
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +59 -3
- data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
- data/lib/ruby_lsp/node_context.rb +28 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
- data/lib/ruby_lsp/requests/code_actions.rb +16 -15
- data/lib/ruby_lsp/requests/completion.rb +22 -13
- data/lib/ruby_lsp/requests/completion_resolve.rb +26 -10
- data/lib/ruby_lsp/requests/definition.rb +21 -5
- data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
- data/lib/ruby_lsp/requests/hover.rb +5 -6
- data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +20 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -1
- data/lib/ruby_lsp/server.rb +10 -4
- metadata +10 -8
@@ -3,6 +3,14 @@
|
|
3
3
|
|
4
4
|
module RubyIndexer
|
5
5
|
class Entry
|
6
|
+
class Visibility < T::Enum
|
7
|
+
enums do
|
8
|
+
PUBLIC = new(:public)
|
9
|
+
PROTECTED = new(:protected)
|
10
|
+
PRIVATE = new(:private)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
extend T::Sig
|
7
15
|
|
8
16
|
sig { returns(String) }
|
@@ -17,7 +25,7 @@ module RubyIndexer
|
|
17
25
|
sig { returns(T::Array[String]) }
|
18
26
|
attr_reader :comments
|
19
27
|
|
20
|
-
sig { returns(
|
28
|
+
sig { returns(Visibility) }
|
21
29
|
attr_accessor :visibility
|
22
30
|
|
23
31
|
sig do
|
@@ -32,7 +40,7 @@ module RubyIndexer
|
|
32
40
|
@name = name
|
33
41
|
@file_path = file_path
|
34
42
|
@comments = comments
|
35
|
-
@visibility = T.let(
|
43
|
+
@visibility = T.let(Visibility::PUBLIC, Visibility)
|
36
44
|
|
37
45
|
@location = T.let(
|
38
46
|
if location.is_a?(Prism::Location)
|
@@ -49,11 +57,35 @@ module RubyIndexer
|
|
49
57
|
)
|
50
58
|
end
|
51
59
|
|
60
|
+
sig { returns(T::Boolean) }
|
61
|
+
def private?
|
62
|
+
visibility == Visibility::PRIVATE
|
63
|
+
end
|
64
|
+
|
52
65
|
sig { returns(String) }
|
53
66
|
def file_name
|
54
67
|
File.basename(@file_path)
|
55
68
|
end
|
56
69
|
|
70
|
+
class ModuleOperation
|
71
|
+
extend T::Sig
|
72
|
+
extend T::Helpers
|
73
|
+
|
74
|
+
abstract!
|
75
|
+
|
76
|
+
sig { returns(String) }
|
77
|
+
attr_reader :module_name
|
78
|
+
|
79
|
+
sig { params(module_name: String).void }
|
80
|
+
def initialize(module_name)
|
81
|
+
@module_name = module_name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Include < ModuleOperation; end
|
86
|
+
class Prepend < ModuleOperation; end
|
87
|
+
class Extend < ModuleOperation; end
|
88
|
+
|
57
89
|
class Namespace < Entry
|
58
90
|
extend T::Sig
|
59
91
|
extend T::Helpers
|
@@ -61,13 +93,40 @@ module RubyIndexer
|
|
61
93
|
abstract!
|
62
94
|
|
63
95
|
sig { returns(T::Array[String]) }
|
64
|
-
|
65
|
-
|
96
|
+
attr_reader :nesting
|
97
|
+
|
98
|
+
sig do
|
99
|
+
params(
|
100
|
+
nesting: T::Array[String],
|
101
|
+
file_path: String,
|
102
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
103
|
+
comments: T::Array[String],
|
104
|
+
).void
|
105
|
+
end
|
106
|
+
def initialize(nesting, file_path, location, comments)
|
107
|
+
@name = T.let(nesting.join("::"), String)
|
108
|
+
# The original nesting where this namespace was discovered
|
109
|
+
@nesting = nesting
|
110
|
+
|
111
|
+
super(@name, file_path, location, comments)
|
66
112
|
end
|
67
113
|
|
68
114
|
sig { returns(T::Array[String]) }
|
69
|
-
def
|
70
|
-
|
115
|
+
def mixin_operation_module_names
|
116
|
+
mixin_operations.map(&:module_name)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Stores all explicit prepend, include and extend operations in the exact order they were discovered in the source
|
120
|
+
# code. Maintaining the order is essential to linearize ancestors the right way when a module is both included
|
121
|
+
# and prepended
|
122
|
+
sig { returns(T::Array[ModuleOperation]) }
|
123
|
+
def mixin_operations
|
124
|
+
@mixin_operations ||= T.let([], T.nilable(T::Array[ModuleOperation]))
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { returns(Integer) }
|
128
|
+
def ancestor_hash
|
129
|
+
mixin_operation_module_names.hash
|
71
130
|
end
|
72
131
|
end
|
73
132
|
|
@@ -84,17 +143,23 @@ module RubyIndexer
|
|
84
143
|
|
85
144
|
sig do
|
86
145
|
params(
|
87
|
-
|
146
|
+
nesting: T::Array[String],
|
88
147
|
file_path: String,
|
89
148
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
90
149
|
comments: T::Array[String],
|
91
150
|
parent_class: T.nilable(String),
|
92
151
|
).void
|
93
152
|
end
|
94
|
-
def initialize(
|
95
|
-
super(
|
153
|
+
def initialize(nesting, file_path, location, comments, parent_class)
|
154
|
+
super(nesting, file_path, location, comments)
|
155
|
+
|
96
156
|
@parent_class = T.let(parent_class, T.nilable(String))
|
97
157
|
end
|
158
|
+
|
159
|
+
sig { override.returns(Integer) }
|
160
|
+
def ancestor_hash
|
161
|
+
[mixin_operation_module_names, @parent_class].hash
|
162
|
+
end
|
98
163
|
end
|
99
164
|
|
100
165
|
class Constant < Entry
|
@@ -188,11 +253,13 @@ module RubyIndexer
|
|
188
253
|
file_path: String,
|
189
254
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
190
255
|
comments: T::Array[String],
|
256
|
+
visibility: Visibility,
|
191
257
|
owner: T.nilable(Entry::Namespace),
|
192
258
|
).void
|
193
259
|
end
|
194
|
-
def initialize(name, file_path, location, comments, owner)
|
260
|
+
def initialize(name, file_path, location, comments, visibility, owner) # rubocop:disable Metrics/ParameterLists
|
195
261
|
super(name, file_path, location, comments)
|
262
|
+
@visibility = visibility
|
196
263
|
@owner = owner
|
197
264
|
end
|
198
265
|
|
@@ -227,11 +294,12 @@ module RubyIndexer
|
|
227
294
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
228
295
|
comments: T::Array[String],
|
229
296
|
parameters_node: T.nilable(Prism::ParametersNode),
|
297
|
+
visibility: Visibility,
|
230
298
|
owner: T.nilable(Entry::Namespace),
|
231
299
|
).void
|
232
300
|
end
|
233
|
-
def initialize(name, file_path, location, comments, parameters_node, owner) # rubocop:disable Metrics/ParameterLists
|
234
|
-
super(name, file_path, location, comments, owner)
|
301
|
+
def initialize(name, file_path, location, comments, parameters_node, visibility, owner) # rubocop:disable Metrics/ParameterLists
|
302
|
+
super(name, file_path, location, comments, visibility, owner)
|
235
303
|
|
236
304
|
@parameters = T.let(list_params(parameters_node), T::Array[Parameter])
|
237
305
|
end
|
@@ -377,8 +445,29 @@ module RubyIndexer
|
|
377
445
|
def initialize(target, unresolved_alias)
|
378
446
|
super(unresolved_alias.name, unresolved_alias.file_path, unresolved_alias.location, unresolved_alias.comments)
|
379
447
|
|
448
|
+
@visibility = unresolved_alias.visibility
|
380
449
|
@target = target
|
381
450
|
end
|
382
451
|
end
|
452
|
+
|
453
|
+
# Represents an instance variable e.g.: @a = 1
|
454
|
+
class InstanceVariable < Entry
|
455
|
+
sig { returns(T.nilable(Entry::Namespace)) }
|
456
|
+
attr_reader :owner
|
457
|
+
|
458
|
+
sig do
|
459
|
+
params(
|
460
|
+
name: String,
|
461
|
+
file_path: String,
|
462
|
+
location: T.any(Prism::Location, RubyIndexer::Location),
|
463
|
+
comments: T::Array[String],
|
464
|
+
owner: T.nilable(Entry::Namespace),
|
465
|
+
).void
|
466
|
+
end
|
467
|
+
def initialize(name, file_path, location, comments, owner)
|
468
|
+
super(name, file_path, location, comments)
|
469
|
+
@owner = owner
|
470
|
+
end
|
471
|
+
end
|
383
472
|
end
|
384
473
|
end
|
@@ -6,6 +6,7 @@ module RubyIndexer
|
|
6
6
|
extend T::Sig
|
7
7
|
|
8
8
|
class UnresolvableAliasError < StandardError; end
|
9
|
+
class NonExistingNamespaceError < StandardError; end
|
9
10
|
|
10
11
|
# The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
|
11
12
|
ENTRY_SIMILARITY_THRESHOLD = 0.7
|
@@ -31,6 +32,9 @@ module RubyIndexer
|
|
31
32
|
|
32
33
|
# Holds all require paths for every indexed item so that we can provide autocomplete for requires
|
33
34
|
@require_paths_tree = T.let(PrefixTree[IndexablePath].new, PrefixTree[IndexablePath])
|
35
|
+
|
36
|
+
# Holds the linearized ancestors list for every namespace
|
37
|
+
@ancestors = T.let({}, T::Hash[String, T::Array[String]])
|
34
38
|
end
|
35
39
|
|
36
40
|
sig { params(indexable: IndexablePath).void }
|
@@ -126,6 +130,16 @@ module RubyIndexer
|
|
126
130
|
results.flat_map(&:first)
|
127
131
|
end
|
128
132
|
|
133
|
+
sig { params(name: String, receiver_name: String).returns(T::Array[Entry]) }
|
134
|
+
def method_completion_candidates(name, receiver_name)
|
135
|
+
ancestors = linearized_ancestors_of(receiver_name)
|
136
|
+
candidates = prefix_search(name).flatten
|
137
|
+
candidates.select! do |entry|
|
138
|
+
entry.is_a?(RubyIndexer::Entry::Member) && ancestors.any?(entry.owner&.name)
|
139
|
+
end
|
140
|
+
candidates
|
141
|
+
end
|
142
|
+
|
129
143
|
# Try to find the entry based on the nesting from the most specific to the least specific. For example, if we have
|
130
144
|
# the nesting as ["Foo", "Bar"] and the name as "Baz", we will try to find it in this order:
|
131
145
|
# 1. Foo::Bar::Baz
|
@@ -185,9 +199,11 @@ module RubyIndexer
|
|
185
199
|
sig { params(indexable_path: IndexablePath, source: T.nilable(String)).void }
|
186
200
|
def index_single(indexable_path, source = nil)
|
187
201
|
content = source || File.read(indexable_path.full_path)
|
202
|
+
dispatcher = Prism::Dispatcher.new
|
203
|
+
|
188
204
|
result = Prism.parse(content)
|
189
|
-
|
190
|
-
|
205
|
+
DeclarationListener.new(self, dispatcher, result, indexable_path.full_path)
|
206
|
+
dispatcher.dispatch(result.value)
|
191
207
|
|
192
208
|
require_path = indexable_path.require_path
|
193
209
|
@require_paths_tree.insert(require_path, indexable_path) if require_path
|
@@ -243,16 +259,175 @@ module RubyIndexer
|
|
243
259
|
sig { params(method_name: String, receiver_name: String).returns(T.nilable(T::Array[Entry::Member])) }
|
244
260
|
def resolve_method(method_name, receiver_name)
|
245
261
|
method_entries = self[method_name]
|
246
|
-
|
247
|
-
return unless
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
262
|
+
ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
|
263
|
+
return unless method_entries
|
264
|
+
|
265
|
+
ancestors.each do |ancestor|
|
266
|
+
found = method_entries.select do |entry|
|
267
|
+
next unless entry.is_a?(Entry::Member)
|
268
|
+
|
269
|
+
entry.owner&.name == ancestor
|
270
|
+
end
|
271
|
+
|
272
|
+
return T.cast(found, T::Array[Entry::Member]) if found.any?
|
273
|
+
end
|
274
|
+
|
275
|
+
nil
|
276
|
+
rescue NonExistingNamespaceError
|
277
|
+
nil
|
278
|
+
end
|
279
|
+
|
280
|
+
# Linearizes the ancestors for a given name, returning the order of namespaces in which Ruby will search for method
|
281
|
+
# or constant declarations.
|
282
|
+
#
|
283
|
+
# When we add an ancestor in Ruby, that namespace might have ancestors of its own. Therefore, we need to linearize
|
284
|
+
# everything recursively to ensure that we are placing ancestors in the right order. For example, if you include a
|
285
|
+
# module that prepends another module, then the prepend module appears before the included module.
|
286
|
+
#
|
287
|
+
# The order of ancestors is [linearized_prepends, self, linearized_includes, linearized_superclass]
|
288
|
+
sig { params(fully_qualified_name: String).returns(T::Array[String]) }
|
289
|
+
def linearized_ancestors_of(fully_qualified_name)
|
290
|
+
# If we already computed the ancestors for this namespace, return it straight away
|
291
|
+
cached_ancestors = @ancestors[fully_qualified_name]
|
292
|
+
return cached_ancestors if cached_ancestors
|
293
|
+
|
294
|
+
ancestors = [fully_qualified_name]
|
295
|
+
|
296
|
+
# Cache the linearized ancestors array eagerly. This is important because we might have circular dependencies and
|
297
|
+
# this will prevent us from falling into an infinite recursion loop. Because we mutate the ancestors array later,
|
298
|
+
# the cache will reflect the final result
|
299
|
+
@ancestors[fully_qualified_name] = ancestors
|
300
|
+
|
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
|
+
# If none of the entries for `name` are namespaces, raise
|
306
|
+
namespaces = entries.filter_map do |entry|
|
307
|
+
case entry
|
308
|
+
when Entry::Namespace
|
309
|
+
entry
|
310
|
+
when Entry::Alias
|
311
|
+
self[entry.target]&.grep(Entry::Namespace)
|
312
|
+
end
|
313
|
+
end.flatten
|
314
|
+
|
315
|
+
raise NonExistingNamespaceError,
|
316
|
+
"None of the entries for #{fully_qualified_name} are modules or classes" if namespaces.empty?
|
317
|
+
|
318
|
+
mixin_operations = namespaces.flat_map(&:mixin_operations)
|
319
|
+
main_namespace_index = 0
|
320
|
+
|
321
|
+
# The original nesting where we discovered this namespace, so that we resolve the correct names of the
|
322
|
+
# included/prepended/extended modules and parent classes
|
323
|
+
nesting = T.must(namespaces.first).nesting
|
324
|
+
|
325
|
+
mixin_operations.each do |operation|
|
326
|
+
resolved_module = resolve(operation.module_name, nesting)
|
327
|
+
next unless resolved_module
|
328
|
+
|
329
|
+
module_fully_qualified_name = T.must(resolved_module.first).name
|
330
|
+
|
331
|
+
case operation
|
332
|
+
when Entry::Prepend
|
333
|
+
# When a module is prepended, Ruby checks if it hasn't been prepended already to prevent adding it in front of
|
334
|
+
# the actual namespace twice. However, it does not check if it has been included because you are allowed to
|
335
|
+
# prepend the same module after it has already been included
|
336
|
+
linearized_prepends = linearized_ancestors_of(module_fully_qualified_name)
|
337
|
+
|
338
|
+
# When there are duplicate prepended modules, we have to insert the new prepends after the existing ones. For
|
339
|
+
# example, if the current ancestors are `["A", "Foo"]` and we try to prepend `["A", "B"]`, then `"B"` has to
|
340
|
+
# be inserted after `"A`
|
341
|
+
uniq_prepends = linearized_prepends - T.must(ancestors[0...main_namespace_index])
|
342
|
+
insert_position = linearized_prepends.length - uniq_prepends.length
|
343
|
+
|
344
|
+
T.unsafe(ancestors).insert(
|
345
|
+
insert_position,
|
346
|
+
*(linearized_prepends - T.must(ancestors[0...main_namespace_index])),
|
347
|
+
)
|
348
|
+
|
349
|
+
main_namespace_index += linearized_prepends.length
|
350
|
+
when Entry::Include
|
351
|
+
# When including a module, Ruby will always prevent duplicate entries in case the module has already been
|
352
|
+
# prepended or included
|
353
|
+
linearized_includes = linearized_ancestors_of(module_fully_qualified_name)
|
354
|
+
T.unsafe(ancestors).insert(main_namespace_index + 1, *(linearized_includes - ancestors))
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Find the first class entry that has a parent class. Notice that if the developer makes a mistake and inherits
|
359
|
+
# from two diffent classes in different files, we simply ignore it
|
360
|
+
superclass = T.cast(namespaces.find { |n| n.is_a?(Entry::Class) && n.parent_class }, T.nilable(Entry::Class))
|
361
|
+
|
362
|
+
if superclass
|
363
|
+
# If the user makes a mistake and creates a class that inherits from itself, this method would throw a stack
|
364
|
+
# error. We need to ensure that this isn't the case
|
365
|
+
parent_class = T.must(superclass.parent_class)
|
366
|
+
|
367
|
+
resolved_parent_class = resolve(parent_class, nesting)
|
368
|
+
parent_class_name = resolved_parent_class&.first&.name
|
369
|
+
|
370
|
+
if parent_class_name && fully_qualified_name != parent_class_name
|
371
|
+
ancestors.concat(linearized_ancestors_of(parent_class_name))
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
ancestors
|
376
|
+
end
|
377
|
+
|
378
|
+
# Resolves an instance variable name for a given owner name. This method will linearize the ancestors of the owner
|
379
|
+
# and find inherited instance variables as well
|
380
|
+
sig { params(variable_name: String, owner_name: String).returns(T.nilable(T::Array[Entry::InstanceVariable])) }
|
381
|
+
def resolve_instance_variable(variable_name, owner_name)
|
382
|
+
entries = T.cast(self[variable_name], T.nilable(T::Array[Entry::InstanceVariable]))
|
383
|
+
return unless entries
|
384
|
+
|
385
|
+
ancestors = linearized_ancestors_of(owner_name)
|
386
|
+
return if ancestors.empty?
|
387
|
+
|
388
|
+
entries.select { |e| ancestors.include?(e.owner&.name) }
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns a list of possible candidates for completion of instance variables for a given owner name. The name must
|
392
|
+
# include the `@` prefix
|
393
|
+
sig { params(name: String, owner_name: String).returns(T::Array[Entry::InstanceVariable]) }
|
394
|
+
def instance_variable_completion_candidates(name, owner_name)
|
395
|
+
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::InstanceVariable])
|
396
|
+
ancestors = linearized_ancestors_of(owner_name)
|
397
|
+
|
398
|
+
variables = entries.uniq(&:name)
|
399
|
+
variables.select! { |e| ancestors.any?(e.owner&.name) }
|
400
|
+
variables
|
401
|
+
end
|
402
|
+
|
403
|
+
# Synchronizes a change made to the given indexable path. This method will ensure that new declarations are indexed,
|
404
|
+
# removed declarations removed and that the ancestor linearization cache is cleared if necessary
|
405
|
+
sig { params(indexable: IndexablePath).void }
|
406
|
+
def handle_change(indexable)
|
407
|
+
original_entries = @files_to_entries[indexable.full_path]
|
408
|
+
|
409
|
+
delete(indexable)
|
410
|
+
index_single(indexable)
|
411
|
+
|
412
|
+
updated_entries = @files_to_entries[indexable.full_path]
|
413
|
+
|
414
|
+
return unless original_entries && updated_entries
|
415
|
+
|
416
|
+
# A change in one ancestor may impact several different others, which could be including that ancestor through
|
417
|
+
# indirect means like including a module that than includes the ancestor. Trying to figure out exactly which
|
418
|
+
# ancestors need to be deleted is too expensive. Therefore, if any of the namespace entries has a change to their
|
419
|
+
# ancestor hash, we clear all ancestors and start linearizing lazily again from scratch
|
420
|
+
original_map = T.cast(
|
421
|
+
original_entries.select { |e| e.is_a?(Entry::Namespace) },
|
422
|
+
T::Array[Entry::Namespace],
|
423
|
+
).to_h { |e| [e.name, e.ancestor_hash] }
|
424
|
+
|
425
|
+
updated_map = T.cast(
|
426
|
+
updated_entries.select { |e| e.is_a?(Entry::Namespace) },
|
427
|
+
T::Array[Entry::Namespace],
|
428
|
+
).to_h { |e| [e.name, e.ancestor_hash] }
|
429
|
+
|
430
|
+
@ancestors.clear if original_map.any? { |name, hash| updated_map[name] != hash }
|
256
431
|
end
|
257
432
|
|
258
433
|
private
|
@@ -5,7 +5,7 @@ require "yaml"
|
|
5
5
|
require "did_you_mean"
|
6
6
|
|
7
7
|
require "ruby_indexer/lib/ruby_indexer/indexable_path"
|
8
|
-
require "ruby_indexer/lib/ruby_indexer/
|
8
|
+
require "ruby_indexer/lib/ruby_indexer/declaration_listener"
|
9
9
|
require "ruby_indexer/lib/ruby_indexer/index"
|
10
10
|
require "ruby_indexer/lib/ruby_indexer/entry"
|
11
11
|
require "ruby_indexer/lib/ruby_indexer/configuration"
|
@@ -14,6 +14,15 @@ module RubyIndexer
|
|
14
14
|
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
15
15
|
end
|
16
16
|
|
17
|
+
def test_conditional_class
|
18
|
+
index(<<~RUBY)
|
19
|
+
class Foo
|
20
|
+
end if condition
|
21
|
+
RUBY
|
22
|
+
|
23
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
24
|
+
end
|
25
|
+
|
17
26
|
def test_class_with_statements
|
18
27
|
index(<<~RUBY)
|
19
28
|
class Foo
|
@@ -60,7 +69,23 @@ module RubyIndexer
|
|
60
69
|
end
|
61
70
|
RUBY
|
62
71
|
|
72
|
+
assert_entry("self::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_dynamically_namespaced_class_doesnt_affect_other_classes
|
76
|
+
index(<<~RUBY)
|
77
|
+
class Foo
|
78
|
+
class self::Bar
|
79
|
+
end
|
80
|
+
|
81
|
+
class Bar
|
82
|
+
end
|
83
|
+
end
|
84
|
+
RUBY
|
85
|
+
|
63
86
|
refute_entry("self::Bar")
|
87
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:6-3")
|
88
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:4-2:5-5")
|
64
89
|
end
|
65
90
|
|
66
91
|
def test_empty_statements_module
|
@@ -72,6 +97,15 @@ module RubyIndexer
|
|
72
97
|
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
73
98
|
end
|
74
99
|
|
100
|
+
def test_conditional_module
|
101
|
+
index(<<~RUBY)
|
102
|
+
module Foo
|
103
|
+
end if condition
|
104
|
+
RUBY
|
105
|
+
|
106
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
107
|
+
end
|
108
|
+
|
75
109
|
def test_module_with_statements
|
76
110
|
index(<<~RUBY)
|
77
111
|
module Foo
|
@@ -106,7 +140,23 @@ module RubyIndexer
|
|
106
140
|
end
|
107
141
|
RUBY
|
108
142
|
|
109
|
-
|
143
|
+
assert_entry("self::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_dynamically_namespaced_module_doesnt_affect_other_modules
|
147
|
+
index(<<~RUBY)
|
148
|
+
module Foo
|
149
|
+
class self::Bar
|
150
|
+
end
|
151
|
+
|
152
|
+
module Bar
|
153
|
+
end
|
154
|
+
end
|
155
|
+
RUBY
|
156
|
+
|
157
|
+
assert_entry("Foo::self::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
158
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:6-3")
|
159
|
+
assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:4-2:5-5")
|
110
160
|
end
|
111
161
|
|
112
162
|
def test_nested_modules_and_classes
|
@@ -240,13 +290,13 @@ module RubyIndexer
|
|
240
290
|
RUBY
|
241
291
|
|
242
292
|
b_const = @index["A::B"].first
|
243
|
-
assert_equal(
|
293
|
+
assert_equal(Entry::Visibility::PRIVATE, b_const.visibility)
|
244
294
|
|
245
295
|
c_const = @index["A::C"].first
|
246
|
-
assert_equal(
|
296
|
+
assert_equal(Entry::Visibility::PRIVATE, c_const.visibility)
|
247
297
|
|
248
298
|
d_const = @index["A::D"].first
|
249
|
-
assert_equal(
|
299
|
+
assert_equal(Entry::Visibility::PUBLIC, d_const.visibility)
|
250
300
|
end
|
251
301
|
|
252
302
|
def test_keeping_track_of_super_classes
|
@@ -319,13 +369,13 @@ module RubyIndexer
|
|
319
369
|
RUBY
|
320
370
|
|
321
371
|
foo = T.must(@index["Foo"][0])
|
322
|
-
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.
|
372
|
+
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.mixin_operation_module_names)
|
323
373
|
|
324
374
|
qux = T.must(@index["Foo::Qux"][0])
|
325
|
-
assert_equal(["Corge", "Corge", "Baz"], qux.
|
375
|
+
assert_equal(["Corge", "Corge", "Baz"], qux.mixin_operation_module_names)
|
326
376
|
|
327
377
|
constant_path_references = T.must(@index["ConstantPathReferences"][0])
|
328
|
-
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.
|
378
|
+
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.mixin_operation_module_names)
|
329
379
|
end
|
330
380
|
|
331
381
|
def test_keeping_track_of_prepended_modules
|
@@ -365,13 +415,59 @@ module RubyIndexer
|
|
365
415
|
RUBY
|
366
416
|
|
367
417
|
foo = T.must(@index["Foo"][0])
|
368
|
-
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.
|
418
|
+
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.mixin_operation_module_names)
|
419
|
+
|
420
|
+
qux = T.must(@index["Foo::Qux"][0])
|
421
|
+
assert_equal(["Corge", "Corge", "Baz"], qux.mixin_operation_module_names)
|
422
|
+
|
423
|
+
constant_path_references = T.must(@index["ConstantPathReferences"][0])
|
424
|
+
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.mixin_operation_module_names)
|
425
|
+
end
|
426
|
+
|
427
|
+
def test_keeping_track_of_extended_modules
|
428
|
+
index(<<~RUBY)
|
429
|
+
class Foo
|
430
|
+
# valid syntaxes that we can index
|
431
|
+
extend A1
|
432
|
+
self.extend A2
|
433
|
+
extend A3, A4
|
434
|
+
self.extend A5, A6
|
435
|
+
|
436
|
+
# valid syntaxes that we cannot index because of their dynamic nature
|
437
|
+
extend some_variable_or_method_call
|
438
|
+
self.extend some_variable_or_method_call
|
439
|
+
|
440
|
+
def something
|
441
|
+
extend A7 # We should not index this because of this dynamic nature
|
442
|
+
end
|
443
|
+
|
444
|
+
# Valid inner class syntax definition with its own modules prepended
|
445
|
+
class Qux
|
446
|
+
extend Corge
|
447
|
+
self.extend Corge
|
448
|
+
extend Baz
|
449
|
+
|
450
|
+
extend some_variable_or_method_call
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
class ConstantPathReferences
|
455
|
+
extend Foo::Bar
|
456
|
+
self.extend Foo::Bar2
|
457
|
+
|
458
|
+
extend dynamic::Bar
|
459
|
+
extend Foo::
|
460
|
+
end
|
461
|
+
RUBY
|
462
|
+
|
463
|
+
foo = T.must(@index["Foo"][0])
|
464
|
+
assert_equal(["A1", "A2", "A3", "A4", "A5", "A6"], foo.mixin_operation_module_names)
|
369
465
|
|
370
466
|
qux = T.must(@index["Foo::Qux"][0])
|
371
|
-
assert_equal(["Corge", "Corge", "Baz"], qux.
|
467
|
+
assert_equal(["Corge", "Corge", "Baz"], qux.mixin_operation_module_names)
|
372
468
|
|
373
469
|
constant_path_references = T.must(@index["ConstantPathReferences"][0])
|
374
|
-
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.
|
470
|
+
assert_equal(["Foo::Bar", "Foo::Bar2"], constant_path_references.mixin_operation_module_names)
|
375
471
|
end
|
376
472
|
end
|
377
473
|
end
|
@@ -68,12 +68,11 @@ module RubyIndexer
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def test_indexables_avoids_duplicates_if_bundle_path_is_inside_project
|
71
|
-
Bundler.settings.
|
72
|
-
|
71
|
+
Bundler.settings.temporary(path: "vendor/bundle") do
|
72
|
+
config = Configuration.new
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
Bundler.settings.set_global("path", nil)
|
74
|
+
assert_includes(config.instance_variable_get(:@excluded_patterns), "#{Dir.pwd}/vendor/bundle/**/*.rb")
|
75
|
+
end
|
77
76
|
end
|
78
77
|
|
79
78
|
def test_indexables_does_not_include_gems_own_installed_files
|