diff --git a/lib/style/pipes.ex b/lib/style/pipes.ex index fa77e6af..deca5df8 100644 --- a/lib/style/pipes.ex +++ b/lib/style/pipes.ex @@ -36,7 +36,7 @@ defmodule Styler.Style.Pipes do @literal ~w(__block__ __aliases__ unquote)a @value_constructors ~w(% %{} .. ..// <<>> @ {} ^ & fn from)a @kernel_ops ~w(++ -- && || in - * + / > < <= >= == and or != !== === <>)a - @special_ops ~w(<- ||| &&& <<< >>> <<~ ~>> <~ ~> <~> <|> ^^^ ~~~)a + @special_ops ~w(<- ||| &&& <<< >>> <<~ ~>> <~ ~> <~>)a @special_ops @literal ++ @value_constructors ++ @kernel_ops ++ @special_ops def run({{:|>, _, _}, _} = zipper, ctx) do @@ -152,8 +152,7 @@ defmodule Styler.Style.Pipes do # a |> fun => a |> fun() defp fix_pipe({:|>, m, [lhs, {fun, m2, nil}]}), do: {:|>, m, [lhs, {fun, m2, []}]} - # a |> then(&fun/1) |> c => a |> fun() |> c() - defp fix_pipe({:|>, m, [lhs, {:then, _, [{:&, _, [{:/, _, [{fun, m2, _}, _]}]}]}]}), do: {:|>, m, [lhs, {fun, m2, []}]} + # a |> then(&fun(&1, d)) |> c => a |> fun(d) |> c() defp fix_pipe({:|>, m, [lhs, {:then, _, [{:&, _, [{fun, m2, [{:&, _, _} | args]}]}]}]} = pipe) do rewrite = {fun, m2, args} @@ -164,13 +163,22 @@ defmodule Styler.Style.Pipes do pipe fun in @special_ops -> - if fun in @kernel_ops, do: {:|>, m, [lhs, {{:., m2, [{:__aliases__, m2, [:Kernel]}, fun]}, m2, args}]}, else: pipe + # we only rewrite unary/infix operators if they're in the Kernel namespace. + # everything else stays as-is in the `then/2` because we can't know what module they're from + if fun in @kernel_ops, + do: {:|>, m, [lhs, {{:., m2, [{:__aliases__, m2, [:Kernel]}, fun]}, m2, args}]}, + else: pipe true -> {:|>, m, [lhs, rewrite]} end end + # a |> then(&fun/1) |> c => a |> fun() |> c() + # recurses to add the `()` to `fun` as it gets unwound + defp fix_pipe({:|>, m, [lhs, {:then, _, [{:&, _, [{:/, _, [fun, {:__block__, _, [1]}]}]}]}]}), + do: fix_pipe({:|>, m, [lhs, fun]}) + # Credo.Check.Readability.PipeIntoAnonymousFunctions # rewrite anonymous function invocation to use `then/2` # `a |> (& &1).() |> c()` => `a |> then(& &1) |> c()` diff --git a/test/style/pipes_test.exs b/test/style/pipes_test.exs index 8adc2a4e..8ae86fe1 100644 --- a/test/style/pipes_test.exs +++ b/test/style/pipes_test.exs @@ -500,17 +500,26 @@ defmodule Styler.Style.PipesTest do test "rewrites then/2 when the passed function is a named function reference" do assert_style "a |> then(&fun/1) |> c", "a |> fun() |> c()" + assert_style "a |> then(&(&1 / 1)) |> c", "a |> Kernel./(1) |> c()" assert_style "a |> then(&DateTime.from_is8601/1) |> c", "a |> DateTime.from_is8601() |> c()" assert_style "a |> then(&DateTime.from_is8601/1)", "DateTime.from_is8601(a)" assert_style "a |> then(&fun(&1)) |> c", "a |> fun() |> c()" assert_style "a |> then(&fun(&1, d)) |> c", "a |> fun(d) |> c()" + assert_style "a |> then(&fun(d, &1)) |> c()" + assert_style "a |> then(&fun(&1, d, %{foo: &1})) |> c()" - # Unary operators + # then + kernel ops assert_style "a |> then(&(-&1)) |> c", "a |> Kernel.-() |> c()" assert_style "a |> then(&(+&1)) |> c", "a |> Kernel.+() |> c()" - assert_style "a |> then(&fun(d, &1)) |> c()" - assert_style "a |> then(&fun(&1, d, %{foo: &1})) |> c()" + for op <- ~w(++ -- && || in - * + / > < <= >= == and or != !== === <>) do + assert_style "a |> then(&(&1 #{op} x)) |> c", "a |> Kernel.#{op}(x) |> c()" + end + + # Doesn't rewrite non-kernel operators + for op <- ~w(<- ||| &&& <<< >>> <<~ ~>> <~ ~> <~>) do + assert_style "a |> then(&(&1 #{op} x)) |> c()" + end end test "adds parens to 1-arity pipes" do