ruby-lsp 0.17.4 → 0.17.13

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +26 -1
  5. data/exe/ruby-lsp-check +1 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
  7. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
  10. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -8
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
  13. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  14. data/lib/ruby_indexer/test/constant_test.rb +17 -17
  15. data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
  16. data/lib/ruby_indexer/test/index_test.rb +367 -17
  17. data/lib/ruby_indexer/test/method_test.rb +58 -25
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
  19. data/lib/ruby_indexer/test/test_case.rb +1 -5
  20. data/lib/ruby_lsp/addon.rb +22 -5
  21. data/lib/ruby_lsp/base_server.rb +8 -3
  22. data/lib/ruby_lsp/document.rb +27 -46
  23. data/lib/ruby_lsp/erb_document.rb +125 -0
  24. data/lib/ruby_lsp/global_state.rb +47 -19
  25. data/lib/ruby_lsp/internal.rb +2 -0
  26. data/lib/ruby_lsp/listeners/completion.rb +161 -57
  27. data/lib/ruby_lsp/listeners/definition.rb +91 -27
  28. data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
  29. data/lib/ruby_lsp/listeners/hover.rb +61 -19
  30. data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
  31. data/lib/ruby_lsp/node_context.rb +65 -5
  32. data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
  33. data/lib/ruby_lsp/requests/code_actions.rb +11 -2
  34. data/lib/ruby_lsp/requests/completion.rb +4 -4
  35. data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
  36. data/lib/ruby_lsp/requests/definition.rb +18 -8
  37. data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
  38. data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
  39. data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
  40. data/lib/ruby_lsp/requests/formatting.rb +15 -0
  41. data/lib/ruby_lsp/requests/hover.rb +5 -5
  42. data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
  43. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  44. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  45. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  46. data/lib/ruby_lsp/requests/support/common.rb +11 -2
  47. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
  48. data/lib/ruby_lsp/ruby_document.rb +74 -0
  49. data/lib/ruby_lsp/server.rb +129 -54
  50. data/lib/ruby_lsp/store.rb +33 -9
  51. data/lib/ruby_lsp/test_helper.rb +3 -1
  52. data/lib/ruby_lsp/type_inferrer.rb +61 -25
  53. data/lib/ruby_lsp/utils.rb +13 -0
  54. metadata +9 -8
  55. data/exe/ruby-lsp-doctor +0 -23
@@ -21,6 +21,8 @@ module RubyLsp
21
21
  Prism::InstanceVariableWriteNode,
22
22
  Prism::SymbolNode,
23
23
  Prism::StringNode,
24
+ Prism::SuperNode,
25
+ Prism::ForwardingSuperNode,
24
26
  ],
25
27
  T::Array[T.class_of(Prism::Node)],
26
28
  )
@@ -40,17 +42,17 @@ module RubyLsp
40
42
  uri: URI::Generic,
41
43
  node_context: NodeContext,
42
44
  dispatcher: Prism::Dispatcher,
43
- typechecker_enabled: T::Boolean,
45
+ sorbet_level: RubyDocument::SorbetLevel,
44
46
  ).void
45
47
  end
46
- 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
47
49
  @response_builder = response_builder
48
50
  @global_state = global_state
49
51
  @index = T.let(global_state.index, RubyIndexer::Index)
50
52
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
51
53
  @path = T.let(uri.to_standardized_path, T.nilable(String))
52
54
  @node_context = node_context
53
- @typechecker_enabled = typechecker_enabled
55
+ @sorbet_level = sorbet_level
54
56
 
55
57
  dispatcher.register(
56
58
  self,
@@ -64,12 +66,14 @@ module RubyLsp
64
66
  :on_instance_variable_operator_write_node_enter,
65
67
  :on_instance_variable_or_write_node_enter,
66
68
  :on_instance_variable_target_node_enter,
69
+ :on_super_node_enter,
70
+ :on_forwarding_super_node_enter,
67
71
  )
68
72
  end
69
73
 
70
74
  sig { params(node: Prism::ConstantReadNode).void }
71
75
  def on_constant_read_node_enter(node)
72
- return if @typechecker_enabled
76
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
73
77
 
74
78
  name = constant_name(node)
