Skip to content

Commit 59d7b65

Browse files
committed
Add styler:disable_alias_lifting directive
1 parent 6961e36 commit 59d7b65

3 files changed

Lines changed: 106 additions & 7 deletions

File tree

docs/module_directives.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ You can specify additional modules to exclude from lifting via the `:alias_lifti
183183
]
184184
```
185185

186+
### Per-File Opt-Out
187+
188+
Drop a `# styler:disable_alias_lifting` comment anywhere in a file to skip alias lifting for that file. Existing aliases are still applied — only the auto-creation of new aliases is suppressed.
189+
190+
```elixir
191+
# styler:disable_alias_lifting
192+
defmodule MyApp.Some.Ignore do
193+
@moduledoc false
194+
195+
Foo.Bar.Baz.bop()
196+
Foo.Bar.Baz.bop()
197+
end
198+
```
199+
186200
## Alias Application
187201

188202
Styler applies aliases in those cases where a developer wrote out a full module name without realizing that the module is already aliased.

lib/style/module_directives.ex

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ defmodule Styler.Style.ModuleDirectives do
5959
require: [],
6060
nondirectives: [],
6161
alias_env: %{},
62-
attrs: MapSet.new()
62+
attrs: MapSet.new(),
63+
lifting_disabled?: false
6364
}
6465

6566
# module directives typically doesn't do anything until it sees a module (typical .ex file) or a directive (like a snippet)
@@ -71,11 +72,13 @@ defmodule Styler.Style.ModuleDirectives do
7172
def run(zipper, %{__MODULE__ => true} = ctx), do: do_run(zipper, ctx)
7273

7374
def run({node, nil} = zipper, ctx) do
75+
ctx = Map.put(ctx, :lifting_disabled?, Enum.any?(ctx.comments, &(&1.text == "# styler:disable_alias_lifting")))
76+
7477
if interesting_zipper = Zipper.find(zipper, &match?({x, _, _} when x in [:defmodule, :@ | @directives], &1)) do
7578
do_run(interesting_zipper, Map.put(ctx, __MODULE__, true))
7679
else
7780
# there's no defmodules or aliasy things - see if we can do some alias lifting?
78-
case lift_aliases(%{@env | nondirectives: [node]}) do
81+
case lift_aliases(%{@env | nondirectives: [node], lifting_disabled?: ctx.lifting_disabled?}) do
7982
%{alias: []} ->
8083
{:halt, zipper, ctx}
8184

@@ -120,7 +123,7 @@ defmodule Styler.Style.ModuleDirectives do
120123
# we want only-child literal block to be handled in the only-child catch-all. it means someone did a weird
121124
# (that would be a literal, so best case someone wrote a string and forgot to put `@moduledoc` before it)
122125
{:__block__, _, [_, _ | _]} ->
123-
{:skip, organize_directives(body_zipper, moduledoc), ctx}
126+
{:skip, organize_directives(body_zipper, ctx, moduledoc), ctx}
124127

125128
# a module whose only child is a moduledoc. nothing to do here!
126129
# seems weird at first blush but lots of projects/libraries do this with their root namespace module
@@ -133,7 +136,7 @@ defmodule Styler.Style.ModuleDirectives do
133136
zipper =
134137
body_zipper
135138
|> Zipper.replace({:__block__, [], [moduledoc, only_child]})
136-
|> organize_directives()
139+
|> organize_directives(ctx)
137140

138141
{:skip, zipper, ctx}
139142
else
@@ -147,7 +150,7 @@ defmodule Styler.Style.ModuleDirectives do
147150
defp do_run({{directive, _, children}, _} = zipper, ctx) when directive in @directives and is_list(children) do
148151
# Need to be careful that we aren't getting false positives on variables or fns like `def import(foo)` or `alias = 1`
149152
case Style.ensure_block_parent(zipper) do
150-
{:ok, zipper} -> {:skip, zipper |> Zipper.up() |> organize_directives(), ctx}
153+
{:ok, zipper} -> {:skip, zipper |> Zipper.up() |> organize_directives(ctx), ctx}
151154
# not actually a directive! carry on.
152155
:error -> {:cont, zipper, ctx}
153156
end
@@ -193,11 +196,11 @@ defmodule Styler.Style.ModuleDirectives do
193196
end
194197
end
195198

