ruby-lsp 0.17.8 → 0.17.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -40,6 +40,27 @@ module RubyIndexer
40
40
  assert_operator(entry.location.end_column, :>, 0)
41
41
  end
42
42
 
43
+ def test_index_core_constants
44
+ entries = @index["RUBY_VERSION"]
45
+ refute_nil(entries)
46
+ assert_equal(1, entries.length)
47
+
48
+ # Complex::I is defined as `Complex::I = ...`
49
+ entries = @index["Complex::I"]
50
+ refute_nil(entries)
51
+ assert_equal(1, entries.length)
52
+
53
+ # Encoding::US_ASCII is defined as
54
+ # ```
55
+ # module Encoding
56
+ # US_ASCII = ...
57
+ # ...
58
+ # ````
59
+ entries = @index["Encoding::US_ASCII"]
60
+ refute_nil(entries)
61
+ assert_equal(1, entries.length)
62
+ end
63
+
43
64
  def test_index_methods
44
65
  entries = @index["initialize"]
45
66
  refute_nil(entries)
@@ -310,6 +331,23 @@ module RubyIndexer
310
331
  assert_kind_of(Entry::BlockParameter, parameters[3])
311
332
  end
312
333
 
334
+ def test_signature_alias
335
+ # In RBS, an alias means that two methods have the same signature.
336
+ # It does not mean the same thing as a Ruby alias.
337
+ any_entries = @index["any?"]
338
+
339
+ assert_equal(["Array", "Enumerable", "Hash"], any_entries.map { _1.owner.name })
340
+
341
+ entry = any_entries.find { |entry| entry.owner.name == "Array" }
342
+
343
+ assert_kind_of(RubyIndexer::Entry::UnresolvedMethodAlias, entry)
344
+ assert_equal("any?", entry.name)
345
+ assert_equal("all?", entry.old_name)
346
+ assert_equal("Array", entry.owner.name)
347
+ assert(entry.file_path.end_with?("core/array.rbs"))
348
+ assert_includes(entry.comments[0], "Returns `true` if any element of `self` meets a given criterion.")
349
+ end
350
+
313
351
  private
314
352
 
315
353
  def parse_rbs_methods(rbs, method_name)
@@ -10,6 +10,16 @@ module RubyLsp
10
10
  end
11
11
  end
12
12
 
13
+ class SorbetLevel < T::Enum
14
+ enums do
15
+ None = new("none")
16
+ Ignore = new("ignore")
17
+ False = new("false")
18
+ True = new("true")
19
+ Strict = new("strict")
20
+ end
21
+ end
22
+
13
23
  extend T::Sig
14
24
  extend T::Helpers
15
25
 
@@ -213,10 +223,23 @@ module RubyLsp
213
223
  NodeContext.new(closest, parent, nesting_nodes, call_node)
214
224
  end
215
225
 
216
- sig { returns(T::Boolean) }
217
- def sorbet_sigil_is_true_or_higher
218
- parse_result.magic_comments.any? do |comment|
219
- comment.key == "typed" && ["true", "strict", "strong"].include?(comment.value)
226
+ sig { returns(SorbetLevel) }
227
+ def sorbet_level
228
+ sigil = parse_result.magic_comments.find do |comment|
229
+ comment.key == "typed"
230
+ end&.value
231
+
232
+ case sigil
233
+ when "ignore"
234
+ SorbetLevel::Ignore
235
+ when "false"
236
+ SorbetLevel::False
237
+ when "true"
238
+ SorbetLevel::True
239
+ when "strict", "strong"
240
+ SorbetLevel::Strict
241
+ else
242
+ SorbetLevel::None
220
243
  end
221
244
  end
222
245
 
@@ -36,10 +36,10 @@ module RubyLsp
36
36
  @test_library = T.let("minitest", String)
37
37
  @has_type_checker = T.let(true, T::Boolean)
38
38
  @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
39
- @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
40
39
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
41
40
  @supports_watching_files = T.let(false, T::Boolean)
42
41
  @experimental_features = T.let(false, T::Boolean)
42
+ @type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
43
43
  end
44
44
 
45
45
  sig { params(identifier: String, instance: Requests::Support::Formatter).void }
@@ -90,6 +90,7 @@ module RubyLsp
90
90
  end
91
91
 
92
92
  @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
93
+ @type_inferrer.experimental_features = @experimental_features
93
94
  end