75
79
  return if name.nil?
@@ -79,14 +83,14 @@ module RubyLsp
79
83
 
80
84
  sig { params(node: Prism::ConstantWriteNode).void }
81
85
  def on_constant_write_node_enter(node)
82
- return if @global_state.has_type_checker
86
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
83
87
 
84
88
  generate_hover(node.name.to_s, node.name_loc)
85
89
  end
86
90
 
87
91
  sig { params(node: Prism::ConstantPathNode).void }
88
92
  def on_constant_path_node_enter(node)
89
- return if @global_state.has_type_checker
93
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
90
94
 
91
95
  name = constant_name(node)
92
96
  return if name.nil?
@@ -101,22 +105,12 @@ module RubyLsp
101
105
  return
102
106
  end
103
107
 
104
- return if @typechecker_enabled
108
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
105
109
 
106
110
  message = node.message
107
111
  return unless message
108
112
 
109
- type = @type_inferrer.infer_receiver_type(@node_context)
110
- return unless type
111
-
112
- methods = @index.resolve_method(message, type)
113
- return unless methods
114
-
115
- title = "#{message}#{T.must(methods.first).decorated_parameters}"
116
-
117
- categorized_markdown_from_index_entries(title, methods).each do |category, content|
118
- @response_builder.push(content, category: category)
119
- end
113
+ handle_method_hover(message)
120
114
  end
121
115
 
122
116
  sig { params(node: Prism::InstanceVariableReadNode).void }
@@ -149,14 +143,62 @@ module RubyLsp
149
143
  handle_instance_variable_hover(node.name.to_s)
150
144
  end
151
145
 
146
+ sig { params(node: Prism::SuperNode).void }
147
+ def on_super_node_enter(node)
148
+ handle_super_node_hover
149
+ end
150
+
151
+ sig { params(node: Prism::ForwardingSuperNode).void }
152
+ def on_forwarding_super_node_enter(node)
153
+ handle_super_node_hover
154
+ end
155
+
152
156
  private
153
157
 
158
+ sig { void }
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
+
163
+ surrounding_method = @node_context.surrounding_method
164
+ return unless surrounding_method
165
+
166
+ handle_method_hover(surrounding_method, inherited_only: true)
167
+ end
168
+
169
+ sig { params(message: String, inherited_only: T::Boolean).void }
170
+ def handle_method_hover(message, inherited_only: false)
171
+ type = @type_inferrer.infer_receiver_type(@node_context)
172
+ return unless type
173
+
174
+ methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
175
+ return unless methods
176
+
177
+ first_method = T.must(methods.first)
178
+
179
+ title = "#{message}#{first_method.decorated_parameters}"
180
+ title << first_method.formatted_signatures
181
+
182
+ if type.is_a?(TypeInferrer::GuessedType)
183
+ title << "\n\nGuessed receiver: #{type.name}"
184
+ @response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
185
+ end
186
+
187
+ categorized_markdown_from_index_entries(title, methods).each do |category, content|
188
+ @response_builder.push(content, category: category)
189
+ end
190
+ end
191
+
154
192
  sig { params(name: String).void }
155
193
  def handle_instance_variable_hover(name)
194
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
195
+ # to provide all features for them
196
+ return if @sorbet_level == RubyDocument::SorbetLevel::Strict
197
+
156
198
  type = @type_inferrer.infer_receiver_type(@node_context)
157
199
  return unless type
158
200
 
159
- entries = @index.resolve_instance_variable(name, type)
201
+ entries = @index.resolve_instance_variable(name, type.name)
160
202
  return unless entries
161
203
 
162
204
  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: RubyDocument::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
  ],
@@ -23,22 +23,82 @@ module RubyLsp
23
23
  params(
24
24
  node: T.nilable(Prism::Node),
25
25
  parent: T.nilable(Prism::Node),
26
- nesting: T::Array[String],
26
+ nesting_nodes: T::Array[T.any(
27
+ Prism::ClassNode,
28
+ Prism::ModuleNode,
29
+ Prism::SingletonClassNode,
30
+ Prism::DefNode,
31
+ Prism::BlockNode,
32
+ Prism::LambdaNode,
33
+ Prism::ProgramNode,
34
+ )],
27
35
  call_node: T.nilable(Prism::CallNode),
28
- surrounding_method: T.nilable(String),
29
36
  ).void
