Skip to content

Commit

Permalink
Apply consistent block style to all nested blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Aug 8, 2024
1 parent 469e38c commit c273e3c
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 27 deletions.
91 changes: 65 additions & 26 deletions lib/ruby_lsp/requests/code_action_resolve.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def initialize(document, code_action)

sig { override.returns(T.any(Interface::CodeAction, Error)) }
def perform
return Error::EmptySelection if @document.source.empty?

case @code_action[:title]
when CodeActions::EXTRACT_TO_VARIABLE_TITLE
refactor_variable
Expand All @@ -63,8 +65,6 @@ def perform

sig { returns(T.any(Interface::CodeAction, Error)) }
def switch_block_style
return Error::EmptySelection if @document.source.empty?

source_range = @code_action.dig(:data, :range)
return Error::EmptySelection if source_range[:start] == source_range[:end]

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

parameters = node.parameters
body = node.body

# If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
# semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
# we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
new_source = if node.opening_loc.slice == "do"
source = +"{ "
source << "#{parameters.slice} " if parameters
source << "#{body.slice.gsub("\n", ";")} " if body
source << "}"
else
indentation = " " * target.location.start_column
source = +"do"
source << " #{parameters.slice}" if parameters
source << "\n#{indentation} "
source << body.slice.gsub(";", "\n") if body
source << "\n#{indentation}end"
end
indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"

Interface::CodeAction.new(
title: CodeActions::SWITCH_BLOCK_STYLE_TITLE,
Expand All @@ -108,7 +90,10 @@ def switch_block_style
version: nil,
),
edits: [
Interface::TextEdit.new(range: range_from_location(node.location), new_text: new_source),
Interface::TextEdit.new(
range: range_from_location(node.location),
new_text: recursively_switch_nested_block_styles(node, indentation),
),
],
),
],
Expand All @@ -118,8 +103,6 @@ def switch_block_style

sig { returns(T.any(Interface::CodeAction, Error)) }
def refactor_variable
return Error::EmptySelection if @document.source.empty?

source_range = @code_action.dig(:data, :range)
return Error::EmptySelection if source_range[:start] == source_range[:end]

Expand Down Expand Up @@ -214,8 +197,6 @@ def refactor_variable

sig { returns(T.any(Interface::CodeAction, Error)) }
def refactor_method
return Error::EmptySelection if @document.source.empty?

source_range = @code_action.dig(:data, :range)
return Error::EmptySelection if source_range[:start] == source_range[:end]

Expand Down Expand Up @@ -277,6 +258,64 @@ def create_text_edit(range, new_text)
new_text: new_text,
)
end

sig { params(node: Prism::BlockNode, indentation: T.nilable(String)).returns(String) }
def recursively_switch_nested_block_styles(node, indentation)
parameters = node.parameters
body = node.body

# We use the indentation to differentiate between do...end and brace style blocks because only the do...end
# style requires the indentation to build the edit.
#
# If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
# semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
# we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
source = +""

if indentation
source << "do"
source << " #{parameters.slice}" if parameters
source << "\n#{indentation} "
source << switch_block_body(body, indentation) if body
source << "\n#{indentation}end"
else
source << "{ "
source << "#{parameters.slice} " if parameters
source << switch_block_body(body, nil) if body
source << "}"
end

source
end

sig { params(body: Prism::Node, indentation: T.nilable(String)).returns(String) }
def switch_block_body(body, indentation)
# Check if there are any nested blocks inside of the current block
body_loc = body.location
nested_block = @document.locate_first_within_range(
{
start: { line: body_loc.start_line - 1, character: body_loc.start_column },
end: { line: body_loc.end_line - 1, character: body_loc.end_column },
},
node_types: [Prism::BlockNode],
)

body_content = body.slice.dup

# If there are nested blocks, then we change their style too and we have to mutate the string using the
# relative position in respect to the beginning of the body
if nested_block.is_a?(Prism::BlockNode)
location = nested_block.location
correction_start = location.start_offset - body_loc.start_offset
correction_end = location.end_offset - body_loc.start_offset
next_indentation = indentation ? "#{indentation} " : nil

body_content[correction_start...correction_end] =
recursively_switch_nested_block_styles(nested_block, next_indentation)
end

indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"character": 3
}
},
"newText": "{ |a| nested_call(fourth_call).each do |b|; end }"
"newText": "{ |a| nested_call(fourth_call).each { |b| } }"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"params": {
"kind": "refactor.rewrite",
"title": "Refactor: Switch block style",
"data": {
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 74
}
},
"uri": "file:///fake"
}
},
"result": {
"title": "Refactor: Switch block style",
"edit": {
"documentChanges": [
{
"textDocument": {
"uri": "file:///fake",
"version": null
},
"edits": [
{
"range": {
"start": {
"line": 0,
"character": 29
},
"end": {
"line": 0,
"character": 74
}
},
"newText": "do |a|\n nested_call(fourth_call).each do |b|\n \n end\nend"
}
]
}
]
}
}
}
1 change: 1 addition & 0 deletions test/fixtures/nested_oneline_blocks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
method_call(other_call).each { |a| nested_call(fourth_call).each { |b| } }

0 comments on commit c273e3c

Please sign in to comment.