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