30
37
  end
31
- def initialize(node, parent, nesting, call_node, surrounding_method)
38
+ def initialize(node, parent, nesting_nodes, call_node)
32
39
  @node = node
33
40
  @parent = parent
34
- @nesting = nesting
41
+ @nesting_nodes = nesting_nodes
35
42
  @call_node = call_node
36
- @surrounding_method = surrounding_method
43
+
44
+ nesting, surrounding_method = handle_nesting_nodes(nesting_nodes)
45
+ @nesting = T.let(nesting, T::Array[String])
46
+ @surrounding_method = T.let(surrounding_method, T.nilable(String))
37
47
  end
38
48
 
39
49
  sig { returns(String) }
40
50
  def fully_qualified_name
41
51
  @fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
42
52
  end
53
+
54
+ sig { returns(T::Array[Symbol]) }
55
+ def locals_for_scope
56
+ locals = []
57
+
58
+ @nesting_nodes.each do |node|
59
+ if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode) ||
60
+ node.is_a?(Prism::DefNode)
61
+ locals.clear
62
+ end
63
+
64
+ locals.concat(node.locals)
65
+ end
66
+
67
+ locals
68
+ end
69
+
70
+ private
71
+
72
+ sig do
73
+ params(nodes: T::Array[T.any(
74
+ Prism::ClassNode,
75
+ Prism::ModuleNode,
76
+ Prism::SingletonClassNode,
77
+ Prism::DefNode,
78
+ Prism::BlockNode,
79
+ Prism::LambdaNode,
80
+ Prism::ProgramNode,
81
+ )]).returns([T::Array[String], T.nilable(String)])
82
+ end
83
+ def handle_nesting_nodes(nodes)
84
+ nesting = []
85
+ surrounding_method = T.let(nil, T.nilable(String))
86
+
87
+ @nesting_nodes.each do |node|
88
+ case node
89
+ when Prism::ClassNode, Prism::ModuleNode
90
+ nesting << node.constant_path.slice
91
+ when Prism::SingletonClassNode
92
+ nesting << "<Class:#{nesting.flat_map { |n| n.split("::") }.last}>"
93
+ when Prism::DefNode
94
+ surrounding_method = node.name.to_s
95
+ next unless node.receiver.is_a?(Prism::SelfNode)
96
+
97
+ nesting << "<Class:#{nesting.flat_map { |n| n.split("::") }.last}>"
98
+ end
99
+ end
100
+
101
+ [nesting, surrounding_method]
102
+ end
43
103
  end
44
104
  end
@@ -23,6 +23,8 @@ module RubyLsp
23
23
  #
24
24
  class CodeActionResolve < Request
25
25
  extend T::Sig
26
+ include Support::Common
27
+
26
28
  NEW_VARIABLE_NAME = "new_variable"
27
29
  NEW_METHOD_NAME = "new_method"
28
30
 
@@ -36,7 +38,7 @@ module RubyLsp
36
38
  end
37
39
  end
38
40
 
39
- sig { params(document: Document, code_action: T::Hash[Symbol, T.untyped]).void }
41
+ sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
40
42
  def initialize(document, code_action)
41
43
  super()
42
44
  @document = document
@@ -45,20 +47,62 @@ module RubyLsp
45
47
 
46
48
  sig { override.returns(T.any(Interface::CodeAction, Error)) }
47
49
  def perform
50
+ return Error::EmptySelection if @document.source.empty?
51
+
48
52
  case @code_action[:title]
49
53
  when CodeActions::EXTRACT_TO_VARIABLE_TITLE
50
54
  refactor_variable
51
55
  when CodeActions::EXTRACT_TO_METHOD_TITLE
52
56
  refactor_method
57
+ when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
58
+ switch_block_style
53
59
  else
54
60
  Error::UnknownCodeAction
55
61
  end
56
62
  end
57
63
 
64
+ private
65
+
58
66
  sig { returns(T.any(Interface::CodeAction, Error)) }
