From 6c1aa4b33b14be7ab9069e01619b48052d0d0e02 Mon Sep 17 00:00:00 2001 From: Krzysztof Wende Date: Tue, 29 Aug 2023 17:36:19 +0200 Subject: [PATCH] Directive order --- lib/recode/task/directive_order.ex | 96 +++++++++++++++++++++++++++ test/recode/task/alias_order_test.exs | 2 + test/recode/task/directive_order.exs | 48 ++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 lib/recode/task/directive_order.ex create mode 100644 test/recode/task/directive_order.exs diff --git a/lib/recode/task/directive_order.ex b/lib/recode/task/directive_order.ex new file mode 100644 index 0000000..b9d1ce0 --- /dev/null +++ b/lib/recode/task/directive_order.ex @@ -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 diff --git a/test/recode/task/alias_order_test.exs b/test/recode/task/alias_order_test.exs index c34b142..427d604 100644 --- a/test/recode/task/alias_order_test.exs +++ b/test/recode/task/alias_order_test.exs @@ -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 """ diff --git a/test/recode/task/directive_order.exs b/test/recode/task/directive_order.exs new file mode 100644 index 0000000..19a995e --- /dev/null +++ b/test/recode/task/directive_order.exs @@ -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