diff --git a/lib/credo_language_server/check.ex b/lib/credo_language_server/check.ex index 5e9b0d5..5da100f 100644 --- a/lib/credo_language_server/check.ex +++ b/lib/credo_language_server/check.ex @@ -37,6 +37,27 @@ defmodule CredoLanguageServer.Check do ] end + def fetch(%{ + check: Credo.Check.Refactor.UnlessWithElse = check, + diagnostic: diagnostic, + uri: uri, + document: document + }) do + [ + CodeAction.DisableCheck.new( + uri: uri, + diagnostic: diagnostic, + text: document, + check: Macro.to_string(check) + ), + CodeAction.UnlessWithElse.new( + uri: uri, + diagnostic: diagnostic, + text: document + ) + ] + end + def fetch(%{ check: check, diagnostic: diagnostic, diff --git a/lib/credo_language_server/code_action/unless_with_else.ex b/lib/credo_language_server/code_action/unless_with_else.ex new file mode 100644 index 0000000..c0b0a76 --- /dev/null +++ b/lib/credo_language_server/code_action/unless_with_else.ex @@ -0,0 +1,60 @@ +defmodule CredoLanguageServer.CodeAction.UnlessWithElse do + @moduledoc false + + alias GenLSP.Structures.{ + CodeAction, + WorkspaceEdit + } + + def new(opts) do + %CodeAction{ + title: "Refactor to use if", + diagnostics: [opts.diagnostic], + edit: %WorkspaceEdit{} + } + end + + def refactor(text) do + {:ok, ast, comments} = + Code.string_to_quoted_with_comments(text, + literal_encoder: &{:ok, {:__block__, &2, [&1]}}, + token_metadata: true, + unescape: false + ) + + IO.inspect(comments) + + ast + |> IO.inspect() + |> transform_ast() + |> Code.quoted_to_algebra(comments: comments) + |> Inspect.Algebra.format(:infinity) + |> IO.iodata_to_binary() + end + + defp transform_ast({:unless, line, [condition, [block1, block2]]}) do + {:if, line, + [condition, [change_block(block2, :do), change_block(block1, :else)]]} + end + + defp change_block({{:__block__, line, _}, rest}, new) do + {{:__block__, line, [new]}, rest} + end + + defp adjust_comment_lines(comments, block_line1, block_line2) do + comments + |> Enum.map(&adjust_comment_line(&1, block_line1, block_line2)) + end + + defp adjust_comment_line(%{line: line} = comment, a, b) when line > b do + diff = b - a + Map.put(comment, :line, line - diff) + end + + defp adjust_comment_line(%{line: line} = comment, a, b) when line < b do + diff = b - a + Map.put(comment, :line, line + diff) + end + + defp adjust_comment_line(comment, _a, _b), do: comment +end diff --git a/test/credo_language_server/code_action/unless_with_else_test.exs b/test/credo_language_server/code_action/unless_with_else_test.exs new file mode 100644 index 0000000..f40a448 --- /dev/null +++ b/test/credo_language_server/code_action/unless_with_else_test.exs @@ -0,0 +1,63 @@ +defmodule CredoLanguageServer.CodeAction.UnlessWithElseTest do + use ExUnit.Case, async: true + + alias CredoLanguageServer.CodeAction.UnlessWithElse + + describe "refactor" do + test "replaces unless with if and swaps code blocks" do + source = """ + unless allowed? do + raise "Not allowed!" + else + proceed_as_planned() + end + """ + + expected_result = + """ + if allowed? do + proceed_as_planned() + else + raise "Not allowed!" + end + """ + |> String.trim_trailing() + + result = UnlessWithElse.refactor(source) + + assert result == expected_result + end + + test "preserves comments" do + source = """ + unless allowed? do + # one + raise "Not allowed!" # two + # three + else + # four + proceed_as_planned() # five + # six + end + """ + + expected_result = + """ + if allowed? do + # four + proceed_as_planned() # five + # six + else + # one + raise "Not allowed!" # two + # three + end + """ + |> String.trim_trailing() + + result = UnlessWithElse.refactor(source) + + assert result == expected_result + end + end +end