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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp-check +1 -1
  4. data/lib/core_ext/uri.rb +9 -4
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +63 -32
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -5
  9. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +38 -4
  10. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +324 -0
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
  13. data/lib/ruby_indexer/test/constant_test.rb +8 -0
  14. data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
  15. data/lib/ruby_indexer/test/index_test.rb +3 -0
  16. data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
  17. data/lib/ruby_indexer/test/method_test.rb +10 -0
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +14 -0
  19. data/lib/ruby_indexer/test/reference_finder_test.rb +242 -0
  20. data/lib/ruby_lsp/addon.rb +69 -7
  21. data/lib/ruby_lsp/erb_document.rb +9 -3
  22. data/lib/ruby_lsp/global_state.rb +8 -0
  23. data/lib/ruby_lsp/internal.rb +4 -0
  24. data/lib/ruby_lsp/listeners/completion.rb +1 -1
  25. data/lib/ruby_lsp/listeners/hover.rb +19 -0
  26. data/lib/ruby_lsp/requests/code_action_resolve.rb +9 -3
  27. data/lib/ruby_lsp/requests/completion.rb +1 -0
  28. data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
  29. data/lib/ruby_lsp/requests/definition.rb +1 -0
  30. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  31. data/lib/ruby_lsp/requests/hover.rb +1 -0
  32. data/lib/ruby_lsp/requests/range_formatting.rb +55 -0
  33. data/lib/ruby_lsp/requests/references.rb +146 -0
  34. data/lib/ruby_lsp/requests/rename.rb +196 -0
  35. data/lib/ruby_lsp/requests/signature_help.rb +6 -1
  36. data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
  37. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
  38. data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
  39. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
  40. data/lib/ruby_lsp/ruby_document.rb +23 -8
  41. data/lib/ruby_lsp/server.rb +98 -10
  42. data/lib/ruby_lsp/static_docs.rb +15 -0
  43. data/lib/ruby_lsp/store.rb +12 -0
  44. data/lib/ruby_lsp/test_helper.rb +1 -1
  45. data/lib/ruby_lsp/type_inferrer.rb +6 -1
  46. data/static_docs/yield.md +81 -0
  47. 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