ruby-lsp 0.16.6 → 0.17.0
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 +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
|