ruby-lsp 0.18.4 → 0.19.1
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/VERSION +1 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/core_ext/uri.rb +9 -4
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +63 -32
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -5
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +38 -4
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +324 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
- data/lib/ruby_indexer/test/constant_test.rb +8 -0
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
- data/lib/ruby_indexer/test/index_test.rb +3 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
- data/lib/ruby_indexer/test/method_test.rb +10 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +14 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +242 -0
- data/lib/ruby_lsp/addon.rb +69 -7
- data/lib/ruby_lsp/erb_document.rb +9 -3
- data/lib/ruby_lsp/global_state.rb +8 -0
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/listeners/completion.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +19 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +9 -3
- data/lib/ruby_lsp/requests/completion.rb +1 -0
- data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
- data/lib/ruby_lsp/requests/definition.rb +1 -0
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -0
- data/lib/ruby_lsp/requests/range_formatting.rb +55 -0
- data/lib/ruby_lsp/requests/references.rb +146 -0
- data/lib/ruby_lsp/requests/rename.rb +196 -0
- data/lib/ruby_lsp/requests/signature_help.rb +6 -1
- data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
- data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
- data/lib/ruby_lsp/ruby_document.rb +23 -8
- data/lib/ruby_lsp/server.rb +98 -10
- data/lib/ruby_lsp/static_docs.rb +15 -0
- data/lib/ruby_lsp/store.rb +12 -0
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +6 -1
- data/static_docs/yield.md +81 -0
- metadata +20 -7
@@ -0,0 +1,324 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class ReferenceFinder
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
class Target
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
abstract!
|
12
|
+
end
|
13
|
+
|
14
|
+
class ConstTarget < Target
|
15
|
+
extend T::Sig
|
16
|
+
|
17
|
+
sig { returns(String) }
|
18
|
+
attr_reader :fully_qualified_name
|
19
|
+
|
20
|
+
sig { params(fully_qualified_name: String).void }
|
21
|
+
def initialize(fully_qualified_name)
|
22
|
+
super()
|
23
|
+
@fully_qualified_name = fully_qualified_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MethodTarget < Target
|
28
|
+
extend T::Sig
|
29
|
+
|
30
|
+
sig { returns(String) }
|
31
|
+
attr_reader :method_name
|
32
|
+
|
33
|
+
sig { params(method_name: String).void }
|
34
|
+
def initialize(method_name)
|
35
|
+
super()
|
36
|
+
@method_name = method_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Reference
|
41
|
+
extend T::Sig
|
42
|
+
|
43
|
+
sig { returns(String) }
|
44
|
+
attr_reader :name
|
45
|
+
|
46
|
+
sig { returns(Prism::Location) }
|
47
|
+
attr_reader :location
|
48
|
+
|
49
|
+
sig { returns(T::Boolean) }
|
50
|
+
attr_reader :declaration
|
51
|
+
|
52
|
+
sig { params(name: String, location: Prism::Location, declaration: T::Boolean).void }
|
53
|
+
def initialize(name, location, declaration:)
|
54
|
+
@name = name
|
55
|
+
@location = location
|
56
|
+
@declaration = declaration
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
sig do
|
61
|
+
params(
|
62
|
+
target: Target,
|
63
|
+
index: RubyIndexer::Index,
|
64
|
+
dispatcher: Prism::Dispatcher,
|
65
|
+
include_declarations: T::Boolean,
|
66
|
+
).void
|
67
|
+
end
|
68
|
+
def initialize(target, index, dispatcher, include_declarations: true)
|
69
|
+
@target = target
|
70
|
+
@index = index
|
71
|
+
@include_declarations = include_declarations
|
72
|
+
@stack = T.let([], T::Array[String])
|
73
|
+
@references = T.let([], T::Array[Reference])
|
74
|
+
|
75
|
+
dispatcher.register(
|
76
|
+
self,
|
77
|
+
:on_class_node_enter,
|
78
|
+
:on_class_node_leave,
|
79
|
+
:on_module_node_enter,
|
80
|
+
:on_module_node_leave,
|
81
|
+
:on_singleton_class_node_enter,
|
82
|
+
:on_singleton_class_node_leave,
|
83
|
+
:on_def_node_enter,
|
84
|
+
:on_def_node_leave,
|
85
|
+
:on_multi_write_node_enter,
|
86
|
+
:on_constant_path_write_node_enter,
|
87
|
+
:on_constant_path_or_write_node_enter,
|
88
|
+
:on_constant_path_operator_write_node_enter,
|
89
|
+
:on_constant_path_and_write_node_enter,
|
90
|
+
:on_constant_or_write_node_enter,
|
91
|
+
:on_constant_path_node_enter,
|
92
|
+
:on_constant_read_node_enter,
|
93
|
+
:on_constant_write_node_enter,
|
94
|
+
:on_constant_or_write_node_enter,
|
95
|
+
:on_constant_and_write_node_enter,
|
96
|
+
:on_constant_operator_write_node_enter,
|
97
|
+
:on_call_node_enter,
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
sig { returns(T::Array[Reference]) }
|
102
|
+
def references
|
103
|
+
return @references if @include_declarations
|
104
|
+
|
105
|
+
@references.reject(&:declaration)
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(node: Prism::ClassNode).void }
|
109
|
+
def on_class_node_enter(node)
|
110
|
+
constant_path = node.constant_path
|
111
|
+
name = constant_path.slice
|
112
|
+
nesting = actual_nesting(name)
|
113
|
+
|
114
|
+
if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
|
115
|
+
@references << Reference.new(name, constant_path.location, declaration: true)
|
116
|
+
end
|
117
|
+
|
118
|
+
@stack << name
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { params(node: Prism::ClassNode).void }
|
122
|
+
def on_class_node_leave(node)
|
123
|
+
@stack.pop
|
124
|
+
end
|
125
|
+
|
126
|
+
sig { params(node: Prism::ModuleNode).void }
|
127
|
+
def on_module_node_enter(node)
|
128
|
+
constant_path = node.constant_path
|
129
|
+
name = constant_path.slice
|
130
|
+
nesting = actual_nesting(name)
|
131
|
+
|
132
|
+
if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
|
133
|
+
@references << Reference.new(name, constant_path.location, declaration: true)
|
134
|
+
end
|
135
|
+
|
136
|
+
@stack << name
|
137
|
+
end
|
138
|
+
|
139
|
+
sig { params(node: Prism::ModuleNode).void }
|
140
|
+
def on_module_node_leave(node)
|
141
|
+
@stack.pop
|
142
|
+
end
|
143
|
+
|
144
|
+
sig { params(node: Prism::SingletonClassNode).void }
|
145
|
+
def on_singleton_class_node_enter(node)
|
146
|
+
expression = node.expression
|
147
|
+
return unless expression.is_a?(Prism::SelfNode)
|
148
|
+
|
149
|
+
@stack << "<Class:#{@stack.last}>"
|
150
|
+
end
|
151
|
+
|
152
|
+
sig { params(node: Prism::SingletonClassNode).void }
|
153
|
+
def on_singleton_class_node_leave(node)
|
154
|
+
@stack.pop
|
155
|
+
end
|
156
|
+
|
157
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
158
|
+
def on_constant_path_node_enter(node)
|
159
|
+
name = constant_name(node)
|
160
|
+
return unless name
|
161
|
+
|
162
|
+
collect_constant_references(name, node.location)
|
163
|
+
end
|
164
|
+
|
165
|
+
sig { params(node: Prism::ConstantReadNode).void }
|
166
|
+
def on_constant_read_node_enter(node)
|
167
|
+
name = constant_name(node)
|
168
|
+
return unless name
|
169
|
+
|
170
|
+
collect_constant_references(name, node.location)
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { params(node: Prism::MultiWriteNode).void }
|
174
|
+
def on_multi_write_node_enter(node)
|
175
|
+
[*node.lefts, *node.rest, *node.rights].each do |target|
|
176
|
+
case target
|
177
|
+
when Prism::ConstantTargetNode, Prism::ConstantPathTargetNode
|
178
|
+
collect_constant_references(target.name.to_s, target.location)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
sig { params(node: Prism::ConstantPathWriteNode).void }
|
184
|
+
def on_constant_path_write_node_enter(node)
|
185
|
+
target = node.target
|
186
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
187
|
+
|
188
|
+
name = constant_name(target)
|
189
|
+
return unless name
|
190
|
+
|
191
|
+
collect_constant_references(name, target.location)
|
192
|
+
end
|
193
|
+
|
194
|
+
sig { params(node: Prism::ConstantPathOrWriteNode).void }
|
195
|
+
def on_constant_path_or_write_node_enter(node)
|
196
|
+
target = node.target
|
197
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
198
|
+
|
199
|
+
name = constant_name(target)
|
200
|
+
return unless name
|
201
|
+
|
202
|
+
collect_constant_references(name, target.location)
|
203
|
+
end
|
204
|
+
|
205
|
+
sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
|
206
|
+
def on_constant_path_operator_write_node_enter(node)
|
207
|
+
target = node.target
|
208
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
209
|
+
|
210
|
+
name = constant_name(target)
|
211
|
+
return unless name
|
212
|
+
|
213
|
+
collect_constant_references(name, target.location)
|
214
|
+
end
|
215
|
+
|
216
|
+
sig { params(node: Prism::ConstantPathAndWriteNode).void }
|
217
|
+
def on_constant_path_and_write_node_enter(node)
|
218
|
+
target = node.target
|
219
|
+
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
220
|
+
|
221
|
+
name = constant_name(target)
|
222
|
+
return unless name
|
223
|
+
|
224
|
+
collect_constant_references(name, target.location)
|
225
|
+
end
|
226
|
+
|
227
|
+
sig { params(node: Prism::ConstantWriteNode).void }
|
228
|
+
def on_constant_write_node_enter(node)
|
229
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
230
|
+
end
|
231
|
+
|
232
|
+
sig { params(node: Prism::ConstantOrWriteNode).void }
|
233
|
+
def on_constant_or_write_node_enter(node)
|
234
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
235
|
+
end
|
236
|
+
|
237
|
+
sig { params(node: Prism::ConstantAndWriteNode).void }
|
238
|
+
def on_constant_and_write_node_enter(node)
|
239
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
240
|
+
end
|
241
|
+
|
242
|
+
sig { params(node: Prism::ConstantOperatorWriteNode).void }
|
243
|
+
def on_constant_operator_write_node_enter(node)
|
244
|
+
collect_constant_references(node.name.to_s, node.name_loc)
|
245
|
+
end
|
246
|
+
|
247
|
+
sig { params(node: Prism::DefNode).void }
|
248
|
+
def on_def_node_enter(node)
|
249
|
+
if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name
|
250
|
+
@references << Reference.new(name, node.name_loc, declaration: true)
|
251
|
+
end
|
252
|
+
|
253
|
+
if node.receiver.is_a?(Prism::SelfNode)
|
254
|
+
@stack << "<Class:#{@stack.last}>"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
sig { params(node: Prism::DefNode).void }
|
259
|
+
def on_def_node_leave(node)
|
260
|
+
if node.receiver.is_a?(Prism::SelfNode)
|
261
|
+
@stack.pop
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
sig { params(node: Prism::CallNode).void }
|
266
|
+
def on_call_node_enter(node)
|
267
|
+
if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name
|
268
|
+
@references << Reference.new(name, T.must(node.message_loc), declaration: false)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
sig { params(name: String).returns(T::Array[String]) }
|
275
|
+
def actual_nesting(name)
|
276
|
+
nesting = @stack + [name]
|
277
|
+
corrected_nesting = []
|
278
|
+
|
279
|
+
nesting.reverse_each do |name|
|
280
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
281
|
+
|
282
|
+
break if name.start_with?("::")
|
283
|
+
end
|
284
|
+
|
285
|
+
corrected_nesting
|
286
|
+
end
|
287
|
+
|
288
|
+
sig { params(name: String, location: Prism::Location).void }
|
289
|
+
def collect_constant_references(name, location)
|
290
|
+
return unless @target.is_a?(ConstTarget)
|
291
|
+
|
292
|
+
entries = @index.resolve(name, @stack)
|
293
|
+
return unless entries
|
294
|
+
|
295
|
+
previous_reference = @references.last
|
296
|
+
|
297
|
+
entries.each do |entry|
|
298
|
+
next unless entry.name == @target.fully_qualified_name
|
299
|
+
|
300
|
+
# When processing a class/module declaration, we eagerly handle the constant reference. To avoid duplicates,
|
301
|
+
# when we find the constant node defining the namespace, then we have to check if it wasn't already added
|
302
|
+
next if previous_reference&.location == location
|
303
|
+
|
304
|
+
@references << Reference.new(name, location, declaration: false)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
sig do
|
309
|
+
params(
|
310
|
+
node: T.any(
|
311
|
+
Prism::ConstantPathNode,
|
312
|
+
Prism::ConstantReadNode,
|
313
|
+
Prism::ConstantPathTargetNode,
|
314
|
+
),
|
315
|
+
).returns(T.nilable(String))
|
316
|
+
end
|
317
|
+
def constant_name(node)
|
318
|
+
node.full_name
|
319
|
+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
320
|
+
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
321
|
+
nil
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
@@ -6,6 +6,7 @@ require "did_you_mean"
|
|
6
6
|
|
7
7
|
require "ruby_indexer/lib/ruby_indexer/indexable_path"
|
8
8
|
require "ruby_indexer/lib/ruby_indexer/declaration_listener"
|
9
|
+
require "ruby_indexer/lib/ruby_indexer/reference_finder"
|
9
10
|
require "ruby_indexer/lib/ruby_indexer/enhancement"
|
10
11
|
require "ruby_indexer/lib/ruby_indexer/index"
|
11
12
|
require "ruby_indexer/lib/ruby_indexer/entry"
|
@@ -159,6 +159,17 @@ module RubyIndexer
|
|
159
159
|
assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:4-2:5-5")
|
160
160
|
end
|
161
161
|
|
162
|
+
def test_nested_modules_and_classes_with_multibyte_characters
|
163
|
+
index(<<~RUBY)
|
164
|
+
module A動物
|
165
|
+
class Bねこ; end
|
166
|
+
end
|
167
|
+
RUBY
|
168
|
+
|
169
|
+
assert_entry("A動物", Entry::Module, "/fake/path/foo.rb:0-0:2-3")
|
170
|
+
assert_entry("A動物::Bねこ", Entry::Class, "/fake/path/foo.rb:1-2:1-16")
|
171
|
+
end
|
172
|
+
|
162
173
|
def test_nested_modules_and_classes
|
163
174
|
index(<<~RUBY)
|
164
175
|
module Foo
|
@@ -619,5 +630,17 @@ module RubyIndexer
|
|
619
630
|
its namespace nesting, and the surrounding CallNode (e.g. a method call).
|
620
631
|
COMMENTS
|
621
632
|
end
|
633
|
+
|
634
|
+
def test_lazy_comment_fetching_does_not_fail_if_file_gets_deleted
|
635
|
+
indexable = IndexablePath.new("#{Dir.pwd}/lib", "lib/ruby_lsp/does_not_exist.rb")
|
636
|
+
|
637
|
+
@index.index_single(indexable, <<~RUBY, collect_comments: false)
|
638
|
+
class Foo
|
639
|
+
end
|
640
|
+
RUBY
|
641
|
+
|
642
|
+
entry = @index["Foo"].first
|
643
|
+
assert_empty(entry.comments)
|
644
|
+
end
|
622
645
|
end
|
623
646
|
end
|
@@ -21,6 +21,14 @@ module RubyIndexer
|
|
21
21
|
assert_entry("BAR", Entry::Constant, "/fake/path/foo.rb:6-0:6-7")
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_constant_with_multibyte_characters
|
25
|
+
index(<<~RUBY)
|
26
|
+
CONST_💎 = "Ruby"
|
27
|
+
RUBY
|
28
|
+
|
29
|
+
assert_entry("CONST_💎", Entry::Constant, "/fake/path/foo.rb:0-0:0-16")
|
30
|
+
end
|
31
|
+
|
24
32
|
def test_constant_or_writes
|
25
33
|
index(<<~RUBY)
|
26
34
|
FOO ||= 1
|
@@ -39,6 +39,7 @@ module RubyIndexer
|
|
39
39
|
location,
|
40
40
|
location,
|
41
41
|
nil,
|
42
|
+
index.configuration.encoding,
|
42
43
|
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
|
43
44
|
Entry::Visibility::PUBLIC,
|
44
45
|
owner,
|
@@ -121,6 +122,7 @@ module RubyIndexer
|
|
121
122
|
location,
|
122
123
|
location,
|
123
124
|
nil,
|
125
|
+
index.configuration.encoding,
|
124
126
|
[],
|
125
127
|
Entry::Visibility::PUBLIC,
|
126
128
|
owner,
|
@@ -1858,6 +1858,9 @@ module RubyIndexer
|
|
1858
1858
|
|
1859
1859
|
entries = @index.entries_for("/fake/path/foo.rb", RubyIndexer::Entry::Namespace)
|
1860
1860
|
assert_equal(["Foo", "Bar", "Bar::<Class:Bar>"], entries.map(&:name))
|
1861
|
+
|
1862
|
+
entries = @index.entries_for("/fake/path/foo.rb")
|
1863
|
+
assert_equal(["Foo", "Bar", "my_def", "Bar::<Class:Bar>", "my_singleton_def"], entries.map(&:name))
|
1861
1864
|
end
|
1862
1865
|
|
1863
1866
|
def test_entries_for_returns_nil_if_no_matches
|
@@ -25,6 +25,18 @@ module RubyIndexer
|
|
25
25
|
assert_equal("Foo::Bar", owner.name)
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_instance_variable_with_multibyte_characters
|
29
|
+
index(<<~RUBY)
|
30
|
+
class Foo
|
31
|
+
def initialize
|
32
|
+
@あ = 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
|
37
|
+
assert_entry("@あ", Entry::InstanceVariable, "/fake/path/foo.rb:2-4:2-6")
|
38
|
+
end
|
39
|
+
|
28
40
|
def test_instance_variable_and_write
|
29
41
|
index(<<~RUBY)
|
30
42
|
module Foo
|
@@ -27,6 +27,16 @@ module RubyIndexer
|
|
27
27
|
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
28
28
|
end
|
29
29
|
|
30
|
+
def test_method_with_multibyte_characters
|
31
|
+
index(<<~RUBY)
|
32
|
+
class Foo
|
33
|
+
def こんにちは; end
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
|
37
|
+
assert_entry("こんにちは", Entry::Method, "/fake/path/foo.rb:1-2:1-16")
|
38
|
+
end
|
39
|
+
|
30
40
|
def test_singleton_method_using_self_receiver
|
31
41
|
index(<<~RUBY)
|
32
42
|
class Foo
|
@@ -76,6 +76,20 @@ module RubyIndexer
|
|
76
76
|
assert_operator(entry.location.end_column, :>, 0)
|
77
77
|
end
|
78
78
|
|
79
|
+
def test_index_global_declaration
|
80
|
+
entries = @index["$DEBUG"]
|
81
|
+
refute_nil(entries)
|
82
|
+
assert_equal(1, entries.length)
|
83
|
+
|
84
|
+
entry = entries.first
|
85
|
+
|
86
|
+
assert_instance_of(Entry::GlobalVariable, entry)
|
87
|
+
assert_equal("$DEBUG", entry.name)
|
88
|
+
assert_match(%r{/gems/rbs-.*/core/global_variables.rbs}, entry.file_path)
|
89
|
+
assert_operator(entry.location.start_column, :<, entry.location.end_column)
|
90
|
+
assert_equal(entry.location.start_line, entry.location.end_line)
|
91
|
+
end
|
92
|
+
|
79
93
|
def test_attaches_correct_owner_to_singleton_methods
|
80
94
|
entries = @index["basename"]
|
81
95
|
refute_nil(entries)
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class ReferenceFinderTest < Minitest::Test
|
8
|
+
def test_finds_constant_references
|
9
|
+
refs = find_const_references("Foo::Bar", <<~RUBY)
|
10
|
+
module Foo
|
11
|
+
class Bar
|
12
|
+
end
|
13
|
+
|
14
|
+
Bar
|
15
|
+
end
|
16
|
+
|
17
|
+
Foo::Bar
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
assert_equal("Bar", refs[0].name)
|
21
|
+
assert_equal(2, refs[0].location.start_line)
|
22
|
+
|
23
|
+
assert_equal("Bar", refs[1].name)
|
24
|
+
assert_equal(5, refs[1].location.start_line)
|
25
|
+
|
26
|
+
assert_equal("Foo::Bar", refs[2].name)
|
27
|
+
assert_equal(8, refs[2].location.start_line)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_finds_constant_references_inside_singleton_contexts
|
31
|
+
refs = find_const_references("Foo::<Class:Foo>::Bar", <<~RUBY)
|
32
|
+
class Foo
|
33
|
+
class << self
|
34
|
+
class Bar
|
35
|
+
end
|
36
|
+
|
37
|
+
Bar
|
38
|
+
end
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
|
42
|
+
assert_equal("Bar", refs[0].name)
|
43
|
+
assert_equal(3, refs[0].location.start_line)
|
44
|
+
|
45
|
+
assert_equal("Bar", refs[1].name)
|
46
|
+
assert_equal(6, refs[1].location.start_line)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_finds_top_level_constant_references
|
50
|
+
refs = find_const_references("Bar", <<~RUBY)
|
51
|
+
class Bar
|
52
|
+
end
|
53
|
+
|
54
|
+
class Foo
|
55
|
+
::Bar
|
56
|
+
|
57
|
+
class << self
|
58
|
+
::Bar
|
59
|
+
end
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
assert_equal("Bar", refs[0].name)
|
64
|
+
assert_equal(1, refs[0].location.start_line)
|
65
|
+
|
66
|
+
assert_equal("::Bar", refs[1].name)
|
67
|
+
assert_equal(5, refs[1].location.start_line)
|
68
|
+
|
69
|
+
assert_equal("::Bar", refs[2].name)
|
70
|
+
assert_equal(8, refs[2].location.start_line)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_finds_method_references
|
74
|
+
refs = find_method_references("foo", <<~RUBY)
|
75
|
+
class Bar
|
76
|
+
def foo
|
77
|
+
end
|
78
|
+
|
79
|
+
def baz
|
80
|
+
foo
|
81
|
+
end
|
82
|
+
end
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
assert_equal(2, refs.size)
|
86
|
+
|
87
|
+
assert_equal("foo", refs[0].name)
|
88
|
+
assert_equal(2, refs[0].location.start_line)
|
89
|
+
|
90
|
+
assert_equal("foo", refs[1].name)
|
91
|
+
assert_equal(6, refs[1].location.start_line)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_does_not_mismatch_on_readers_and_writers
|
95
|
+
refs = find_method_references("foo", <<~RUBY)
|
96
|
+
class Bar
|
97
|
+
def foo
|
98
|
+
end
|
99
|
+
|
100
|
+
def foo=(value)
|
101
|
+
end
|
102
|
+
|
103
|
+
def baz
|
104
|
+
self.foo = 1
|
105
|
+
self.foo
|
106
|
+
end
|
107
|
+
end
|
108
|
+
RUBY
|
109
|
+
|
110
|
+
# We want to match `foo` but not `foo=`
|
111
|
+
assert_equal(2, refs.size)
|
112
|
+
|
113
|
+
assert_equal("foo", refs[0].name)
|
114
|
+
assert_equal(2, refs[0].location.start_line)
|
115
|
+
|
116
|
+
assert_equal("foo", refs[1].name)
|
117
|
+
assert_equal(10, refs[1].location.start_line)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_matches_writers
|
121
|
+
refs = find_method_references("foo=", <<~RUBY)
|
122
|
+
class Bar
|
123
|
+
def foo
|
124
|
+
end
|
125
|
+
|
126
|
+
def foo=(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def baz
|
130
|
+
self.foo = 1
|
131
|
+
self.foo
|
132
|
+
end
|
133
|
+
end
|
134
|
+
RUBY
|
135
|
+
|
136
|
+
# We want to match `foo=` but not `foo`
|
137
|
+
assert_equal(2, refs.size)
|
138
|
+
|
139
|
+
assert_equal("foo=", refs[0].name)
|
140
|
+
assert_equal(5, refs[0].location.start_line)
|
141
|
+
|
142
|
+
assert_equal("foo=", refs[1].name)
|
143
|
+
assert_equal(9, refs[1].location.start_line)
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_find_inherited_methods
|
147
|
+
refs = find_method_references("foo", <<~RUBY)
|
148
|
+
class Bar
|
149
|
+
def foo
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Baz < Bar
|
154
|
+
super.foo
|
155
|
+
end
|
156
|
+
RUBY
|
157
|
+
|
158
|
+
assert_equal(2, refs.size)
|
159
|
+
|
160
|
+
assert_equal("foo", refs[0].name)
|
161
|
+
assert_equal(2, refs[0].location.start_line)
|
162
|
+
|
163
|
+
assert_equal("foo", refs[1].name)
|
164
|
+
assert_equal(7, refs[1].location.start_line)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_finds_methods_created_in_mixins
|
168
|
+
refs = find_method_references("foo", <<~RUBY)
|
169
|
+
module Mixin
|
170
|
+
def foo
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Bar
|
175
|
+
include Mixin
|
176
|
+
end
|
177
|
+
|
178
|
+
Bar.foo
|
179
|
+
RUBY
|
180
|
+
|
181
|
+
assert_equal(2, refs.size)
|
182
|
+
|
183
|
+
assert_equal("foo", refs[0].name)
|
184
|
+
assert_equal(2, refs[0].location.start_line)
|
185
|
+
|
186
|
+
assert_equal("foo", refs[1].name)
|
187
|
+
assert_equal(10, refs[1].location.start_line)
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_finds_singleton_methods
|
191
|
+
# The current implementation matches on both `Bar.foo` and `Bar#foo` even though they are different
|
192
|
+
|
193
|
+
refs = find_method_references("foo", <<~RUBY)
|
194
|
+
class Bar
|
195
|
+
class << self
|
196
|
+
def foo
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def foo
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
Bar.foo
|
205
|
+
RUBY
|
206
|
+
|
207
|
+
assert_equal(3, refs.size)
|
208
|
+
|
209
|
+
assert_equal("foo", refs[0].name)
|
210
|
+
assert_equal(3, refs[0].location.start_line)
|
211
|
+
|
212
|
+
assert_equal("foo", refs[1].name)
|
213
|
+
assert_equal(7, refs[1].location.start_line)
|
214
|
+
|
215
|
+
assert_equal("foo", refs[2].name)
|
216
|
+
assert_equal(11, refs[2].location.start_line)
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def find_const_references(const_name, source)
|
222
|
+
target = ReferenceFinder::ConstTarget.new(const_name)
|
223
|
+
find_references(target, source)
|
224
|
+
end
|
225
|
+
|
226
|
+
def find_method_references(method_name, source)
|
227
|
+
target = ReferenceFinder::MethodTarget.new(method_name)
|
228
|
+
find_references(target, source)
|
229
|
+
end
|
230
|
+
|
231
|
+
def find_references(target, source)
|
232
|
+
file_path = "/fake.rb"
|
233
|
+
index = Index.new
|
234
|
+
index.index_single(IndexablePath.new(nil, file_path), source)
|
235
|
+
parse_result = Prism.parse(source)
|
236
|
+
dispatcher = Prism::Dispatcher.new
|
237
|
+
finder = ReferenceFinder.new(target, index, dispatcher)
|
238
|
+
dispatcher.visit(parse_result.value)
|
239
|
+
finder.references
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|