ruby-lsp 0.18.3 → 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 (54) 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 +52 -8
  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 +22 -0
  19. data/lib/ruby_indexer/test/reference_finder_test.rb +242 -0
  20. data/lib/ruby_lsp/addon.rb +79 -17
  21. data/lib/ruby_lsp/base_server.rb +6 -0
  22. data/lib/ruby_lsp/erb_document.rb +9 -3
  23. data/lib/ruby_lsp/global_state.rb +8 -0
  24. data/lib/ruby_lsp/internal.rb +5 -1
  25. data/lib/ruby_lsp/listeners/completion.rb +1 -1
  26. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  27. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +24 -21
  28. data/lib/ruby_lsp/requests/code_action_resolve.rb +9 -3
  29. data/lib/ruby_lsp/requests/completion.rb +1 -0
  30. data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
  31. data/lib/ruby_lsp/requests/definition.rb +1 -0
  32. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  33. data/lib/ruby_lsp/requests/hover.rb +1 -0
  34. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  35. data/lib/ruby_lsp/requests/range_formatting.rb +55 -0
  36. data/lib/ruby_lsp/requests/references.rb +146 -0
  37. data/lib/ruby_lsp/requests/rename.rb +196 -0
  38. data/lib/ruby_lsp/requests/signature_help.rb +6 -1
  39. data/lib/ruby_lsp/requests/support/common.rb +2 -2
  40. data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
  41. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
  42. data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
  43. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
  44. data/lib/ruby_lsp/ruby_document.rb +23 -8
  45. data/lib/ruby_lsp/scope.rb +47 -0
  46. data/lib/ruby_lsp/server.rb +127 -34
  47. data/lib/ruby_lsp/static_docs.rb +15 -0
  48. data/lib/ruby_lsp/store.rb +12 -0
  49. data/lib/ruby_lsp/test_helper.rb +1 -1
  50. data/lib/ruby_lsp/type_inferrer.rb +6 -1
  51. data/lib/ruby_lsp/utils.rb +3 -6
  52. data/static_docs/yield.md +81 -0
  53. metadata +21 -8
  54. data/lib/ruby_lsp/parameter_scope.rb +0 -33
@@ -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)
@@ -348,6 +362,14 @@ module RubyIndexer
348
362
  assert_includes(entry.comments, "Returns `true` if any element of `self` meets a given criterion.")
349
363
  end
350
364
 
365
+ def test_indexing_untyped_functions
366
+ entries = @index.resolve_method("call", "Method")
367
+
368
+ parameters = entries.first.signatures.first.parameters
369
+ assert_equal(1, parameters.length)
370
+ assert_instance_of(Entry::ForwardingParameter, parameters.first)
371
+ end
372
+
351
373
  private
352
374
 
353
375
  def parse_rbs_methods(rbs, method_name)