ruby-lsp 0.19.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) 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 +2 -2
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +85 -36
  6. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +5 -1
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +46 -98
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +7 -6
  9. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +22 -0
  10. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +20 -5
  11. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +76 -14
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +12 -0
  13. data/lib/ruby_indexer/test/enhancements_test.rb +5 -7
  14. data/lib/ruby_indexer/test/global_variable_test.rb +49 -0
  15. data/lib/ruby_indexer/test/index_test.rb +3 -0
  16. data/lib/ruby_indexer/test/rbs_indexer_test.rb +14 -0
  17. data/lib/ruby_indexer/test/reference_finder_test.rb +162 -6
  18. data/lib/ruby_lsp/erb_document.rb +20 -2
  19. data/lib/ruby_lsp/internal.rb +3 -1
  20. data/lib/ruby_lsp/listeners/definition.rb +20 -0
  21. data/lib/ruby_lsp/listeners/folding_ranges.rb +3 -3
  22. data/lib/ruby_lsp/requests/code_action_resolve.rb +16 -4
  23. data/lib/ruby_lsp/requests/completion.rb +1 -0
  24. data/lib/ruby_lsp/requests/definition.rb +2 -0
  25. data/lib/ruby_lsp/requests/document_highlight.rb +5 -1
  26. data/lib/ruby_lsp/requests/hover.rb +1 -0
  27. data/lib/ruby_lsp/requests/range_formatting.rb +57 -0
  28. data/lib/ruby_lsp/requests/references.rb +146 -0
  29. data/lib/ruby_lsp/requests/rename.rb +16 -9
  30. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  31. data/lib/ruby_lsp/requests/signature_help.rb +6 -1
  32. data/lib/ruby_lsp/requests/support/common.rb +1 -1
  33. data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
  34. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
  35. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +6 -6
  36. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
  37. data/lib/ruby_lsp/response_builders/document_symbol.rb +2 -2
  38. data/lib/ruby_lsp/response_builders/hover.rb +2 -2
  39. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +14 -8
  40. data/lib/ruby_lsp/response_builders/signature_help.rb +2 -2
  41. data/lib/ruby_lsp/ruby_document.rb +44 -8
  42. data/lib/ruby_lsp/server.rb +63 -2
  43. data/lib/ruby_lsp/type_inferrer.rb +1 -1
  44. metadata +8 -5
@@ -44,6 +44,8 @@ module RubyIndexer
44
44
  when RBS::AST::Declarations::Constant
45
45
  namespace_nesting = declaration.name.namespace.path.map(&:to_s)
46
46
  handle_constant(declaration, namespace_nesting, pathname.to_s)
47
+ when RBS::AST::Declarations::Global
48
+ handle_global_variable(declaration, pathname)
47
49
  else # rubocop:disable Style/EmptyElse
48
50
  # Other kinds not yet handled
49
51
  end
@@ -59,9 +61,9 @@ module RubyIndexer
59
61
  comments = comments_to_string(declaration)
60
62
  entry = if declaration.is_a?(RBS::AST::Declarations::Class)
61
63
  parent_class = declaration.super_class&.name&.name&.to_s
62
- Entry::Class.new(nesting, file_path, location, location, comments, @index.configuration.encoding, parent_class)
64
+ Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
63
65
  else
64
- Entry::Module.new(nesting, file_path, location, location, comments, @index.configuration.encoding)
66
+ Entry::Module.new(nesting, file_path, location, location, comments)
65
67
  end
66
68
  add_declaration_mixins_to_entry(declaration, entry)
67
69
  @index.add(entry)
@@ -134,7 +136,6 @@ module RubyIndexer
134
136
  location,
135
137
  location,
136
138
  comments,
137
- @index.configuration.encoding,
138
139
  signatures,
139
140
  visibility,
140
141
  real_owner,
@@ -267,7 +268,21 @@ module RubyIndexer
267
268
  file_path,