59
- def refactor_variable
60
- return Error::EmptySelection if @document.source.empty?
67
+ def switch_block_style
68
+ source_range = @code_action.dig(:data, :range)
69
+ return Error::EmptySelection if source_range[:start] == source_range[:end]
70
+
71
+ target = @document.locate_first_within_range(
72
+ @code_action.dig(:data, :range),
73
+ node_types: [Prism::CallNode],
74
+ )
75
+
76
+ return Error::InvalidTargetRange unless target.is_a?(Prism::CallNode)
77
+
78
+ node = target.block
79
+ return Error::InvalidTargetRange unless node.is_a?(Prism::BlockNode)
61
80
 
81
+ indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
82
+
83
+ Interface::CodeAction.new(
84
+ title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
85
+ edit: Interface::WorkspaceEdit.new(
86
+ document_changes: [
87
+ Interface::TextDocumentEdit.new(
88
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
89
+ uri: @code_action.dig(:data, :uri),
90
+ version: nil,
91
+ ),
92
+ edits: [
93
+ Interface::TextEdit.new(
94
+ range: range_from_location(node.location),
95
+ new_text: recursively_switch_nested_block_styles(node, indentation),
96
+ ),
97
+ ],
98
+ ),
99
+ ],
100
+ ),
101
+ )
102
+ end
103
+
104
+ sig { returns(T.any(Interface::CodeAction, Error)) }
105
+ def refactor_variable
62
106
  source_range = @code_action.dig(:data, :range)
63
107
  return Error::EmptySelection if source_range[:start] == source_range[:end]
64
108
 
@@ -69,7 +113,7 @@ module RubyLsp
69
113
 
70
114
  # Find the closest statements node, so that we place the refactor in a valid position
71
115
  node_context = @document
72
- .locate(@document.tree, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
116
+ .locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
73
117
 
74
118
  closest_statements = node_context.node
75
119
  parent_statements = node_context.parent
@@ -153,8 +197,6 @@ module RubyLsp
153
197
 
154
198
  sig { returns(T.any(Interface::CodeAction, Error)) }
155
199
  def refactor_method
156
- return Error::EmptySelection if @document.source.empty?
157
-
158
200
  source_range = @code_action.dig(:data, :range)
159
201
  return Error::EmptySelection if source_range[:start] == source_range[:end]
160
202
 
@@ -164,7 +206,7 @@ module RubyLsp
164
206
  extracted_source = T.must(@document.source[start_index...end_index])
165
207
 
166
208
  # Find the closest method declaration node, so that we place the refactor in a valid position
167
- node_context = @document.locate(@document.tree, start_index, node_types: [Prism::DefNode])
209
+ node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
168
210
  closest_def = T.cast(node_context.node, Prism::DefNode)
169
211
  return Error::InvalidTargetRange if closest_def.nil?
170
212
 
@@ -206,8 +248,6 @@ module RubyLsp
206
248
  )
207
249
  end
208
250
 
209
- private
210
-
211
251
  sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) }
212
252
  def create_text_edit(range, new_text)
213
253
  Interface::TextEdit.new(
@@ -218,6 +258,64 @@ module RubyLsp
218
258
  new_text: new_text,
219
259
  )
220
260
  end
