Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add remove debugger code action #426

Merged
merged 11 commits into from
May 1, 2024
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
Loading