268
269
  to_ruby_indexer_location(declaration.location),
269
270
  comments_to_string(declaration),
270
- @index.configuration.encoding,
271
+ ))
272
+ end
273
+
274
+ sig { params(declaration: RBS::AST::Declarations::Global, pathname: Pathname).void }
275
+ def handle_global_variable(declaration, pathname)
276
+ name = declaration.name.to_s
277
+ file_path = pathname.to_s
278
+ location = to_ruby_indexer_location(declaration.location)
279
+ comments = comments_to_string(declaration)
280
+
281
+ @index.add(Entry::GlobalVariable.new(
282
+ name,
283
+ file_path,
284
+ location,
285
+ comments,
271
286
  ))
272
287
  end
273
288
 
@@ -283,7 +298,6 @@ module RubyIndexer
283
298
  file_path,
284
299
  to_ruby_indexer_location(member.location),
285
300
  comments,
286
- @index.configuration.encoding,
287
301
  )
288
302
 
289
303
  @index.add(entry)
@@ -294,6 +308,7 @@ module RubyIndexer
294
308
  RBS::AST::Declarations::Class,
295
309
  RBS::AST::Declarations::Module,
296
310
  RBS::AST::Declarations::Constant,
311
+ RBS::AST::Declarations::Global,
297
312
  RBS::AST::Members::MethodDefinition,
298
313
  RBS::AST::Members::Alias,
299
314
  )).returns(T.nilable(String))
@@ -5,6 +5,38 @@ module RubyIndexer
5
5
  class ReferenceFinder
6
6
  extend T::Sig
7
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
+
8
40
  class Reference
9
41
  extend T::Sig
10
42
 
@@ -14,26 +46,29 @@ module RubyIndexer
14
46
  sig { returns(Prism::Location) }
15
47
  attr_reader :location
16
48
 
17
- sig { params(name: String, location: Prism::Location).void }
18
- def initialize(name, location)
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:)
19
54
  @name = name
20
55
  @location = location
56
+ @declaration = declaration
21
57
  end
22
58
  end
23
59
 
24
- sig { returns(T::Array[Reference]) }
25
- attr_reader :references
26
-
27
60
  sig do
28
61
  params(
29
- fully_qualified_name: String,
62
+ target: Target,
30
63
  index: RubyIndexer::Index,
31
64
  dispatcher: Prism::Dispatcher,
65
+ include_declarations: T::Boolean,
32
66
  ).void
33
67
  end
34
- def initialize(fully_qualified_name, index, dispatcher)
35
- @fully_qualified_name = fully_qualified_name
68
+ def initialize(target, index, dispatcher, include_declarations: true)
69
+ @target = target
36
70
  @index = index
71
+ @include_declarations = include_declarations
37
72
  @stack = T.let([], T::Array[String])
38
73
  @references = T.let([], T::Array[Reference])
39
74
 
@@ -59,17 +94,25 @@ module RubyIndexer
59
94
  :on_constant_or_write_node_enter,
60
95
  :on_constant_and_write_node_enter,
61
96
  :on_constant_operator_write_node_enter,
97
+ :on_call_node_enter,
62
98
  )
63
99
  end
64
100
 
101
+ sig { returns(T::Array[Reference]) }
102
+ def references
103
+ return @references if @include_declarations
104
+
105
+ @references.reject(&:declaration)
106
+ end
107
+
65
108
  sig { params(node: Prism::ClassNode).void }
66
109
  def on_class_node_enter(node)
67
110
  constant_path = node.constant_path
68
111
  name = constant_path.slice
69
112
  nesting = actual_nesting(name)
70
113
 
71
- if nesting.join("::") == @fully_qualified_name
72
- @references << Reference.new(name, constant_path.location)
114
+ if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
115
+ @references << Reference.new(name, constant_path.location, declaration: true)
73
116
  end
74
117
 
