diff --git a/lib/alias_env.ex b/lib/alias_env.ex new file mode 100644 index 0000000..30993f7 --- /dev/null +++ b/lib/alias_env.ex @@ -0,0 +1,49 @@ +defmodule Styler.AliasEnv do + @moduledoc """ + A datastructure for maintaining something like compiler alias state when traversing AST. + + Not anywhere as correct as what the compiler gives us, but close enough for open source work. + + A `dealias` is a map from what an alias's `as` to its resolution in a context. In other words + + alias Foo.Bar + + would create the dealias map + + %{:Bar => [:Foo, :Bar]} + """ + def define(dealiases \\ %{}, ast) + def define(d, list) when is_list(list), do: Enum.reduce(list, d, &define(&2, &1)) + def define(d, {:alias, _, [{:__aliases__, _, aliases}]}), do: do_define(d, aliases, List.last(aliases)) + def define(d, {:alias, _, [{:__aliases__, _, aliases}, [{_as, {:__aliases__, _, [as]}}]]}), do: do_define(d, aliases, as) + # `alias __MODULE__` or other oddities i'm not bothering to get right + def define(dealiases, {:alias, _, _}), do: dealiases + + defp do_define(dealiases, modules, as), do: Map.put(dealiases, as, do_expand(dealiases, modules)) + + # no need to traverse ast if there are no aliases + def expand(dealiases, ast) when map_size(dealiases) == 0, do: ast + + def expand(dealiases, {:alias, m, [{:__aliases__, m_, modules} | rest]}), + do: {:alias, m, [{:__aliases__, m_, do_expand(dealiases, modules)} | rest]} + + def expand(dealiases, ast) do + Macro.prewalk(ast, fn + {:__aliases__, meta, modules} -> {:__aliases__, meta, do_expand(dealiases, modules)} + ast -> ast + end) + end + + # if the list of modules is itself already aliased, dealias it with the compound alias + # given: + # alias Foo.Bar + # Bar.Baz.Bop.baz() + # + # lifting Bar.Baz.Bop should result in: + # alias Foo.Bar + # alias Foo.Bar.Baz.Bop + # Bop.baz() + defp do_expand(dealiases, [first | rest] = modules) do + if dealias = dealiases[first], do: dealias ++ rest, else: modules + end +end diff --git a/lib/dealias.ex b/lib/dealias.ex deleted file mode 100644 index 8541ecc..0000000 --- a/lib/dealias.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Styler.Dealias do - @moduledoc """ - A datastructure for maintaining something like compiler alias state when traversing AST. - - Not anywhere as correct as what the compiler gives us, but close enough for open source work. - """ - def new(aliases), do: Enum.reduce(aliases, %{}, &put(&2, &1)) - - def put(dealiases, ast) - def put(d, list) when is_list(list), do: Enum.reduce(list, d, &put(&2, &1)) - def put(d, {:alias, _, [{:__aliases__, _, aliases}]}), do: do_put(d, aliases, List.last(aliases)) - def put(d, {:alias, _, [{:__aliases__, _, aliases}, [{_as, {:__aliases__, _, [as]}}]]}), do: do_put(d, aliases, as) - # `alias __MODULE__` or other oddities i'm not bothering to get right - def put(dealiases, {:alias, _, _}), do: dealiases - - defp do_put(dealiases, modules, as) do - Map.put(dealiases, as, do_dealias(dealiases, modules)) - end - - # no need to traverse ast if there are no aliases - def apply(dealiases, ast) when map_size(dealiases) == 0, do: ast - - def apply(dealiases, {:alias, m, [{:__aliases__, m_, modules} | rest]}), - do: {:alias, m, [{:__aliases__, m_, do_dealias(dealiases, modules)} | rest]} - - def apply(dealiases, ast) do - Macro.prewalk(ast, fn - {:__aliases__, meta, modules} -> {:__aliases__, meta, do_dealias(dealiases, modules)} - ast -> ast - end) - end - - # if the list of modules is itself already aliased, dealias it with the compound alias - # given: - # alias Foo.Bar - # Bar.Baz.Bop.baz() - # - # lifting Bar.Baz.Bop should result in: - # alias Foo.Bar - # alias Foo.Bar.Baz.Bop - # Bop.baz() - defp do_dealias(dealiases, [first | rest] = modules) do - if dealias = dealiases[first], do: dealias ++ rest, else: modules - end -end diff --git a/lib/style/module_directives.ex b/lib/style/module_directives.ex index ecb21fa..aeaeb8c 100644 --- a/lib/style/module_directives.ex +++ b/lib/style/module_directives.ex @@ -88,7 +88,7 @@ defmodule Styler.Style.ModuleDirectives do """ @behaviour Styler.Style - alias Styler.Dealias + alias Styler.AliasEnv alias Styler.Style alias Styler.Zipper @@ -235,7 +235,7 @@ defmodule Styler.Style.ModuleDirectives do |> Enum.reduce(@acc, fn {:@, _, [{attr_directive, _, _}]} = ast, acc when attr_directive in @attr_directives -> # attr_directives are moved above aliases, so we need to dealias them - {ast, acc} = acc.dealiases |> Dealias.apply(ast) |> lift_module_attrs(acc) + {ast, acc} = acc.dealiases |> AliasEnv.expand(ast) |> lift_module_attrs(acc) %{acc | attr_directive => [ast | acc[attr_directive]]} {:@, _, [{attr, _, _}]} = ast, acc -> @@ -245,8 +245,8 @@ defmodule Styler.Style.ModuleDirectives do {ast, acc} = lift_module_attrs(ast, acc) ast = expand(ast) # import and used get hoisted above aliases, so need to dealias - ast = if directive in ~w(import use)a, do: Dealias.apply(acc.dealiases, ast), else: ast - dealiases = if directive == :alias, do: Dealias.put(acc.dealiases, ast), else: acc.dealiases + ast = if directive in ~w(import use)a, do: AliasEnv.expand(acc.dealiases, ast), else: ast + dealiases = if directive == :alias, do: AliasEnv.define(acc.dealiases, ast), else: acc.dealiases # the reverse accounts for `expand` putting things in reading order, whereas we're accumulating in reverse %{acc | directive => Enum.reverse(ast, acc[directive]), dealiases: dealiases} @@ -327,7 +327,7 @@ defmodule Styler.Style.ModuleDirectives do defp lift_aliases(%{alias: aliases, require: requires, nondirectives: nondirectives} = acc) do # we can't use the dealias map built into state as that's what things look like before sorting # now that we've sorted, it could be different! - dealiases = Dealias.new(aliases) + dealiases = AliasEnv.define(aliases) excluded = dealiases |> Map.keys() |> Enum.into(Styler.Config.get(:lifting_excludes)) liftable = find_liftable_aliases(requires ++ nondirectives, excluded) @@ -339,7 +339,7 @@ defmodule Styler.Style.ModuleDirectives do aliases = liftable - |> Enum.map(&Dealias.apply(dealiases, {:alias, m, [{:__aliases__, [{:last, m} | m], &1}]})) + |> Enum.map(&AliasEnv.expand(dealiases, {:alias, m, [{:__aliases__, [{:last, m} | m], &1}]})) |> Enum.concat(aliases) |> sort()