ruby-lsp 0.17.5 → 0.17.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30ab87e9f42251fafcf8c085d14272b6e0cc0fb6e6968db3fc4c304e92000c63
4
- data.tar.gz: 305592ad82c3e41b72dff4c0a48ff60e6158ffba6d06b802844418c3873a6ef9
3
+ metadata.gz: 671bab4fdbd77d1980781f55874d6cd448e1dcd11a6f534a176dfb00b098d2eb
4
+ data.tar.gz: bd12fd2ef58588b3b5386fec48c0f561140efff180988f1971b67f635f0e1042
5
5
  SHA512:
6
- metadata.gz: 3a38745ee99e4e62843f3bc2a698ec8c447d9bbb39091e158b2a64e36e46a1612962d1062635a007e13aabd3709733b9726a302ef680bd2881673e6abd48e038
7
- data.tar.gz: 57b5327b0f17af4530b21ba3c51eb2bff13912e76df77f78284cd9644cebef252e1cbdd38b960de19cc64a68d9daf3788c6e78ef3b95b4bb5d6dc7f97e56c58f
6
+ metadata.gz: f3a2a3115e1745ad7263b7e113ef13795f9a1ed1ee118128a9ab2aaad703b17551acbd929f820700e6650efb76421418a13a78270ac8d3aaff080edc46731aee
7
+ data.tar.gz: d29bb4d6c60727eef1a10905762a6d0c8946b7afc8aac6148ef970c8d752365f1577403497702f52a7211689cde7940b3d7cd1e1e0eb03d038e0dc6f7ceed738
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.5
1
+ 0.17.7
@@ -288,6 +288,14 @@ module RubyIndexer
288
288
  class BlockParameter < Parameter
289
289
  DEFAULT_NAME = T.let(:"<anonymous block>", Symbol)
290
290
 
291
+ class << self
292
+ extend T::Sig
293
+ sig { returns(BlockParameter) }
294
+ def anonymous
295
+ new(name: DEFAULT_NAME)
296
+ end
297
+ end
298
+
291
299
  sig { override.returns(Symbol) }
292
300
  def decorated_name
293
301
  :"&#{@name}"
@@ -331,14 +331,17 @@ module RubyIndexer
331
331
  params(
332
332
  method_name: String,
333
333
  receiver_name: String,
334
+ inherited_only: T::Boolean,
334
335
  ).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
335
336
  end
336
- def resolve_method(method_name, receiver_name)
337
+ def resolve_method(method_name, receiver_name, inherited_only: false)
337
338
  method_entries = self[method_name]
338
339
  return unless method_entries
339
340
 
340
341
  ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
341
342
  ancestors.each do |ancestor|
343
+ next if inherited_only && ancestor == receiver_name
344
+
342
345
  found = method_entries.filter_map do |entry|
343
346
  case entry
344
347
  when Entry::Member, Entry::MethodAlias
@@ -389,7 +392,7 @@ module RubyIndexer
389
392
  # If we don't have an entry for `name`, raise
390
393
  entries = self[fully_qualified_name]
391
394
 
392
- if singleton_levels > 0 && !entries
395
+ if singleton_levels > 0 && !entries && indexed?(attached_class_name)
393
396
  entries = [existing_or_new_singleton_class(attached_class_name)]
394
397
  end
395
398
 
@@ -15,20 +15,25 @@ module RubyIndexer
15
15
  loader = RBS::EnvironmentLoader.new
16
16
  RBS::Environment.from_loader(loader).resolve_type_names
17
17
 
18
- loader.each_signature do |source, pathname, _buffer, declarations, _directives|
19
- process_signature(source, pathname, declarations)
18
+ loader.each_signature do |_source, pathname, _buffer, declarations, _directives|
19
+ process_signature(pathname, declarations)
20
20
  end
21
21
  end
22
22
 
23
- private
24
-
25
- sig { params(source: T.untyped, pathname: Pathname, declarations: T::Array[RBS::AST::Declarations::Base]).void }
26
- def process_signature(source, pathname, declarations)
23
+ sig do
24
+ params(
25
+ pathname: Pathname,
26
+ declarations: T::Array[RBS::AST::Declarations::Base],
27
+ ).void
28
+ end
29
+ def process_signature(pathname, declarations)
27
30
  declarations.each do |declaration|
28
31
  process_declaration(declaration, pathname)
29
32
  end
30
33
  end
31
34
 
35
+ private
36
+
32
37
  sig { params(declaration: RBS::AST::Declarations::Base, pathname: Pathname).void }
33
38
  def process_declaration(declaration, pathname)
34
39
  case declaration
@@ -122,7 +127,102 @@ module RubyIndexer
122
127
  end
123
128
 
