ruby-lsp 0.17.4 → 0.17.13

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +26 -1
  5. data/exe/ruby-lsp-check +1 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
  7. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
  10. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -8
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
  13. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  14. data/lib/ruby_indexer/test/constant_test.rb +17 -17
  15. data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
  16. data/lib/ruby_indexer/test/index_test.rb +367 -17
  17. data/lib/ruby_indexer/test/method_test.rb +58 -25
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
  19. data/lib/ruby_indexer/test/test_case.rb +1 -5
  20. data/lib/ruby_lsp/addon.rb +22 -5
  21. data/lib/ruby_lsp/base_server.rb +8 -3
  22. data/lib/ruby_lsp/document.rb +27 -46
  23. data/lib/ruby_lsp/erb_document.rb +125 -0
  24. data/lib/ruby_lsp/global_state.rb +47 -19
  25. data/lib/ruby_lsp/internal.rb +2 -0
  26. data/lib/ruby_lsp/listeners/completion.rb +161 -57
  27. data/lib/ruby_lsp/listeners/definition.rb +91 -27
  28. data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
  29. data/lib/ruby_lsp/listeners/hover.rb +61 -19
  30. data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
  31. data/lib/ruby_lsp/node_context.rb +65 -5
  32. data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
  33. data/lib/ruby_lsp/requests/code_actions.rb +11 -2
  34. data/lib/ruby_lsp/requests/completion.rb +4 -4
  35. data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
  36. data/lib/ruby_lsp/requests/definition.rb +18 -8
  37. data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
  38. data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
  39. data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
  40. data/lib/ruby_lsp/requests/formatting.rb +15 -0
  41. data/lib/ruby_lsp/requests/hover.rb +5 -5
  42. data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
  43. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  44. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  45. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  46. data/lib/ruby_lsp/requests/support/common.rb +11 -2
  47. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
  48. data/lib/ruby_lsp/ruby_document.rb +74 -0
  49. data/lib/ruby_lsp/server.rb +129 -54
  50. data/lib/ruby_lsp/store.rb +33 -9
  51. data/lib/ruby_lsp/test_helper.rb +3 -1
  52. data/lib/ruby_lsp/type_inferrer.rb +61 -25
  53. data/lib/ruby_lsp/utils.rb +13 -0
  54. metadata +9 -8
  55. data/exe/ruby-lsp-doctor +0 -23
@@ -123,8 +123,9 @@ module RubyIndexer
123
123
 
124
124
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
125
125
  entry = T.must(@index["bar"].first)
126
- assert_equal(1, entry.parameters.length)
127
- parameter = entry.parameters.first
126
+ parameters = entry.signatures.first.parameters
127
+ assert_equal(1, parameters.length)
128
+ parameter = parameters.first
128
129
  assert_equal(:a, parameter.name)
129
130
  assert_instance_of(Entry::RequiredParameter, parameter)
130
131
  end
@@ -139,8 +140,9 @@ module RubyIndexer
139
140
 
140
141
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
141
142
  entry = T.must(@index["bar"].first)
142
- assert_equal(1, entry.parameters.length)
143
- parameter = entry.parameters.first
143
+ parameters = entry.signatures.first.parameters
144
+ assert_equal(1, parameters.length)
145
+ parameter = parameters.first
144
146
  assert_equal(:"(a, (b, ))", parameter.name)
145
147
  assert_instance_of(Entry::RequiredParameter, parameter)
146
148
  end
@@ -155,8 +157,9 @@ module RubyIndexer
155
157
 
156
158
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
157
159
  entry = T.must(@index["bar"].first)
158
- assert_equal(1, entry.parameters.length)
159
- parameter = entry.parameters.first
160
+ parameters = entry.signatures.first.parameters
161
+ assert_equal(1, parameters.length)
162
+ parameter = parameters.first
160
163
  assert_equal(:a, parameter.name)