196-
defp organize_directives(parent, moduledoc \\ nil) do
199+
defp organize_directives(parent, ctx, moduledoc \\ nil) do
197200
acc =
198201
parent
199202
|> Zipper.children()
200-
|> Enum.reduce(@env, fn
203+
|> Enum.reduce(%{@env | lifting_disabled?: ctx.lifting_disabled?}, fn
201204
{:@, _, [{attr_directive, _, _}]} = ast, acc when attr_directive in @attr_directives ->
202205
# attr_directives are moved above aliases, so we need to expand them
203206
ast = AliasEnv.expand_ast(acc.alias_env, ast)
@@ -226,6 +229,7 @@ defmodule Styler.Style.ModuleDirectives do
226229
{:use, uses} -> {:use, uses |> Enum.reverse() |> Style.reset_newlines()}
227230
{directive, to_sort} when directive in ~w(behaviour import alias require)a -> {directive, sort(to_sort)}
228231
{:alias_env, d} -> {:alias_env, d}
232+
{:lifting_disabled?, d} -> {:lifting_disabled?, d}
229233
{k, v} -> {k, Enum.reverse(v)}
230234
end)
231235
|> redefine_alias_env()
@@ -265,6 +269,8 @@ defmodule Styler.Style.ModuleDirectives do
265269
# alias_env have to be recomputed after we've sorted our `alias` nodes
266270
defp redefine_alias_env(%{alias: aliases} = acc), do: %{acc | alias_env: AliasEnv.define(aliases)}
267271

272+
defp lift_aliases(%{lifting_disabled?: true} = acc), do: acc
273+
268274
defp lift_aliases(%{alias: aliases, require: requires, nondirectives: nondirectives, alias_env: alias_env} = acc) do
269275
liftable = find_liftable_aliases(requires ++ nondirectives, alias_env)
270276

test/style/module_directives/alias_lifting_test.exs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,83 @@ defmodule Styler.Style.ModuleDirectives.AliasLiftingTest do
606606
"""
607607
end
608608
end
609+
610+
describe "# styler:disable_alias_lifting" do
611+
test "skips lifting when set" do
612+
assert_style """
613+
# styler:disable_alias_lifting
614+
defmodule A do
615+
@moduledoc false
616+
617+
Foo.Bar.Baz.bop()
618+
Foo.Bar.Baz.bop()
619+
end
620+
"""
621+
end
622+
623+
test "still applies existing aliases when lifting is disabled" do
624+
assert_style(
625+
"""
626+
# styler:disable_alias_lifting
627+
defmodule A do
628+
@moduledoc false
629+
alias Foo.Bar.Baz
630+
631+
Foo.Bar.Baz.bop()
632+
Foo.Bar.Baz.bop()
633+
end
634+
""",
635+
"""
636+
# styler:disable_alias_lifting
637+
defmodule A do
638+
@moduledoc false
639+
640+
alias Foo.Bar.Baz
641+
642+
Baz.bop()
643+
Baz.bop()
644+
end
645+
"""
646+
)
647+
end
648+
649+
test "comment can sit anywhere in the file" do
650+
assert_style """
651+
defmodule A do
652+
@moduledoc false
653+
654+
# styler:disable_alias_lifting
655+
Foo.Bar.Baz.bop()
656+
Foo.Bar.Baz.bop()
657+
end
658+
"""
659+
end
660+
661+
test "applies file-wide, including nested defmodules" do
662+
assert_style """
663+
# styler:disable_alias_lifting
664+
defmodule A do
665+
@moduledoc false
666+
667+
Foo.Bar.Baz.bop()
668+
Foo.Bar.Baz.bop()
669+
670+
defmodule Inner do
671+
@moduledoc false
672+
673+
Quux.Quuz.Quoz.bop()
674+
Quux.Quuz.Quoz.bop()
675+
end
676+
end
677+
"""
678+
end
679+
680+
test "applies in snippets without a defmodule" do
681+
assert_style """
682+
# styler:disable_alias_lifting
683+
Foo.Bar.Baz.bop()
684+
Foo.Bar.Baz.bop()
685+
"""
686+
end
687+
end
609688
end

0 commit comments

Comments
 (0)