124
129
  real_owner = member.singleton? ? @index.existing_or_new_singleton_class(owner.name) : owner
125
- @index.add(Entry::Method.new(name, file_path, location, location, comments, [], visibility, real_owner))
130
+ signatures = signatures(member)
131
+ @index.add(Entry::Method.new(name, file_path, location, location, comments, signatures, visibility, real_owner))
132
+ end
133
+
134
+ sig { params(member: RBS::AST::Members::MethodDefinition).returns(T::Array[Entry::Signature]) }
135
+ def signatures(member)
136
+ member.overloads.map do |overload|
137
+ parameters = process_overload(overload)
138
+ Entry::Signature.new(parameters)
139
+ end
140
+ end
141
+
142
+ sig { params(overload: RBS::AST::Members::MethodDefinition::Overload).returns(T::Array[Entry::Parameter]) }
143
+ def process_overload(overload)
144
+ function = T.cast(overload.method_type.type, RBS::Types::Function)
145
+ parameters = parse_arguments(function)
146
+
147
+ block = overload.method_type.block
148
+ parameters << Entry::BlockParameter.anonymous if block&.required
149
+
150
+ parameters
151
+ end
152
+
153
+ sig { params(function: RBS::Types::Function).returns(T::Array[Entry::Parameter]) }
154
+ def parse_arguments(function)
155
+ parameters = []
156
+ parameters.concat(process_required_and_optional_positionals(function))
157
+ parameters.concat(process_trailing_positionals(function)) if function.trailing_positionals
158
+ parameters << process_rest_positionals(function) if function.rest_positionals
159
+ parameters.concat(process_required_keywords(function)) if function.required_keywords
160
+ parameters.concat(process_optional_keywords(function)) if function.optional_keywords
161
+ parameters << process_rest_keywords(function) if function.rest_keywords
162
+ parameters
163
+ end
164
+
165
+ sig { params(function: RBS::Types::Function).returns(T::Array[Entry::RequiredParameter]) }
166
+ def process_required_and_optional_positionals(function)
167
+ argument_offset = 0
168
+
169
+ required = function.required_positionals.map.with_index(argument_offset) do |param, i|
170
+ # Some parameters don't have names, e.g.
171
+ # def self.try_convert: [U] (untyped) -> ::Array[U]?
172
+ name = param.name || :"arg#{i}"
173
+ argument_offset += 1
174
+
175
+ Entry::RequiredParameter.new(name: name)
176
+ end
177
+
178
+ optional = function.optional_positionals.map.with_index(argument_offset) do |param, i|
179
+ # Optional positionals may be unnamed, e.g.
180
+ # def self.polar: (Numeric, ?Numeric) -> Complex
181
+ name = param.name || :"arg#{i}"
182
+
183
+ Entry::OptionalParameter.new(name: name)
184
+ end
185
+
186
+ required + optional
187
+ end
188
+
189
+ sig { params(function: RBS::Types::Function).returns(T::Array[Entry::OptionalParameter]) }
190
+ def process_trailing_positionals(function)
191
+ function.trailing_positionals.map do |param|
192
+ Entry::OptionalParameter.new(name: param.name)
193
+ end
194
+ end
195
+
196
+ sig { params(function: RBS::Types::Function).returns(Entry::RestParameter) }
197
+ def process_rest_positionals(function)
198
+ rest = function.rest_positionals
199
+
200
+ rest_name = rest.name || Entry::RestParameter::DEFAULT_NAME
201
+
202
+ Entry::RestParameter.new(name: rest_name)
203
+ end
204
+
205
+ sig { params(function: RBS::Types::Function).returns(T::Array[Entry::KeywordParameter]) }
206
+ def process_required_keywords(function)
207
+ function.required_keywords.map do |name, _param|
208
+ Entry::KeywordParameter.new(name: name)
209
+ end
210
+ end
211
+
212
+ sig { params(function: RBS::Types::Function).returns(T::Array[Entry::OptionalKeywordParameter]) }
213
+ def process_optional_keywords(function)
214
+ function.optional_keywords.map do |name, _param|
215
+ Entry::OptionalKeywordParameter.new(name: name)
216
+ end
217
+ end
218
+
219
+ sig { params(function: RBS::Types::Function).returns(Entry::KeywordRestParameter) }
220
+ def process_rest_keywords(function)
221
+ param = function.rest_keywords
222
+
223
+ name = param.name || Entry::KeywordRestParameter::DEFAULT_NAME
224
+
225
+ Entry::KeywordRestParameter.new(name: name)
126
226
  end
127
227
  end
128
228
  end
@@ -285,6 +285,43 @@ module RubyIndexer
285
285
  assert_includes(second_entry.comments, "Hello from second `bar`")
