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.
- checksums.yaml +4 -4
- data/README.md +11 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +26 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
- data/lib/ruby_indexer/ruby_indexer.rb +1 -8
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +17 -17
- data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
- data/lib/ruby_indexer/test/index_test.rb +367 -17
- data/lib/ruby_indexer/test/method_test.rb +58 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +22 -5
- data/lib/ruby_lsp/base_server.rb +8 -3
- data/lib/ruby_lsp/document.rb +27 -46
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +47 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +161 -57
- data/lib/ruby_lsp/listeners/definition.rb +91 -27
- data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
- data/lib/ruby_lsp/listeners/hover.rb +61 -19
- data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
- data/lib/ruby_lsp/node_context.rb +65 -5
- data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
- data/lib/ruby_lsp/requests/code_actions.rb +11 -2
- data/lib/ruby_lsp/requests/completion.rb +4 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
- data/lib/ruby_lsp/requests/definition.rb +18 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
- data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
- data/lib/ruby_lsp/requests/formatting.rb +15 -0
- data/lib/ruby_lsp/requests/hover.rb +5 -5
- data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +11 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +74 -0
- data/lib/ruby_lsp/server.rb +129 -54
- data/lib/ruby_lsp/store.rb +33 -9
- data/lib/ruby_lsp/test_helper.rb +3 -1
- data/lib/ruby_lsp/type_inferrer.rb +61 -25
- data/lib/ruby_lsp/utils.rb +13 -0
- metadata +9 -8
- 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
|
-
|
127
|
-
|
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
|
-
|
143
|
-
|
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
|
-
|
159
|
-
|
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
|
-
|
175
|
-
|
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
|
-
|
195
|
-
|
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
|
-
|
220
|
-
|
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
|
-
|
230
|
-
|
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
|
-
|
240
|
-
|
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
|
-
|
257
|
-
|
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
|
-
|
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
|
-
|
290
|
+
parameters = entry.signatures.first.parameters
|
291
|
+
assert_equal(1, parameters.length)
|
281
292
|
|
282
|
-
param =
|
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
|
-
|
298
|
-
|
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
|
-
|
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.
|
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.
|
48
|
+
refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
|
53
49
|
end
|
54
50
|
end
|
55
51
|
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -49,15 +49,18 @@ module RubyLsp
|
|
49
49
|
super
|
50
50
|
end
|
51
51
|
|
52
|
-
# Discovers and loads all addons. Returns
|
53
|
-
sig
|
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").
|
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
|
-
|
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[
|
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,
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|