From 8f4ed00c8720f3b0b5c61c8fd9c57b16261d22a6 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Fri, 26 Jul 2024 09:44:48 -0400 Subject: [PATCH] Add the switch block style refactor --- lib/ruby_lsp/requests/code_action_resolve.rb | 63 ++++++++++++++++++- lib/ruby_lsp/requests/code_actions.rb | 6 ++ .../aref_call_aref_assign.exp.json | 47 ++++++++++++++ .../nested_block_calls.exp.json | 47 ++++++++++++++ test/fixtures/nested_block_calls.rb | 4 ++ 5 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 test/expectations/code_action_resolve/aref_call_aref_assign.exp.json create mode 100644 test/expectations/code_action_resolve/nested_block_calls.exp.json create mode 100644 test/fixtures/nested_block_calls.rb diff --git a/lib/ruby_lsp/requests/code_action_resolve.rb b/lib/ruby_lsp/requests/code_action_resolve.rb index 962c3f2a5..c4e384a51 100644 --- a/lib/ruby_lsp/requests/code_action_resolve.rb +++ b/lib/ruby_lsp/requests/code_action_resolve.rb @@ -23,6 +23,8 @@ module Requests # class CodeActionResolve < Request extend T::Sig + include Support::Common + NEW_VARIABLE_NAME = "new_variable" NEW_METHOD_NAME = "new_method" @@ -50,11 +52,70 @@ def perform refactor_variable when CodeActions::EXTRACT_TO_METHOD_TITLE refactor_method + when CodeActions::SWITCH_BLOCK_STYLE_TITLE + switch_block_style else Error::UnknownCodeAction end end + private + + 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] + + target = @document.locate_first_within_range( + @code_action.dig(:data, :range), + node_types: [Prism::CallNode], + ) + + return Error::InvalidTargetRange unless target.is_a?(Prism::CallNode) + + 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 + + Interface::CodeAction.new( + title: CodeActions::SWITCH_BLOCK_STYLE_TITLE, + edit: Interface::WorkspaceEdit.new( + document_changes: [ + Interface::TextDocumentEdit.new( + text_document: Interface::OptionalVersionedTextDocumentIdentifier.new( + uri: @code_action.dig(:data, :uri), + version: nil, + ), + edits: [ + Interface::TextEdit.new(range: range_from_location(node.location), new_text: new_source), + ], + ), + ], + ), + ) + end + sig { returns(T.any(Interface::CodeAction, Error)) } def refactor_variable return Error::EmptySelection if @document.source.empty? @@ -206,8 +267,6 @@ def refactor_method ) end - private - sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) } def create_text_edit(range, new_text) Interface::TextEdit.new( diff --git a/lib/ruby_lsp/requests/code_actions.rb b/lib/ruby_lsp/requests/code_actions.rb index da6b83a71..76ad02454 100644 --- a/lib/ruby_lsp/requests/code_actions.rb +++ b/lib/ruby_lsp/requests/code_actions.rb @@ -21,6 +21,7 @@ class CodeActions < Request EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable" EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method" + SWITCH_BLOCK_STYLE_TITLE = "Refactor: Switch block style" class << self extend T::Sig @@ -66,6 +67,11 @@ def perform kind: Constant::CodeActionKind::REFACTOR_EXTRACT, data: { range: @range, uri: @uri.to_s }, ) + code_actions << Interface::CodeAction.new( + title: SWITCH_BLOCK_STYLE_TITLE, + kind: Constant::CodeActionKind::REFACTOR_REWRITE, + data: { range: @range, uri: @uri.to_s }, + ) end code_actions diff --git a/test/expectations/code_action_resolve/aref_call_aref_assign.exp.json b/test/expectations/code_action_resolve/aref_call_aref_assign.exp.json new file mode 100644 index 000000000..d9c668996 --- /dev/null +++ b/test/expectations/code_action_resolve/aref_call_aref_assign.exp.json @@ -0,0 +1,47 @@ +{ + "params": { + "kind": "refactor.rewrite", + "title": "Refactor: Switch block style", + "data": { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 58 + } + }, + "uri": "file:///fake" + } + }, + "result": { + "title": "Refactor: Switch block style", + "edit": { + "documentChanges": [ + { + "textDocument": { + "uri": "file:///fake", + "version": null + }, + "edits": [ + { + "range": { + "start": { + "line": 0, + "character": 26 + }, + "end": { + "line": 0, + "character": 58 + } + }, + "newText": "do |a|\n a[\"field\"] == \"expected\"\nend" + } + ] + } + ] + } + } +} diff --git a/test/expectations/code_action_resolve/nested_block_calls.exp.json b/test/expectations/code_action_resolve/nested_block_calls.exp.json new file mode 100644 index 000000000..4c0cad78a --- /dev/null +++ b/test/expectations/code_action_resolve/nested_block_calls.exp.json @@ -0,0 +1,47 @@ +{ + "params": { + "kind": "refactor.rewrite", + "title": "Refactor: Switch block style", + "data": { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 3, + "character": 3 + } + }, + "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": 3, + "character": 3 + } + }, + "newText": "{ |a| nested_call(fourth_call).each do |b|; end }" + } + ] + } + ] + } + } +} diff --git a/test/fixtures/nested_block_calls.rb b/test/fixtures/nested_block_calls.rb new file mode 100644 index 000000000..43de2c3b0 --- /dev/null +++ b/test/fixtures/nested_block_calls.rb @@ -0,0 +1,4 @@ +method_call(other_call).each do |a| + nested_call(fourth_call).each do |b| + end +end