161
164
  assert_instance_of(Entry::OptionalParameter, parameter)
162
165
  end
@@ -171,8 +174,9 @@ module RubyIndexer
171
174
 
172
175
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
173
176
  entry = T.must(@index["bar"].first)
174
- assert_equal(2, entry.parameters.length)
175
- a, b = entry.parameters
177
+ parameters = entry.signatures.first.parameters
178
+ assert_equal(2, parameters.length)
179
+ a, b = parameters
176
180
 
177
181
  assert_equal(:a, a.name)
178
182
  assert_instance_of(Entry::KeywordParameter, a)
@@ -191,8 +195,9 @@ module RubyIndexer
191
195
 
192
196
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
193
197
  entry = T.must(@index["bar"].first)
194
- assert_equal(2, entry.parameters.length)
195
- a, b = entry.parameters
198
+ parameters = entry.signatures.first.parameters
199
+ assert_equal(2, parameters.length)
200
+ a, b = parameters
196
201
 
197
202
  assert_equal(:a, a.name)
198
203
  assert_instance_of(Entry::RestParameter, a)
@@ -216,8 +221,9 @@ module RubyIndexer
216
221
 
217
222
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
218
223
  entry = T.must(@index["bar"].first)
219
- assert_equal(2, entry.parameters.length)
220
- a, b = entry.parameters
224
+ parameters = entry.signatures.first.parameters
225
+ assert_equal(2, parameters.length)
226
+ a, b = parameters
221
227
 
222
228
  assert_equal(:a, a.name)
223
229
  assert_instance_of(Entry::RestParameter, a)
@@ -226,8 +232,9 @@ module RubyIndexer
226
232
  assert_instance_of(Entry::RequiredParameter, b)
227
233
 
228
234
  entry = T.must(@index["baz"].first)
229
- assert_equal(2, entry.parameters.length)
230
- a, b = entry.parameters
235
+ parameters = entry.signatures.first.parameters
236
+ assert_equal(2, parameters.length)
237
+ a, b = parameters
231
238
 
232
239
  assert_equal(:a, a.name)
233
240
  assert_instance_of(Entry::KeywordRestParameter, a)
@@ -236,8 +243,9 @@ module RubyIndexer
236
243
  assert_instance_of(Entry::RequiredParameter, b)
237
244
 
238
245
  entry = T.must(@index["qux"].first)
239
- assert_equal(2, entry.parameters.length)
240
- _a, second = entry.parameters
246
+ parameters = entry.signatures.first.parameters
247
+ assert_equal(2, parameters.length)
248
+ _a, second = parameters
241
249
 
242
250
  assert_equal(:"(b, c)", second.name)
243
251
  assert_instance_of(Entry::RequiredParameter, second)
@@ -253,8 +261,9 @@ module RubyIndexer
253
261
 
254
262
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
255
263
  entry = T.must(@index["bar"].first)
256
- assert_equal(1, entry.parameters.length)
257
- param = entry.parameters.first
264
+ parameters = entry.signatures.first.parameters
265
+ assert_equal(1, parameters.length)
266
+ param = parameters.first
258
267
 
259
268
  assert_equal(:"(a, *b)", param.name)
260
269
  assert_instance_of(Entry::RequiredParameter, param)
@@ -272,14 +281,16 @@ module RubyIndexer
272
281
  RUBY
273
282
 
274
283
  entry = T.must(@index["bar"].first)
275
- param = entry.parameters.first
284
+ parameters = entry.signatures.first.parameters
285
+ param = parameters.first
276
286
  assert_equal(:block, param.name)
277
287
  assert_instance_of(Entry::BlockParameter, param)
278
288
 
279
289
  entry = T.must(@index["baz"].first)
280
- assert_equal(1, entry.parameters.length)
290
+ parameters = entry.signatures.first.parameters
291
+ assert_equal(1, parameters.length)
281
292
 
