Skip to content

Commit

Permalink
feat: add remove debugger code action (#426)
Browse files Browse the repository at this point in the history
* feat: add remove debugger code action

A Code Action that removes the following warning checks from Credo:
  - Credo.Check.Warning.Dbg
  - Credo.Check.Warning.IExPry
  - Credo.Check.Warning.IoInspect
  - Credo.Check.Warning.IoPuts
  - Credo.Check.Warning.MixEnv


Co-authored-by: Mitchell Hanberg <[email protected]>
  • Loading branch information
NJichev and mhanberg committed May 1, 2024
1 parent d03c1ad commit 7f2f4f4
Show file tree
Hide file tree
Showing 7 changed files with 607 additions and 5 deletions.
18 changes: 17 additions & 1 deletion lib/next_ls/extensions/credo_extension/code_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@ defmodule NextLS.CredoExtension.CodeAction do
@behaviour NextLS.CodeActionable

alias NextLS.CodeActionable.Data
alias NextLS.CredoExtension.CodeAction.RemoveDebugger

@debug_checks ~w(
Elixir.Credo.Check.Warning.Dbg
Elixir.Credo.Check.Warning.IExPry
Elixir.Credo.Check.Warning.IoInspect
Elixir.Credo.Check.Warning.IoPuts
Elixir.Credo.Check.Warning.MixEnv
)
@impl true
def from(%Data{} = _data), do: []
def from(%Data{} = data) do
case data.diagnostic.data do
%{"check" => check} when check in @debug_checks ->
RemoveDebugger.new(data.diagnostic, data.document, data.uri)

_ ->
[]
end
end
end
149 changes: 149 additions & 0 deletions lib/next_ls/extensions/credo_extension/code_action/remove_debugger.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
defmodule NextLS.CredoExtension.CodeAction.RemoveDebugger do
@moduledoc false

alias GenLSP.Structures.CodeAction
alias GenLSP.Structures.Diagnostic
alias GenLSP.Structures.Position
alias GenLSP.Structures.Range
alias GenLSP.Structures.TextEdit
alias GenLSP.Structures.WorkspaceEdit
alias NextLS.EditHelpers
alias Sourceror.Zipper, as: Z

@line_length 121

def new(%Diagnostic{} = diagnostic, text, uri) do
range = diagnostic.range

with {:ok, ast, comments} <- parse(text),
{:ok, debugger_node} <- find_debugger(ast, range) do
indent = EditHelpers.get_indent(text, range.start.line)
ast_without_debugger = remove_debugger(debugger_node)
range = make_range(debugger_node)

comments =
Enum.filter(comments, fn comment ->
comment.line > range.start.line && comment.line <= range.end.line
end)

to_algebra_opts = [comments: comments]
doc = Code.quoted_to_algebra(ast_without_debugger, to_algebra_opts)
formatted = doc |> Inspect.Algebra.format(@line_length) |> IO.iodata_to_binary()

[
%CodeAction{
title: make_title(debugger_node),
diagnostics: [diagnostic],
edit: %WorkspaceEdit{
changes: %{
uri => [
%TextEdit{
new_text: EditHelpers.add_indent_to_edit(formatted, indent),
range: range
}
]
}
}
}
]
else
_ ->
[]
end
end

defp find_debugger(ast, range) do
pos = %{
start: [line: range.start.line + 1, column: range.start.character + 1],
end: [line: range.end.line + 1, column: range.end.character + 1]
}

{_, results} =
ast
|> Z.zip()
|> Z.traverse([], fn tree, acc ->
node = Z.node(tree)
range = Sourceror.get_range(node)

# range.start <= diagnostic_pos.start <= diagnostic_pos.end <= range.end
if (matches_debug?(node) || matches_pipe?(node)) && range &&
Sourceror.compare_positions(range.start, pos.start) in [:lt, :eq] &&
Sourceror.compare_positions(range.end, pos.end) in [:gt, :eq] do
{tree, [node | acc]}
else
{tree, acc}
end
end)

result =
Enum.min_by(
results,
fn node ->
range = Sourceror.get_range(node)

pos.start[:column] - range.start[:column] + range.end[:column] - pos.end[:column]
end,
fn -> nil end
)

result =
Enum.find(results, result, fn
{:|>, _, [_first, ^result]} -> true
_ -> false
end)

case result do
nil -> {:error, "could find a debugger to remove"}
node -> {:ok, node}
end
end

defp parse(lines) do
lines
|> Enum.join("\n")
|> Spitfire.parse_with_comments(literal_encoder: &{:ok, {:__block__, &2, [&1]}})
|> case do
{:error, ast, comments, _errors} ->
{:ok, ast, comments}

other ->
other
end
end

defp make_range(original_ast) do
range = Sourceror.get_range(original_ast)

%Range{
start: %Position{line: range.start[:line] - 1, character: range.start[:column] - 1},
end: %Position{line: range.end[:line] - 1, character: range.end[:column] - 1}
}
end

defp matches_pipe?({:|>, _, [_, arg]}), do: matches_debug?(arg)
defp matches_pipe?(_), do: false

defp matches_debug?({:dbg, _, _}), do: true

defp matches_debug?({{:., _, [{:__aliases__, _, [:IO]}, f]}, _, _}) when f in [:puts, :inspect], do: true

defp matches_debug?({{:., _, [{:__aliases__, _, [:IEx]}, :pry]}, _, _}), do: true
defp matches_debug?({{:., _, [{:__aliases__, _, [:Mix]}, :env]}, _, _}), do: true
defp matches_debug?({{:., _, [{:__aliases__, _, [:Kernel]}, :dbg]}, _, _}), do: true
defp matches_debug?(_), do: false

defp remove_debugger({:|>, _, [arg, _function]}), do: arg
defp remove_debugger({{:., _, [{:__aliases__, _, [:IO]}, :inspect]}, _, [arg | _]}), do: arg
defp remove_debugger({{:., _, [{:__aliases__, _, [:Kernel]}, :dbg]}, _, [arg | _]}), do: arg
defp remove_debugger({:dbg, _, [arg | _]}), do: arg
defp remove_debugger(_node), do: {:__block__, [], []}

defp make_title({_, ctx, _} = node), do: "Remove `#{format_node(node)}` #{ctx[:line]}:#{ctx[:column]}"
defp format_node({:|>, _, [_arg, function]}), do: format_node(function)

defp format_node({{:., _, [{:__aliases__, _, [module]}, function]}, _, args}),
do: "&#{module}.#{function}/#{Enum.count(args)}"

defp format_node({:dbg, _, args}), do: "&dbg/#{Enum.count(args)}"
defp format_node(node), do: Macro.to_string(node)
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ defmodule NextLS.MixProject do
{:burrito, "~> 1.0", only: [:dev, :prod]},
{:bypass, "~> 2.1", only: :test},
{:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:credo, github: "rrrene/credo", branch: "master", only: [:dev, :test], runtime: false},
{:ex_doc, ">= 0.0.0", only: :dev},
{:styler, "~> 0.8", only: :dev}
]
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
"credo": {:git, "https://github.com/rrrene/credo.git", "3612dc2417455c2487043bf947b048b90a882b47", [branch: "master"]},
"ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"},
"db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
Expand Down
Loading

0 comments on commit 7f2f4f4

Please sign in to comment.