ruby-lsp 0.17.8 → 0.17.10

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.
@@ -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|