94
95
 
95
96
  sig { returns(String) }
@@ -56,7 +56,7 @@ module RubyLsp
56
56
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
57
57
  global_state: GlobalState,
58
58
  node_context: NodeContext,
59
- typechecker_enabled: T::Boolean,
59
+ sorbet_level: Document::SorbetLevel,
60
60
  dispatcher: Prism::Dispatcher,
61
61
  uri: URI::Generic,
62
62
  trigger_character: T.nilable(String),
@@ -66,7 +66,7 @@ module RubyLsp
66
66
  response_builder,
67
67
  global_state,
68
68
  node_context,
69
- typechecker_enabled,
69
+ sorbet_level,
70
70
  dispatcher,
71
71
  uri,
72
72
  trigger_character
@@ -76,7 +76,7 @@ module RubyLsp
76
76
  @index = T.let(global_state.index, RubyIndexer::Index)
77
77
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
78
78
  @node_context = node_context
79
- @typechecker_enabled = typechecker_enabled
79
+ @sorbet_level = sorbet_level
80
80
  @uri = uri
81
81
  @trigger_character = trigger_character
82
82
 
@@ -97,7 +97,9 @@ module RubyLsp
97
97
  # Handle completion on regular constant references (e.g. `Bar`)
98
98
  sig { params(node: Prism::ConstantReadNode).void }
99
99
  def on_constant_read_node_enter(node)
100
- return if @global_state.has_type_checker
100
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
101
+ # no sigil, Sorbet will still provide completion for constants
102
+ return if @sorbet_level != Document::SorbetLevel::Ignore
101
103
 
102
104
  name = constant_name(node)
103
105
  return if name.nil?
@@ -118,7 +120,9 @@ module RubyLsp
118
120
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
119
121
  sig { params(node: Prism::ConstantPathNode).void }
120
122
  def on_constant_path_node_enter(node)
121
- return if @global_state.has_type_checker
123
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
124
+ # no sigil, Sorbet will still provide completion for constants
125
+ return if @sorbet_level != Document::SorbetLevel::Ignore
122
126
 
123
127
  name = constant_name(node)
124
128
  return if name.nil?
@@ -128,28 +132,32 @@ module RubyLsp
128
132
 
129
133
  sig { params(node: Prism::CallNode).void }
130
134
  def on_call_node_enter(node)
131
- receiver = node.receiver
132
-
133
- # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
134
- # methods). However, in addition to providing method completion, we also need to show possible constant
135
- # completions
136
- if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
137
- node.call_operator == "::"
138
-
139
- name = constant_name(receiver)
140
-
141
- if name
142
- start_loc = node.location
143
- end_loc = T.must(node.call_operator_loc)
144
-
145
- constant_path_completion(
146
- "#{name}::",
147
- Interface::Range.new(
148
- start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
149
- end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
150
- ),
151
- )
152
- return
135
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
136
+ # no sigil, Sorbet will still provide completion for constants
137
+ if @sorbet_level == Document::SorbetLevel::Ignore
138
+ receiver = node.receiver
139
+
140
+ # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
141
+ # singleton methods). However, in addition to providing method completion, we also need to show possible
142
+ # constant completions
143
+ if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
144
+ node.call_operator == "::"
145
+
146
+ name = constant_name(receiver)
147
+
148
+ if name
149
+ start_loc = node.location
150
+ end_loc = T.must(node.call_operator_loc)
151
+
152
+ constant_path_completion(
153
+ "#{name}::",
154
+ Interface::Range.new(
155
+ start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
156
+ end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
157
+ ),
158
+ )
159
+ return
160
+ end
153
161
  end
154
162
  end
155
163
 
@@ -162,7 +170,7 @@ module RubyLsp
162
170
  when "require_relative"
163
171
  complete_require_relative(node)
164
172
  else
165
- complete_methods(node, name) unless @typechecker_enabled
173
+ complete_methods(node, name)
166
174
  end
167
175
  end
168
176
 
@@ -247,10 +255,14 @@ module RubyLsp
247
255
 
248
256
  sig { params(name: String, location: Prism::Location).void }
249
257
  def handle_instance_variable_completion(name, location)
258
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
259
+ # to provide all features for them
260
+ return if @sorbet_level == Document::SorbetLevel::Strict
261
+
250
262
  type = @type_inferrer.infer_receiver_type(@node_context)