75
118
  @stack << name
@@ -86,8 +129,8 @@ module RubyIndexer
86
129
  name = constant_path.slice
87
130
  nesting = actual_nesting(name)
88
131
 
89
- if nesting.join("::") == @fully_qualified_name
90
- @references << Reference.new(name, constant_path.location)
132
+ if @target.is_a?(ConstTarget) && nesting.join("::") == @target.fully_qualified_name
133
+ @references << Reference.new(name, constant_path.location, declaration: true)
91
134
  end
92
135
 
93
136
  @stack << name
@@ -203,6 +246,10 @@ module RubyIndexer
203
246
 
204
247
  sig { params(node: Prism::DefNode).void }
205
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
+
206
253
  if node.receiver.is_a?(Prism::SelfNode)
207
254
  @stack << "<Class:#{@stack.last}>"
208
255
  end
@@ -215,6 +262,13 @@ module RubyIndexer
215
262
  end
216
263
  end
217
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
+
218
272
  private
219
273
 
220
274
  sig { params(name: String).returns(T::Array[String]) }
@@ -233,13 +287,21 @@ module RubyIndexer
233
287
 
234
288
  sig { params(name: String, location: Prism::Location).void }
235
289
  def collect_constant_references(name, location)
290
+ return unless @target.is_a?(ConstTarget)
291
+
236
292
  entries = @index.resolve(name, @stack)
237
293
  return unless entries
238
294
 
295
+ previous_reference = @references.last
296
+
239
297
  entries.each do |entry|
240
- next unless entry.name == @fully_qualified_name
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
241
303
 
242
- @references << Reference.new(name, location)
304
+ @references << Reference.new(name, location, declaration: false)
243
305
  end
244
306
  end
245
307
 
@@ -630,5 +630,17 @@ module RubyIndexer
630
630
  its namespace nesting, and the surrounding CallNode (e.g. a method call).
631
631
  COMMENTS
632
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
633
645
  end
634
646
  end
@@ -9,14 +9,14 @@ module RubyIndexer
9
9
  enhancement_class = Class.new do
10
10
  include Enhancement
11
11
 
12
- def on_call_node(index, owner, node, file_path)
12
+ def on_call_node(index, owner, node, file_path, code_units_cache)
13
13
  return unless owner
14
14
  return unless node.name == :extend
15
15
 
16
16
  arguments = node.arguments&.arguments
17
17
  return unless arguments
18
18
 
19
- location = node.location
19
+ location = Location.from_prism_location(node.location, code_units_cache)
20
20
 
21
21
  arguments.each do |node|
22
22
  next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
@@ -39,7 +39,6 @@ module RubyIndexer
39
39
  location,
40
40
  location,
41
41
  nil,
