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

Add configuration for modules to exclude for alias lifting #140

Merged
merged 3 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 6 additions & 6 deletions lib/style/module_directives.ex
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ defmodule Styler.Style.ModuleDirectives do
# we want only-child literal block to be handled in the only-child catch-all. it means someone did a weird
# (that would be a literal, so best case someone wrote a string and forgot to put `@moduledoc` before it)
{:__block__, _, [_, _ | _]} ->
{:skip, organize_directives(body_zipper, moduledoc), ctx}
{:skip, organize_directives(body_zipper, ctx, moduledoc), ctx}

# a module whose only child is a moduledoc. nothing to do here!
# seems weird at first blush but lots of projects/libraries do this with their root namespace module
Expand All @@ -128,7 +128,7 @@ defmodule Styler.Style.ModuleDirectives do
def run({{directive, _, children}, _} = zipper, ctx) when directive in @directives and is_list(children) do
# Need to be careful that we aren't getting false positives on variables or fns like `def import(foo)` or `alias = 1`
case Style.ensure_block_parent(zipper) do
{:ok, zipper} -> {:skip, zipper |> Zipper.up() |> organize_directives(), ctx}
{:ok, zipper} -> {:skip, zipper |> Zipper.up() |> organize_directives(ctx), ctx}
# not actually a directive! carry on.
:error -> {:cont, zipper, ctx}
end
Expand Down Expand Up @@ -173,7 +173,7 @@ defmodule Styler.Style.ModuleDirectives do
# a dynamic module name, like `defmodule my_variable do ... end`
defp moduledoc(_), do: nil

defp organize_directives(parent, moduledoc \\ nil) do
defp organize_directives(parent, ctx, moduledoc \\ nil) do
{directives, nondirectives} =
parent
|> Zipper.children()
Expand Down Expand Up @@ -201,7 +201,7 @@ defmodule Styler.Style.ModuleDirectives do
aliases = expand_and_sort(directives[:alias] || [])
requires = expand_and_sort(directives[:require] || [])

{aliases, requires, nondirectives} = lift_aliases(aliases, requires, nondirectives)
{aliases, requires, nondirectives} = lift_aliases(aliases, requires, nondirectives, ctx.config.lift_exclude)

directives =
[
Expand Down Expand Up @@ -229,9 +229,9 @@ defmodule Styler.Style.ModuleDirectives do
end
end

defp lift_aliases(aliases, requires, nondirectives) do
defp lift_aliases(aliases, requires, nondirectives, excluded_by_config) do
excluded =
Enum.reduce(aliases, @stdlib, fn
Enum.reduce(aliases, MapSet.union(@stdlib, excluded_by_config), fn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be worth pre-calculating this when Styler first initializes and putting it in persistent_term so that it only needs to be calculated once, since it won't vary over the course of a single formatter run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooooooo i was sad about calculating it every time, but hadn't thought to globally cache it via persistent_term! that's a beaut. could do that with the whole config so that you don't have to pass it down thru context, oo la la

{:alias, _, [{:__aliases__, _, aliases}]}, excluded -> MapSet.put(excluded, List.last(aliases))
{:alias, _, [{:__aliases__, _, _}, [{_as, {:__aliases__, _, [as]}}]]}, excluded -> MapSet.put(excluded, as)
# `alias __MODULE__` or other oddities
Expand Down
11 changes: 7 additions & 4 deletions lib/styler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ defmodule Styler do
def style({ast, comments}, file, opts) do
on_error = opts[:on_error] || :log
zipper = Zipper.zip(ast)
context = %{comments: comments, file: file}
excludes = MapSet.new(List.wrap(opts[:alias_lifting_exclude]))

context = %{comments: comments, file: file, config: %{lift_exclude: excludes}}

{{ast, _}, %{comments: comments}} =
Enum.reduce(@styles, {zipper, context}, fn style, {zipper, context} ->
Expand All @@ -58,13 +60,14 @@ defmodule Styler do
def features(_opts), do: [sigils: [], extensions: [".ex", ".exs"]]

@impl Format
def format(input, formatter_opts, opts \\ []) do
def format(input, formatter_opts) do
file = formatter_opts[:file]
styler_opts = formatter_opts[:styler] || []

{ast, comments} =
input
|> string_to_quoted_with_comments(to_string(file))
|> style(file, opts)
|> style(file, styler_opts)

quoted_to_string(ast, comments, formatter_opts)
end
Expand All @@ -81,7 +84,7 @@ defmodule Styler do
end

@doc false
def literal_encoder(a, b), do: {:ok, {:__block__, b, [a]}}
def literal_encoder(literal, meta), do: {:ok, {:__block__, meta, [literal]}}

@doc false
# Turns an ast and comments back into code, formatting it along the way.
Expand Down
12 changes: 12 additions & 0 deletions test/style/module_directives/alias_lifting_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ defmodule Styler.Style.ModuleDirectives.AliasLiftingTest do
end

describe "it doesn't lift" do
test "collisions with configured modules" do
assert_style(
"""
alias Foo.Bar

A.B.C
A.B.C
""",
alias_lifting_exclude: ~w(C)a
)
end

test "collisions with std lib" do
assert_style """
defmodule DontYouDare do
Expand Down
20 changes: 11 additions & 9 deletions test/support/style_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@ defmodule Styler.StyleCase do

using do
quote do
import unquote(__MODULE__), only: [assert_style: 1, assert_style: 2, style: 1]
import unquote(__MODULE__), only: [assert_style: 1, assert_style: 2, style: 1, style: 2]
end
end

defmacro assert_style(before, expected \\ nil) do
expected = expected || before

quote bind_quoted: [before: before, expected: expected] do
defmacro assert_style(before, expected, options) do
quote bind_quoted: [before: before, expected: expected, options: options] do
alias Styler.Zipper

expected = String.trim(expected)
{styled_ast, styled, styled_comments} = style(before)
{styled_ast, styled, styled_comments} = style(before, options)

if styled != expected and ExUnit.configuration()[:trace] do
IO.puts("\n======Given=============\n")
Expand Down Expand Up @@ -89,7 +87,7 @@ defmodule Styler.StyleCase do
end)

assert expected == styled
{_, restyled, _} = style(styled)
{_, restyled, _} = style(styled, options)

assert restyled == styled, """
expected styling to be idempotent, but a second pass resulted in more changes.
Expand All @@ -107,9 +105,13 @@ defmodule Styler.StyleCase do
end
end

def style(code) do
def assert_style(no_change), do: assert_style(no_change, no_change, [])
def assert_style(before, expected) when is_binary(expected), do: assert_style(before, expected, [])
def assert_style(no_change, opts) when is_list(opts), do: assert_style(no_change, no_change, opts)

def style(code, options \\ []) do
{ast, comments} = Styler.string_to_quoted_with_comments(code)
{styled_ast, comments} = Styler.style({ast, comments}, "testfile", on_error: :raise)
{styled_ast, comments} = Styler.style({ast, comments}, "testfile", [{:on_error, :raise} | options])

try do
styled_code = styled_ast |> Styler.quoted_to_string(comments) |> String.trim_trailing("\n")
Expand Down
Loading