Skip to content

Commit

Permalink
hmm hmm hmm
Browse files Browse the repository at this point in the history
  • Loading branch information
novaugust committed Mar 11, 2024
1 parent fb73330 commit 2b25881
Showing 1 changed file with 82 additions and 1 deletion.
83 changes: 82 additions & 1 deletion lib/style/module_directives.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ defmodule Styler.Style.ModuleDirectives do
@attr_directives ~w(moduledoc shortdoc behaviour)a ++ @callback_attrs
@defstruct ~w(schema embedded_schema defstruct)a

# taken from Credo
@excluded_namespaces MapSet.new(~w(File IO Inspect Kernel Macro Supervisor Task Version)a)
@stdlib MapSet.new(~w(Access Agent Application Atom Base Behaviour
Bitwise Code Date DateTime Dict Enum Exception
File Float GenEvent GenServer HashDict HashSet
Integer IO Kernel Keyword List Macro Map MapSet
Module NaiveDateTime Node OptionParser Path Port
Process Protocol Range Record Regex Registry Set
Stream String StringIO Supervisor System Task Time
Tuple URI Version)a)

@moduledoc_false {:@, [line: nil], [{:moduledoc, [line: nil], [{:__block__, [line: nil], [false]}]}]}

def run({{:defmodule, _, children}, _} = zipper, ctx) do
Expand Down Expand Up @@ -183,10 +194,78 @@ defmodule Styler.Style.ModuleDirectives do

uses = (directives[:use] || []) |> Enum.flat_map(&expand_directive/1) |> reset_newlines()
imports = expand_and_sort(directives[:import] || [])
requires = expand_and_sort(directives[:require] || [])

all_aliases = directives[:alias] || []
aliases = expand_and_sort(all_aliases)

aliased =
aliases
|> Enum.flat_map(fn
{:alias, _, [{:__aliases__, _, aliases}]} -> [aliases]
_ -> []
end)
|> MapSet.new(&List.last/1)

excluded_first = MapSet.union(aliased, @excluded_namespaces)
excluded_last = MapSet.union(aliased, @stdlib)

to_alias =
nondirectives
|> Zipper.zip()
|> Zipper.traverse_while(%{}, fn
# Credo doesn't look at :@ nodes. I kinda like it? especially for typespecs.
{{directive, _, _}, _} = zipper, acc when directive in ~w(@ use)a ->
{:skip, zipper, acc}

# A.B.C
{{:__aliases__, _, [first, _, _ | _] = aliases}, _} = zipper, acc ->
last = List.last(aliases)
acc =
if last in excluded_last or first in excluded_first,
do: acc,
else: Map.update(acc, aliases, false, fn _ -> true end)

{:skip, zipper, acc}

zipper, acc ->
{:cont, zipper, acc}
end)
|> elem(1)
# if we have `Foo.Bar.Baz` and `Foo.Bar.Bop.Baz` both not aliased, we'll create a collision by extracting both.
# grouping by last alias lets us detect these collisions
|> Enum.group_by(fn {aliases, _} -> List.last(aliases) end)
|> Enum.filter(fn
# only extract if it doesn't conflict and was used more than once
{_, [{_, true}]} -> true
_ -> false
end)
|> MapSet.new(fn {_, [{aliases, _}]} -> aliases end)

replace_new_aliases = fn
{{:__aliases__, meta, [_, _, _ | _] = aliases}, z_meta} = zipper ->
if aliases in to_alias, do: {{:__aliases__, meta, [List.last(aliases)]}, z_meta}, else: zipper

zipper ->
zipper
end

# @TODO bug here if the first line of the parent is a comment
# diff:
# """
# -
# - # a comment on use
# use Ecto.Type
#
# + alias Some.Long.Alias
# +
# + # a comment on use
aliases =
expand_and_sort(
aliases ++ Enum.map(to_alias, &{:alias, [line: 0], [{:__aliases__, [last: [line: 0], line: 0], &1}]})
)

requires = expand_and_sort(directives[:require] || []) |> Zipper.zip() |> Zipper.traverse(replace_new_aliases) |> Zipper.node()

directives =
[
shortdocs,
Expand All @@ -209,6 +288,8 @@ defmodule Styler.Style.ModuleDirectives do
Zipper.update(parent, &Zipper.replace_children(&1, directives))

true ->
nondirectives = nondirectives |> Zipper.zip() |> Zipper.traverse(replace_new_aliases) |> Zipper.node()

parent
|> Zipper.update(&Zipper.replace_children(&1, directives))
|> Zipper.down()
Expand Down

0 comments on commit 2b25881

Please sign in to comment.