251
263
  return unless type
252
264
 
253
- @index.instance_variable_completion_candidates(name, type).each do |entry|
265
+ @index.instance_variable_completion_candidates(name, type.name).each do |entry|
254
266
  variable_name = entry.name
255
267
 
256
268
  label_details = Interface::CompletionItemLabelDetails.new(
@@ -321,12 +333,16 @@ module RubyLsp
321
333
 
322
334
  sig { params(node: Prism::CallNode, name: String).void }
323
335
  def complete_methods(node, name)
324
- # If the node has a receiver, then we don't need to provide local nor keyword completions
325
- if !@global_state.has_type_checker && !node.receiver
336
+ # If the node has a receiver, then we don't need to provide local nor keyword completions. Sorbet can provide
337
+ # local and keyword completion for any file with a Sorbet level of true or higher
338
+ if !sorbet_level_true_or_higher?(@sorbet_level) && !node.receiver
326
339
  add_local_completions(node, name)
327
340
  add_keyword_completions(node, name)
328
341
  end
329
342
 
343
+ # Sorbet can provide completion for methods invoked on self on typed true or higher files
344
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
345
+
330
346
  type = @type_inferrer.infer_receiver_type(@node_context)
331
347
  return unless type
332
348
 
@@ -350,8 +366,11 @@ module RubyLsp
350
366
 
351
367
  return unless range
352
368
 
353
- @index.method_completion_candidates(method_name, type).each do |entry|
369
+ guessed_type = type.name
370
+
371
+ @index.method_completion_candidates(method_name, type.name).each do |entry|
354
372
  entry_name = entry.name
373
+ owner_name = entry.owner&.name
355
374
 
356
375
  label_details = Interface::CompletionItemLabelDetails.new(
357
376
  description: entry.file_name,
@@ -364,7 +383,8 @@ module RubyLsp
364
383
  text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
365
384
  kind: Constant::CompletionItemKind::METHOD,
366
385
  data: {
367
- owner_name: entry.owner&.name,
386
+ owner_name: owner_name,
387
+ guessed_type: guessed_type,
368
388
  },
369
389
  )
370
390
  end
@@ -20,10 +20,10 @@ module RubyLsp
20
20
  uri: URI::Generic,
21
21
  node_context: NodeContext,
22
22
  dispatcher: Prism::Dispatcher,
23
- typechecker_enabled: T::Boolean,
23
+ sorbet_level: Document::SorbetLevel,
24
24
  ).void
25
25
  end
26
- def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
26
+ def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
27
27
  @response_builder = response_builder
28
28
  @global_state = global_state
29
29
  @index = T.let(global_state.index, RubyIndexer::Index)
@@ -31,7 +31,7 @@ module RubyLsp
31
31
  @language_id = language_id
32
32
  @uri = uri
33
33
  @node_context = node_context
34
- @typechecker_enabled = typechecker_enabled
34
+ @sorbet_level = sorbet_level
35
35
 
