ruby-lsp 0.20.1 → 0.22.1
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 +19 -4
- data/exe/ruby-lsp-launcher +124 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +233 -59
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +34 -16
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +15 -15
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +4 -4
- data/lib/ruby_indexer/test/configuration_test.rb +10 -0
- data/lib/ruby_indexer/test/constant_test.rb +8 -8
- data/lib/ruby_indexer/test/enhancements_test.rb +169 -41
- data/lib/ruby_indexer/test/index_test.rb +41 -2
- data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
- data/lib/ruby_indexer/test/method_test.rb +139 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +9 -2
- data/lib/ruby_lsp/base_server.rb +14 -5
- data/lib/ruby_lsp/client_capabilities.rb +67 -0
- data/lib/ruby_lsp/document.rb +1 -1
- data/lib/ruby_lsp/global_state.rb +33 -20
- data/lib/ruby_lsp/internal.rb +3 -0
- data/lib/ruby_lsp/listeners/completion.rb +62 -0
- data/lib/ruby_lsp/listeners/definition.rb +48 -13
- data/lib/ruby_lsp/listeners/document_highlight.rb +91 -4
- data/lib/ruby_lsp/listeners/document_symbol.rb +37 -4
- data/lib/ruby_lsp/listeners/hover.rb +52 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/completion.rb +7 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +28 -11
- data/lib/ruby_lsp/requests/document_highlight.rb +7 -1
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
- data/lib/ruby_lsp/requests/hover.rb +26 -6
- data/lib/ruby_lsp/requests/rename.rb +1 -1
- data/lib/ruby_lsp/requests/request.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +12 -1
- data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
- data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
- data/lib/ruby_lsp/server.rb +85 -55
- data/lib/ruby_lsp/setup_bundler.rb +154 -47
- data/lib/ruby_lsp/store.rb +0 -4
- data/lib/ruby_lsp/utils.rb +63 -0
- metadata +8 -3
@@ -646,7 +646,7 @@ module RubyIndexer
|
|
646
646
|
(positionals.empty? && forwarding_arguments.any?) ||
|
647
647
|
(
|
648
648
|
# Check if positional arguments match. This includes required, optional, rest arguments. We also need to
|
649
|
-
# verify if there's a trailing
|
649
|
+
# verify if there's a trailing forwarding argument, like `def foo(a, ...); end`
|
650
650
|
positional_arguments_match?(positionals, forwarding_arguments, keyword_args, min_pos, max_pos) &&
|
651
651
|
# If the positional arguments match, we move on to checking keyword, optional keyword and keyword rest
|
652
652
|
# arguments. If there's a forward argument, then it will always match. If the method accepts a keyword rest
|
@@ -7,6 +7,7 @@ module RubyIndexer
|
|
7
7
|
|
8
8
|
class UnresolvableAliasError < StandardError; end
|
9
9
|
class NonExistingNamespaceError < StandardError; end
|
10
|
+
class IndexNotEmptyError < StandardError; end
|
10
11
|
|
11
12
|
# The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
|
12
13
|
ENTRY_SIMILARITY_THRESHOLD = 0.7
|
@@ -39,9 +40,6 @@ module RubyIndexer
|
|
39
40
|
# Holds the linearized ancestors list for every namespace
|
40
41
|
@ancestors = T.let({}, T::Hash[String, T::Array[String]])
|
41
42
|
|
42
|
-
# List of classes that are enhancing the index
|
43
|
-
@enhancements = T.let([], T::Array[Enhancement])
|
44
|
-
|
45
43
|
# Map of module name to included hooks that have to be executed when we include the given module
|
46
44
|
@included_hooks = T.let(
|
47
45
|
{},
|
@@ -51,12 +49,6 @@ module RubyIndexer
|
|
51
49
|
@configuration = T.let(RubyIndexer::Configuration.new, Configuration)
|
52
50
|
end
|
53
51
|
|
54
|
-
# Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
|
55
|
-
sig { params(enhancement: Enhancement).void }
|
56
|
-
def register_enhancement(enhancement)
|
57
|
-
@enhancements << enhancement
|
58
|
-
end
|
59
|
-
|
60
52
|
# Register an included `hook` that will be executed when `module_name` is included into any namespace
|
61
53
|
sig { params(module_name: String, hook: T.proc.params(index: Index, base: Entry::Namespace).void).void }
|
62
54
|
def register_included_hook(module_name, &hook)
|
@@ -360,6 +352,15 @@ module RubyIndexer
|
|
360
352
|
).void
|
361
353
|
end
|
362
354
|
def index_all(indexable_paths: @configuration.indexables, &block)
|
355
|
+
# When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
|
356
|
+
# existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
|
357
|
+
# behavior and can take appropriate action.
|
358
|
+
# binding.break
|
359
|
+
if @entries.any?
|
360
|
+
raise IndexNotEmptyError,
|
361
|
+
"The index is not empty. To prevent invalid entries, `index_all` can only be called once."
|
362
|
+
end
|
363
|
+
|
363
364
|
RBSIndexer.new(self).index_ruby_core
|
364
365
|
# Calculate how many paths are worth 1% of progress
|
365
366
|
progress_step = (indexable_paths.length / 100.0).ceil
|
@@ -386,7 +387,6 @@ module RubyIndexer
|
|
386
387
|
result,
|
387
388
|
indexable_path.full_path,
|
388
389
|
collect_comments: collect_comments,
|
389
|
-
enhancements: @enhancements,
|
390
390
|
)
|
391
391
|
dispatcher.dispatch(result.value)
|
392
392
|
|
@@ -784,7 +784,7 @@ module RubyIndexer
|
|
784
784
|
singleton_levels
|
785
785
|
)
|
786
786
|
# Find the first class entry that has a parent class. Notice that if the developer makes a mistake and inherits
|
787
|
-
# from two
|
787
|
+
# from two different classes in different files, we simply ignore it
|
788
788
|
superclass = T.cast(
|
789
789
|
if singleton_levels > 0
|
790
790
|
self[attached_class_name]&.find { |n| n.is_a?(Entry::Class) && n.parent_class }
|
@@ -974,10 +974,10 @@ module RubyIndexer
|
|
974
974
|
[]
|
975
975
|
end
|
976
976
|
|
977
|
-
# Removes
|
978
|
-
# of the ["A", "B"] nesting, then we should not concatenate the nesting with the name or else we'll end up
|
979
|
-
# `A::B::A::B::Foo`. This method will remove any redundant parts from the final name based on the reference and
|
980
|
-
# nesting
|
977
|
+
# Removes redundancy from a constant reference's full name. For example, if we find a reference to `A::B::Foo`
|
978
|
+
# inside of the ["A", "B"] nesting, then we should not concatenate the nesting with the name or else we'll end up
|
979
|
+
# with `A::B::A::B::Foo`. This method will remove any redundant parts from the final name based on the reference and
|
980
|
+
# the nesting
|
981
981
|
sig { params(name: String, nesting: T::Array[String]).returns(String) }
|
982
982
|
def build_non_redundant_full_name(name, nesting)
|
983
983
|
# If there's no nesting, then we can just return the name as is
|
@@ -72,7 +72,7 @@ module RubyIndexer
|
|
72
72
|
assert_entry("self::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
75
|
+
def test_dynamically_namespaced_class_does_not_affect_other_classes
|
76
76
|
index(<<~RUBY)
|
77
77
|
class Foo
|
78
78
|
class self::Bar
|
@@ -143,7 +143,7 @@ module RubyIndexer
|
|
143
143
|
assert_entry("self::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
144
144
|
end
|
145
145
|
|
146
|
-
def
|
146
|
+
def test_dynamically_namespaced_module_does_not_affect_other_modules
|
147
147
|
index(<<~RUBY)
|
148
148
|
module Foo
|
149
149
|
class self::Bar
|
@@ -302,10 +302,10 @@ module RubyIndexer
|
|
302
302
|
RUBY
|
303
303
|
|
304
304
|
b_const = @index["A::B"].first
|
305
|
-
|
305
|
+
assert_predicate(b_const, :private?)
|
306
306
|
|
307
307
|
c_const = @index["A::C"].first
|
308
|
-
|
308
|
+
assert_predicate(c_const, :private?)
|
309
309
|
|
310
310
|
d_const = @index["A::D"].first
|
311
311
|
assert_equal(Entry::Visibility::PUBLIC, d_const.visibility)
|
@@ -160,5 +160,15 @@ module RubyIndexer
|
|
160
160
|
)
|
161
161
|
end
|
162
162
|
end
|
163
|
+
|
164
|
+
def test_includes_top_level_files
|
165
|
+
Dir.mktmpdir do |dir|
|
166
|
+
FileUtils.touch(File.join(dir, "find_me.rb"))
|
167
|
+
@config.workspace_path = dir
|
168
|
+
|
169
|
+
indexables = @config.indexables
|
170
|
+
assert(indexables.find { |i| File.basename(i.full_path) == "find_me.rb" })
|
171
|
+
end
|
172
|
+
end
|
163
173
|
end
|
164
174
|
end
|
@@ -130,13 +130,13 @@ module RubyIndexer
|
|
130
130
|
RUBY
|
131
131
|
|
132
132
|
b_const = @index["A::B"].first
|
133
|
-
|
133
|
+
assert_predicate(b_const, :private?)
|
134
134
|
|
135
135
|
c_const = @index["A::C"].first
|
136
|
-
|
136
|
+
assert_predicate(c_const, :private?)
|
137
137
|
|
138
138
|
d_const = @index["A::D"].first
|
139
|
-
|
139
|
+
assert_predicate(d_const, :public?)
|
140
140
|
end
|
141
141
|
|
142
142
|
def test_marking_constants_as_private_reopening_namespaces
|
@@ -163,13 +163,13 @@ module RubyIndexer
|
|
163
163
|
RUBY
|
164
164
|
|
165
165
|
a_const = @index["A::B::CONST_A"].first
|
166
|
-
|
166
|
+
assert_predicate(a_const, :private?)
|
167
167
|
|
168
168
|
b_const = @index["A::B::CONST_B"].first
|
169
|
-
|
169
|
+
assert_predicate(b_const, :private?)
|
170
170
|
|
171
171
|
c_const = @index["A::B::CONST_C"].first
|
172
|
-
|
172
|
+
assert_predicate(c_const, :private?)
|
173
173
|
end
|
174
174
|
|
175
175
|
def test_marking_constants_as_private_with_receiver
|
@@ -187,10 +187,10 @@ module RubyIndexer
|
|
187
187
|
RUBY
|
188
188
|
|
189
189
|
a_const = @index["A::B::CONST_A"].first
|
190
|
-
|
190
|
+
assert_predicate(a_const, :private?)
|
191
191
|
|
192
192
|
b_const = @index["A::B::CONST_B"].first
|
193
|
-
|
193
|
+
assert_predicate(b_const, :private?)
|
194
194
|
end
|
195
195
|
|
196
196
|
def test_indexing_constant_aliases
|
@@ -5,26 +5,28 @@ require_relative "test_case"
|
|
5
5
|
|
6
6
|
module RubyIndexer
|
7
7
|
class EnhancementTest < TestCase
|
8
|
-
def
|
9
|
-
|
10
|
-
|
8
|
+
def teardown
|
9
|
+
super
|
10
|
+
Enhancement.clear
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
+
def test_enhancing_indexing_included_hook
|
14
|
+
Class.new(Enhancement) do
|
15
|
+
def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
16
|
+
owner = @listener.current_owner
|
13
17
|
return unless owner
|
14
|
-
return unless
|
18
|
+
return unless call_node.name == :extend
|
15
19
|
|
16
|
-
arguments =
|
20
|
+
arguments = call_node.arguments&.arguments
|
17
21
|
return unless arguments
|
18
22
|
|
19
|
-
location = Location.from_prism_location(node.location, code_units_cache)
|
20
|
-
|
21
23
|
arguments.each do |node|
|
22
24
|
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
|
23
25
|
|
24
26
|
module_name = node.full_name
|
25
27
|
next unless module_name == "ActiveSupport::Concern"
|
26
28
|
|
27
|
-
|
29
|
+
@listener.register_included_hook do |index, base|
|
28
30
|
class_methods_name = "#{owner.name}::ClassMethods"
|
29
31
|
|
30
32
|
if index.indexed?(class_methods_name)
|
@@ -33,16 +35,11 @@ module RubyIndexer
|
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
|
38
|
+
@listener.add_method(
|
37
39
|
"new_method",
|
38
|
-
|
39
|
-
location,
|
40
|
-
location,
|
41
|
-
nil,
|
40
|
+
call_node.location,
|
42
41
|
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
|
43
|
-
|
44
|
-
owner,
|
45
|
-
))
|
42
|
+
)
|
46
43
|
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
47
44
|
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
48
45
|
# Do nothing
|
@@ -50,7 +47,6 @@ module RubyIndexer
|
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
53
|
-
@index.register_enhancement(enhancement_class.new)
|
54
50
|
index(<<~RUBY)
|
55
51
|
module ActiveSupport
|
56
52
|
module Concern
|
@@ -98,11 +94,9 @@ module RubyIndexer
|
|
98
94
|
end
|
99
95
|
|
100
96
|
def test_enhancing_indexing_configuration_dsl
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
def on_call_node(index, owner, node, file_path, code_units_cache)
|
105
|
-
return unless owner
|
97
|
+
Class.new(Enhancement) do
|
98
|
+
def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
99
|
+
return unless @listener.current_owner
|
106
100
|
|
107
101
|
name = node.name
|
108
102
|
return unless name == :has_many
|
@@ -113,22 +107,14 @@ module RubyIndexer
|
|
113
107
|
association_name = arguments.first
|
114
108
|
return unless association_name.is_a?(Prism::SymbolNode)
|
115
109
|
|
116
|
-
|
117
|
-
|
118
|
-
index.add(Entry::Method.new(
|
110
|
+
@listener.add_method(
|
119
111
|
T.must(association_name.value),
|
120
|
-
|
121
|
-
location,
|
122
|
-
location,
|
123
|
-
nil,
|
112
|
+
association_name.location,
|
124
113
|
[],
|
125
|
-
|
126
|
-
owner,
|
127
|
-
))
|
114
|
+
)
|
128
115
|
end
|
129
116
|
end
|
130
117
|
|
131
|
-
@index.register_enhancement(enhancement_class.new)
|
132
118
|
index(<<~RUBY)
|
133
119
|
module ActiveSupport
|
134
120
|
module Concern
|
@@ -160,11 +146,9 @@ module RubyIndexer
|
|
160
146
|
assert_entry("posts", Entry::Method, "/fake/path/foo.rb:23-11:23-17")
|
161
147
|
end
|
162
148
|
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
def on_call_node(index, owner, node, file_path, code_units_cache)
|
149
|
+
def test_error_handling_in_on_call_node_enter_enhancement
|
150
|
+
Class.new(Enhancement) do
|
151
|
+
def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
168
152
|
raise "Error"
|
169
153
|
end
|
170
154
|
|
@@ -175,7 +159,38 @@ module RubyIndexer
|
|
175
159
|
end
|
176
160
|
end
|
177
161
|
|
178
|
-
|
162
|
+
_stdout, stderr = capture_io do
|
163
|
+
index(<<~RUBY)
|
164
|
+
module ActiveSupport
|
165
|
+
module Concern
|
166
|
+
def self.extended(base)
|
167
|
+
base.class_eval("def new_method(a); end")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
RUBY
|
172
|
+
end
|
173
|
+
|
174
|
+
assert_match(
|
175
|
+
%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' on call node enter enhancement},
|
176
|
+
stderr,
|
177
|
+
)
|
178
|
+
# The module should still be indexed
|
179
|
+
assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_error_handling_in_on_call_node_leave_enhancement
|
183
|
+
Class.new(Enhancement) do
|
184
|
+
def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
185
|
+
raise "Error"
|
186
|
+
end
|
187
|
+
|
188
|
+
class << self
|
189
|
+
def name
|
190
|
+
"TestEnhancement"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
179
194
|
|
180
195
|
_stdout, stderr = capture_io do
|
181
196
|
index(<<~RUBY)
|
@@ -189,9 +204,122 @@ module RubyIndexer
|
|
189
204
|
RUBY
|
190
205
|
end
|
191
206
|
|
192
|
-
assert_match(
|
207
|
+
assert_match(
|
208
|
+
%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' on call node leave enhancement},
|
209
|
+
stderr,
|
210
|
+
)
|
193
211
|
# The module should still be indexed
|
194
212
|
assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
|
195
213
|
end
|
214
|
+
|
215
|
+
def test_advancing_namespace_stack_from_enhancement
|
216
|
+
Class.new(Enhancement) do
|
217
|
+
def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
218
|
+
owner = @listener.current_owner
|
219
|
+
return unless owner
|
220
|
+
|
221
|
+
case call_node.name
|
222
|
+
when :class_methods
|
223
|
+
@listener.add_module("ClassMethods", call_node.location, call_node.location)
|
224
|
+
when :extend
|
225
|
+
arguments = call_node.arguments&.arguments
|
226
|
+
return unless arguments
|
227
|
+
|
228
|
+
arguments.each do |node|
|
229
|
+
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
|
230
|
+
|
231
|
+
module_name = node.full_name
|
232
|
+
next unless module_name == "ActiveSupport::Concern"
|
233
|
+
|
234
|
+
@listener.register_included_hook do |index, base|
|
235
|
+
class_methods_name = "#{owner.name}::ClassMethods"
|
236
|
+
|
237
|
+
if index.indexed?(class_methods_name)
|
238
|
+
singleton = index.existing_or_new_singleton_class(base.name)
|
239
|
+
singleton.mixin_operations << Entry::Include.new(class_methods_name)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
247
|
+
return unless call_node.name == :class_methods
|
248
|
+
|
249
|
+
@listener.pop_namespace_stack
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
index(<<~RUBY)
|
254
|
+
module ActiveSupport
|
255
|
+
module Concern
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
module MyConcern
|
260
|
+
extend ActiveSupport::Concern
|
261
|
+
|
262
|
+
class_methods do
|
263
|
+
def foo; end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class User
|
268
|
+
include MyConcern
|
269
|
+
end
|
270
|
+
RUBY
|
271
|
+
|
272
|
+
assert_equal(
|
273
|
+
[
|
274
|
+
"User::<Class:User>",
|
275
|
+
"MyConcern::ClassMethods",
|
276
|
+
"Object::<Class:Object>",
|
277
|
+
"BasicObject::<Class:BasicObject>",
|
278
|
+
"Class",
|
279
|
+
"Module",
|
280
|
+
"Object",
|
281
|
+
"Kernel",
|
282
|
+
"BasicObject",
|
283
|
+
],
|
284
|
+
@index.linearized_ancestors_of("User::<Class:User>"),
|
285
|
+
)
|
286
|
+
|
287
|
+
refute_nil(@index.resolve_method("foo", "User::<Class:User>"))
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_creating_anonymous_classes_from_enhancement
|
291
|
+
Class.new(Enhancement) do
|
292
|
+
def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
293
|
+
case call_node.name
|
294
|
+
when :context
|
295
|
+
arguments = call_node.arguments&.arguments
|
296
|
+
first_argument = arguments&.first
|
297
|
+
return unless first_argument.is_a?(Prism::StringNode)
|
298
|
+
|
299
|
+
@listener.add_class(
|
300
|
+
"<RSpec:#{first_argument.content}>",
|
301
|
+
call_node.location,
|
302
|
+
first_argument.location,
|
303
|
+
)
|
304
|
+
when :subject
|
305
|
+
@listener.add_method("subject", call_node.location, [])
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
310
|
+
return unless call_node.name == :context
|
311
|
+
|
312
|
+
@listener.pop_namespace_stack
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
index(<<~RUBY)
|
317
|
+
context "does something" do
|
318
|
+
subject { call_whatever }
|
319
|
+
end
|
320
|
+
RUBY
|
321
|
+
|
322
|
+
refute_nil(@index.resolve_method("subject", "<RSpec:does something>"))
|
323
|
+
end
|
196
324
|
end
|
197
325
|
end
|
@@ -904,7 +904,7 @@ module RubyIndexer
|
|
904
904
|
assert_equal(14, entry.location.start_line)
|
905
905
|
end
|
906
906
|
|
907
|
-
def
|
907
|
+
def test_resolving_inherited_aliased_namespace
|
908
908
|
index(<<~RUBY)
|
909
909
|
module Bar
|
910
910
|
TARGET = 123
|
@@ -1490,7 +1490,7 @@ module RubyIndexer
|
|
1490
1490
|
assert_kind_of(Entry::UnresolvedMethodAlias, entry)
|
1491
1491
|
end
|
1492
1492
|
|
1493
|
-
def
|
1493
|
+
def test_unresolvable_method_aliases
|
1494
1494
|
index(<<~RUBY)
|
1495
1495
|
class Foo
|
1496
1496
|
alias bar baz
|
@@ -1672,6 +1672,38 @@ module RubyIndexer
|
|
1672
1672
|
)
|
1673
1673
|
end
|
1674
1674
|
|
1675
|
+
def test_extend_self
|
1676
|
+
@index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
|
1677
|
+
module Foo
|
1678
|
+
def bar
|
1679
|
+
end
|
1680
|
+
|
1681
|
+
extend self
|
1682
|
+
|
1683
|
+
def baz
|
1684
|
+
end
|
1685
|
+
end
|
1686
|
+
RUBY
|
1687
|
+
|
1688
|
+
["bar", "baz"].product(["Foo", "Foo::<Class:Foo>"]).each do |method, receiver|
|
1689
|
+
entry = @index.resolve_method(method, receiver)&.first
|
1690
|
+
refute_nil(entry)
|
1691
|
+
assert_equal(method, T.must(entry).name)
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
assert_equal(
|
1695
|
+
[
|
1696
|
+
"Foo::<Class:Foo>",
|
1697
|
+
"Foo",
|
1698
|
+
"Module",
|
1699
|
+
"Object",
|
1700
|
+
"Kernel",
|
1701
|
+
"BasicObject",
|
1702
|
+
],
|
1703
|
+
@index.linearized_ancestors_of("Foo::<Class:Foo>"),
|
1704
|
+
)
|
1705
|
+
end
|
1706
|
+
|
1675
1707
|
def test_linearizing_singleton_ancestors
|
1676
1708
|
@index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
|
1677
1709
|
module First
|
@@ -2023,5 +2055,12 @@ module RubyIndexer
|
|
2023
2055
|
),
|
2024
2056
|
)
|
2025
2057
|
end
|
2058
|
+
|
2059
|
+
def test_prevents_multiple_calls_to_index_all
|
2060
|
+
# For this test class, `index_all` is already called once in `setup`.
|
2061
|
+
assert_raises(Index::IndexNotEmptyError) do
|
2062
|
+
@index.index_all
|
2063
|
+
end
|
2064
|
+
end
|
2026
2065
|
end
|
2027
2066
|
end
|
@@ -209,7 +209,7 @@ module RubyIndexer
|
|
209
209
|
end
|
210
210
|
RUBY
|
211
211
|
|
212
|
-
# If the surrounding method is
|
212
|
+
# If the surrounding method is being defined on any dynamic value that isn't `self`, then we attribute the
|
213
213
|
# instance variable to the wrong owner since there's no way to understand that statically
|
214
214
|
entry = T.must(@index["@a"]&.first)
|
215
215
|
owner = T.must(entry.owner)
|
@@ -123,6 +123,145 @@ module RubyIndexer
|
|
123
123
|
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
|
124
124
|
end
|
125
125
|
|
126
|
+
def test_visibility_tracking_with_module_function
|
127
|
+
index(<<~RUBY)
|
128
|
+
module Test
|
129
|
+
def foo; end
|
130
|
+
def bar; end
|
131
|
+
module_function :foo, "bar"
|
132
|
+
end
|
133
|
+
RUBY
|
134
|
+
|
135
|
+
["foo", "bar"].each do |keyword|
|
136
|
+
entries = T.must(@index[keyword])
|
137
|
+
# should receive two entries because module_function creates a singleton method
|
138
|
+
# for the Test module and a private method for classes include the Test module
|
139
|
+
assert_equal(entries.size, 2)
|
140
|
+
first_entry, second_entry = *entries
|
141
|
+
# The first entry points to the location of the module_function call
|
142
|
+
assert_equal("Test", first_entry.owner.name)
|
143
|
+
assert_instance_of(Entry::Module, first_entry.owner)
|
144
|
+
assert_predicate(first_entry, :private?)
|
145
|
+
# The second entry points to the public singleton method
|
146
|
+
assert_equal("Test::<Class:Test>", second_entry.owner.name)
|
147
|
+
assert_instance_of(Entry::SingletonClass, second_entry.owner)
|
148
|
+
assert_equal(Entry::Visibility::PUBLIC, second_entry.visibility)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_private_class_method_visibility_tracking_string_symbol_arguments
|
153
|
+
index(<<~RUBY)
|
154
|
+
class Test
|
155
|
+
def self.foo
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.bar
|
159
|
+
end
|
160
|
+
|
161
|
+
private_class_method("foo", :bar)
|
162
|
+
|
163
|
+
def self.baz
|
164
|
+
end
|
165
|
+
end
|
166
|
+
RUBY
|
167
|
+
|
168
|
+
["foo", "bar"].each do |keyword|
|
169
|
+
entries = T.must(@index[keyword])
|
170
|
+
assert_equal(1, entries.size)
|
171
|
+
entry = entries.first
|
172
|
+
assert_predicate(entry, :private?)
|
173
|
+
end
|
174
|
+
|
175
|
+
entries = T.must(@index["baz"])
|
176
|
+
assert_equal(1, entries.size)
|
177
|
+
entry = entries.first
|
178
|
+
assert_predicate(entry, :public?)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_private_class_method_visibility_tracking_array_argument
|
182
|
+
index(<<~RUBY)
|
183
|
+
class Test
|
184
|
+
def self.foo
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.bar
|
188
|
+
end
|
189
|
+
|
190
|
+
private_class_method(["foo", :bar])
|
191
|
+
|
192
|
+
def self.baz
|
193
|
+
end
|
194
|
+
end
|
195
|
+
RUBY
|
196
|
+
|
197
|
+
["foo", "bar"].each do |keyword|
|
198
|
+
entries = T.must(@index[keyword])
|
199
|
+
assert_equal(1, entries.size)
|
200
|
+
entry = entries.first
|
201
|
+
assert_predicate(entry, :private?)
|
202
|
+
end
|
203
|
+
|
204
|
+
entries = T.must(@index["baz"])
|
205
|
+
assert_equal(1, entries.size)
|
206
|
+
entry = entries.first
|
207
|
+
assert_predicate(entry, :public?)
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_private_class_method_visibility_tracking_method_argument
|
211
|
+
index(<<~RUBY)
|
212
|
+
class Test
|
213
|
+
private_class_method def self.foo
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.bar
|
217
|
+
end
|
218
|
+
end
|
219
|
+
RUBY
|
220
|
+
|
221
|
+
entries = T.must(@index["foo"])
|
222
|
+
assert_equal(1, entries.size)
|
223
|
+
entry = entries.first
|
224
|
+
assert_predicate(entry, :private?)
|
225
|
+
|
226
|
+
entries = T.must(@index["bar"])
|
227
|
+
assert_equal(1, entries.size)
|
228
|
+
entry = entries.first
|
229
|
+
assert_predicate(entry, :public?)
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_comments_documentation
|
233
|
+
index(<<~RUBY)
|
234
|
+
# Documentation for Foo
|
235
|
+
|
236
|
+
class Foo
|
237
|
+
# ####################
|
238
|
+
# Documentation for bar
|
239
|
+
# ####################
|
240
|
+
#
|
241
|
+
def bar
|
242
|
+
end
|
243
|
+
|
244
|
+
# test
|
245
|
+
|
246
|
+
# Documentation for baz
|
247
|
+
def baz; end
|
248
|
+
def ban; end
|
249
|
+
end
|
250
|
+
RUBY
|
251
|
+
|
252
|
+
foo_comment = @index["Foo"].first.comments
|
253
|
+
assert_equal("Documentation for Foo", foo_comment)
|
254
|
+
|
255
|
+
bar_comment = @index["bar"].first.comments
|
256
|
+
assert_equal("####################\nDocumentation for bar\n####################\n", bar_comment)
|
257
|
+
|
258
|
+
baz_comment = @index["baz"].first.comments
|
259
|
+
assert_equal("Documentation for baz", baz_comment)
|
260
|
+
|
261
|
+
ban_comment = @index["ban"].first.comments
|
262
|
+
assert_empty(ban_comment)
|
263
|
+
end
|
264
|
+
|
126
265
|
def test_method_with_parameters
|
127
266
|
index(<<~RUBY)
|
128
267
|
class Foo
|