Skip to content

Commit 435639b

Browse files
committed
Apply consistent block style to all nested blocks
1 parent a7e2d74 commit 435639b

File tree

4 files changed

+112
-21
lines changed

4 files changed

+112
-21
lines changed

lib/ruby_lsp/requests/code_action_resolve.rb

+63-20
Original file line numberDiff line numberDiff line change
@@ -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
],
@@ -277,6 +262,64 @@ def create_text_edit(range, new_text)
277262
new_text: new_text,
278263
)
279264
end
265+
266+
sig { params(node: Prism::BlockNode, indentation: T.nilable(String)).returns(String) }
267+
def recursively_switch_nested_block_styles(node, indentation)
268+
parameters = node.parameters
269+
body = node.body
270+
271+
# We use the indentation to differentiate between do...end and brace style blocks because only the do...end
272+
# style requires the indentation to build the edit.
273+
#
274+
# If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
275+
# semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
276+
# we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
277+
source = +""
278+
279+
if indentation
280+
source << "do"
281+
source << " #{parameters.slice}" if parameters
282+
source << "\n#{indentation} "
283+
source << switch_block_body(body, indentation) if body
284+
source << "\n#{indentation}end"
285+
else
286+
source << "{ "
287+
source << "#{parameters.slice} " if parameters
288+
source << switch_block_body(body, nil) if body
289+
source << "}"
290+
end
291+
292+
source
293+
end
294+
295+
sig { params(body: Prism::Node, indentation: T.nilable(String)).returns(String) }
296+
def switch_block_body(body, indentation)
297+
# Check if there are any nested blocks inside of the current block
298+
body_loc = body.location
299+
nested_block = @document.locate_first_within_range(
300+
{
301+
start: { line: body_loc.start_line - 1, character: body_loc.start_column },
302+
end: { line: body_loc.end_line - 1, character: body_loc.end_column },
303+
},
304+
node_types: [Prism::BlockNode],
305+
)
306+
307+
body_content = body.slice.dup
308+
309+
# If there are nested blocks, then we change their style too and we have to mutate the string using the
310+
# relative position in respect to the beginning of the body
311+
if nested_block.is_a?(Prism::BlockNode)
312+
location = nested_block.location
313+
correction_start = location.start_offset - body_loc.start_offset
314+
correction_end = location.end_offset - body_loc.start_offset
315+
next_indentation = indentation ? "#{indentation} " : nil
316+
317+
body_content[correction_start...correction_end] =
318+
recursively_switch_nested_block_styles(nested_block, next_indentation)
319+
end
320+
321+
indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
322+
end
280323
end
281324
end
282325
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)