ruby-lsp 0.17.11 → 0.17.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13815c41a1b7509782277cb5dac09481130e18efc4253a01cede31ca41b335a7
4
- data.tar.gz: 40e6584ff6ff743ea1e62a29d6090d80bf318ae3dd5d90808de1b35723bea063
3
+ metadata.gz: 34cae8b53cfb0811cb26d3b3dd4198a72585d1f15a98a7d8f656fd1676886630
4
+ data.tar.gz: f4868222c46c4cbad9a099fce69562838b5dda567f1666a82752f45fe3f501c5
5
5
  SHA512:
6
- metadata.gz: 74da4b254b598cd4b10701b65c49d045cc33be83330399e67602f8e43597c9e54567715343a67fa720b31084de540e498cc3d5525e64679500d52b4842e44cf7
7
- data.tar.gz: 313695187e34ceb34c50d320bbe0abe0bda18d99157e8f4710faf8f72bc5028abd813511665fd4eac274dd73606eebb1c901b949fa307a8f925c2bfaa39b0c45
6
+ metadata.gz: db8e991497aa6e9388b2778fb9a417adbef07c6bb29165ff9a1718ab7cf1db6d2a6586453320d72adf0cffe78b846b553c3a0ba24b63807c52170f02f1b7d2e3
7
+ data.tar.gz: 80aac045ca6aaf7dbb9344f947a9b1419eaac4711448d997c902f0fb9da68e7c2338e0a5b1d81a30c0a2609defe1b656457d1a4a23b0ba5288162d142cf7c9e9
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.11
1
+ 0.17.12
@@ -8,12 +8,22 @@ module RubyIndexer
8
8
  OBJECT_NESTING = T.let(["Object"].freeze, T::Array[String])
9
9
  BASIC_OBJECT_NESTING = T.let(["BasicObject"].freeze, T::Array[String])
10
10
 
11
+ sig { returns(T::Array[String]) }
12
+ attr_reader :indexing_errors
13
+
11
14
  sig do
12
- params(index: Index, dispatcher: Prism::Dispatcher, parse_result: Prism::ParseResult, file_path: String).void
15
+ params(
16
+ index: Index,
17
+ dispatcher: Prism::Dispatcher,
18
+ parse_result: Prism::ParseResult,
19
+ file_path: String,
20
+ enhancements: T::Array[Enhancement],
21
+ ).void
13
22
  end
14
- def initialize(index, dispatcher, parse_result, file_path)
23
+ def initialize(index, dispatcher, parse_result, file_path, enhancements: [])
15
24
  @index = index
16
25
  @file_path = file_path
26
+ @enhancements = enhancements
17
27
  @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