261
+
262
+ sig { params(node: Prism::BlockNode, indentation: T.nilable(String)).returns(String) }
263
+ def recursively_switch_nested_block_styles(node, indentation)
264
+ parameters = node.parameters
265
+ body = node.body
266
+
267
+ # We use the indentation to differentiate between do...end and brace style blocks because only the do...end
268
+ # style requires the indentation to build the edit.
269
+ #
270
+ # If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
271
+ # semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
272
+ # we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
273
+ source = +""
274
+
275
+ if indentation
276
+ source << "do"
277
+ source << " #{parameters.slice}" if parameters
278
+ source << "\n#{indentation} "
279
+ source << switch_block_body(body, indentation) if body
280
+ source << "\n#{indentation}end"
281
+ else
282
+ source << "{ "
283
+ source << "#{parameters.slice} " if parameters
284
+ source << switch_block_body(body, nil) if body
285
+ source << "}"
286
+ end
287
+
288
+ source
289
+ end
290
+
291
+ sig { params(body: Prism::Node, indentation: T.nilable(String)).returns(String) }
292
+ def switch_block_body(body, indentation)
293
+ # Check if there are any nested blocks inside of the current block
294
+ body_loc = body.location
295
+ nested_block = @document.locate_first_within_range(
296
+ {
297
+ start: { line: body_loc.start_line - 1, character: body_loc.start_column },
298
+ end: { line: body_loc.end_line - 1, character: body_loc.end_column },
299
+ },
300
+ node_types: [Prism::BlockNode],
301
+ )
302
+
303
+ body_content = body.slice.dup
304
+
305
+ # If there are nested blocks, then we change their style too and we have to mutate the string using the
306
+ # relative position in respect to the beginning of the body
307
+ if nested_block.is_a?(Prism::BlockNode)
308
+ location = nested_block.location
309
+ correction_start = location.start_offset - body_loc.start_offset
310
+ correction_end = location.end_offset - body_loc.start_offset
311
+ next_indentation = indentation ? "#{indentation} " : nil
312
+
313
+ body_content[correction_start...correction_end] =
314
+ recursively_switch_nested_block_styles(nested_block, next_indentation)
315
+ end
316
+
317
+ indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
318
+ end
221
319
  end
222
320
  end
223
321
  end
@@ -21,13 +21,17 @@ module RubyLsp
21
21
 
22
22
  EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
23
23
  EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
24
+ TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
24
25
 
25
26
  class << self
26
27
  extend T::Sig
27
28
 
28
- sig { returns(Interface::CodeActionOptions) }
29
+ sig { returns(Interface::CodeActionRegistrationOptions) }
29
30
  def provider
30
- Interface::CodeActionOptions.new(resolve_provider: true)
31
+ Interface::CodeActionRegistrationOptions.new(
32
+ document_selector: [Interface::DocumentFilter.new(language: "ruby")],
33
+ resolve_provider: true,
34
+ )
31
35
  end
32
36
  end
33
37
 
@@ -66,6 +70,11 @@ module RubyLsp
66
70
  kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
67
71
  data: { range: @range, uri: @uri.to_s },
68
72
  )
73
+ code_actions << Interface::CodeAction.new(
74
+ title: TOGGLE_BLOCK_STYLE_TITLE,
75
+ kind: Constant::CodeActionKind::REFACTOR_REWRITE,
76
+ data: { range: @range, uri: @uri.to_s },
77
+ )
69
78
  end
70
79
 
71
80
  code_actions
@@ -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: RubyDocument::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
@@ -61,7 +61,7 @@ module RubyLsp
61
61
  # back by 1, so that we find the right node
62
62
  char_position = document.create_scanner.find_char_position(params[:position]) - 1
63
63
  node_context = document.locate(
64
- document.tree,
64
+ document.parse_result.value,
65
65
  char_position,
66
66
  node_types: [
67
67
  Prism::CallNode,
@@ -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),
@@ -38,13 +38,16 @@ module RubyLsp
38
38
 
39
39
  sig { override.returns(T::Hash[Symbol, T.untyped]) }
40
40
  def perform
41
+ return @item if @item.dig(:data, :skip_resolve)
42
+
41
43
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
42
44
  # a completion resolve request must always return the original completion item without modifying ANY fields
43
- # other than label details and documentation. If we modify anything, the completion behaviour might be broken.
45
+ # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
46
+ # be broken.
44
47
  #
45
48
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
46
49
  # `label` for the text edit instead
47
- label = @item[:label]
50
+ label = @item[:label].dup
48
51
  entries = @index[label] || []
49
52
 
50
53
  owner_name = @item.dig(:data, :owner_name)
@@ -59,18 +62,20 @@ module RubyLsp
59
62
  first_entry = T.must(entries.first)
60
63
 
61
64
  if first_entry.is_a?(RubyIndexer::Entry::Member)
62
- detail = first_entry.decorated_parameters
63
- label = "#{label}#{first_entry.decorated_parameters}"
65
+ label = +"#{label}#{first_entry.decorated_parameters}"
66
+ label << first_entry.formatted_signatures
64
67
  end
65
68
 
66
- @item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
67
- description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
68
- detail: detail,
69
- )
69
+ guessed_type = @item.dig(:data, :guessed_type)
70
+
71
+ extra_links = if guessed_type
72
+ label << "\n\nGuessed receiver: #{guessed_type}"
73
+ "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
74
+ end
70
75
 
