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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +241 -91
  4. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +74 -102
  5. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +81 -19
  6. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +50 -2
  7. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  8. data/lib/ruby_indexer/test/index_test.rb +326 -27
  9. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  10. data/lib/ruby_indexer/test/method_test.rb +54 -24
  11. data/lib/ruby_indexer/test/rbs_indexer_test.rb +27 -2
  12. data/lib/ruby_lsp/document.rb +37 -8
  13. data/lib/ruby_lsp/global_state.rb +7 -3
  14. data/lib/ruby_lsp/internal.rb +1 -0
  15. data/lib/ruby_lsp/listeners/completion.rb +53 -14
  16. data/lib/ruby_lsp/listeners/definition.rb +11 -7
  17. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  18. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  19. data/lib/ruby_lsp/node_context.rb +6 -1
  20. data/lib/ruby_lsp/requests/completion.rb +5 -4
  21. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
  22. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  23. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  24. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  25. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
  26. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  27. data/lib/ruby_lsp/requests.rb +2 -0
  28. data/lib/ruby_lsp/server.rb +54 -15
  29. data/lib/ruby_lsp/test_helper.rb +1 -1
  30. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  31. metadata +5 -2
@@ -13,7 +13,7 @@ module RubyIndexer
13
13
  end
14
14
  RUBY
15
15
 
16
- assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::SingletonMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
87
- assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
88
- assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
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::InstanceMethod, "/fake/path/foo.rb:3-2:3-14", visibility: Entry::Visibility::PRIVATE)
107
- assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:6-4:6-16", visibility: Entry::Visibility::PUBLIC)
108
- assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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 first; end
367
+ def first_method; end
363
368
 
364
369
  module Bar
365
- def second; end
370
+ def second_method; end
366
371
  end
367
372
 
368
- def third; end
373
+ def third_method; end
369
374
  end
370
375
  RUBY
371
376
 
372
- entry = T.must(@index["first"]&.first)
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.must(@index["second"]&.first)
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.must(@index["third"]&.first)
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
- assert_equal(1, entries.length)
12
- entry = entries.first
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
@@ -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
- nesting = T.let([], T::Array[T.any(Prism::ClassNode, Prism::ModuleNode)])
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 = nesting.last
155
- nesting.pop if previous_level && loc.start_offset > previous_level.location.end_offset
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
- if candidate.is_a?(Prism::ClassNode) || candidate.is_a?(Prism::ModuleNode)
160
- nesting << candidate
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 = nesting.last
193
- nesting.pop if last_level && last_level.constant_path == closest
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.map { |n| n.constant_path.location.slice }, call_node)
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 :typechecker
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
- @typechecker = T.let(true, T::Boolean)
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
- @typechecker = detect_typechecker(direct_dependencies)
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?
@@ -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(response_builder, global_state, node_context, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
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.typechecker
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.typechecker
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
- complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
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
- T.must(namespace).join("::")
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
- @index.instance_variable_completion_candidates(name, @node_context.fully_qualified_name).each do |entry|
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 complete_self_receiver_method(node, name)
261
- receiver_entries = @index[@node_context.fully_qualified_name]
262
- return unless receiver_entries
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
- receiver = T.must(receiver_entries.first)
293
+ @index.method_completion_candidates(method_name, type).each do |entry|
294
+ entry_name = entry.name
265
295
 
266
- @index.method_completion_candidates(name, receiver.name).each do |entry|
267
- @response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
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: "(#{entry.parameters.map(&:decorated_name).join(", ")})",
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, self_receiver?(node))
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, false)
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
- entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
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, self_receiver: T::Boolean).void }
145
- def handle_method_definition(message, self_receiver)
146
- methods = if self_receiver
147
- @index.resolve_method(message, @node_context.fully_qualified_name)
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.typechecker
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.typechecker
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
- methods = @index.resolve_method(message, @node_context.fully_qualified_name)
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
- categorized_markdown_from_index_entries(message, methods).each do |category, content|
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
- entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
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
- methods = @index.resolve_method(message, @node_context.fully_qualified_name)
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) }