18
28
  @comments_by_line = T.let(
19
29
  parse_result.comments.to_h do |c|
@@ -29,6 +39,7 @@ module RubyIndexer
29
39
 
30
40
  # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
31
41
  @owner_stack = T.let([], T::Array[Entry::Namespace])
42
+ @indexing_errors = T.let([], T::Array[String])
32
43
 
33
44
  dispatcher.register(
34
45
  self,
@@ -279,6 +290,12 @@ module RubyIndexer
279
290
  when :private
280
291
  @visibility_stack.push(Entry::Visibility::PRIVATE)
281
292
  end
293
+
294
+ @enhancements.each do |enhancement|
295
+ enhancement.on_call_node(@index, @owner_stack.last, node, @file_path)
296
+ rescue StandardError => e
297
+ @indexing_errors << "Indexing error in #{@file_path} with '#{enhancement.class.name}' enhancement: #{e.message}"
298
+ end
282
299
  end
283
300
 
284
301
  sig { params(node: Prism::CallNode).void }
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ module Enhancement
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ interface!
10
+
11
+ requires_ancestor { Object }
12
+
13
+ # The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
14
+ # register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
15
+ # `ClassMethods` modules
16
+ sig do
17
+ abstract.params(
18
+ index: Index,
19
+ owner: T.nilable(Entry::Namespace),
20
+ node: Prism::CallNode,
21
+ file_path: String,
22
+ ).void
23
+ end
24
+ def on_call_node(index, owner, node, file_path); end
25
+ end
26
+ end
@@ -342,6 +342,19 @@ module RubyIndexer
342
342
 
343
343
  "(#{first_signature.format})"
344
344
  end
345
+
346
+ sig { returns(String) }
347
+ def formatted_signatures
348
+ overloads_count = signatures.size
349
+ case overloads_count
350
+ when 1
351
+ ""
352
+ when 2
353
+ "\n(+1 overload)"
354
+ else
355
+ "\n(+#{overloads_count - 1} overloads)"
356
+ end
357
+ end
345
358
  end
346
359
 
347
360
  class Accessor < Member
@@ -542,6 +555,16 @@ module RubyIndexer
542
555
  def decorated_parameters
543
556
  @target.decorated_parameters
544
557
  end
558
+
559
+ sig { returns(String) }
560
+ def formatted_signatures
561
+ @target.formatted_signatures
562
+ end
563
+
564
+ sig { returns(T::Array[Signature]) }
565
+ def signatures
566
+ @target.signatures
567
+ end
545
568
  end
546
569
 
547
570
  # Ruby doesn't support method overloading, so a method will have only one signature.
@@ -35,6 +35,27 @@ module RubyIndexer
35
35
 
36
36
  # Holds the linearized ancestors list for every namespace
37
37
  @ancestors = T.let({}, T::Hash[String, T::Array[String]])
38
+
39
+ # List of classes that are enhancing the index
40
+ @enhancements = T.let([], T::Array[Enhancement])
41
+
42
+ # Map of module name to included hooks that have to be executed when we include the given module
43
+ @included_hooks = T.let(
44
+ {},
45
+ T::Hash[String, T::Array[T.proc.params(index: Index, base: Entry::Namespace).void]],
46
+ )
47
+ end
48
+
49
+ # Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
50
+ sig { params(enhancement: Enhancement).void }
51
+ def register_enhancement(enhancement)
52
+ @enhancements << enhancement
53
+ end
54
+
55
+ # Register an included `hook` that will be executed when `module_name` is included into any namespace
56
+ sig { params(module_name: String, hook: T.proc.params(index: Index, base: Entry::Namespace).void).void }
57
+ def register_included_hook(module_name, &hook)
58
+ (@included_hooks[module_name] ||= []) << hook
38
59
  end
39
60
 
40
61
  sig { params(indexable: IndexablePath).void }
@@ -296,11 +317,25 @@ module RubyIndexer
296
317
  dispatcher = Prism::Dispatcher.new
297
318
 
298
319
  result = Prism.parse(content)
299
- DeclarationListener.new(self, dispatcher, result, indexable_path.full_path)
320
+ listener = DeclarationListener.new(
321
+ self,
322
+ dispatcher,
323
+ result,
324
+ indexable_path.full_path,
325
+ enhancements: @enhancements,
326
+ )
300
327
  dispatcher.dispatch(result.value)
301
328
 
329
+ indexing_errors = listener.indexing_errors.uniq
330
+
302
331
  require_path = indexable_path.require_path
303
332
  @require_paths_tree.insert(require_path, indexable_path) if require_path
333
+
334
+ if indexing_errors.any?
335
+ indexing_errors.each do |error|
336
+ $stderr.puts error
337
+ end
338
+ end
304
339
  rescue Errno::EISDIR, Errno::ENOENT
305
340
  # If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
306
341
  # it
@@ -457,6 +492,12 @@ module RubyIndexer
457
492
  end
458
493
  end
459
494
 
495
+ # We only need to run included hooks when linearizing singleton classes. Included hooks are typically used to add
496
+ # new singleton methods or to extend a module through an include. There's no need to support instance methods, the
497
+ # inclusion of another module or the prepending of another module, because those features are already a part of
498
+ # Ruby and can be used directly without any metaprogramming
499
+ run_included_hooks(attached_class_name, nesting) if singleton_levels > 0
500
+
460
501
  linearize_mixins(ancestors, namespaces, nesting)
461
502
  linearize_superclass(
462
503
  ancestors,
@@ -570,6 +611,34 @@ module RubyIndexer
570
611
 
571
612
  private
572
613
 
614
+ # Runs the registered included hooks
615
+ sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
616
+ def run_included_hooks(fully_qualified_name, nesting)
617
+ return if @included_hooks.empty?
618
+
619
+ namespaces = self[fully_qualified_name]&.grep(Entry::Namespace)
620
+ return unless namespaces
621
+
622
+ namespaces.each do |namespace|
623
+ namespace.mixin_operations.each do |operation|
624
+ next unless operation.is_a?(Entry::Include)
625
+
626
+ # First we resolve the include name, so that we know the actual module being referred to in the include
627
+ resolved_modules = resolve(operation.module_name, nesting)
628
+ next unless resolved_modules
629
+
630
+ module_name = T.must(resolved_modules.first).name
631
+
632
+ # Then we grab any hooks registered for that module
633
+ hooks = @included_hooks[module_name]
634
+ next unless hooks
635
+
636
+ # We invoke the hooks with the index and the namespace that included the module
637
+ hooks.each { |hook| hook.call(self, namespace) }
638
+ end
639
+ end
640
+ end
641
+
573
642
  # Linearize mixins for an array of namespace entries. This method will mutate the `ancestors` array with the
574
643
  # linearized ancestors of the mixins
575
644
  sig do
@@ -6,6 +6,7 @@ require "did_you_mean"
6
6
 
7
7
  require "ruby_indexer/lib/ruby_indexer/indexable_path"
8
8
  require "ruby_indexer/lib/ruby_indexer/declaration_listener"
9
+ require "ruby_indexer/lib/ruby_indexer/enhancement"
9
10
  require "ruby_indexer/lib/ruby_indexer/index"
10
11
  require "ruby_indexer/lib/ruby_indexer/entry"
11
12
  require "ruby_indexer/lib/ruby_indexer/configuration"
@@ -0,0 +1,197 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class EnhancementTest < TestCase
8
+ def test_enhancing_indexing_included_hook
9
+ enhancement_class = Class.new do
10
+ include Enhancement
11
+
12
+ def on_call_node(index, owner, node, file_path)
13
+ return unless owner
14
+ return unless node.name == :extend
15
+
16
+ arguments = node.arguments&.arguments
17
+ return unless arguments
18
+
19
+ location = node.location
20
+
21
+ arguments.each do |node|
22
+ next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
23
+
24
+ module_name = node.full_name
25
+ next unless module_name == "ActiveSupport::Concern"
26
+
27
+ index.register_included_hook(owner.name) do |index, base|
28
+ class_methods_name = "#{owner.name}::ClassMethods"
29
+
30
+ if index.indexed?(class_methods_name)
31
+ singleton = index.existing_or_new_singleton_class(base.name)
32
+ singleton.mixin_operations << Entry::Include.new(class_methods_name)
33
+ end
34
+ end
35
+
36
+ index.add(Entry::Method.new(
37
+ "new_method",
38
+ file_path,
39
+ location,
40
+ location,
41
+ [],
42
+ [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
43
+ Entry::Visibility::PUBLIC,
44
+ owner,
45
+ ))
46
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
47
+ Prism::ConstantPathNode::MissingNodesInConstantPathError
48
+ # Do nothing
49
+ end
50
+ end
51
+ end
52
+
53
+ @index.register_enhancement(enhancement_class.new)
54
+ index(<<~RUBY)
55
+ module ActiveSupport
56
+ module Concern
57
+ def self.extended(base)
58
+ base.class_eval("def new_method(a); end")
59
+ end
60
+ end
61
+ end
62
+
63
+ module ActiveRecord
64
+ module Associations
65
+ extend ActiveSupport::Concern
66
+
67
+ module ClassMethods
68
+ def belongs_to(something); end
69
+ end
70
+ end
71
+
72
+ class Base
73
+ include Associations
74
+ end
75
+ end
76
+
77
+ class User < ActiveRecord::Base
78
+ end
79
+ RUBY
80
+
81
+ assert_equal(
82
+ [
83
+ "User::<Class:User>",
84
+ "ActiveRecord::Base::<Class:Base>",
85
+ "ActiveRecord::Associations::ClassMethods",
86
+ "Object::<Class:Object>",
87
+ "BasicObject::<Class:BasicObject>",
88
+ "Class",
89
+ "Module",
90
+ "Object",
91
+ "Kernel",
92
+ "BasicObject",
93
+ ],
94
+ @index.linearized_ancestors_of("User::<Class:User>"),
95
+ )
96
+
97
+ assert_entry("new_method", Entry::Method, "/fake/path/foo.rb:10-4:10-33")
98
+ end
99
+
100
+ def test_enhancing_indexing_configuration_dsl
101
+ enhancement_class = Class.new do
102
+ include Enhancement
103
+
104
+ def on_call_node(index, owner, node, file_path)
105
+ return unless owner
106
+
107
+ name = node.name
108
+ return unless name == :has_many
109
+
110
+ arguments = node.arguments&.arguments
111
+ return unless arguments
112
+
113
+ association_name = arguments.first
114
+ return unless association_name.is_a?(Prism::SymbolNode)
115
+
116
+ location = association_name.location
117
+
118
+ index.add(Entry::Method.new(
119
+ T.must(association_name.value),
120
+ file_path,
121
+ location,
122
+ location,
123
+ [],
124
+ [],
125
+ Entry::Visibility::PUBLIC,
126
+ owner,
127
+ ))
128
+ end
129
+ end
130
+
131
+ @index.register_enhancement(enhancement_class.new)
132
+ index(<<~RUBY)
133
+ module ActiveSupport
134
+ module Concern
135
+ def self.extended(base)
136
+ base.class_eval("def new_method(a); end")
137
+ end
138
+ end
139
+ end
140
+
141
+ module ActiveRecord
142
+ module Associations
143
+ extend ActiveSupport::Concern
144
+
145
+ module ClassMethods
146
+ def belongs_to(something); end
147
+ end
148
+ end
149
+
150
+ class Base
151
+ include Associations
152
+ end
153
+ end
154
+
155
+ class User < ActiveRecord::Base
156
+ has_many :posts
157
+ end
158
+ RUBY
159
+
160
+ assert_entry("posts", Entry::Method, "/fake/path/foo.rb:23-11:23-17")
161
+ end
162
+
163
+ def test_error_handling_in_enhancement
164
+ enhancement_class = Class.new do
165
+ include Enhancement
166
+
167
+ def on_call_node(index, owner, node, file_path)
168
+ raise "Error"
169
+ end
170
+
171
+ class << self
172
+ def name
173
+ "TestEnhancement"
174
+ end
175
+ end
176
+ end
177
+
178
+ @index.register_enhancement(enhancement_class.new)
179
+
180
+ _stdout, stderr = capture_io do
181
+ index(<<~RUBY)
182
+ module ActiveSupport
183
+ module Concern
184
+ def self.extended(base)
185
+ base.class_eval("def new_method(a); end")
186
+ end
187
+ end
188
+ end
189
+ RUBY
190
+ end
191
+
192
+ assert_match(%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' enhancement}, stderr)
193
+ # The module should still be indexed
194
+ assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
195
+ end
196
+ end
197
+ end
@@ -223,6 +223,40 @@ module RubyLsp
223
223
  NodeContext.new(closest, parent, nesting_nodes, call_node)
224
224
  end
225
225
 
226
+ sig do
227
+ params(
228
+ range: T::Hash[Symbol, T.untyped],
229
+ node_types: T::Array[T.class_of(Prism::Node)],
230
+ ).returns(T.nilable(Prism::Node))
231
+ end
232
+ def locate_first_within_range(range, node_types: [])
233
+ scanner = create_scanner
234
+ start_position = scanner.find_char_position(range[:start])
235
+ end_position = scanner.find_char_position(range[:end])
236
+ desired_range = (start_position...end_position)
237
+ queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
238
+
239
+ until queue.empty?
240
+ candidate = queue.shift
241
+
242
+ # Skip nil child nodes
243
+ next if candidate.nil?
244
+
245
+ # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
246
+ # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
247
+ # sibling
248
+ T.unsafe(queue).unshift(*candidate.child_nodes)
249
+
250
+ # Skip if the current node doesn't cover the desired position
251
+ loc = candidate.location
252
+
253
+ if desired_range.cover?(loc.start_offset...loc.end_offset) &&
254
+ (node_types.empty? || node_types.any? { |type| candidate.class == type })
255
+ return candidate
256
+ end
257
+ end
258
+ end
259
+
226
260
  sig { returns(SorbetLevel) }
227
261
  def sorbet_level
228
262
  sigil = parse_result.magic_comments.find do |comment|
@@ -174,7 +174,10 @@ module RubyLsp
174
174
  methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
175
175
  return unless methods
176
176
 
177
- title = "#{message}#{T.must(methods.first).decorated_parameters}"
177
+ first_method = T.must(methods.first)
178
+
179
+ title = "#{message}#{first_method.decorated_parameters}"
180
+ title << first_method.formatted_signatures
178
181
 
179
182
  if type.is_a?(TypeInferrer::GuessedType)
180
183
  title << "\n\nGuessed receiver: #{type.name}"
@@ -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
 
@@ -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::SWITCH_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::SWITCH_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
 
@@ -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
 
@@ -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,6 +21,7 @@ module RubyLsp
21
21
 
22
22
  EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
23
23
  EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
24
+ SWITCH_BLOCK_STYLE_TITLE = "Refactor: Switch block style"
24
25
 
25
26
  class << self
26
27
  extend T::Sig
@@ -69,6 +70,11 @@ module RubyLsp
69
70
  kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
70
71
  data: { range: @range, uri: @uri.to_s },
71
72
  )
73
+ code_actions << Interface::CodeAction.new(
74
+ title: SWITCH_BLOCK_STYLE_TITLE,
75
+ kind: Constant::CodeActionKind::REFACTOR_REWRITE,
76
+ data: { range: @range, uri: @uri.to_s },
77
+ )
72
78
  end
73
79
 
74
80
  code_actions
@@ -63,6 +63,7 @@ module RubyLsp
63
63
 
64
64
  if first_entry.is_a?(RubyIndexer::Entry::Member)
65
65
  label = +"#{label}#{first_entry.decorated_parameters}"
66
+ label << first_entry.formatted_signatures
66
67
  end
67
68
 
68
69
  guessed_type = @item.dig(:data, :guessed_type)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.11
4
+ version: 0.17.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-02 00:00:00.000000000 Z
11
+ date: 2024-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -98,6 +98,7 @@ files:
98
98
  - lib/ruby-lsp.rb
99
99
  - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
100
100
  - lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
101
+ - lib/ruby_indexer/lib/ruby_indexer/enhancement.rb
101
102
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb
102
103
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
103
104
  - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
@@ -108,6 +109,7 @@ files:
108
109
  - lib/ruby_indexer/test/classes_and_modules_test.rb
109
110
  - lib/ruby_indexer/test/configuration_test.rb
110
111
  - lib/ruby_indexer/test/constant_test.rb
112
+ - lib/ruby_indexer/test/enhancements_test.rb
111
113
  - lib/ruby_indexer/test/index_test.rb
112
114
  - lib/ruby_indexer/test/instance_variables_test.rb
113
115
  - lib/ruby_indexer/test/method_test.rb