36
36
  dispatcher.register(
37
37
  self,
@@ -53,6 +53,11 @@ module RubyLsp
53
53
 
54
54
  sig { params(node: Prism::CallNode).void }
55
55
  def on_call_node_enter(node)
56
+ # Sorbet can handle go to definition for methods invoked on self on typed true or higher
57
+ if (@sorbet_level == Document::SorbetLevel::True || @sorbet_level == Document::SorbetLevel::Strict) &&
58
+ self_receiver?(node)
59
+ end
60
+
56
61
  message = node.message
57
62
  return unless message
58
63
 
@@ -60,7 +65,7 @@ module RubyLsp
60
65
 
61
66
  # Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
62
67
  # treating method calls' type as `nil` will allow users to get some completion support first
63
- if @language_id == Document::LanguageId::ERB && inferrer_receiver_type == "Object"
68
+ if @language_id == Document::LanguageId::ERB && inferrer_receiver_type&.name == "Object"
64
69
  inferrer_receiver_type = nil
65
70
  end
66
71
 
@@ -149,6 +154,9 @@ module RubyLsp
149
154
 
150
155
  sig { void }
151
156
  def handle_super_node_definition
157
+ # Sorbet can handle super hover on typed true or higher
158
+ return if sorbet_level_true_or_higher?(@sorbet_level)
159
+
152
160
  surrounding_method = @node_context.surrounding_method
153
161
  return unless surrounding_method
154
162
 
@@ -161,10 +169,14 @@ module RubyLsp
161
169
 
162
170
  sig { params(name: String).void }
163
171
  def handle_instance_variable_definition(name)
172
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
173
+ # to provide all features for them
174
+ return if @sorbet_level == Document::SorbetLevel::Strict
175
+
164
176
  type = @type_inferrer.infer_receiver_type(@node_context)
165
177
  return unless type
166
178
 
167
- entries = @index.resolve_instance_variable(name, type)
179
+ entries = @index.resolve_instance_variable(name, type.name)
168
180
  return unless entries
169
181
 
170
182
  entries.each do |entry|
@@ -182,10 +194,10 @@ module RubyLsp
182
194
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
183
195
  end
184
196
 
185
- sig { params(message: String, receiver_type: T.nilable(String), inherited_only: T::Boolean).void }
197
+ sig { params(message: String, receiver_type: T.nilable(TypeInferrer::Type), inherited_only: T::Boolean).void }
186
198
  def handle_method_definition(message, receiver_type, inherited_only: false)
187
199
  methods = if receiver_type
188
- @index.resolve_method(message, receiver_type, inherited_only: inherited_only)
200
+ @index.resolve_method(message, receiver_type.name, inherited_only: inherited_only)
189
201
  else
190
202
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
191
203
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -196,7 +208,7 @@ module RubyLsp
196
208
 
197
209
  methods.each do |target_method|
198
210
  file_path = target_method.file_path
199
- next if @typechecker_enabled && not_in_dependencies?(file_path)
211
+ next if sorbet_level_true_or_higher?(@sorbet_level) && not_in_dependencies?(file_path)
200
212
 
201
213
  @response_builder << Interface::LocationLink.new(
202
214
  target_uri: URI::Generic.from_path(path: file_path).to_s,
@@ -253,10 +265,10 @@ module RubyLsp
253
265
 
254
266
  entries.each do |entry|
255
267
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
256
- # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
257
- # in the project, even if the files are typed false
268
+ # additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
269
+ # ignore
258
270
  file_path = entry.file_path
259
- next if @typechecker_enabled && not_in_dependencies?(file_path)
271
+ next if @sorbet_level != Document::SorbetLevel::Ignore && not_in_dependencies?(file_path)
260
272
 
261
273
  @response_builder << Interface::LocationLink.new(
262
274
  target_uri: URI::Generic.from_path(path: file_path).to_s,
@@ -180,7 +180,11 @@ module RubyLsp
180
180
  def on_call_node_enter(node)
181
181
  return unless matches?(node, [Prism::CallNode, Prism::DefNode])
182
182
 
183
- add_highlight(Constant::DocumentHighlightKind::READ, node.location)
183
+ loc = node.message_loc
184
+ # if we have `foo.` it's a call node but there is no message yet.
185
+ return unless loc
186
+
187
+ add_highlight(Constant::DocumentHighlightKind::READ, loc)
184
188
  end
185
189
 
186
190
  sig { params(node: Prism::DefNode).void }
@@ -42,17 +42,17 @@ module RubyLsp
42
42
  uri: URI::Generic,
43
43
  node_context: NodeContext,
44
44
  dispatcher: Prism::Dispatcher,
45
- typechecker_enabled: T::Boolean,
45
+ sorbet_level: Document::SorbetLevel,
46
46
  ).void
47
47
  end
48
- def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
48
+ def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
49
49
  @response_builder = response_builder
50
50
  @global_state = global_state
51
51
  @index = T.let(global_state.index, RubyIndexer::Index)
52
52
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
53
53
  @path = T.let(uri.to_standardized_path, T.nilable(String))
54
54
  @node_context = node_context
55
- @typechecker_enabled = typechecker_enabled
55
+ @sorbet_level = sorbet_level
56
56
 
57
57
  dispatcher.register(
58
58
  self,
@@ -73,7 +73,7 @@ module RubyLsp
73
73
 
74
74
  sig { params(node: Prism::ConstantReadNode).void }
75
75
  def on_constant_read_node_enter(node)
76
- return if @typechecker_enabled
76
+ return if @sorbet_level != Document::SorbetLevel::Ignore
77
77
 
78
78
  name = constant_name(node)
79
79
  return if name.nil?
@@ -83,14 +83,14 @@ module RubyLsp
83
83
 
84
84
  sig { params(node: Prism::ConstantWriteNode).void }
85
85
  def on_constant_write_node_enter(node)
86
- return if @global_state.has_type_checker
86
+ return if @sorbet_level != Document::SorbetLevel::Ignore
87
87
 
88
88
  generate_hover(node.name.to_s, node.name_loc)
89
89
  end
90
90
 
91
91
  sig { params(node: Prism::ConstantPathNode).void }
92
92
  def on_constant_path_node_enter(node)
93
- return if @global_state.has_type_checker
93
+ return if @sorbet_level != Document::SorbetLevel::Ignore
94
94
 
95
95
  name = constant_name(node)
96
96
  return if name.nil?
@@ -105,7 +105,7 @@ module RubyLsp
105
105
  return
106
106
  end
107
107
 
108
- return if @typechecker_enabled
108
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
109
109
 
110
110
  message = node.message
111
111
  return unless message
@@ -157,6 +157,9 @@ module RubyLsp
157
157
 
158
158
  sig { void }
159
159
  def handle_super_node_hover
160
+ # Sorbet can handle super hover on typed true or higher
161
+ return if sorbet_level_true_or_higher?(@sorbet_level)
162
+
160
163
  surrounding_method = @node_context.surrounding_method
161
164
  return unless surrounding_method
162
165
 
@@ -168,11 +171,16 @@ module RubyLsp
168
171
  type = @type_inferrer.infer_receiver_type(@node_context)
169
172
  return unless type
170
173
 
171
- methods = @index.resolve_method(message, type, inherited_only: inherited_only)
174
+ methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
172
175
  return unless methods
173
176
 
174
177
  title = "#{message}#{T.must(methods.first).decorated_parameters}"
175
178
 
179
+ if type.is_a?(TypeInferrer::GuessedType)
180
+ title << "\n\nGuessed receiver: #{type.name}"
181
+ @response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
182
+ end
183
+
176
184
  categorized_markdown_from_index_entries(title, methods).each do |category, content|
177
185
  @response_builder.push(content, category: category)
178
186
  end
@@ -180,10 +188,14 @@ module RubyLsp
180
188
 
181
189
  sig { params(name: String).void }
182
190
  def handle_instance_variable_hover(name)
191
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
192
+ # to provide all features for them
193
+ return if @sorbet_level == Document::SorbetLevel::Strict
194
+
183
195
  type = @type_inferrer.infer_receiver_type(@node_context)
184
196
  return unless type
185
197
 
186
- entries = @index.resolve_instance_variable(name, type)
198
+ entries = @index.resolve_instance_variable(name, type.name)
187
199
  return unless entries
188
200
 
189
201
  categorized_markdown_from_index_entries(name, entries).each do |category, content|
@@ -13,11 +13,11 @@ module RubyLsp
13
13
  global_state: GlobalState,
14
14
  node_context: NodeContext,
15
15
  dispatcher: Prism::Dispatcher,
16
- typechecker_enabled: T::Boolean,
16
+ sorbet_level: Document::SorbetLevel,
17
17
  ).void
18
18
  end
19
- def initialize(response_builder, global_state, node_context, dispatcher, typechecker_enabled)
20
- @typechecker_enabled = typechecker_enabled
19
+ def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
20
+ @sorbet_level = sorbet_level
21
21
  @response_builder = response_builder
22
22
  @global_state = global_state
23
23
  @index = T.let(global_state.index, RubyIndexer::Index)
@@ -28,7 +28,7 @@ module RubyLsp
28
28
 
29
29
  sig { params(node: Prism::CallNode).void }
30
30
  def on_call_node_enter(node)
31
- return if @typechecker_enabled
31
+ return if sorbet_level_true_or_higher?(@sorbet_level)
32
32
 
33
33
  message = node.message
34
34
  return unless message
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  type = @type_inferrer.infer_receiver_type(@node_context)
37
37
  return unless type
38
38
 
39
- methods = @index.resolve_method(message, type)
39
+ methods = @index.resolve_method(message, type.name)
40
40
  return unless methods
41
41
 
42
42
  target_method = methods.first
@@ -61,6 +61,13 @@ module RubyLsp
61
61
  active_parameter += 1
62
62
  end
63
63
 
64
+ title = +""
65
+
66
+ extra_links = if type.is_a?(TypeInferrer::GuessedType)
67
+ title << "\n\nGuessed receiver: #{type.name}"
68
+ "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
69
+ end
70
+
64
71
  signature_help = Interface::SignatureHelp.new(
65
72
  signatures: [
66
73
  Interface::SignatureInformation.new(
@@ -68,7 +75,7 @@ module RubyLsp
68
75
  parameters: parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
69
76
  documentation: Interface::MarkupContent.new(
70
77
  kind: "markdown",
71
- value: markdown_from_index_entries("", methods),
78
+ value: markdown_from_index_entries(title, methods, extra_links: extra_links),
72
79
  ),
73
80
  ),
74
81
  ],
@@ -89,12 +89,12 @@ module RubyLsp
89
89
  when Prism::ClassNode, Prism::ModuleNode
90
90
  nesting << node.constant_path.slice
91
91
  when Prism::SingletonClassNode
92
- nesting << "<Class:#{nesting.last}>"
92
+ nesting << "<Class:#{nesting.flat_map { |n| n.split("::") }.last}>"
93
93
  when Prism::DefNode
94
94
  surrounding_method = node.name.to_s
95
95
  next unless node.receiver.is_a?(Prism::SelfNode)
96
96
 
97
- nesting << "<Class:#{nesting.last}>"
97
+ nesting << "<Class:#{nesting.flat_map { |n| n.split("::") }.last}>"
98
98
  end
99
99
  end
100
100
 
@@ -49,11 +49,11 @@ module RubyLsp
49
49
  document: Document,
50
50
  global_state: GlobalState,
51
51
  params: T::Hash[Symbol, T.untyped],
52
- typechecker_enabled: T::Boolean,
52
+ sorbet_level: Document::SorbetLevel,
53
53
  dispatcher: Prism::Dispatcher,
54
54
  ).void
55
55
  end
56
- def initialize(document, global_state, params, typechecker_enabled, dispatcher)
56
+ def initialize(document, global_state, params, sorbet_level, dispatcher)
57
57
  super()
58
58
  @target = T.let(nil, T.nilable(Prism::Node))
59
59
  @dispatcher = dispatcher
@@ -84,7 +84,7 @@ module RubyLsp
84
84
  @response_builder,
85
85
  global_state,
86
86
  node_context,
87
- typechecker_enabled,
87
+ sorbet_level,
88
88
  dispatcher,
89
89
  document.uri,
90
90
  params.dig(:context, :triggerCharacter),
@@ -47,7 +47,7 @@ module RubyLsp
47
47
  #
48
48
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
49
49
  # `label` for the text edit instead
50
- label = @item[:label]
50
+ label = @item[:label].dup
51
51
  entries = @index[label] || []
52
52
 
53
53
  owner_name = @item.dig(:data, :owner_name)
@@ -62,12 +62,19 @@ module RubyLsp
62
62
  first_entry = T.must(entries.first)
63
63
 
64
64
  if first_entry.is_a?(RubyIndexer::Entry::Member)
65
- label = "#{label}#{first_entry.decorated_parameters}"
65
+ label = +"#{label}#{first_entry.decorated_parameters}"
66
+ end
67
+
68
+ guessed_type = @item.dig(:data, :guessed_type)
69
+
70
+ extra_links = if guessed_type
71
+ label << "\n\nGuessed receiver: #{guessed_type}"
72
+ "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
66
73
  end
67
74
 
68
75
  @item[:documentation] = Interface::MarkupContent.new(
69
76
  kind: "markdown",
70
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
77
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
71
78
  )
72
79
 
73
80
  @item
@@ -36,10 +36,10 @@ module RubyLsp
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
39
- typechecker_enabled: T::Boolean,
39
+ sorbet_level: Document::SorbetLevel,
40
40
  ).void
41
41
  end
42
- def initialize(document, global_state, position, dispatcher, typechecker_enabled)
42
+ def initialize(document, global_state, position, dispatcher, sorbet_level)
43
43
  super()
44
44
  @response_builder = T.let(
45
45
  ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)].new,
@@ -96,7 +96,7 @@ module RubyLsp
96
96
  document.uri,
97
97
  node_context,
98
98
  dispatcher,
99
- typechecker_enabled,
99
+ sorbet_level,
100
100
  )
101
101
 
102
102
  Addon.addons.each do |addon|