286
286
  end
287
287
 
288
+ def test_resolve_method_inherited_only
289
+ index(<<~RUBY)
290
+ class Bar
291
+ def baz; end
292
+ end
293
+
294
+ class Foo < Bar
295
+ def baz; end
296
+ end
297
+ RUBY
298
+
299
+ entry = T.must(@index.resolve_method("baz", "Foo", inherited_only: true).first)
300
+
301
+ assert_equal("Bar", T.must(entry.owner).name)
302
+ end
303
+
304
+ def test_resolve_method_inherited_only_for_prepended_module
305
+ index(<<~RUBY)
306
+ module Bar
307
+ def baz
308
+ super
309
+ end
310
+ end
311
+
312
+ class Foo
313
+ prepend Bar
314
+
315
+ def baz; end
316
+ end
317
+ RUBY
318
+
319
+ # This test is just to document the fact that we don't yet support resolving inherited methods for modules that
320
+ # are prepended. The only way to support this is to find all namespaces that have the module a subtype, so that we
321
+ # can show the results for everywhere the module has been prepended.
322
+ assert_nil(@index.resolve_method("baz", "Bar", inherited_only: true))
323
+ end
324
+
288
325
  def test_prefix_search_for_methods
289
326
  index(<<~RUBY)
290
327
  module Foo
@@ -1707,5 +1744,11 @@ module RubyIndexer
1707
1744
  @index.linearized_ancestors_of("A::<Class:A>"),
1708
1745
  )
1709
1746
  end
1747
+
1748
+ def test_linearizing_a_singleton_class_with_no_attached
1749
+ assert_raises(Index::NonExistingNamespaceError) do
1750
+ @index.linearized_ancestors_of("A::<Class:A>")
1751
+ end
1752
+ end
1710
1753
  end
1711
1754
  end
@@ -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
@@ -74,5 +74,253 @@ module RubyIndexer
74
74
 
75
75
  assert_same(entry.location, entry.name_location)
76
76
  end
