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 +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +8 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +5 -2
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +107 -7
- data/lib/ruby_indexer/test/index_test.rb +43 -0
- data/lib/ruby_indexer/test/method_test.rb +37 -24
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +248 -0
- data/lib/ruby_lsp/document.rb +14 -24
- data/lib/ruby_lsp/global_state.rb +8 -4
- data/lib/ruby_lsp/listeners/completion.rb +27 -0
- data/lib/ruby_lsp/listeners/definition.rb +27 -3
- data/lib/ruby_lsp/listeners/hover.rb +38 -11
- data/lib/ruby_lsp/node_context.rb +65 -5
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -0
- data/lib/ruby_lsp/requests/definition.rb +2 -0
- data/lib/ruby_lsp/type_inferrer.rb +17 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 671bab4fdbd77d1980781f55874d6cd448e1dcd11a6f534a176dfb00b098d2eb
|
4
|
+
data.tar.gz: bd12fd2ef58588b3b5386fec48c0f561140efff180988f1971b67f635f0e1042
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3a2a3115e1745ad7263b7e113ef13795f9a1ed1ee118128a9ab2aaad703b17551acbd929f820700e6650efb76421418a13a78270ac8d3aaff080edc46731aee
|
7
|
+
data.tar.gz: d29bb4d6c60727eef1a10905762a6d0c8946b7afc8aac6148ef970c8d752365f1577403497702f52a7211689cde7940b3d7cd1e1e0eb03d038e0dc6f7ceed738
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
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 |
|
19
|
-
process_signature(
|
18
|
+
loader.each_signature do |_source, pathname, _buffer, declarations, _directives|
|
19
|
+
process_signature(pathname, declarations)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
38
|
+
def initialize(node, parent, nesting_nodes, call_node)
|
32
39
|
@node = node
|
33
40
|
@parent = parent
|
34
|
-
@
|
41
|
+
@nesting_nodes = nesting_nodes
|
35
42
|
@call_node = call_node
|
36
|
-
|
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
|
@@ -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
|
-
|
25
|
-
|
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
|
-
|
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.
|
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-
|
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
|