282
- param = entry.parameters.first
293
+ param = parameters.first
283
294
  assert_equal(Entry::BlockParameter::DEFAULT_NAME, param.name)
284
295
  assert_instance_of(Entry::BlockParameter, param)
285
296
  end
@@ -294,8 +305,9 @@ module RubyIndexer
294
305
 
295
306
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
296
307
  entry = T.must(@index["bar"].first)
297
- assert_equal(2, entry.parameters.length)
298
- first, second = entry.parameters
308
+ parameters = entry.signatures.first.parameters
309
+ assert_equal(2, parameters.length)
310
+ first, second = parameters
299
311
 
300
312
  assert_equal(Entry::RestParameter::DEFAULT_NAME, first.name)
301
313
  assert_instance_of(Entry::RestParameter, first)
@@ -314,7 +326,8 @@ module RubyIndexer
314
326
 
315
327
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
316
328
  entry = T.must(@index["bar"].first)
317
- assert_empty(entry.parameters)
329
+ parameters = entry.signatures.first.parameters
330
+ assert_empty(parameters)
318
331
  end
319
332
 
320
333
  def test_keeps_track_of_method_owner
@@ -401,7 +414,7 @@ module RubyIndexer
401
414
  assert_entry("foo", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:2-15:2-19")
402
415
  assert_entry("bar", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:3-15:3-20")
403
416
  # Foo plus 3 valid aliases
404
- assert_equal(4, @index.instance_variable_get(:@entries).length - @default_indexed_entries.length)
417
+ assert_equal(4, @index.length - @default_indexed_entries.length)
405
418
  end
406
419
 
407
420
  def test_singleton_methods
@@ -428,5 +441,25 @@ module RubyIndexer
428
441
  # the exact same
429
442
  assert_same(bar_owner, baz_owner)
430
443
  end
444
+
445
+ def test_name_location_points_to_method_identifier_location
446
+ index(<<~RUBY)
447
+ class Foo
448
+ def bar
449
+ a = 123
450
+ a + 456
451
+ end
452
+ end
453
+ RUBY
454
+
455
+ entry = T.must(@index["bar"].first)
456
+ refute_equal(entry.location, entry.name_location)
457
+
458
+ name_location = entry.name_location
459
+ assert_equal(2, name_location.start_line)
460
+ assert_equal(2, name_location.end_line)
461
+ assert_equal(6, name_location.start_column)
462
+ assert_equal(9, name_location.end_column)
463
+ end
431
464
  end
432
465
  end
@@ -40,6 +40,27 @@ module RubyIndexer
40
40
  assert_operator(entry.location.end_column, :>, 0)
41
41
  end
42
42
 
43
+ def test_index_core_constants
44
+ entries = @index["RUBY_VERSION"]
45
+ refute_nil(entries)
46
+ assert_equal(1, entries.length)
47
+
48
+ # Complex::I is defined as `Complex::I = ...`
49
+ entries = @index["Complex::I"]
50
+ refute_nil(entries)
51
+ assert_equal(1, entries.length)
52
+
53
+ # Encoding::US_ASCII is defined as
54
+ # ```
55
+ # module Encoding
56
+ # US_ASCII = ...
57
+ # ...
58
+ # ````
59
+ entries = @index["Encoding::US_ASCII"]
60
+ refute_nil(entries)
61
+ assert_equal(1, entries.length)
62
+ end
63
+
43
64
  def test_index_methods
44
65
  entries = @index["initialize"]
45
66
  refute_nil(entries)
@@ -63,5 +84,281 @@ module RubyIndexer
63
84
  assert_instance_of(Entry::SingletonClass, owner)
64
85
  assert_equal("File::<Class:File>", owner.name)
65
86
  end
87
+
88
+ def test_location_and_name_location_are_the_same
89
+ # NOTE: RBS does not store the name location for classes, modules or methods. This behaviour is not exactly what
90
+ # we would like, but for now we assign the same location to both
91
+
92
+ entries = @index["Array"]
93
+ refute_nil(entries)
94
+ entry = entries.find { |entry| entry.is_a?(Entry::Class) }
95
+
96
+ assert_same(entry.location, entry.name_location)
97
+ end
98
+
99
+ def test_rbs_method_with_required_positionals
100
+ entries = @index["crypt"]
101
+ assert_equal(1, entries.length)
102
+
103
+ entry = entries.first
104
+ signatures = entry.signatures
105
+ assert_equal(1, signatures.length)
106
+
107
+ first_signature = signatures.first
108
+
109
+ # (::string salt_str) -> ::String
110
+
111
+ assert_equal(1, first_signature.parameters.length)
112
+ assert_kind_of(Entry::RequiredParameter, first_signature.parameters[0])
113
+ assert_equal(:salt_str, first_signature.parameters[0].name)
114
+ end
115
+
116
+ def test_rbs_method_with_unnamed_required_positionals
117
+ entries = @index["try_convert"]
118
+ entry = entries.find { |entry| entry.owner.name == "Array::<Class:Array>" }
119
+
120
+ parameters = entry.signatures[0].parameters
121
+
122
+ assert_equal([:arg0], parameters.map(&:name))
123
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
124
+ end
125
+
126
+ def test_rbs_method_with_optional_positionals
127
+ entries = @index["polar"]
128
+ entry = entries.find { |entry| entry.owner.name == "Complex::<Class:Complex>" }
129
+
130
+ # def self.polar: (Numeric, ?Numeric) -> Complex
131
+
132
+ parameters = entry.signatures[0].parameters
133
+
134
+ assert_equal([:arg0, :arg1], parameters.map(&:name))
135
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
136
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
137
+ end
138
+
139
+ def test_rbs_method_with_optional_parameter
140
+ entries = @index["chomp"]
141
+ assert_equal(1, entries.length)
142
+
143
+ entry = entries.first
144
+ signatures = entry.signatures
145
+ assert_equal(1, signatures.length)
146
+
147
+ first_signature = signatures.first
148
+
149
+ # (?::string? separator) -> ::String
150
+
151
+ assert_equal(1, first_signature.parameters.length)
152
+ assert_kind_of(Entry::OptionalParameter, first_signature.parameters[0])
153
+ assert_equal(:separator, first_signature.parameters[0].name)
154
+ end
155
+
156
+ def test_rbs_method_with_required_and_optional_parameters
157
+ entries = @index["gsub"]
158
+ assert_equal(1, entries.length)
159
+
160
+ entry = entries.first
161
+
162
+ signatures = entry.signatures
163
+ assert_equal(3, signatures.length)
164
+
165
+ # (::Regexp | ::string pattern, ::string | ::hash[::String, ::_ToS] replacement) -> ::String
166
+ # | (::Regexp | ::string pattern) -> ::Enumerator[::String, ::String]
167
+ # | (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String
168
+
169
+ parameters = signatures[0].parameters
170
+ assert_equal([:pattern, :replacement], parameters.map(&:name))
171
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
172
+ assert_kind_of(Entry::RequiredParameter, parameters[1])
173
+
174
+ parameters = signatures[1].parameters
175
+ assert_equal([:pattern], parameters.map(&:name))
176
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
177
+
178
+ parameters = signatures[2].parameters
179
+ assert_equal([:pattern, :"<anonymous block>"], parameters.map(&:name))
180
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
181
+ assert_kind_of(Entry::BlockParameter, parameters[1])
182
+ end
183
+
184
+ def test_rbs_anonymous_block_parameter
185
+ entries = @index["open"]
186
+ entry = entries.find { |entry| entry.owner.name == "File::<Class:File>" }
187
+
188
+ assert_equal(2, entry.signatures.length)
189
+
190
+ # (::String name, ?::String mode, ?::Integer perm) -> ::IO?
191
+ # | [T] (::String name, ?::String mode, ?::Integer perm) { (::IO) -> T } -> T
192
+
193
+ parameters = entry.signatures[0].parameters
194
+ assert_equal([:file_name, :mode, :perm], parameters.map(&:name))
195
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
196
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
197
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
198
+
199
+ parameters = entry.signatures[1].parameters
200
+ assert_equal([:file_name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
201
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
202
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
203
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
204
+ assert_kind_of(Entry::BlockParameter, parameters[3])
205
+ end
206
+
207
+ def test_rbs_method_with_rest_positionals
208
+ entries = @index["count"]
209
+ entry = entries.find { |entry| entry.owner.name == "String" }
210
+
211
+ parameters = entry.signatures.first.parameters
212
+ assert_equal(1, entry.signatures.length)
213
+
214
+ # (::String::selector selector_0, *::String::selector more_selectors) -> ::Integer
215
+
216
+ assert_equal([:selector_0, :more_selectors], parameters.map(&:name))
217
+ assert_kind_of(RubyIndexer::Entry::RequiredParameter, parameters[0])
218
+ assert_kind_of(RubyIndexer::Entry::RestParameter, parameters[1])
219
+ end
220
+
221
+ def test_rbs_method_with_trailing_positionals
222
+ entries = @index["select"] # https://ruby-doc.org/3.3.3/IO.html#method-c-select
223
+ entry = entries.find { |entry| entry.owner.name == "IO::<Class:IO>" }
224
+
225
+ signatures = entry.signatures
226
+ assert_equal(2, signatures.length)
227
+
228
+ # def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] # rubocop:disable Layout/LineLength
229
+ # | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]? # rubocop:disable Layout/LineLength
230
+
231
+ parameters = signatures[0].parameters
232
+ assert_equal([:read_array, :write_array, :error_array], parameters.map(&:name))
233
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
234
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
235
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
236
+
237
+ parameters = signatures[1].parameters
238
+ assert_equal([:read_array, :write_array, :error_array, :timeout], parameters.map(&:name))
239
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
240
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
241
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
242
+ assert_kind_of(Entry::OptionalParameter, parameters[3])
243
+ end
244
+
245
+ def test_rbs_method_with_optional_keywords
246
+ entries = @index["step"]
247
+ entry = entries.find { |entry| entry.owner.name == "Numeric" }
248
+
249
+ signatures = entry.signatures
250
+ assert_equal(4, signatures.length)
251
+
252
+ # (?::Numeric limit, ?::Numeric step) { (::Numeric) -> void } -> self
253
+ # | (?::Numeric limit, ?::Numeric step) -> ::Enumerator[::Numeric, self]
254
+ # | (?by: ::Numeric, ?to: ::Numeric) { (::Numeric) -> void } -> self
255
+ # | (?by: ::Numeric, ?to: ::Numeric) -> ::Enumerator[::Numeric, self]
256
+
257
+ parameters = signatures[0].parameters
258
+ assert_equal([:limit, :step, :"<anonymous block>"], parameters.map(&:name))
259
+ assert_kind_of(Entry::OptionalParameter, parameters[0])
260
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
261
+ assert_kind_of(Entry::BlockParameter, parameters[2])
262
+
263
+ parameters = signatures[1].parameters
264
+ assert_equal([:limit, :step], parameters.map(&:name))
265
+ assert_kind_of(Entry::OptionalParameter, parameters[0])
266
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
267
+
268
+ parameters = signatures[2].parameters
269
+ assert_equal([:by, :to, :"<anonymous block>"], parameters.map(&:name))
270
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
271
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
272
+ assert_kind_of(Entry::BlockParameter, parameters[2])
273
+
274
+ parameters = signatures[3].parameters
275
+ assert_equal([:by, :to], parameters.map(&:name))
276
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
277
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
278
+ end
279
+
280
+ def test_rbs_method_with_required_keywords
281
+ # There are no methods in Core that have required keyword arguments,
282
+ # so we test against RBS directly
283
+
284
+ rbs = <<~RBS
285
+ class File
286
+ def foo: (a: ::Numeric sz, b: ::Numeric) -> void
287
+ end
288
+ RBS
289
+ signatures = parse_rbs_methods(rbs, "foo")
290
+ parameters = signatures[0].parameters
291
+ assert_equal([:a, :b], parameters.map(&:name))
292
+ assert_kind_of(Entry::KeywordParameter, parameters[0])
293
+ assert_kind_of(Entry::KeywordParameter, parameters[1])
294
+ end
295
+
296
+ def test_rbs_method_with_rest_keywords
297
+ entries = @index["method_missing"]
298
+ entry = entries.find { |entry| entry.owner.name == "BasicObject" }
299
+ signatures = entry.signatures
300
+ assert_equal(1, signatures.length)
301
+
302
+ # (Symbol, *untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
303
+
304
+ parameters = signatures[0].parameters
305
+ assert_equal([:arg0, :"<anonymous splat>", :"<anonymous keyword splat>"], parameters.map(&:name))
306
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
307
+ assert_kind_of(Entry::RestParameter, parameters[1])
308
+ assert_kind_of(Entry::KeywordRestParameter, parameters[2])
309
+ end
310
+
311
+ def test_parse_simple_rbs
312
+ rbs = <<~RBS
313
+ class File
314
+ def self?.open: (String name, ?String mode, ?Integer perm) -> IO?
315
+ | [T] (String name, ?String mode, ?Integer perm) { (IO) -> T } -> T
316
+ end
317
+ RBS
318
+ signatures = parse_rbs_methods(rbs, "open")
319
+ assert_equal(2, signatures.length)
320
+ parameters = signatures[0].parameters
321
+ assert_equal([:name, :mode, :perm], parameters.map(&:name))
322
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
323
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
324
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
325
+
326
+ parameters = signatures[1].parameters
327
+ assert_equal([:name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
328
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
329
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
330
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
331
+ assert_kind_of(Entry::BlockParameter, parameters[3])
332
+ end
333
+
334
+ def test_signature_alias
335
+ # In RBS, an alias means that two methods have the same signature.
336
+ # It does not mean the same thing as a Ruby alias.
337
+ any_entries = @index["any?"]
338
+
339
+ assert_equal(["Array", "Enumerable", "Hash"], any_entries.map { _1.owner.name })
340
+
341
+ entry = any_entries.find { |entry| entry.owner.name == "Array" }
342
+
343
+ assert_kind_of(RubyIndexer::Entry::UnresolvedMethodAlias, entry)
344
+ assert_equal("any?", entry.name)
345
+ assert_equal("all?", entry.old_name)
346
+ assert_equal("Array", entry.owner.name)
347
+ assert(entry.file_path.end_with?("core/array.rbs"))
348
+ assert_includes(entry.comments[0], "Returns `true` if any element of `self` meets a given criterion.")
349
+ end
350
+
351
+ private
352
+
353
+ def parse_rbs_methods(rbs, method_name)
354
+ buffer = RBS::Buffer.new(content: rbs, name: "")
355
+ _, _, declarations = RBS::Parser.parse_signature(buffer)
356
+ index = RubyIndexer::Index.new
357
+ indexer = RubyIndexer::RBSIndexer.new(index)
358
+ pathname = Pathname.new("file.rbs")
359
+ indexer.process_signature(pathname, declarations)
360
+ entry = T.must(index[method_name]).first
361
+ T.cast(entry, Entry::Method).signatures
362
+ end
66
363
  end
67
364
  end
@@ -40,16 +40,12 @@ module RubyIndexer
40
40
  assert_nil(entries, "Expected #{expected_name} to not be indexed")
41
41
  end
42
42
 
43
- def assert_no_entries
44
- assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
45
- end
46
-
47
43
  def assert_no_indexed_entries
48
44
  assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
49
45
  end
50
46
 
51
47
  def assert_no_entry(entry)
52
- refute(@index.instance_variable_get(:@entries).key?(entry), "Expected '#{entry}' to not be indexed")
48
+ refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
53
49
  end
54
50
  end
55
51
  end
@@ -49,15 +49,18 @@ module RubyLsp
49
49
  super
50
50
  end
51
51
 
52
- # Discovers and loads all addons. Returns the list of activated addons
53
- sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[Addon]) }
52
+ # Discovers and loads all addons. Returns a list of errors when trying to require addons
53
+ sig do
54
+ params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[StandardError])
55
+ end
54
56
  def load_addons(global_state, outgoing_queue)
55
57
  # Require all addons entry points, which should be placed under
56
58
  # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
57
- Gem.find_files("ruby_lsp/**/addon.rb").each do |addon|
59
+ errors = Gem.find_files("ruby_lsp/**/addon.rb").filter_map do |addon|
58
60
  require File.expand_path(addon)
61
+ nil
59
62
  rescue => e
60
- $stderr.puts(e.full_message)
63
+ e
61
64
  end
62
65
 
63
66
  # Instantiate all discovered addon classes
@@ -71,6 +74,17 @@ module RubyLsp
71
74
  rescue => e
72
75
  addon.add_error(e)
73
76
  end
77
+
78
+ errors
79
+ end
80
+
81
+ # Intended for use by tests for addons
82
+ sig { params(addon_name: String).returns(Addon) }
83
+ def get(addon_name)
84
+ addon = addons.find { |addon| addon.name == addon_name }
85
+ raise "Could not find addon '#{addon_name}'" unless addon
86
+
87
+ addon
74
88
  end
75
89
  end
76
90
 
@@ -157,7 +171,10 @@ module RubyLsp
157
171
  # Creates a new Definition listener. This method is invoked on every Definition request
158
172
  sig do
159
173
  overridable.params(
160
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
174
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
175
+ Interface::Location,
176
+ Interface::LocationLink,
177
+ )],
161
178
  uri: URI::Generic,
162
179
  node_context: NodeContext,
163
180
  dispatcher: Prism::Dispatcher,
@@ -53,7 +53,7 @@ module RubyLsp
53
53
 
54
54
  # We don't want to try to parse documents on text synchronization notifications
55
55
  @store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
56
- rescue Errno::ENOENT
56
+ rescue Store::NonExistingDocumentError
57
57
  # If we receive a request for a file that no longer exists, we don't want to fail
58
58
  end
59
59
  end
@@ -65,7 +65,7 @@ module RubyLsp
65
65
  when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
66
66
  process_message(message)
67
67
  when "shutdown"
68
- $stderr.puts("Shutting down Ruby LSP...")
68
+ send_log_message("Shutting down Ruby LSP...")
69
69
 
70
70
  shutdown
71
71
 
@@ -76,7 +76,7 @@ module RubyLsp
76
76
  when "exit"
77
77
  @mutex.synchronize do
78
78
  status = @incoming_queue.closed? ? 0 : 1
79
- $stderr.puts("Shutdown complete with status #{status}")
79
+ send_log_message("Shutdown complete with status #{status}")
80
80
  exit(status)
81
81
  end
82
82
  else
@@ -145,5 +145,10 @@ module RubyLsp
145
145
  def send_empty_response(id)
146
146
  send_message(Result.new(id: id, response: nil))
147
147
  end
148
+
149
+ sig { params(message: String, type: Integer).void }
150
+ def send_log_message(message, type: Constant::MessageType::LOG)
151
+ send_message(Notification.window_log_message(message, type: Constant::MessageType::LOG))
152
+ end
148
153
  end
149
154
  end