Skip to content

Commit

Permalink
fix: traverse lists without entering child nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Sep 23, 2024
1 parent 19b3fac commit b326269
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 50 deletions.
59 changes: 40 additions & 19 deletions lib/igniter/code/common.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ defmodule Igniter.Code.Common do
end
end

@doc """
Moves to the last node before the node that matches the predicate, going upwards.
"""
@spec move_upwards_until(Zipper.t(), (Zipper.t() -> boolean())) :: {:ok, Zipper.t()} | :error
def move_upwards_until(zipper, pred) do
if pred.(zipper) do
{:ok, Zipper.down(zipper) || zipper}
else
case Zipper.up(zipper) do
nil ->
{:ok, zipper}

next ->
move_upwards(next, pred)
end
end
end

@doc """
Removes any nodes matching the provided pattern, until there are no matches left.
"""
Expand Down Expand Up @@ -377,6 +395,8 @@ defmodule Igniter.Code.Common do
Zipper.replace(zipper, code)
end

def extendable_block?(%Zipper{node: node}), do: extendable_block?(node)

def extendable_block?({:__block__, meta, contents}) when is_list(contents) do
!meta[:token] && !meta[:format] && !meta[:delimiter]
end
Expand Down Expand Up @@ -485,24 +505,29 @@ defmodule Igniter.Code.Common do
def maybe_move_to_single_child_block(nil), do: nil

def maybe_move_to_single_child_block(zipper) do
case zipper.node do
{:__block__, _, [_]} = block ->
if extendable_block?(block) do
if single_child_block?(zipper) do
zipper
|> Zipper.down()
|> case do
nil ->
zipper
|> Zipper.down()
|> case do
nil ->
zipper

zipper ->
maybe_move_to_single_child_block(zipper)
end
else
zipper
end
zipper ->
maybe_move_to_single_child_block(zipper)
end
else
zipper
end
end

@spec single_child_block?(Zipper.t()) :: boolean()
def single_child_block?(zipper) do
case zipper.node do
{:__block__, _, [_]} = block ->
extendable_block?(block)

_ ->
zipper
false
end
end

Expand Down Expand Up @@ -604,10 +629,6 @@ defmodule Igniter.Code.Common do
|> maybe_move_to_single_child_block()
end

defp multi_child_block?(zipper) do
node_matches_pattern?(zipper, {:__block__, _, [_, _ | _]})
end

@doc """
Moves rightwards, entering blocks (and exiting them if no match is found), until the provided predicate returns `true`.
Expand All @@ -624,7 +645,7 @@ defmodule Igniter.Code.Common do
pred.(zipper_in_single_child_block) ->
{:ok, zipper_in_single_child_block}

multi_child_block?(zipper) ->
extendable_block?(zipper) && length(elem(zipper.node, 2)) > 1 ->
zipper
|> Zipper.down()
|> case do
Expand Down
19 changes: 18 additions & 1 deletion lib/igniter/code/list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,24 @@ defmodule Igniter.Code.List do
:error

zipper ->
Common.move_right(zipper, pred)
do_move_to_list_item(zipper, pred)
end
end

@doc "Moves to the list item matching the given predicate, assuming you are currently inside the list"
def do_move_to_list_item(zipper, pred) do
if pred.(zipper) do
{:ok, zipper}
else
zipper
|> Zipper.right()
|> case do
nil ->
:error

right ->
do_move_to_list_item(right, pred)
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/igniter/code/tuple.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule Igniter.Code.Tuple do
@doc "Returns `true` if the zipper is at a literal tuple, `false` if not."
@spec tuple?(Zipper.t()) :: boolean()
def tuple?(item) do
item = Igniter.Code.Common.maybe_move_to_single_child_block(item)

case item.node do
{:{}, _, _} -> true
{_, _} -> true
Expand Down
52 changes: 22 additions & 30 deletions lib/igniter/project/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -224,48 +224,40 @@ defmodule Igniter.Project.Application do
end

def skip_after(zipper, opts) do
zipper
|> then(fn zipper ->
if opts[:nested?] do
Zipper.right(zipper)
Igniter.Code.List.do_move_to_list_item(zipper, fn item ->
with {:is_tuple, true} <- {:is_tuple, Igniter.Code.Tuple.tuple?(item)},
{:ok, item} <- Igniter.Code.Tuple.tuple_elem(item, 0),
item <- Igniter.Code.Common.expand_alias(item),
module when is_atom(module) <- alias_to_mod(item.node),
true <- opts[:after].(module) do
true
else
zipper
end
end)
|> case do
nil ->
{:ok, zipper}

right_zipper ->
Igniter.Code.Common.move_right(right_zipper, fn item ->
with {:is_tuple, true} <- {:is_tuple, Igniter.Code.Tuple.tuple?(item)},
{:ok, item} <- Igniter.Code.Tuple.tuple_elem(item, 0),
item <- Igniter.Code.Common.expand_alias(item),
{:is_tuple, false} ->
with item <- Igniter.Code.Common.expand_alias(item),
module when is_atom(module) <- alias_to_mod(item.node),
true <- opts[:after].(module) do
true
else
{:is_tuple, false} ->
with item <- Igniter.Code.Common.expand_alias(item),
module when is_atom(module) <- alias_to_mod(item.node),
true <- opts[:after].(module) do
true
else
_ ->
false
end

_ ->
false
end
end)
end

_ ->
false
end
end)
|> case do
{:ok, zipper} ->
skip_after(zipper, Keyword.put(opts, :nested?, true))
case Zipper.right(zipper) do
nil ->
{:after, zipper}

zipper ->
skip_after(zipper, Keyword.put(opts, :nested?, true))
end

:error ->
if opts[:nested?] do
if Keyword.get(opts, :nested?) do
{:after, zipper}
else
{:before, zipper}
Expand Down
22 changes: 22 additions & 0 deletions test/igniter/project/application_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,28 @@ defmodule Igniter.Project.ApplicationTest do
""")
end

test "using `after: fn _ -> true end` with tuples in the list" do
test_project()
|> Igniter.Project.Application.add_new_child({Foo, a: :b})
|> Igniter.Project.Application.add_new_child(Something)
|> Igniter.Project.Application.add_new_child(SomethingAtTheEnd, after: fn _ -> true end)
|> assert_creates("lib/test/application.ex", """
defmodule Test.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [Something, {Foo, [a: :b]}, SomethingAtTheEnd]
opts = [strategy: :one_for_one, name: Test.Supervisor]
Supervisor.start_link(children, opts)
end
end
""")
end

test "supports taking code as the second argument" do
test_project()
|> Igniter.Project.Application.add_new_child(
Expand Down

0 comments on commit b326269

Please sign in to comment.