Skip to content

Commit

Permalink
Handed additional cases
Browse files Browse the repository at this point in the history
  * Handled removing aliases defined with as:
  * Only remove aliases on the line that contains the cursor
  • Loading branch information
scohen committed May 30, 2024
1 parent 0c1e987 commit 9d47ae7
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
alias Lexical.RemoteControl.Analyzer
alias Lexical.RemoteControl.CodeAction
alias Lexical.RemoteControl.CodeAction.Diagnostic
alias Sourceror.Zipper

import Record

Expand All @@ -45,8 +46,8 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
@behaviour CodeAction.Handler

@impl CodeAction.Handler
def actions(%Document{} = document, %Range{} = range, diagnontics) do
Enum.reduce(diagnontics, [], fn %Diagnostic{} = diagnostic, acc ->
def actions(%Document{} = document, %Range{} = range, diagnostics) do
Enum.reduce(diagnostics, [], fn %Diagnostic{} = diagnostic, acc ->
case to_edit(document, range.start, diagnostic) do
{:ok, module_name, edit} ->
changes = Changes.new(document, [edit])
Expand All @@ -65,13 +66,12 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
[:source]
end

@alias_regex ~r/unused alias (\w+)/
defp to_edit(%Document{} = document, %Position{} = position, %Diagnostic{} = diagnostic) do
with [[_, module_string]] <- Regex.scan(@alias_regex, diagnostic.message),
with {:ok, module_string} <- fetch_unused_alias_module_string(diagnostic),
{:ok, _doc, %Analysis{} = analysis} <- Document.Store.fetch(document.uri, :analysis),
last_segment = String.to_atom(module_string),
{:ok, full_alias} <- fetch_full_alias(analysis, position, last_segment),
{:ok, alias_meta} <- fetch_alias_metadata(analysis, full_alias, last_segment),
{:ok, alias_meta} <- fetch_alias_metadata(position, analysis, full_alias, last_segment),
{:ok, edit} <- fetch_edit(alias_meta) do
{:ok, module_string, edit}
else
Expand All @@ -80,40 +80,101 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
end
end

defp fetch_alias_metadata(%Analysis{} = analysis, full_alias, last_segment) do
{_, result} =
Macro.prewalk(analysis.ast, :error, fn
{:alias, _, [{:__aliases__, _, ^full_alias}]} = node, :error ->
metadata =
single_alias_metadata(
document: analysis.document,
range: Ast.Range.fetch!(node, analysis.document)
)

{node, {:ok, metadata}}

{:alias, _, [{{:., _, _}, _, multi_alias_list}]} = node, :error ->
case Enum.find(multi_alias_list, &segment_matches?(&1, last_segment)) do
nil ->
{node, :error}

alias_node ->
metadata =
multi_alias_metadata(
document: analysis.document,
multi_alias_range: Ast.Range.fetch!(node, analysis.document),
removed_alias_range: Ast.Range.fetch!(alias_node, analysis.document),
alias_count: length(multi_alias_list)
)

{node, {:ok, metadata}}
end

node, acc ->
{node, acc}
end)

result
@alias_regex ~r/unused alias (\w+)/
defp fetch_unused_alias_module_string(%Diagnostic{} = diagnostic) do
case Regex.scan(@alias_regex, diagnostic.message) do
[[_, module_string]] -> {:ok, module_string}
_ -> :error
end
end

defp fetch_alias_metadata(
%Position{} = cursor,
%Analysis{} = analysis,
full_alias,
last_segment
) do
zipper = Zipper.zip(analysis.ast)
document = analysis.document

with :error <- find_single_alias(document, cursor, zipper, full_alias, last_segment) do
find_multi_alias(document, cursor, zipper, last_segment)
end
end

defp find_single_alias(
%Document{} = document,
%Position{} = cursor,
%Zipper{} = zipper,
full_alias,
last_segment
) do
finder = fn
{:alias, _, [{:__aliases__, _, ^full_alias}]} = node ->
cursor_in_node?(document, cursor, node)

{:alias, _,
[
{:__aliases__, _, ^full_alias},
[{{:__block__, _, [:as]}, {:__aliases__, _, [^last_segment]}}]
]} = node ->
cursor_in_node?(document, cursor, node)

_ ->
false
end

case Zipper.find(zipper, finder) do
nil ->
:error

%Zipper{node: node} ->
metadata =
single_alias_metadata(document: document, range: Ast.Range.fetch!(node, document))

{:ok, metadata}
end
end

defp find_multi_alias(
%Document{} = document,
%Position{} = cursor,
%Zipper{} = zipper,
last_segment
) do
finder = fn
{:alias, _, [{{:., _, _}, _, multi_alias_list}]} = node ->
Enum.find_value(multi_alias_list, &segment_matches?(&1, last_segment)) and
cursor_in_node?(document, cursor, node)

_ ->
false
end

case Zipper.find(zipper, finder) do
nil ->
:error

%Zipper{node: {:alias, _, [{{:., _, _}, _, multi_alias_list}]}} = zipper ->
alias_node = Enum.find(multi_alias_list, &segment_matches?(&1, last_segment))

multi_alias =
multi_alias_metadata(
document: document,
multi_alias_range: Ast.Range.fetch!(zipper.node, document),
removed_alias_range: Ast.Range.fetch!(alias_node, document),
alias_count: length(multi_alias_list)
)

{:ok, multi_alias}
end
end

defp cursor_in_node?(%Document{} = document, %Position{} = cursor, node) do
case Ast.Range.fetch(node, document) do
{:ok, range} -> Range.contains?(range, cursor)
_ -> false
end
end

defp fetch_full_alias(%Analysis{} = analysis, %Position{} = position, last_segment) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,31 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAliasTest do
] =~ removed
end

test "works using as" do
{:ok, removed} =
~q[
alias Foo.Bar.Baz, as: Quux|
]
|> remove_alias(alias: "Quux")

assert "" == removed
end

test "only deletes the alias on the cursor's line" do
{:ok, removed} =
~q[
alias Foo.Bar
alias Something.Else
alias Foo.Bar|
]
|> remove_alias(alias: "Bar")

assert ~q[
alias Foo.Bar
alias Something.Else
] =~ removed
end

test "leaves things alone if the message is different" do
assert {:ok, "alias This.Is.Correct"} ==
remove_alias("alias This.Is.Correct|", message: "ugly code")
Expand Down Expand Up @@ -136,6 +161,25 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAliasTest do
end
] =~ removed
end

test "removes an alias at the end of an alias block" do
{:ok, removed} =
~q[
defmodule MyModule do
alias Foo.Bar.Baz
alias Quux.Stuff
alias Yet.More.Things|
end
]
|> remove_alias(alias: "Things")

assert ~q[
defmodule MyModule do
alias Foo.Bar.Baz
alias Quux.Stuff
end
] =~ removed
end
end

describe "in deeply nested modules" do
Expand Down Expand Up @@ -246,7 +290,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAliasTest do
~q[
alias Grandparent.Parent.{
Child.Stinky,
Child.Smelly,
Child.Smelly|,
Other.Reeky
}
]
Expand Down

0 comments on commit 9d47ae7

Please sign in to comment.