77
+
78
+ def test_rbs_method_with_required_positionals
79
+ entries = @index["crypt"]
80
+ assert_equal(1, entries.length)
81
+
82
+ entry = entries.first
83
+ signatures = entry.signatures
84
+ assert_equal(1, signatures.length)
85
+
86
+ first_signature = signatures.first
87
+
88
+ # (::string salt_str) -> ::String
89
+
90
+ assert_equal(1, first_signature.parameters.length)
91
+ assert_kind_of(Entry::RequiredParameter, first_signature.parameters[0])
92
+ assert_equal(:salt_str, first_signature.parameters[0].name)
93
+ end
94
+
95
+ def test_rbs_method_with_unnamed_required_positionals
96
+ entries = @index["try_convert"]
97
+ entry = entries.find { |entry| entry.owner.name == "Array::<Class:Array>" }
98
+
99
+ parameters = entry.signatures[0].parameters
100
+
101
+ assert_equal([:arg0], parameters.map(&:name))
102
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
103
+ end
104
+
105
+ def test_rbs_method_with_optional_positionals
106
+ entries = @index["polar"]
107
+ entry = entries.find { |entry| entry.owner.name == "Complex::<Class:Complex>" }
108
+
109
+ # def self.polar: (Numeric, ?Numeric) -> Complex
110
+
111
+ parameters = entry.signatures[0].parameters
112
+
113
+ assert_equal([:arg0, :arg1], parameters.map(&:name))
114
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
115
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
116
+ end
117
+
118
+ def test_rbs_method_with_optional_parameter
119
+ entries = @index["chomp"]
120
+ assert_equal(1, entries.length)
121
+
122
+ entry = entries.first
123
+ signatures = entry.signatures
124
+ assert_equal(1, signatures.length)
125
+
126
+ first_signature = signatures.first
127
+
128
+ # (?::string? separator) -> ::String
129
+
130
+ assert_equal(1, first_signature.parameters.length)
131
+ assert_kind_of(Entry::OptionalParameter, first_signature.parameters[0])
132
+ assert_equal(:separator, first_signature.parameters[0].name)
133
+ end
134
+
135
+ def test_rbs_method_with_required_and_optional_parameters
136
+ entries = @index["gsub"]
137
+ assert_equal(1, entries.length)
138
+
139
+ entry = entries.first
140
+
141
+ signatures = entry.signatures
142
+ assert_equal(3, signatures.length)
143
+
144
+ # (::Regexp | ::string pattern, ::string | ::hash[::String, ::_ToS] replacement) -> ::String
145
+ # | (::Regexp | ::string pattern) -> ::Enumerator[::String, ::String]
146
+ # | (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String
147
+
148
+ parameters = signatures[0].parameters
149
+ assert_equal([:pattern, :replacement], parameters.map(&:name))
150
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
151
+ assert_kind_of(Entry::RequiredParameter, parameters[1])
152
+
153
+ parameters = signatures[1].parameters
154
+ assert_equal([:pattern], parameters.map(&:name))
155
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
156
+
157
+ parameters = signatures[2].parameters
158
+ assert_equal([:pattern, :"<anonymous block>"], parameters.map(&:name))
159
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
160
+ assert_kind_of(Entry::BlockParameter, parameters[1])
161
+ end
162
+
163
+ def test_rbs_anonymous_block_parameter
164
+ entries = @index["open"]
165
+ entry = entries.find { |entry| entry.owner.name == "File::<Class:File>" }
166
+
167
+ assert_equal(2, entry.signatures.length)
168
+
169
+ # (::String name, ?::String mode, ?::Integer perm) -> ::IO?
170
+ # | [T] (::String name, ?::String mode, ?::Integer perm) { (::IO) -> T } -> T
171
+
172
+ parameters = entry.signatures[0].parameters
173
+ assert_equal([:file_name, :mode, :perm], parameters.map(&:name))
174
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
175
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
176
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
177
+
178
+ parameters = entry.signatures[1].parameters
179
+ assert_equal([:file_name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
180
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
181
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
182
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
183
+ assert_kind_of(Entry::BlockParameter, parameters[3])
184
+ end
185
+
186
+ def test_rbs_method_with_rest_positionals
187
+ entries = @index["count"]
188
+ entry = entries.find { |entry| entry.owner.name == "String" }
189
+
190
+ parameters = entry.signatures.first.parameters
191
+ assert_equal(1, entry.signatures.length)
192
+
193
+ # (::String::selector selector_0, *::String::selector more_selectors) -> ::Integer
194
+
195
+ assert_equal([:selector_0, :more_selectors], parameters.map(&:name))
196
+ assert_kind_of(RubyIndexer::Entry::RequiredParameter, parameters[0])
197
+ assert_kind_of(RubyIndexer::Entry::RestParameter, parameters[1])
198
+ end
199
+
200
+ def test_rbs_method_with_trailing_positionals
201
+ entries = @index["select"] # https://ruby-doc.org/3.3.3/IO.html#method-c-select
202
+ entry = entries.find { |entry| entry.owner.name == "IO::<Class:IO>" }
203
+
204
+ signatures = entry.signatures
205
+ assert_equal(2, signatures.length)
206
+
207
+ # 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
208
+ # | [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
209
+
210
+ parameters = signatures[0].parameters
211
+ assert_equal([:read_array, :write_array, :error_array], parameters.map(&:name))
212
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
213
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
214
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
215
+
216
+ parameters = signatures[1].parameters
217
+ assert_equal([:read_array, :write_array, :error_array, :timeout], parameters.map(&:name))
218
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
219
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
220
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
221
+ assert_kind_of(Entry::OptionalParameter, parameters[3])
222
+ end
223
+
224
+ def test_rbs_method_with_optional_keywords
225
+ entries = @index["step"]
226
+ entry = entries.find { |entry| entry.owner.name == "Numeric" }
227
+
228
+ signatures = entry.signatures
229
+ assert_equal(4, signatures.length)
230
+
231
+ # (?::Numeric limit, ?::Numeric step) { (::Numeric) -> void } -> self
232
+ # | (?::Numeric limit, ?::Numeric step) -> ::Enumerator[::Numeric, self]
233
+ # | (?by: ::Numeric, ?to: ::Numeric) { (::Numeric) -> void } -> self
234
+ # | (?by: ::Numeric, ?to: ::Numeric) -> ::Enumerator[::Numeric, self]
235
+
236
+ parameters = signatures[0].parameters
237
+ assert_equal([:limit, :step, :"<anonymous block>"], parameters.map(&:name))
238
+ assert_kind_of(Entry::OptionalParameter, parameters[0])
239
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
240
+ assert_kind_of(Entry::BlockParameter, parameters[2])
241
+
242
+ parameters = signatures[1].parameters
243
+ assert_equal([:limit, :step], parameters.map(&:name))
244
+ assert_kind_of(Entry::OptionalParameter, parameters[0])
245
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
246
+
247
+ parameters = signatures[2].parameters
248
+ assert_equal([:by, :to, :"<anonymous block>"], parameters.map(&:name))
249
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
250
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
251
+ assert_kind_of(Entry::BlockParameter, parameters[2])
252
+
253
+ parameters = signatures[3].parameters
254
+ assert_equal([:by, :to], parameters.map(&:name))
255
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
256
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
257
+ end
258
+
259
+ def test_rbs_method_with_required_keywords
260
+ # There are no methods in Core that have required keyword arguments,
261
+ # so we test against RBS directly
262
+
263
+ rbs = <<~RBS
264
+ class File
265
+ def foo: (a: ::Numeric sz, b: ::Numeric) -> void
266
+ end
267
+ RBS
268
+ signatures = parse_rbs_methods(rbs, "foo")
269
+ parameters = signatures[0].parameters
270
+ assert_equal([:a, :b], parameters.map(&:name))
271
+ assert_kind_of(Entry::KeywordParameter, parameters[0])
272
+ assert_kind_of(Entry::KeywordParameter, parameters[1])
273
+ end
274
+
275
+ def test_rbs_method_with_rest_keywords
276
+ entries = @index["method_missing"]
277
+ entry = entries.find { |entry| entry.owner.name == "BasicObject" }
278
+ signatures = entry.signatures
279
+ assert_equal(1, signatures.length)
280
+
281
+ # (Symbol, *untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
282
+
283
+ parameters = signatures[0].parameters
284
+ assert_equal([:arg0, :"<anonymous splat>", :"<anonymous keyword splat>"], parameters.map(&:name))
285
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
286
+ assert_kind_of(Entry::RestParameter, parameters[1])
287
+ assert_kind_of(Entry::KeywordRestParameter, parameters[2])
288
+ end
289
+
290
+ def test_parse_simple_rbs
291
+ rbs = <<~RBS
292
+ class File
293
+ def self?.open: (String name, ?String mode, ?Integer perm) -> IO?
294
+ | [T] (String name, ?String mode, ?Integer perm) { (IO) -> T } -> T
295
+ end
296
+ RBS
297
+ signatures = parse_rbs_methods(rbs, "open")
298
+ assert_equal(2, signatures.length)
299
+ parameters = signatures[0].parameters
300
+ assert_equal([:name, :mode, :perm], parameters.map(&:name))
301
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
302
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
303
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
304
+
305
+ parameters = signatures[1].parameters
306
+ assert_equal([:name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
307
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
308
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
309
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
310
+ assert_kind_of(Entry::BlockParameter, parameters[3])
311
+ end
312
+
313
+ private
314
+
315
+ def parse_rbs_methods(rbs, method_name)
316
+ buffer = RBS::Buffer.new(content: rbs, name: "")
317
+ _, _, declarations = RBS::Parser.parse_signature(buffer)
318
+ index = RubyIndexer::Index.new
319
+ indexer = RubyIndexer::RBSIndexer.new(index)
320
+ pathname = Pathname.new("file.rbs")
321
+ indexer.process_signature(pathname, declarations)
322
+ entry = T.must(index[method_name]).first
323
+ T.cast(entry, Entry::Method).signatures
324
+ end
77
325
  end
78
326
  end
@@ -127,8 +127,18 @@ module RubyLsp
127
127
  parent = T.let(nil, T.nilable(Prism::Node))
128
128
  nesting_nodes = T.let(
129
129
  [],
130
- T::Array[T.any(Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode)],
130
+ T::Array[T.any(
131
+ Prism::ClassNode,
132
+ Prism::ModuleNode,
133
+ Prism::SingletonClassNode,
134
+ Prism::DefNode,
135
+ Prism::BlockNode,
136
+ Prism::LambdaNode,
137
+ Prism::ProgramNode,
138
+ )],
131
139
  )
140
+
141
+ nesting_nodes << node if node.is_a?(Prism::ProgramNode)
132
142
  call_node = T.let(nil, T.nilable(Prism::CallNode))
133
143
 
134
144
  until queue.empty?
@@ -158,11 +168,8 @@ module RubyLsp
158
168
  # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
159
169
  # target when it is a constant
160
170
  case candidate
161
- when Prism::ClassNode, Prism::ModuleNode
162
- nesting_nodes << candidate
163
- when Prism::SingletonClassNode
164
- nesting_nodes << candidate
165
- when Prism::DefNode
171
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
172
+ Prism::LambdaNode
166
173
  nesting_nodes << candidate
167
174
  end
168
175
 
@@ -203,24 +210,7 @@ module RubyLsp
203
210
  end
204
211
  end
205
212
 
206
- nesting = []
207
- surrounding_method = T.let(nil, T.nilable(String))
208
-
209
- nesting_nodes.each do |node|
210
- case node
211
- when Prism::ClassNode, Prism::ModuleNode
212
- nesting << node.constant_path.slice
213
- when Prism::SingletonClassNode
214
- nesting << "<Class:#{nesting.last}>"
215
- when Prism::DefNode
216
- surrounding_method = node.name.to_s
217
- next unless node.receiver.is_a?(Prism::SelfNode)
218
-
219
- nesting << "<Class:#{nesting.last}>"
220
- end
221
- end
222
-
223
- NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
213
+ NodeContext.new(closest, parent, nesting_nodes, call_node)
224
214
  end
225
215
 
226
216
  sig { returns(T::Boolean) }
@@ -69,7 +69,7 @@ module RubyLsp
69
69
  @formatter = detect_formatter(direct_dependencies, all_dependencies) if @formatter == "auto"
70
70
 
71
71
  specified_linters = options.dig(:initializationOptions, :linters)
72
- @linters = specified_linters || detect_linters(direct_dependencies)
72
+ @linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
73
73
  @test_library = detect_test_library(direct_dependencies)
74
74
  @has_type_checker = detect_typechecker(direct_dependencies)
75
75
 
@@ -127,10 +127,14 @@ module RubyLsp
127
127
 
128
128
  # Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
129
129
  # single linter. To have multiple linters running, the user must configure them manually
130
- sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
131
- def detect_linters(dependencies)
130
+ sig { params(dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(T::Array[String]) }
131
+ def detect_linters(dependencies, all_dependencies)
132
132
  linters = []
133
- linters << "rubocop" if dependencies.any?(/^rubocop/)
133
+
134
+ if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
135
+ linters << "rubocop"
136
+ end
137
+
134
138
  linters
135
139
  end
136
140
 
@@ -277,6 +277,8 @@ module RubyLsp
277
277
 
278
278
  sig { params(node: Prism::CallNode, name: String).void }
279
279
  def complete_methods(node, name)
280
+ add_local_completions(node, name)
281
+
280
282
  type = @type_inferrer.infer_receiver_type(@node_context)
281
283
  return unless type
282
284
 
@@ -322,6 +324,31 @@ module RubyLsp
322
324
  # We have not indexed this namespace, so we can't provide any completions
323
325
  end
324
326
 
327
+ sig { params(node: Prism::CallNode, name: String).void }
328
+ def add_local_completions(node, name)
329
+ return if @global_state.has_type_checker
330
+
331
+ # If the call node has a receiver, then it cannot possibly be a local variable
332
+ return if node.receiver
333
+
334
+ range = range_from_location(T.must(node.message_loc))
335
+
336
+ @node_context.locals_for_scope.each do |local|
337
+ local_name = local.to_s
338
+ next unless local_name.start_with?(name)
339
+
340
+ @response_builder << Interface::CompletionItem.new(
341
+ label: local_name,
342
+ filter_text: local_name,
343
+ text_edit: Interface::TextEdit.new(range: range, new_text: local_name),
344
+ kind: Constant::CompletionItemKind::VARIABLE,
345
+ data: {
346
+ skip_resolve: true,
347
+ },
348
+ )
349
+ end
350
+ end
351
+
325
352
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
326
353
  def build_completion(label, node)
327
354
  # We should use the content location as we only replace the content and not the delimiters of the string
@@ -46,6 +46,8 @@ module RubyLsp
46
46
  :on_instance_variable_or_write_node_enter,
47
47
  :on_instance_variable_target_node_enter,
48
48
  :on_string_node_enter,
49
+ :on_super_node_enter,
50
+ :on_forwarding_super_node_enter,
49
51
  )
50
52
  end
51
53
 
@@ -133,8 +135,30 @@ module RubyLsp
133
135
  handle_instance_variable_definition(node.name.to_s)
134
136
  end
135
137
 
138
+ sig { params(node: Prism::SuperNode).void }
139
+ def on_super_node_enter(node)
140
+ handle_super_node_definition
141
+ end
142
+
143
+ sig { params(node: Prism::ForwardingSuperNode).void }
144
+ def on_forwarding_super_node_enter(node)
145
+ handle_super_node_definition
146
+ end
147
+
136
148
  private
137
149
 
150
+ sig { void }
151
+ def handle_super_node_definition
152
+ surrounding_method = @node_context.surrounding_method
153
+ return unless surrounding_method
154
+
155
+ handle_method_definition(
156
+ surrounding_method,
157
+ @type_inferrer.infer_receiver_type(@node_context),
158
+ inherited_only: true,
159
+ )
160
+ end
161
+
138
162
  sig { params(name: String).void }
139
163
  def handle_instance_variable_definition(name)
140
164
  type = @type_inferrer.infer_receiver_type(@node_context)
@@ -158,10 +182,10 @@ module RubyLsp
158
182
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
159
183
  end
160
184
 
161
- sig { params(message: String, receiver_type: T.nilable(String)).void }
162
- def handle_method_definition(message, receiver_type)
185
+ sig { params(message: String, receiver_type: T.nilable(String), inherited_only: T::Boolean).void }
186
+ def handle_method_definition(message, receiver_type, inherited_only: false)
163
187
  methods = if receiver_type
164
- @index.resolve_method(message, receiver_type)
188
+ @index.resolve_method(message, receiver_type, inherited_only: inherited_only)
165
189
  else
166
190
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
167
191
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -21,6 +21,8 @@ module RubyLsp
21
21
  Prism::InstanceVariableWriteNode,
22
22
  Prism::SymbolNode,
23
23
  Prism::StringNode,
24
+ Prism::SuperNode,
25
+ Prism::ForwardingSuperNode,
24
26
  ],
25
27
  T::Array[T.class_of(Prism::Node)],
26
28
  )
@@ -64,6 +66,8 @@ module RubyLsp
64
66
  :on_instance_variable_operator_write_node_enter,
65
67
  :on_instance_variable_or_write_node_enter,
66
68
  :on_instance_variable_target_node_enter,
69
+ :on_super_node_enter,
70
+ :on_forwarding_super_node_enter,
67
71
  )
68
72
  end
69
73
 
@@ -106,17 +110,7 @@ module RubyLsp
106
110
  message = node.message
107
111
  return unless message
108
112
 
109
- type = @type_inferrer.infer_receiver_type(@node_context)
110
- return unless type
111
-
112
- methods = @index.resolve_method(message, type)
113
- return unless methods
114
-
115
- title = "#{message}#{T.must(methods.first).decorated_parameters}"
116
-
117
- categorized_markdown_from_index_entries(title, methods).each do |category, content|
118
- @response_builder.push(content, category: category)
119
- end
113
+ handle_method_hover(message)
120
114
  end
121
115
 
122
116
  sig { params(node: Prism::InstanceVariableReadNode).void }
@@ -149,8 +143,41 @@ module RubyLsp
149
143
  handle_instance_variable_hover(node.name.to_s)
150
144
  end
151
145
 
146
+ sig { params(node: Prism::SuperNode).void }
147
+ def on_super_node_enter(node)
148
+ handle_super_node_hover
149
+ end
150
+
151
+ sig { params(node: Prism::ForwardingSuperNode).void }
152
+ def on_forwarding_super_node_enter(node)
153
+ handle_super_node_hover
154
+ end
155
+
152
156
  private
153
157
 
158
+ sig { void }
159
+ def handle_super_node_hover
160
+ surrounding_method = @node_context.surrounding_method
161
+ return unless surrounding_method
162
+
163
+ handle_method_hover(surrounding_method, inherited_only: true)
164
+ end
165
+
166
+ sig { params(message: String, inherited_only: T::Boolean).void }
167
+ def handle_method_hover(message, inherited_only: false)
168
+ type = @type_inferrer.infer_receiver_type(@node_context)
169
+ return unless type
170
+
171
+ methods = @index.resolve_method(message, type, inherited_only: inherited_only)
172
+ return unless methods
173
+
174
+ title = "#{message}#{T.must(methods.first).decorated_parameters}"
175
+
176
+ categorized_markdown_from_index_entries(title, methods).each do |category, content|
177
+ @response_builder.push(content, category: category)
178
+ end
179
+ end
180
+
154
181
  sig { params(name: String).void }
155
182
  def handle_instance_variable_hover(name)
156
183
  type = @type_inferrer.infer_receiver_type(@node_context)
@@ -23,22 +23,82 @@ module RubyLsp
23
23
  params(
24
24
  node: T.nilable(Prism::Node),
25
25
  parent: T.nilable(Prism::Node),
26
- nesting: T::Array[String],
26
+ nesting_nodes: T::Array[T.any(
27
+ Prism::ClassNode,
28
+ Prism::ModuleNode,
29
+ Prism::SingletonClassNode,
30
+ Prism::DefNode,
31
+ Prism::BlockNode,
32
+ Prism::LambdaNode,
33
+ Prism::ProgramNode,
34
+ )],
27
35
  call_node: T.nilable(Prism::CallNode),
28
- surrounding_method: T.nilable(String),
29
36
  ).void
30
37
  end
31
- def initialize(node, parent, nesting, call_node, surrounding_method)
38
+ def initialize(node, parent, nesting_nodes, call_node)
32
39
  @node = node
33
40
  @parent = parent
34
- @nesting = nesting
41
+ @nesting_nodes = nesting_nodes
35
42
  @call_node = call_node
36
- @surrounding_method = surrounding_method
43
+
44
+ nesting, surrounding_method = handle_nesting_nodes(nesting_nodes)
45
+ @nesting = T.let(nesting, T::Array[String])
46
+ @surrounding_method = T.let(surrounding_method, T.nilable(String))
37
47
  end
38
48
 
39
49
  sig { returns(String) }
40
50
  def fully_qualified_name
41
51
  @fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
42
52
  end
53
+
54
+ sig { returns(T::Array[Symbol]) }
55
+ def locals_for_scope
56
+ locals = []
57
+
58
+ @nesting_nodes.each do |node|
59
+ if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode) ||
60
+ node.is_a?(Prism::DefNode)
61
+ locals.clear
62
+ end
63
+
64
+ locals.concat(node.locals)
65
+ end
66
+
67
+ locals
68
+ end
69
+
70
+ private
71
+
72
+ sig do
73
+ params(nodes: T::Array[T.any(
74
+ Prism::ClassNode,
75
+ Prism::ModuleNode,
76
+ Prism::SingletonClassNode,
77
+ Prism::DefNode,
78
+ Prism::BlockNode,
79
+ Prism::LambdaNode,
80
+ Prism::ProgramNode,
81
+ )]).returns([T::Array[String], T.nilable(String)])
82
+ end
83
+ def handle_nesting_nodes(nodes)
84
+ nesting = []
85
+ surrounding_method = T.let(nil, T.nilable(String))
86
+
87
+ @nesting_nodes.each do |node|
88
+ case node
89
+ when Prism::ClassNode, Prism::ModuleNode
90
+ nesting << node.constant_path.slice
91
+ when Prism::SingletonClassNode
92
+ nesting << "<Class:#{nesting.last}>"
93
+ when Prism::DefNode
94
+ surrounding_method = node.name.to_s
95
+ next unless node.receiver.is_a?(Prism::SelfNode)
96
+
97
+ nesting << "<Class:#{nesting.last}>"
98
+ end
99
+ end
100
+
101
+ [nesting, surrounding_method]
102
+ end
43
103
  end