42
- index.configuration.encoding,
43
42
  [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
44
43
  Entry::Visibility::PUBLIC,
45
44
  owner,
@@ -102,7 +101,7 @@ module RubyIndexer
102
101
  enhancement_class = Class.new do
103
102
  include Enhancement
104
103
 
105
- def on_call_node(index, owner, node, file_path)
104
+ def on_call_node(index, owner, node, file_path, code_units_cache)
106
105
  return unless owner
107
106
 
108
107
  name = node.name
@@ -114,7 +113,7 @@ module RubyIndexer
114
113
  association_name = arguments.first
115
114
  return unless association_name.is_a?(Prism::SymbolNode)
116
115
 
117
- location = association_name.location
116
+ location = Location.from_prism_location(association_name.location, code_units_cache)
118
117
 
119
118
  index.add(Entry::Method.new(
120
119
  T.must(association_name.value),
@@ -122,7 +121,6 @@ module RubyIndexer
122
121
  location,
123
122
  location,
124
123
  nil,
125
- index.configuration.encoding,
126
124
  [],
127
125
  Entry::Visibility::PUBLIC,
128
126
  owner,
@@ -166,7 +164,7 @@ module RubyIndexer
166
164
  enhancement_class = Class.new do
167
165
  include Enhancement
168
166
 
169
- def on_call_node(index, owner, node, file_path)
167
+ def on_call_node(index, owner, node, file_path, code_units_cache)
170
168
  raise "Error"
171
169
  end
172
170
 
@@ -0,0 +1,49 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class GlobalVariableTest < TestCase
8
+ def test_global_variable_and_write
9
+ index(<<~RUBY)
10
+ $foo &&= 1
11
+ RUBY
12
+
13
+ assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
14
+ end
15
+
16
+ def test_global_variable_operator_write
17
+ index(<<~RUBY)
18
+ $foo += 1
19
+ RUBY
20
+
21
+ assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
22
+ end
23
+
24
+ def test_global_variable_or_write
25
+ index(<<~RUBY)
26
+ $foo ||= 1
27
+ RUBY
28
+
29
+ assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
30
+ end
31
+
32
+ def test_global_variable_target_node
33
+ index(<<~RUBY)
34
+ $foo, $bar = 1
35
+ RUBY
36
+
37
+ assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
38
+ assert_entry("$bar", Entry::GlobalVariable, "/fake/path/foo.rb:0-6:0-10")
39
+ end
40
+
41
+ def test_global_variable_write
42
+ index(<<~RUBY)
43
+ $foo = 1
44
+ RUBY
45
+
46
+ assert_entry("$foo", Entry::GlobalVariable, "/fake/path/foo.rb:0-0:0-4")
47
+ end
48
+ end
49
+ end
@@ -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
@@ -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)
@@ -6,7 +6,7 @@ require "test_helper"
6
6
  module RubyIndexer
7
7
  class ReferenceFinderTest < Minitest::Test
8
8
  def test_finds_constant_references
9
- refs = find_references("Foo::Bar", <<~RUBY)
9
+ refs = find_const_references("Foo::Bar", <<~RUBY)
10
10
  module Foo
11
11
  class Bar
12
12
  end
@@ -28,7 +28,7 @@ module RubyIndexer
28
28
  end
29
29
 
30
30
  def test_finds_constant_references_inside_singleton_contexts
31
- refs = find_references("Foo::<Class:Foo>::Bar", <<~RUBY)
31
+ refs = find_const_references("Foo::<Class:Foo>::Bar", <<~RUBY)
32
32
  class Foo
33
33
  class << self
34
34
  class Bar
@@ -47,7 +47,7 @@ module RubyIndexer
47
47
  end
48
48
 
49
49
  def test_finds_top_level_constant_references
50
- refs = find_references("Bar", <<~RUBY)
50
+ refs = find_const_references("Bar", <<~RUBY)
51
51
  class Bar
52
52
  end
53
53
 
@@ -70,17 +70,173 @@ module RubyIndexer
70
70
  assert_equal(8, refs[2].location.start_line)
71
71
  end
72
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
+
73
219
  private
74
220
 
75
- def find_references(fully_qualified_name, source)
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)
76
232
  file_path = "/fake.rb"
77
233
  index = Index.new
78
234
  index.index_single(IndexablePath.new(nil, file_path), source)
79
235
  parse_result = Prism.parse(source)
80
236
  dispatcher = Prism::Dispatcher.new
81
- finder = ReferenceFinder.new(fully_qualified_name, index, dispatcher)
237
+ finder = ReferenceFinder.new(target, index, dispatcher)
82
238
  dispatcher.visit(parse_result.value)
83
- finder.references.uniq(&:location)
239
+ finder.references
84
240
  end
85
241
  end
86
242
  end
@@ -6,10 +6,18 @@ module RubyLsp
6
6
  extend T::Sig
7
7
  extend T::Generic
8
8
 
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
9
11
  sig { returns(String) }
10
12
  attr_reader :host_language_source
11
13
 
12
- ParseResultType = type_member { { fixed: Prism::ParseResult } }
14
+ sig do
15
+ returns(T.any(
16
+ T.proc.params(arg0: Integer).returns(Integer),
17
+ Prism::CodeUnitsCache,
18
+ ))
19
+ end
20
+ attr_reader :code_units_cache
13
21
 
14
22
  sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
15
23
  def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
@@ -17,6 +25,10 @@ module RubyLsp
17
25
  # overrides this with the proper virtual host language source
18
26
  @host_language_source = T.let("", String)
19
27
  super
28
+ @code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
29
+ T.proc.params(arg0: Integer).returns(Integer),
30
+ Prism::CodeUnitsCache,
31
+ ))
20
32
  end
