ruby-lsp 0.17.3 → 0.17.4
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/declaration_listener.rb +241 -91
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +74 -102
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +81 -19
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +50 -2
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
- data/lib/ruby_indexer/test/index_test.rb +326 -27
- data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
- data/lib/ruby_indexer/test/method_test.rb +54 -24
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +27 -2
- data/lib/ruby_lsp/document.rb +37 -8
- data/lib/ruby_lsp/global_state.rb +7 -3
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/completion.rb +53 -14
- data/lib/ruby_lsp/listeners/definition.rb +11 -7
- data/lib/ruby_lsp/listeners/hover.rb +14 -7
- data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
- data/lib/ruby_lsp/node_context.rb +6 -1
- data/lib/ruby_lsp/requests/completion.rb +5 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
- data/lib/ruby_lsp/requests/support/common.rb +19 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/server.rb +54 -15
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +86 -0
- metadata +5 -2
@@ -13,7 +13,7 @@ module RubyIndexer
|
|
13
13
|
end
|
14
14
|
RUBY
|
15
15
|
|
16
|
-
assert_entry("bar", Entry::
|
16
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_conditional_method
|
@@ -24,7 +24,7 @@ module RubyIndexer
|
|
24
24
|
end
|
25
25
|
RUBY
|
26
26
|
|
27
|
-
assert_entry("bar", Entry::
|
27
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
28
28
|
end
|
29
29
|
|
30
30
|
def test_singleton_method_using_self_receiver
|
@@ -35,7 +35,12 @@ module RubyIndexer
|
|
35
35
|
end
|
36
36
|
RUBY
|
37
37
|
|
38
|
-
assert_entry("bar", Entry::
|
38
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
39
|
+
|
40
|
+
entry = T.must(@index["bar"].first)
|
41
|
+
owner = T.must(entry.owner)
|
42
|
+
assert_equal("Foo::<Class:Foo>", owner.name)
|
43
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
39
44
|
end
|
40
45
|
|
41
46
|
def test_singleton_method_using_other_receiver_is_not_indexed
|
@@ -83,9 +88,9 @@ module RubyIndexer
|
|
83
88
|
def baz; end
|
84
89
|
RUBY
|
85
90
|
|
86
|
-
assert_entry("foo", Entry::
|
87
|
-
assert_entry("bar", Entry::
|
88
|
-
assert_entry("baz", Entry::
|
91
|
+
assert_entry("foo", Entry::Method, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
|
92
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
|
93
|
+
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
|
89
94
|
end
|
90
95
|
|
91
96
|
def test_visibility_tracking_with_nested_class_or_modules
|
@@ -103,9 +108,9 @@ module RubyIndexer
|
|
103
108
|
end
|
104
109
|
RUBY
|
105
110
|
|
106
|
-
assert_entry("foo", Entry::
|
107
|
-
assert_entry("bar", Entry::
|
108
|
-
assert_entry("baz", Entry::
|
111
|
+
assert_entry("foo", Entry::Method, "/fake/path/foo.rb:3-2:3-14", visibility: Entry::Visibility::PRIVATE)
|
112
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:6-4:6-16", visibility: Entry::Visibility::PUBLIC)
|
113
|
+
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
|
109
114
|
end
|
110
115
|
|
111
116
|
def test_method_with_parameters
|
@@ -116,7 +121,7 @@ module RubyIndexer
|
|
116
121
|
end
|
117
122
|
RUBY
|
118
123
|
|
119
|
-
assert_entry("bar", Entry::
|
124
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
120
125
|
entry = T.must(@index["bar"].first)
|
121
126
|
assert_equal(1, entry.parameters.length)
|
122
127
|
parameter = entry.parameters.first
|
@@ -132,7 +137,7 @@ module RubyIndexer
|
|
132
137
|
end
|
133
138
|
RUBY
|
134
139
|
|
135
|
-
assert_entry("bar", Entry::
|
140
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
136
141
|
entry = T.must(@index["bar"].first)
|
137
142
|
assert_equal(1, entry.parameters.length)
|
138
143
|
parameter = entry.parameters.first
|
@@ -148,7 +153,7 @@ module RubyIndexer
|
|
148
153
|
end
|
149
154
|
RUBY
|
150
155
|
|
151
|
-
assert_entry("bar", Entry::
|
156
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
152
157
|
entry = T.must(@index["bar"].first)
|
153
158
|
assert_equal(1, entry.parameters.length)
|
154
159
|
parameter = entry.parameters.first
|
@@ -164,7 +169,7 @@ module RubyIndexer
|
|
164
169
|
end
|
165
170
|
RUBY
|
166
171
|
|
167
|
-
assert_entry("bar", Entry::
|
172
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
168
173
|
entry = T.must(@index["bar"].first)
|
169
174
|
assert_equal(2, entry.parameters.length)
|
170
175
|
a, b = entry.parameters
|
@@ -184,7 +189,7 @@ module RubyIndexer
|
|
184
189
|
end
|
185
190
|
RUBY
|
186
191
|
|
187
|
-
assert_entry("bar", Entry::
|
192
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
188
193
|
entry = T.must(@index["bar"].first)
|
189
194
|
assert_equal(2, entry.parameters.length)
|
190
195
|
a, b = entry.parameters
|
@@ -209,7 +214,7 @@ module RubyIndexer
|
|
209
214
|
end
|
210
215
|
RUBY
|
211
216
|
|
212
|
-
assert_entry("bar", Entry::
|
217
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
213
218
|
entry = T.must(@index["bar"].first)
|
214
219
|
assert_equal(2, entry.parameters.length)
|
215
220
|
a, b = entry.parameters
|
@@ -246,7 +251,7 @@ module RubyIndexer
|
|
246
251
|
end
|
247
252
|
RUBY
|
248
253
|
|
249
|
-
assert_entry("bar", Entry::
|
254
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
250
255
|
entry = T.must(@index["bar"].first)
|
251
256
|
assert_equal(1, entry.parameters.length)
|
252
257
|
param = entry.parameters.first
|
@@ -287,7 +292,7 @@ module RubyIndexer
|
|
287
292
|
end
|
288
293
|
RUBY
|
289
294
|
|
290
|
-
assert_entry("bar", Entry::
|
295
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
291
296
|
entry = T.must(@index["bar"].first)
|
292
297
|
assert_equal(2, entry.parameters.length)
|
293
298
|
first, second = entry.parameters
|
@@ -307,7 +312,7 @@ module RubyIndexer
|
|
307
312
|
end
|
308
313
|
RUBY
|
309
314
|
|
310
|
-
assert_entry("bar", Entry::
|
315
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
311
316
|
entry = T.must(@index["bar"].first)
|
312
317
|
assert_empty(entry.parameters)
|
313
318
|
end
|
@@ -359,23 +364,23 @@ module RubyIndexer
|
|
359
364
|
def test_properly_tracks_multiple_levels_of_nesting
|
360
365
|
index(<<~RUBY)
|
361
366
|
module Foo
|
362
|
-
def
|
367
|
+
def first_method; end
|
363
368
|
|
364
369
|
module Bar
|
365
|
-
def
|
370
|
+
def second_method; end
|
366
371
|
end
|
367
372
|
|
368
|
-
def
|
373
|
+
def third_method; end
|
369
374
|
end
|
370
375
|
RUBY
|
371
376
|
|
372
|
-
entry = T.
|
377
|
+
entry = T.cast(@index["first_method"]&.first, Entry::Method)
|
373
378
|
assert_equal("Foo", T.must(entry.owner).name)
|
374
379
|
|
375
|
-
entry = T.
|
380
|
+
entry = T.cast(@index["second_method"]&.first, Entry::Method)
|
376
381
|
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
377
382
|
|
378
|
-
entry = T.
|
383
|
+
entry = T.cast(@index["third_method"]&.first, Entry::Method)
|
379
384
|
assert_equal("Foo", T.must(entry.owner).name)
|
380
385
|
end
|
381
386
|
|
@@ -398,5 +403,30 @@ module RubyIndexer
|
|
398
403
|
# Foo plus 3 valid aliases
|
399
404
|
assert_equal(4, @index.instance_variable_get(:@entries).length - @default_indexed_entries.length)
|
400
405
|
end
|
406
|
+
|
407
|
+
def test_singleton_methods
|
408
|
+
index(<<~RUBY)
|
409
|
+
class Foo
|
410
|
+
def self.bar; end
|
411
|
+
|
412
|
+
class << self
|
413
|
+
def baz; end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
RUBY
|
417
|
+
|
418
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:1-19")
|
419
|
+
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:4-4:4-16")
|
420
|
+
|
421
|
+
bar_owner = T.must(T.must(@index["bar"].first).owner)
|
422
|
+
baz_owner = T.must(T.must(@index["baz"].first).owner)
|
423
|
+
|
424
|
+
assert_instance_of(Entry::SingletonClass, bar_owner)
|
425
|
+
assert_instance_of(Entry::SingletonClass, baz_owner)
|
426
|
+
|
427
|
+
# Regardless of whether the method was added through `self.something` or `class << self`, the owner object must be
|
428
|
+
# the exact same
|
429
|
+
assert_same(bar_owner, baz_owner)
|
430
|
+
end
|
401
431
|
end
|
402
432
|
end
|
@@ -8,8 +8,9 @@ module RubyIndexer
|
|
8
8
|
def test_index_core_classes
|
9
9
|
entries = @index["Array"]
|
10
10
|
refute_nil(entries)
|
11
|
-
|
12
|
-
|
11
|
+
# Array is a class but also an instance method on Kernel
|
12
|
+
assert_equal(2, entries.length)
|
13
|
+
entry = entries.find { |entry| entry.is_a?(RubyIndexer::Entry::Class) }
|
13
14
|
assert_match(%r{/gems/rbs-.*/core/array.rbs}, entry.file_path)
|
14
15
|
assert_equal("array.rbs", entry.file_name)
|
15
16
|
assert_equal("Object", entry.parent_class)
|
@@ -38,5 +39,29 @@ module RubyIndexer
|
|
38
39
|
assert_equal(0, entry.location.start_column)
|
39
40
|
assert_operator(entry.location.end_column, :>, 0)
|
40
41
|
end
|
42
|
+
|
43
|
+
def test_index_methods
|
44
|
+
entries = @index["initialize"]
|
45
|
+
refute_nil(entries)
|
46
|
+
entry = entries.find { |entry| entry.owner.name == "Array" }
|
47
|
+
assert_match(%r{/gems/rbs-.*/core/array.rbs}, entry.file_path)
|
48
|
+
assert_equal("array.rbs", entry.file_name)
|
49
|
+
assert_equal(Entry::Visibility::PUBLIC, entry.visibility)
|
50
|
+
|
51
|
+
# Using fixed positions would be fragile, so let's just check some basics.
|
52
|
+
assert_operator(entry.location.start_line, :>, 0)
|
53
|
+
assert_operator(entry.location.end_line, :>, entry.location.start_line)
|
54
|
+
assert_equal(2, entry.location.start_column)
|
55
|
+
assert_operator(entry.location.end_column, :>, 0)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_attaches_correct_owner_to_singleton_methods
|
59
|
+
entries = @index["basename"]
|
60
|
+
refute_nil(entries)
|
61
|
+
|
62
|
+
owner = entries.first.owner
|
63
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
64
|
+
assert_equal("File::<Class:File>", owner.name)
|
65
|
+
end
|
41
66
|
end
|
42
67
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -127,7 +127,10 @@ module RubyLsp
|
|
127
127
|
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
128
128
|
closest = node
|
129
129
|
parent = T.let(nil, T.nilable(Prism::Node))
|
130
|
-
|
130
|
+
nesting_nodes = T.let(
|
131
|
+
[],
|
132
|
+
T::Array[T.any(Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode)],
|
133
|
+
)
|
131
134
|
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
132
135
|
|
133
136
|
until queue.empty?
|
@@ -151,13 +154,18 @@ module RubyLsp
|
|
151
154
|
|
152
155
|
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
|
153
156
|
# need to pop the stack
|
154
|
-
previous_level =
|
155
|
-
|
157
|
+
previous_level = nesting_nodes.last
|
158
|
+
nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
|
156
159
|
|
157
160
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
158
161
|
# target when it is a constant
|
159
|
-
|
160
|
-
|
162
|
+
case candidate
|
163
|
+
when Prism::ClassNode, Prism::ModuleNode
|
164
|
+
nesting_nodes << candidate
|
165
|
+
when Prism::SingletonClassNode
|
166
|
+
nesting_nodes << candidate
|
167
|
+
when Prism::DefNode
|
168
|
+
nesting_nodes << candidate
|
161
169
|
end
|
162
170
|
|
163
171
|
if candidate.is_a?(Prism::CallNode)
|
@@ -189,11 +197,32 @@ module RubyLsp
|
|
189
197
|
# The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
|
190
198
|
# though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
|
191
199
|
if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
|
192
|
-
last_level =
|
193
|
-
|
200
|
+
last_level = nesting_nodes.last
|
201
|
+
|
202
|
+
if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
|
203
|
+
last_level.constant_path == closest
|
204
|
+
nesting_nodes.pop
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
nesting = []
|
209
|
+
surrounding_method = T.let(nil, T.nilable(String))
|
210
|
+
|
211
|
+
nesting_nodes.each do |node|
|
212
|
+
case node
|
213
|
+
when Prism::ClassNode, Prism::ModuleNode
|
214
|
+
nesting << node.constant_path.slice
|
215
|
+
when Prism::SingletonClassNode
|
216
|
+
nesting << "<Class:#{nesting.last}>"
|
217
|
+
when Prism::DefNode
|
218
|
+
surrounding_method = node.name.to_s
|
219
|
+
next unless node.receiver.is_a?(Prism::SelfNode)
|
220
|
+
|
221
|
+
nesting << "<Class:#{nesting.last}>"
|
222
|
+
end
|
194
223
|
end
|
195
224
|
|
196
|
-
NodeContext.new(closest, parent, nesting
|
225
|
+
NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
|
197
226
|
end
|
198
227
|
|
199
228
|
sig { returns(T::Boolean) }
|
@@ -12,7 +12,7 @@ module RubyLsp
|
|
12
12
|
attr_accessor :formatter
|
13
13
|
|
14
14
|
sig { returns(T::Boolean) }
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :has_type_checker
|
16
16
|
|
17
17
|
sig { returns(RubyIndexer::Index) }
|
18
18
|
attr_reader :index
|
@@ -23,6 +23,9 @@ module RubyLsp
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
24
|
attr_reader :supports_watching_files
|
25
25
|
|
26
|
+
sig { returns(TypeInferrer) }
|
27
|
+
attr_reader :type_inferrer
|
28
|
+
|
26
29
|
sig { void }
|
27
30
|
def initialize
|
28
31
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -31,8 +34,9 @@ module RubyLsp
|
|
31
34
|
@formatter = T.let("auto", String)
|
32
35
|
@linters = T.let([], T::Array[String])
|
33
36
|
@test_library = T.let("minitest", String)
|
34
|
-
@
|
37
|
+
@has_type_checker = T.let(true, T::Boolean)
|
35
38
|
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
39
|
+
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
36
40
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
37
41
|
@supports_watching_files = T.let(false, T::Boolean)
|
38
42
|
end
|
@@ -66,7 +70,7 @@ module RubyLsp
|
|
66
70
|
specified_linters = options.dig(:initializationOptions, :linters)
|
67
71
|
@linters = specified_linters || detect_linters(direct_dependencies)
|
68
72
|
@test_library = detect_test_library(direct_dependencies)
|
69
|
-
@
|
73
|
+
@has_type_checker = detect_typechecker(direct_dependencies)
|
70
74
|
|
71
75
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
72
76
|
@encoding = if !encodings || encodings.empty?
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -28,6 +28,7 @@ require "ruby_lsp/utils"
|
|
28
28
|
require "ruby_lsp/parameter_scope"
|
29
29
|
require "ruby_lsp/global_state"
|
30
30
|
require "ruby_lsp/server"
|
31
|
+
require "ruby_lsp/type_inferrer"
|
31
32
|
require "ruby_lsp/requests"
|
32
33
|
require "ruby_lsp/response_builders"
|
33
34
|
require "ruby_lsp/node_context"
|
@@ -15,15 +15,26 @@ module RubyLsp
|
|
15
15
|
typechecker_enabled: T::Boolean,
|
16
16
|
dispatcher: Prism::Dispatcher,
|
17
17
|
uri: URI::Generic,
|
18
|
+
trigger_character: T.nilable(String),
|
18
19
|
).void
|
19
20
|
end
|
20
|
-
def initialize(
|
21
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
22
|
+
response_builder,
|
23
|
+
global_state,
|
24
|
+
node_context,
|
25
|
+
typechecker_enabled,
|
26
|
+
dispatcher,
|
27
|
+
uri,
|
28
|
+
trigger_character
|
29
|
+
)
|
21
30
|
@response_builder = response_builder
|
22
31
|
@global_state = global_state
|
23
32
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
33
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
24
34
|
@node_context = node_context
|
25
35
|
@typechecker_enabled = typechecker_enabled
|
26
36
|
@uri = uri
|
37
|
+
@trigger_character = trigger_character
|
27
38
|
|
28
39
|
dispatcher.register(
|
29
40
|
self,
|
@@ -42,7 +53,7 @@ module RubyLsp
|
|
42
53
|
# Handle completion on regular constant references (e.g. `Bar`)
|
43
54
|
sig { params(node: Prism::ConstantReadNode).void }
|
44
55
|
def on_constant_read_node_enter(node)
|
45
|
-
return if @global_state.
|
56
|
+
return if @global_state.has_type_checker
|
46
57
|
|
47
58
|
name = constant_name(node)
|
48
59
|
return if name.nil?
|
@@ -63,7 +74,7 @@ module RubyLsp
|
|
63
74
|
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
64
75
|
sig { params(node: Prism::ConstantPathNode).void }
|
65
76
|
def on_constant_path_node_enter(node)
|
66
|
-
return if @global_state.
|
77
|
+
return if @global_state.has_type_checker
|
67
78
|
|
68
79
|
name = constant_name(node)
|
69
80
|
return if name.nil?
|
@@ -107,7 +118,7 @@ module RubyLsp
|
|
107
118
|
when "require_relative"
|
108
119
|
complete_require_relative(node)
|
109
120
|
else
|
110
|
-
|
121
|
+
complete_methods(node, name) unless @typechecker_enabled
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
@@ -158,7 +169,7 @@ module RubyLsp
|
|
158
169
|
name.delete_suffix("::")
|
159
170
|
else
|
160
171
|
*namespace, incomplete_name = name.split("::")
|
161
|
-
|
172
|
+
namespace.join("::")
|
162
173
|
end
|
163
174
|
|
164
175
|
nesting = @node_context.nesting
|
@@ -192,7 +203,10 @@ module RubyLsp
|
|
192
203
|
|
193
204
|
sig { params(name: String, location: Prism::Location).void }
|
194
205
|
def handle_instance_variable_completion(name, location)
|
195
|
-
@
|
206
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
207
|
+
return unless type
|
208
|
+
|
209
|
+
@index.instance_variable_completion_candidates(name, type).each do |entry|
|
196
210
|
variable_name = entry.name
|
197
211
|
|
198
212
|
@response_builder << Interface::CompletionItem.new(
|
@@ -257,20 +271,45 @@ module RubyLsp
|
|
257
271
|
end
|
258
272
|
|
259
273
|
sig { params(node: Prism::CallNode, name: String).void }
|
260
|
-
def
|
261
|
-
|
262
|
-
return unless
|
274
|
+
def complete_methods(node, name)
|
275
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
276
|
+
return unless type
|
277
|
+
|
278
|
+
# When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the source
|
279
|
+
# code, leading to us searching for the wrong name. What we want to do instead is show every available method
|
280
|
+
# when dot is pressed
|
281
|
+
method_name = @trigger_character == "." ? nil : name
|
282
|
+
|
283
|
+
range = if method_name
|
284
|
+
range_from_location(T.must(node.message_loc))
|
285
|
+
else
|
286
|
+
loc = T.must(node.call_operator_loc)
|
287
|
+
Interface::Range.new(
|
288
|
+
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
289
|
+
end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
290
|
+
)
|
291
|
+
end
|
263
292
|
|
264
|
-
|
293
|
+
@index.method_completion_candidates(method_name, type).each do |entry|
|
294
|
+
entry_name = entry.name
|
265
295
|
|
266
|
-
|
267
|
-
|
296
|
+
@response_builder << Interface::CompletionItem.new(
|
297
|
+
label: entry_name,
|
298
|
+
filter_text: entry_name,
|
299
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
|
300
|
+
kind: Constant::CompletionItemKind::METHOD,
|
301
|
+
data: {
|
302
|
+
owner_name: entry.owner&.name,
|
303
|
+
},
|
304
|
+
)
|
268
305
|
end
|
306
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
307
|
+
# We have not indexed this namespace, so we can't provide any completions
|
269
308
|
end
|
270
309
|
|
271
310
|
sig do
|
272
311
|
params(
|
273
|
-
entry: RubyIndexer::Entry::Member,
|
312
|
+
entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
|
274
313
|
node: Prism::CallNode,
|
275
314
|
).returns(Interface::CompletionItem)
|
276
315
|
end
|
@@ -283,7 +322,7 @@ module RubyLsp
|
|
283
322
|
text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
|
284
323
|
kind: Constant::CompletionItemKind::METHOD,
|
285
324
|
label_details: Interface::CompletionItemLabelDetails.new(
|
286
|
-
detail:
|
325
|
+
detail: entry.decorated_parameters,
|
287
326
|
description: entry.file_name,
|
288
327
|
),
|
289
328
|
documentation: Interface::MarkupContent.new(
|
@@ -23,6 +23,7 @@ module RubyLsp
|
|
23
23
|
@response_builder = response_builder
|
24
24
|
@global_state = global_state
|
25
25
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
26
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
26
27
|
@uri = uri
|
27
28
|
@node_context = node_context
|
28
29
|
@typechecker_enabled = typechecker_enabled
|
@@ -48,7 +49,7 @@ module RubyLsp
|
|
48
49
|
message = node.message
|
49
50
|
return unless message
|
50
51
|
|
51
|
-
handle_method_definition(message,
|
52
|
+
handle_method_definition(message, @type_inferrer.infer_receiver_type(@node_context))
|
52
53
|
end
|
53
54
|
|
54
55
|
sig { params(node: Prism::StringNode).void }
|
@@ -70,7 +71,7 @@ module RubyLsp
|
|
70
71
|
value = expression.value
|
71
72
|
return unless value
|
72
73
|
|
73
|
-
handle_method_definition(value,
|
74
|
+
handle_method_definition(value, nil)
|
74
75
|
end
|
75
76
|
|
76
77
|
sig { params(node: Prism::ConstantPathNode).void }
|
@@ -123,7 +124,10 @@ module RubyLsp
|
|
123
124
|
|
124
125
|
sig { params(name: String).void }
|
125
126
|
def handle_instance_variable_definition(name)
|
126
|
-
|
127
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
128
|
+
return unless type
|
129
|
+
|
130
|
+
entries = @index.resolve_instance_variable(name, type)
|
127
131
|
return unless entries
|
128
132
|
|
129
133
|
entries.each do |entry|
|
@@ -141,10 +145,10 @@ module RubyLsp
|
|
141
145
|
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
142
146
|
end
|
143
147
|
|
144
|
-
sig { params(message: String,
|
145
|
-
def handle_method_definition(message,
|
146
|
-
methods = if
|
147
|
-
@index.resolve_method(message,
|
148
|
+
sig { params(message: String, receiver_type: T.nilable(String)).void }
|
149
|
+
def handle_method_definition(message, receiver_type)
|
150
|
+
methods = if receiver_type
|
151
|
+
@index.resolve_method(message, receiver_type)
|
148
152
|
else
|
149
153
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
150
154
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
@@ -47,6 +47,7 @@ module RubyLsp
|
|
47
47
|
@response_builder = response_builder
|
48
48
|
@global_state = global_state
|
49
49
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
50
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
50
51
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
51
52
|
@node_context = node_context
|
52
53
|
@typechecker_enabled = typechecker_enabled
|
@@ -78,14 +79,14 @@ module RubyLsp
|
|
78
79
|
|
79
80
|
sig { params(node: Prism::ConstantWriteNode).void }
|
80
81
|
def on_constant_write_node_enter(node)
|
81
|
-
return if @global_state.
|
82
|
+
return if @global_state.has_type_checker
|
82
83
|
|
83
84
|
generate_hover(node.name.to_s, node.name_loc)
|
84
85
|
end
|
85
86
|
|
86
87
|
sig { params(node: Prism::ConstantPathNode).void }
|
87
88
|
def on_constant_path_node_enter(node)
|
88
|
-
return if @global_state.
|
89
|
+
return if @global_state.has_type_checker
|
89
90
|
|
90
91
|
name = constant_name(node)
|
91
92
|
return if name.nil?
|
@@ -95,8 +96,6 @@ module RubyLsp
|
|
95
96
|
|
96
97
|
sig { params(node: Prism::CallNode).void }
|
97
98
|
def on_call_node_enter(node)
|
98
|
-
return unless self_receiver?(node)
|
99
|
-
|
100
99
|
if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
|
101
100
|
generate_gem_hover(node)
|
102
101
|
return
|
@@ -107,10 +106,15 @@ module RubyLsp
|
|
107
106
|
message = node.message
|
108
107
|
return unless message
|
109
108
|
|
110
|
-
|
109
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
110
|
+
return unless type
|
111
|
+
|
112
|
+
methods = @index.resolve_method(message, type)
|
111
113
|
return unless methods
|
112
114
|
|
113
|
-
|
115
|
+
title = "#{message}#{T.must(methods.first).decorated_parameters}"
|
116
|
+
|
117
|
+
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
114
118
|
@response_builder.push(content, category: category)
|
115
119
|
end
|
116
120
|
end
|
@@ -149,7 +153,10 @@ module RubyLsp
|
|
149
153
|
|
150
154
|
sig { params(name: String).void }
|
151
155
|
def handle_instance_variable_hover(name)
|
152
|
-
|
156
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
157
|
+
return unless type
|
158
|
+
|
159
|
+
entries = @index.resolve_instance_variable(name, type)
|
153
160
|
return unless entries
|
154
161
|
|
155
162
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
@@ -21,6 +21,7 @@ module RubyLsp
|
|
21
21
|
@response_builder = response_builder
|
22
22
|
@global_state = global_state
|
23
23
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
24
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
24
25
|
@node_context = node_context
|
25
26
|
dispatcher.register(self, :on_call_node_enter)
|
26
27
|
end
|
@@ -28,12 +29,14 @@ module RubyLsp
|
|
28
29
|
sig { params(node: Prism::CallNode).void }
|
29
30
|
def on_call_node_enter(node)
|
30
31
|
return if @typechecker_enabled
|
31
|
-
return unless self_receiver?(node)
|
32
32
|
|
33
33
|
message = node.message
|
34
34
|
return unless message
|
35
35
|
|
36
|
-
|
36
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
37
|
+
return unless type
|
38
|
+
|
39
|
+
methods = @index.resolve_method(message, type)
|
37
40
|
return unless methods
|
38
41
|
|
39
42
|
target_method = methods.first
|
@@ -16,19 +16,24 @@ module RubyLsp
|
|
16
16
|
sig { returns(T.nilable(Prism::CallNode)) }
|
17
17
|
attr_reader :call_node
|
18
18
|
|
19
|
+
sig { returns(T.nilable(String)) }
|
20
|
+
attr_reader :surrounding_method
|
21
|
+
|
19
22
|
sig do
|
20
23
|
params(
|
21
24
|
node: T.nilable(Prism::Node),
|
22
25
|
parent: T.nilable(Prism::Node),
|
23
26
|
nesting: T::Array[String],
|
24
27
|
call_node: T.nilable(Prism::CallNode),
|
28
|
+
surrounding_method: T.nilable(String),
|
25
29
|
).void
|
26
30
|
end
|
27
|
-
def initialize(node, parent, nesting, call_node)
|
31
|
+
def initialize(node, parent, nesting, call_node, surrounding_method)
|
28
32
|
@node = node
|
29
33
|
@parent = parent
|
30
34
|
@nesting = nesting
|
31
35
|
@call_node = call_node
|
36
|
+
@surrounding_method = surrounding_method
|
32
37
|
end
|
33
38
|
|
34
39
|
sig { returns(String) }
|