ruby-lsp 0.18.3 → 0.19.1

Sign up to get free protection for your applications and to get access to all the features.
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)