forked from elixir-tools/next-ls
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathremove_debugger.ex
149 lines (123 loc) · 4.58 KB
/
remove_debugger.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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