44
104
  end
@@ -38,6 +38,8 @@ module RubyLsp
38
38
 
39
39
  sig { override.returns(T::Hash[Symbol, T.untyped]) }
40
40
  def perform
41
+ return @item if @item.dig(:data, :skip_resolve)
42
+
41
43
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
42
44
  # a completion resolve request must always return the original completion item without modifying ANY fields
43
45
  # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
@@ -62,6 +62,8 @@ module RubyLsp
62
62
  Prism::InstanceVariableWriteNode,
63
63
  Prism::SymbolNode,
64
64
  Prism::StringNode,
65
+ Prism::SuperNode,
66
+ Prism::ForwardingSuperNode,
65
67
  ],
66
68
  )
67
69
 
@@ -20,16 +20,9 @@ module RubyLsp
20
20
  when Prism::CallNode
21
21
  infer_receiver_for_call_node(node, node_context)
22
22
  when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
23
- Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode
24
- nesting = node_context.nesting
25
- # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
26
- # inherits from Object
27
- return "Object" if nesting.empty?
28
-
29
- fully_qualified_name = node_context.fully_qualified_name
30
- return fully_qualified_name if node_context.surrounding_method
31
-
32
- "#{fully_qualified_name}::<Class:#{nesting.last}>"
23
+ Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
24
+ Prism::SuperNode, Prism::ForwardingSuperNode
25
+ self_receiver_handling(node_context)
33
26
  end
