Skip to content

Commit c273e3c

Browse files
committed
Apply consistent block style to all nested blocks
1 parent 469e38c commit c273e3c

File tree

4 files changed

+114
-27
lines changed

4 files changed

+114
-27
lines changed

lib/ruby_lsp/requests/code_action_resolve.rb

+65-26
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def initialize(document, code_action)
4747

4848
sig { override.returns(T.any(Interface::CodeAction, Error)) }
4949
def perform
50+
return Error::EmptySelection if @document.source.empty?
51+
5052
case @code_action[:title]
5153
when CodeActions::EXTRACT_TO_VARIABLE_TITLE
5254
refactor_variable
@@ -63,8 +65,6 @@ def perform
6365

6466
sig { returns(T.any(Interface::CodeAction, Error)) }
6567
def switch_block_style
66-
return Error::EmptySelection if @document.source.empty?
67-
6868
source_range = @code_action.dig(:data, :range)
6969
return Error::EmptySelection if source_range[:start] == source_range[:end]
7070

@@ -78,25 +78,7 @@ def switch_block_style
7878
node = target.block
7979
return Error::InvalidTargetRange unless node.is_a?(Prism::BlockNode)
8080

81-
parameters = node.parameters
82-
body = node.body
83-
84-
# If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
85-
# semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
86-
# we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
87-
new_source = if node.opening_loc.slice == "do"
88-
source = +"{ "
89-
source << "#{parameters.slice} " if parameters
90-
source << "#{body.slice.gsub("\n", ";")} " if body
91-
source << "}"
92-
else
93-
indentation = " " * target.location.start_column
94-
source = +"do"
95-
source << " #{parameters.slice}" if parameters
96-
source << "\n#{indentation} "
97-
source << body.slice.gsub(";", "\n") if body
98-
source << "\n#{indentation}end"
99-
end
81+
indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
10082

10183
Interface::CodeAction.new(
10284
title: CodeActions::SWITCH_BLOCK_STYLE_TITLE,
@@ -108,7 +90,10 @@ def switch_block_style
10890
version: nil,
10991
),
11092
edits: [
111-
Interface::TextEdit.new(range: range_from_location(node.location), new_text: new_source),
93+
Interface::TextEdit.new(
94+
range: range_from_location(node.location),
95+
new_text: recursively_switch_nested_block_styles(node, indentation),
96+
),
11297
],
11398
),
11499
],
@@ -118,8 +103,6 @@ def switch_block_style
118103

119104
sig { returns(T.any(Interface::CodeAction, Error)) }
120105
def refactor_variable
121-
return Error::EmptySelection if @document.source.empty?
122-
123106
source_range = @code_action.dig(:data, :range)
124107
return Error::EmptySelection if source_range[:start] == source_range[:end]
125108

@@ -214,8 +197,6 @@ def refactor_variable
214197

215198
sig { returns(T.any(Interface::CodeAction, Error)) }
216199
def refactor_method
217-
return Error::EmptySelection if @document.source.empty?
218-
219200
source_range = @code_action.dig(:data, :range)
220201
return Error::EmptySelection if source_range[:start] == source_range[:end]
221202

@@ -277,6 +258,64 @@ def create_text_edit(range, new_text)
277258
new_text: new_text,
278259
)
279260
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
280319
end
281320
end
282321
end

test/expectations/code_action_resolve/nested_block_calls.exp.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"character": 3
3838
}
3939
},
40-
"newText": "{ |a| nested_call(fourth_call).each do |b|; end }"
40+
"newText": "{ |a| nested_call(fourth_call).each { |b| } }"
4141
}
4242
]
4343
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"params": {
3+
"kind": "refactor.rewrite",
4+
"title": "Refactor: Switch block style",
5+
"data": {
6+
"range": {
7+
"start": {
8+
"line": 0,
9+
"character": 0
10+
},
11+
"end": {
12+
"line": 0,
13+
"character": 74
14+
}
15+
},
16+
"uri": "file:///fake"
17+
}
18+
},
19+
"result": {
20+
"title": "Refactor: Switch block style",
21+
"edit": {
22+
"documentChanges": [
23+
{
24+
"textDocument": {
25+
"uri": "file:///fake",
26+
"version": null
27+
},
28+
"edits": [
29+
{
30+
"range": {
31+
"start": {
32+
"line": 0,
33+
"character": 29
34+
},
35+
"end": {
36+
"line": 0,
37+
"character": 74
38+
}
39+
},
40+
"newText": "do |a|\n nested_call(fourth_call).each do |b|\n \n end\nend"
41+
}
42+
]
43+
}
44+
]
45+
}
46+
}
47+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
method_call(other_call).each { |a| nested_call(fourth_call).each { |b| } }

0 commit comments

Comments
 (0)