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

Draft: Directive order #70

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions lib/recode/task/directive_order.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
defmodule Recode.Task.DirectiveOrder do
@shortdoc "Checks if directives (use/import/alias/require) are sorted ."

@moduledoc """
Alphabetically sorted lists are easier to read.

# preferred

alias Alpha
alias Bravo
alias Delta.{Echo, Foxtrot}

# not preferred

alias Delta.{Foxtrot, Echo}
alias Alpha
alias Bravo
"""

use Recode.Task, corrector: true, category: :readability

alias Recode.AST
alias Recode.Task.AliasOrder
alias Rewrite.Source
alias Sourceror.Zipper

@nodes [:use, :import, :alias, :require]

@impl Recode.Task
def run(source, opts) do
source
|> Source.get(:quoted)
|> Zipper.zip()
|> do_run(source, opts[:autocorrect])
end

defp do_run(_zipper, _source, false) do
throw("Not implemented yet")
end

defp do_run(zipper, source, true) do
new_zipper =
zipper
|> Zipper.traverse_while([[]], &alias_order/2)
|> update()

Source.update(source, AliasOrder, :quoted, Zipper.root(new_zipper))
end

defp alias_order(%Zipper{node: {node, _meta, _args} = ast} = zipper, [group | groups])
when node in @nodes do
last_node = if group == [], do: node, else: elem(hd(group), 0)

if last_node == node do
{:skip, zipper, [[ast | group] | groups]}
else
{:skip, zipper, [[ast], group | groups]}
end
end

defp alias_order(zipper, acc) do
{:cont, zipper, acc}
end

defp update({zipper, []}), do: zipper

defp update({zipper, acc}) do
groups = Enum.map(acc, &Enum.reverse/1) |> Enum.reverse()

[[first | _] | _] = groups
sorted = sorted(groups)

rewound = Zipper.find(zipper, :next, fn item -> item == first end)

Enum.reduce(sorted, rewound, fn group, zipper ->
group
|> Enum.reduce(zipper, fn ast, z ->
Zipper.update(z, fn _ast -> AST.put_newlines(ast, 1) end) |> skip()
end)
|> prev()
|> Zipper.update(fn ast -> AST.put_newlines(ast, 2) end)
|> skip()
end)
end

defp sorted(groups) do
Enum.sort(groups, fn
[ast1 | _], [ast2 | _] ->
Enum.find_index(@nodes, fn node -> node == elem(ast1, 0) end) <=
Enum.find_index(@nodes, fn node -> node == elem(ast2, 0) end)
end)
end

defp skip(zipper), do: Zipper.right(zipper) || Zipper.next(zipper)
defp prev(zipper), do: Zipper.left(zipper) || Zipper.prev(zipper)
end
2 changes: 2 additions & 0 deletions test/recode/task/alias_order_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
defmodule Recode.Task.AliasOrderTest do
use RecodeCase

alias Rewrite.Source
alias Recode.Task.AliasOrder
alias Recode.Task.DirectiveOrder

test "keeps a single alias" do
"""
Expand Down
48 changes: 48 additions & 0 deletions test/recode/task/directive_order.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule Recode.Task.DirectiveOrderTest do
use RecodeCase

alias Recode.Task.DirectiveOrder

test "sorts Use/Import/Alias/Require" do
code = """
defmodule MyModule do

require Alpha
alias Delta
alias Alpha.{Bravo, Charlie}
use Epsilon
alias Alpha
use Alpha


import Bravo


defp test(), do: 1
end
"""

expected = """
defmodule MyModule do
use Epsilon

use Alpha

import Bravo

alias Delta
alias Alpha.{Bravo, Charlie}

alias Alpha

require Alpha

defp test(), do: 1
end
"""

code
|> run_task(DirectiveOrder, autocorrect: true)
|> assert_code(expected)
end
end