34
27
  end
35
28
 
@@ -41,15 +34,7 @@ module RubyLsp
41
34
 
42
35
  case receiver
43
36
  when Prism::SelfNode, nil
44
- nesting = node_context.nesting
45
- # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
46
- # inherits from Object
47
- return "Object" if nesting.empty?
48
- return node_context.fully_qualified_name if node_context.surrounding_method
49
-
50
- # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
51
- # context
52
- "#{nesting.join("::")}::<Class:#{nesting.last}>"
37
+ self_receiver_handling(node_context)
53
38
  when Prism::ConstantPathNode, Prism::ConstantReadNode
54
39
  # When the receiver is a constant reference, we have to try to resolve it to figure out the right
55
40
  # receiver. But since the invocation is directly on the constant, that's the singleton context of that
@@ -68,6 +53,19 @@ module RubyLsp
68
53
  end
69
54
  end
70
55
 
56
+ sig { params(node_context: NodeContext).returns(String) }
57
+ def self_receiver_handling(node_context)
58
+ nesting = node_context.nesting
59
+ # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
60
+ # inherits from Object
61
+ return "Object" if nesting.empty?
62
+ return node_context.fully_qualified_name if node_context.surrounding_method
63
+
64
+ # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
65
+ # context
66
+ "#{nesting.join("::")}::<Class:#{nesting.last}>"
67
+ end
68
+
71
69
  sig do
72
70
  params(
73
71
  node: T.any(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.5
4
+ version: 0.17.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-09 00:00:00.000000000 Z
11
+ date: 2024-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol