Packages
igniter
0.5.17
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.30
0.6.29
0.6.28
0.6.27
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.52
0.5.51
0.5.50
0.5.49
0.5.48
0.5.47
0.5.46
0.5.45
0.5.44
0.5.43
0.5.42
0.5.41
0.5.40
0.5.39
0.5.38
0.5.37
0.5.36
0.5.35
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.78
0.3.77
0.3.76
0.3.75
0.3.74
0.3.73
0.3.72
0.3.71
0.3.70
0.3.69
0.3.68
0.3.67
0.3.66
0.3.65
0.3.64
0.3.63
0.3.62
0.3.61
0.3.60
0.3.59
0.3.58
0.3.57
0.3.56
0.3.55
0.3.54
0.3.53
0.3.52
0.3.51
0.3.50
0.3.49
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A code generation and project patching framework
Current section
Files
Jump to
Current section
Files
lib/igniter/code/function.ex
defmodule Igniter.Code.Function do
@moduledoc """
Utilities for working with functions.
"""
require Igniter.Code.Common
alias Igniter.Code.Common
alias Sourceror.Zipper
@doc """
Returns `true` if the argument at the provided index exists and matches the provided pattern
Note: to check for argument equality, use `argument_equals?/3` instead.
"""
defmacro argument_matches_pattern?(zipper, index, pattern) do
quote do
Igniter.Code.Function.argument_matches_predicate?(
unquote(zipper),
unquote(index),
fn zipper ->
match?(unquote(pattern), zipper.node)
end
)
end
end
@spec move_to_defp(Zipper.t(), fun :: atom, arity :: integer | list(integer)) ::
{:ok, Zipper.t()} | :error
def move_to_defp(zipper, fun, arity) do
do_move_to_def(zipper, fun, arity, :defp)
end
@spec move_to_def(Zipper.t()) :: {:ok, Zipper.t()} | :error
def move_to_def(zipper) do
move_to_function_call(zipper, :def, :any)
end
@spec move_to_def(Zipper.t(), fun :: atom, arity :: integer | list(integer)) ::
{:ok, Zipper.t()} | :error
def move_to_def(zipper, fun, arity) do
do_move_to_def(zipper, fun, arity, :def)
end
defp do_move_to_def(zipper, fun, [arity], kind) do
do_move_to_def(zipper, fun, arity, kind)
end
defp do_move_to_def(zipper, fun, [arity | rest], kind) do
case do_move_to_def(zipper, fun, arity, kind) do
{:ok, zipper} -> {:ok, zipper}
:error -> do_move_to_def(zipper, fun, rest, kind)
end
end
defp do_move_to_def(zipper, fun, arity, kind) do
case Common.move_to_pattern(
zipper,
{^kind, _, [{^fun, _, args}, _]} when length(args) == arity
) do
:error ->
if arity == 0 do
case Common.move_to_pattern(
zipper,
{^kind, _, [{^fun, _, context}, _]} when is_atom(context)
) do
:error ->
:error
{:ok, zipper} ->
Common.move_to_do_block(zipper)
end
else
:error
end
{:ok, zipper} ->
Common.move_to_do_block(zipper)
end
end
@doc "Moves to a function call by the given name and arity, matching the given predicate, in the current scope"
@spec move_to_function_call_in_current_scope(
Zipper.t(),
atom,
non_neg_integer() | list(non_neg_integer()) | :any
) ::
{:ok, Zipper.t()} | :error
def move_to_function_call_in_current_scope(zipper, name, arity, predicate \\ fn _ -> true end)
def move_to_function_call_in_current_scope(zipper, name, [arity | arities], predicate) do
case move_to_function_call_in_current_scope(zipper, name, arity, predicate) do
:error ->
move_to_function_call_in_current_scope(zipper, name, arities, predicate)
{:ok, zipper} ->
{:ok, zipper}
end
end
def move_to_function_call_in_current_scope(_, _, [], _) do
:error
end
def move_to_function_call_in_current_scope(%Zipper{} = zipper, name, arity, predicate) do
if function_call?(zipper, name, arity) && predicate.(zipper) do
{:ok, zipper}
else
Common.move_right(zipper, fn zipper ->
function_call?(zipper, name, arity) && predicate.(zipper)
end)
end
end
@doc "Moves to a function call by the given name and arity, matching the given predicate, in the current or lower scope"
@spec move_to_function_call(
Zipper.t(),
atom | {atom, atom},
:any | non_neg_integer() | list(non_neg_integer())
) ::
{:ok, Zipper.t()} | :error
def move_to_function_call(zipper, name, arity, predicate \\ fn _ -> true end)
def move_to_function_call(zipper, name, [arity | arities], predicate) do
case move_to_function_call(zipper, name, arity, predicate) do
:error ->
move_to_function_call(zipper, name, arities, predicate)
{:ok, zipper} ->
{:ok, zipper}
end
end
def move_to_function_call(_, _, [], _) do
:error
end
def move_to_function_call(%Zipper{} = zipper, name, arity, predicate) do
if function_call?(zipper, name, arity) && predicate.(zipper) do
{:ok, zipper}
else
Common.move_next(zipper, fn zipper ->
function_call?(zipper, name, arity) && predicate.(zipper)
end)
end
end
@doc """
Returns `true` if the node is a function call of the given name
If an `atom` is provided, it only matches functions in the form of `function(name)`.
If an `{module, atom}` is provided, it matches functions called on the given module,
taking into account any imports or aliases.
"""
@spec function_call?(Zipper.t(), atom | {module, atom}, arity :: integer | :any | list(integer)) ::
boolean()
def function_call?(zipper, name, arity \\ :any)
def function_call?(%Zipper{} = zipper, name, arity) when is_atom(name) do
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
|> case do
{^name, _, args} when arity == :any or length(args) == arity ->
true
{{^name, _, context}, _, args}
when is_atom(context) and (arity == :any or length(args) == arity) ->
true
{:|>, _, [_, {{^name, _, context}, _, rest}]}
when is_atom(context) and (arity == :any or length(rest) == arity - 1) ->
true
{:|>, _, [_, {^name, _, rest}]}
when arity == :any or length(rest) == arity - 1 ->
true
_ ->
false
end
end
def function_call?(%Zipper{} = zipper, {module, name}, arity) when is_atom(name) do
node =
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
function_call_shape? =
case node do
{{:., _, [{:__aliases__, _, _} = alias, ^name]}, _, args}
when arity == :any or length(args) == arity ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{{:., _, [{:__aliases__, _, _} = alias, {^name, _, context}]}, _, args}
when is_atom(context) and (arity == :any or length(args) == arity) ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, _} = alias, ^name]}, _, args}
]}
when arity == :any or length(args) == arity - 1 ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, _} = alias, {^name, _, context}]}, _, args}
]}
when is_atom(context) and (arity == :any or length(args) == arity - 1) ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{^name, _, args} when is_list(args) and (arity == :any or length(args) == arity) ->
imported?(zipper, module, name, length(args))
{{^name, _, context}, _, args}
when is_atom(context) and (arity == :any or length(args) == arity) ->
imported?(zipper, module, name, length(args))
{:|>, _, [_, {{^name, _, context}, _, rest}]}
when is_atom(context) and (arity == :any or length(rest) == arity - 1) ->
imported?(zipper, module, name, length(rest) + 1)
{:|>, _, [_, {^name, _, rest}]}
when arity == :any or length(rest) == arity - 1 ->
imported?(zipper, module, name, length(rest) + 1)
_node ->
false
end
if function_call_shape? do
case Zipper.up(zipper) do
%{node: {:&, _, _}} ->
false
_ ->
true
end
else
false
end
end
@doc false
def imported?(zipper, module, name, arity) do
case Igniter.Code.Common.current_env(zipper) do
{:ok, env} ->
Enum.any?(env.functions ++ env.macros, fn {imported_module, funcs} ->
imported_module == module &&
Enum.any?(funcs, fn {imported_name, imported_arity} ->
name == imported_name && (arity == :any || imported_arity == arity)
end)
end)
_ ->
false
end
end
@doc """
Returns true if the value is a function literal.
Examples:
- `fn x -> x end`
- `&(&1 + &2)`
- `&SomeMod.fun/2`
To refine the check, you can use `name` and `arity`.
## Names
- `:any` - matches any function literal, named or not
- `:any_named` - matches any named function literal
- `:anonymous` - matches any anonymous function literal
- `{module, name}` - matches a function literal with the given module and name
"""
@spec function?(
Zipper.t(),
name :: :any | :any_named | {module(), atom()} | :anonymous,
arity :: :any | non_neg_integer() | [non_neg_integer()]
) ::
boolean
def function?(zipper, name \\ :any, arity \\ :any)
def function?(zipper, name, arity) when is_list(arity) do
Enum.any?(arity, fn arity -> function?(zipper, name, arity) end)
end
def function?(%Zipper{}, name, _arity)
when is_atom(name) and name not in [:any, :any_named, :anonymous] do
raise ArgumentError,
"The name argument must be one of `:any`, `:any_named`, `:anonymous` or a `{module, name}` tuple."
end
def function?(%Zipper{} = zipper, :anonymous, arity) do
node =
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
case node do
{:&, _, [{{:., _, [{:__aliases__, _, _}, _]}, _, _}]} ->
false
{:&, _, [{:&, _, _}]} ->
arity == :any or arity == 1
{:&, _, [{name, _, _}]} when is_atom(name) ->
false
{:&, _, [body]} ->
arity == :any or count_captures(body) == arity
{:fn, _, [{:->, _, [[{:when, _, args}], _body]} | _]}
when arity == :any or length(args) == arity ->
true
{:fn, _, [{:->, _, [args, _body]} | _]} when arity == :any or length(args) == arity ->
true
{:fn, _, _} ->
true
_ ->
false
end
end
def function?(%Zipper{} = zipper, {module, name}, arity) do
node =
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
case node do
{:&, _, [{:/, _, [{^name, _, context}, actual_arity]}]}
when is_atom(context) and (arity == :any or actual_arity == arity) ->
imported?(zipper, module, name, actual_arity)
{:&, _, [{:/, _, [{^name, _, context}, {:__block__, _, [actual_arity]}]}]}
when is_atom(context) and (arity == :any or actual_arity == arity) ->
imported?(zipper, module, name, actual_arity)
{:&, _, [{:/, _, [^name, actual_arity]}]}
when arity == :any or actual_arity == arity ->
imported?(zipper, module, name, actual_arity)
{:&, _, [{:/, _, [^name, {:__block__, _, [actual_arity]}]}]}
when arity == :any or actual_arity == arity ->
imported?(zipper, module, name, actual_arity)
{:&, _,
[
{:/, _,
[
{{:., _, [{:__aliases__, _, _} = alias, ^name]}, _, _},
actual_arity
]}
]}
when arity == :any or actual_arity == arity ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{:&, _,
[
{:/, _,
[
{{:., _, [{:__aliases__, _, _} = alias, ^name]}, _, _},
{:__block__, _, [actual_arity]}
]}
]}
when arity == :any or actual_arity == arity ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{:&, _, [call]} ->
case call do
{{:., _, [{:__aliases__, _, _} = alias, ^name]}, _, args}
when arity == :any or length(args) == arity ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{{:., _, [{:__aliases__, _, _} = alias, {^name, _, context}]}, _, args}
when is_atom(context) and (arity == :any or length(args) == arity) ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, _} = alias, ^name]}, _, args}
]}
when arity == :any or length(args) == arity - 1 ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, _} = alias, {^name, _, context}]}, _, args}
]}
when is_atom(context) and (arity == :any or length(args) == arity - 1) ->
Common.nodes_equal?(Zipper.replace(zipper, alias), module)
{^name, _, args} when arity == :any or length(args) == arity ->
imported?(zipper, module, name, length(args))
{{^name, _, context}, _, args}
when is_atom(context) and (arity == :any or length(args) == arity) ->
imported?(zipper, module, name, length(args))
{:|>, _, [_, {{^name, _, context}, _, rest}]}
when is_atom(context) and (arity == :any or length(rest) == arity - 1) ->
imported?(zipper, module, name, length(rest) + 1)
{:|>, _, [_, {^name, _, rest}]}
when arity == :any or length(rest) == arity - 1 ->
imported?(zipper, module, name, length(rest) + 1)
_ ->
false
end
_ ->
false
end
end
def function?(%Zipper{} = zipper, :any, arity) do
function?(zipper, :any_named, arity) or function?(zipper, :anonymous, arity)
end
def function(%Zipper{} = zipper, :any_named, arity) do
node =
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
case node do
{:&, _, [{:/, _, [{name, _, context}, actual_arity]}]}
when is_atom(name) and is_atom(context) and
(arity == :any or actual_arity == arity) ->
true
{:&, _, [{:/, _, [{name, _, context}, {:__block__, _, [actual_arity]}]}]}
when is_atom(name) and is_atom(context) and
(arity == :any or actual_arity == arity) ->
true
{:&, _, [{:/, _, [name, actual_arity]}]}
when is_atom(name) and (arity == :any or actual_arity == arity) ->
true
{:&, _, [{:/, _, [name, {:__block__, _, [actual_arity]}]}]}
when is_atom(name) and (arity == :any or actual_arity == arity) ->
true
{:&, _,
[
{:/, _,
[
{{:., _, [{:__aliases__, _, _}, name]}, _, _},
actual_arity
]}
]}
when is_atom(name) and (arity == :any or actual_arity == arity) ->
true
{:&, _,
[
{:/, _,
[
{{:., _, [{:__aliases__, _, _}, name]}, _, _},
{:__block__, _, [actual_arity]}
]}
]}
when is_atom(name) and (arity == :any or actual_arity == arity) ->
true
{:&, _, [call]} ->
case call do
{{:., _, [{:__aliases__, _, _}, name]}, _, args}
when is_atom(name) and (arity == :any or length(args) == arity) ->
true
{{:., _, [{:__aliases__, _, _}, {name, _, context}]}, _, args}
when is_atom(name) and is_atom(context) and (arity == :any or length(args) == arity) ->
true
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, _split}, name]}, _, args}
]}
when is_atom(name) or (arity == :any or length(args) == arity - 1) ->
true
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, _}, {name, _, context}]}, _, args}
]}
when is_atom(name) and is_atom(context) and (arity == :any or length(args) == arity - 1) ->
true
{name, _, args} when is_atom(name) and (arity == :any or length(args) == arity) ->
true
{{name, _, context}, _, args}
when is_atom(name) and is_atom(context) and
(arity == :any or length(args) == arity) ->
true
{:|>, _, [_, {name, _, context} | rest]}
when is_atom(name) and is_atom(context) and (arity == :any or length(rest) == arity - 1) ->
true
{:|>, _, [_, name | rest]}
when is_atom(name) and (arity == :any or length(rest) == arity - 1) ->
true
_ ->
false
end
_ ->
false
end
end
@doc """
Gets the name of a local function call.
Returns `:error` if the node is not a function call or cannot be determined.
"""
@spec get_local_function_call_name(Zipper.t()) :: {:ok, atom()} | :error
def get_local_function_call_name(%Zipper{} = zipper) do
case get_local_function_call(zipper) do
{:ok, {name, _arity}} -> {:ok, name}
:error -> :error
end
end
@doc """
Gets the name and arity of a local function call.
Returns `:error` if the node is not a function call or cannot be determined.
"""
@spec get_local_function_call(Zipper.t()) :: {:ok, {atom(), non_neg_integer()}} | :error
def get_local_function_call(%Zipper{} = zipper) do
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
|> case do
{:__block__, _, _} ->
:error
{:|>, _, [_, {{name, _, context}, _, args}]} when is_atom(context) and is_atom(name) ->
{:ok, {name, length(args) + 1}}
{:|>, _, [_, {name, _, args}]} when is_atom(name) ->
{:ok, {name, length(args) + 1}}
{name, _, args} when is_atom(name) ->
{:ok, {name, length(args)}}
{{name, _, context}, _, args} when is_atom(context) and is_atom(name) and is_list(args) ->
{:ok, {name, length(args)}}
_ ->
:error
end
end
@doc "Returns `true` if the node is a function call"
@spec function_call?(Zipper.t()) :: boolean()
def function_call?(%Zipper{} = zipper) do
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.node()
|> case do
{:|>, _,
[
_,
{{:., _, [_, name]}, _, _}
]}
when is_atom(name) ->
true
{:|>, _,
[
_,
{{:., _, [_, {name, _, context}]}, _, _args}
]}
when is_atom(name) and is_atom(context) ->
true
{:|>, _, [_, {{name, _, context}, _, _}]} when is_atom(context) and is_atom(name) ->
true
{:|>, _, [_, {name, _, _}]} when is_atom(name) ->
true
{name, _, _} when is_atom(name) ->
true
{{name, _, context}, _, _} when is_atom(context) and is_atom(name) ->
true
{{:., _, [_, name]}, _, _} when is_atom(name) ->
true
{{:., _, [_, {name, _, context}]}, _, _}
when is_atom(name) and is_atom(context) ->
true
_ ->
false
end
end
@doc "Updates the `nth` argument of a function call, leaving the zipper at the function call's node."
@spec update_nth_argument(
Zipper.t(),
non_neg_integer(),
(Zipper.t() ->
{:ok, Zipper.t()} | :error)
) ::
{:ok, Zipper.t()} | :error
def update_nth_argument(zipper, index, func) do
Common.within(zipper, fn zipper ->
if pipeline?(zipper) do
if index == 0 do
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
func.(zipper)
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Zipper.rightmost()
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.move_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
func.(nth)
end
end
end
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.move_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
func.(nth)
end
end
end
end)
end
@doc "Moves to the `nth` argument of a function call."
@spec move_to_nth_argument(
Zipper.t(),
non_neg_integer()
) ::
{:ok, Zipper.t()} | :error
def move_to_nth_argument(zipper, index) do
if function_call?(zipper) do
if pipeline?(zipper) do
if index == 0 do
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
{:ok, zipper}
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Zipper.rightmost()
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.move_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
{:ok, nth}
end
end
end
end
else
offset =
case zipper.node do
{{:., _, _}, _, _args} ->
1
_ ->
0
end
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.move_right(index + offset)
|> case do
:error ->
:error
{:ok, nth} ->
{:ok, nth}
end
end
end
else
:error
end
end
@doc "Appends an argument to a function call, leaving the zipper at the function call's node."
@spec append_argument(Zipper.t(), any()) :: {:ok, Zipper.t()} | :error
def append_argument(zipper, value) do
if function_call?(zipper) do
if pipeline?(zipper) do
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
{:ok, Zipper.append_child(zipper, value)}
end
else
{:ok, Zipper.append_child(zipper, value)}
end
else
:error
end
end
@doc """
Checks if the provided function call (in a Zipper) has an argument that equals
`term` at `index`.
"""
@spec argument_equals?(Zipper.t(), integer(), any()) :: boolean()
def argument_equals?(zipper, index, term) do
if function_call?(zipper) do
Igniter.Code.Function.argument_matches_predicate?(
zipper,
index,
&Igniter.Code.Common.nodes_equal?(&1, term)
)
else
false
end
end
@doc "Returns true if the argument at the given index matches the provided predicate"
@spec argument_matches_predicate?(Zipper.t(), non_neg_integer(), (Zipper.t() -> boolean)) ::
boolean()
def argument_matches_predicate?(zipper, index, func) do
if function_call?(zipper) do
if pipeline?(zipper) do
if index == 0 do
zipper
|> Zipper.down()
|> case do
nil -> nil
zipper -> func.(zipper)
end
else
zipper
|> Zipper.down()
|> Zipper.right()
|> argument_matches_predicate?(index - 1, func)
end
else
case Zipper.node(zipper) do
{{:., _, [_mod, name]}, _, args} when is_atom(name) and is_list(args) ->
zipper
|> Zipper.down()
|> Common.move_right(index + 1)
|> case do
:error ->
false
{:ok, zipper} ->
zipper
|> Common.maybe_move_to_single_child_block()
|> func.()
end
_ ->
zipper
|> Zipper.down()
|> case do
nil ->
false
zipper ->
zipper
|> Common.move_right(index)
|> case do
:error ->
false
{:ok, zipper} ->
zipper
|> Common.maybe_move_to_single_child_block()
|> func.()
end
end
end
end
else
false
end
end
defp pipeline?(zipper) do
case zipper.node do
{:|>, _, _} -> true
_ -> false
end
end
# Counts up all the arguments and generates new unique arguments for them.
# Works around the caveat that each usage of a unique `&n` variable must only
# be counted once.
defp count_captures(args) do
Macro.prewalk(args, [], fn
{:&, _, [v]} = ast, acc when is_integer(v) ->
{ast, [v | acc]}
ast, acc ->
{ast, acc}
end)
|> elem(1)
|> Enum.uniq()
|> Enum.count()
end
end