71
76
  @item[:documentation] = Interface::MarkupContent.new(
72
77
  kind: "markdown",
73
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
78
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
74
79
  )
75
80
 
76
81
  @item
@@ -30,20 +30,26 @@ module RubyLsp
30
30
  extend T::Sig
31
31
  extend T::Generic
32
32
 
33
+ SPECIAL_METHOD_CALLS = [
34
+ :require,
35
+ :require_relative,
36
+ :autoload,
37
+ ].freeze
38
+
33
39
  sig do
34
40
  params(
35
41
  document: Document,
36
42
  global_state: GlobalState,
37
43
  position: T::Hash[Symbol, T.untyped],
38
44
  dispatcher: Prism::Dispatcher,
39
- typechecker_enabled: T::Boolean,
45
+ sorbet_level: RubyDocument::SorbetLevel,
40
46
  ).void
41
47
  end
42
- def initialize(document, global_state, position, dispatcher, typechecker_enabled)
48
+ def initialize(document, global_state, position, dispatcher, sorbet_level)
43
49
  super()
44
50
  @response_builder = T.let(
45
- ResponseBuilders::CollectionResponseBuilder[Interface::Location].new,
46
- ResponseBuilders::CollectionResponseBuilder[Interface::Location],
51
+ ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)].new,
52
+ ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)],
47
53
  )
48
54
  @dispatcher = dispatcher
49
55
 
@@ -62,6 +68,8 @@ module RubyLsp
62
68
  Prism::InstanceVariableWriteNode,
63
69
  Prism::SymbolNode,
64
70
  Prism::StringNode,
71
+ Prism::SuperNode,
72
+ Prism::ForwardingSuperNode,
65
73
  ],
66
74
  )
67
75
 
@@ -76,8 +84,9 @@ module RubyLsp
76
84
  parent,
77
85
  position,
78
86
  )
79
- elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
80
- !covers_position?(target.message_loc, position)
87
+ elsif target.is_a?(Prism::CallNode) && !SPECIAL_METHOD_CALLS.include?(target.message) && !covers_position?(
88
+ target.message_loc, position
89
+ )
81
90
  # If the target is a method call, we need to ensure that the requested position is exactly on top of the
82
91
  # method identifier. Otherwise, we risk showing definitions for unrelated things
83
92
  target = nil
@@ -90,10 +99,11 @@ module RubyLsp
90
99
  Listeners::Definition.new(
91
100
  @response_builder,
92
101
  global_state,
102
+ document.language_id,
93
103
  document.uri,
94
104
  node_context,
95
105
  dispatcher,
96
- typechecker_enabled,
106
+ sorbet_level,
97
107
  )
98
108
 
99
109
  Addon.addons.each do |addon|
@@ -104,7 +114,7 @@ module RubyLsp
104
114
  @target = T.let(target, T.nilable(Prism::Node))
105
115
  end
106
116
 
107
- sig { override.returns(T::Array[Interface::Location]) }
117
+ sig { override.returns(T::Array[T.any(Interface::Location, Interface::LocationLink)]) }
108
118
  def perform
109
119
  @dispatcher.dispatch_once(@target) if @target
110
120
  @response_builder.response
@@ -22,12 +22,13 @@ module RubyLsp
22
22
  class << self
23
23
  extend T::Sig
24
24
 
25
- sig { returns(T::Hash[Symbol, T::Boolean]) }
25
+ sig { returns(Interface::DiagnosticRegistrationOptions) }
26
26
  def provider
27
- {
28
- interFileDependencies: false,
29
- workspaceDiagnostics: false,
30
- }
27
+ Interface::DiagnosticRegistrationOptions.new(
28
+ document_selector: [Interface::DocumentFilter.new(language: "ruby")],
29
+ inter_file_dependencies: false,
30
+ workspace_diagnostics: false,
31
+ )
31
32
  end
32
33
  end
33
34