21
33
 
22
34
  sig { override.returns(T::Boolean) }
@@ -30,6 +42,7 @@ module RubyLsp
30
42
  # Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
31
43
  # which they will be evaluated
32
44
  @parse_result = Prism.parse(scanner.ruby, partial_script: true)
45
+ @code_units_cache = @parse_result.code_units_cache(@encoding)
33
46
  true
34
47
  end
35
48
 
@@ -50,7 +63,12 @@ module RubyLsp
50
63
  ).returns(NodeContext)
51
64
  end
52
65
  def locate_node(position, node_types: [])
53
- RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
66
+ RubyDocument.locate(
67
+ @parse_result.value,
68
+ create_scanner.find_char_position(position),
69
+ code_units_cache: @code_units_cache,
70
+ node_types: node_types,
71
+ )
54
72
  end
55
73
 
56
74
  sig { params(char_position: Integer).returns(T.nilable(T::Boolean)) }
@@ -75,10 +75,12 @@ require "ruby_lsp/requests/hover"
75
75
  require "ruby_lsp/requests/inlay_hints"
76
76
  require "ruby_lsp/requests/on_type_formatting"
77
77
  require "ruby_lsp/requests/prepare_type_hierarchy"
78
+ require "ruby_lsp/requests/range_formatting"
79
+ require "ruby_lsp/requests/references"
80
+ require "ruby_lsp/requests/rename"
78
81
  require "ruby_lsp/requests/selection_ranges"
79
82
  require "ruby_lsp/requests/semantic_highlighting"
80
83
  require "ruby_lsp/requests/show_syntax_tree"
81
84
  require "ruby_lsp/requests/signature_help"
82
85
  require "ruby_lsp/requests/type_hierarchy_supertypes"
83
86
  require "ruby_lsp/requests/workspace_symbol"
84
- require "ruby_lsp/requests/rename"
@@ -39,6 +39,7 @@ module RubyLsp
39
39
  :on_block_argument_node_enter,
40
40
  :on_constant_read_node_enter,
41
41
  :on_constant_path_node_enter,
42
+ :on_global_variable_read_node_enter,
42
43
  :on_instance_variable_read_node_enter,
43
44
  :on_instance_variable_write_node_enter,
44
45
  :on_instance_variable_and_write_node_enter,
@@ -120,6 +121,25 @@ module RubyLsp
120
121
  find_in_index(name)
121
122
  end
122
123
 
124
+ sig { params(node: Prism::GlobalVariableReadNode).void }
125
+ def on_global_variable_read_node_enter(node)
126
+ entries = @index[node.name.to_s]
127
+
128
+ return unless entries
129
+
130
+ entries.each do |entry|
131
+ location = entry.location
132
+
133
+ @response_builder << Interface::Location.new(
134
+ uri: URI::Generic.from_path(path: entry.file_path).to_s,
135
+ range: Interface::Range.new(
136
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
137
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
138
+ ),
139
+ )
140
+ end
141
+ end
142
+
123
143
  sig { params(node: Prism::InstanceVariableReadNode).void }
124
144
  def on_instance_variable_read_node_enter(node)
125
145
  handle_instance_variable_definition(node.name.to_s)