ruby-lsp 0.17.5 → 0.17.7

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 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