ruby-lsp 0.17.3 → 0.17.4
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/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) }
|