Code.require_file "test_helper.exs", __DIR__

defmodule Macro.ExternalTest do
  defmacro external do
    line = 18
    file = __ENV__.file
    ^line = __CALLER__.line
    ^file = __CALLER__.file
    ^line = Macro.Env.location(__CALLER__)[:line]
    ^file = Macro.Env.location(__CALLER__)[:file]
  end

  defmacro oror(left, right) do
    quote do: unquote(left) || unquote(right)
  end
end

defmodule MacroTest do
  use ExUnit.Case, async: true

  # Changing the lines above will make compilation
  # fail since we are asserting on the caller lines
  import Macro.ExternalTest

  ## Escape

  test "escape handle tuples with size different than two" do
    assert {:{}, [], [:a]} == Macro.escape({:a})
    assert {:{}, [], [:a, :b, :c]} == Macro.escape({:a, :b, :c})
    assert {:{}, [], [:a, {:{}, [], [1, 2, 3]}, :c]} == Macro.escape({:a, {1, 2, 3}, :c})
  end

  test "escape simply returns tuples with size equal to two" do
    assert {:a, :b} == Macro.escape({:a, :b})
  end

  test "escape simply returns any other structure" do
    assert [1, 2, 3] == Macro.escape([1, 2, 3])
  end

  test "escape handles maps" do
    assert {:%{}, [], [a: 1]} = Macro.escape(%{a: 1})
  end

  test "escape handles bitstring" do
    assert {:<<>>, [], [{:::, [], [1, 4]}, ","]} == Macro.escape(<<300::12>>)
  end

  test "escape works recursively" do
    assert [1, {:{}, [], [:a, :b, :c]}, 3] == Macro.escape([1, {:a, :b, :c}, 3])
  end

  test "escape improper" do
    assert [{:|, [], [1, 2]}] == Macro.escape([1 | 2])
    assert [1, {:|, [], [2, 3]}] == Macro.escape([1, 2 | 3])
  end

  test "escape with unquote" do
    contents = quote unquote: false, do: unquote(1)
    assert Macro.escape(contents, unquote: true) == 1

    contents = quote unquote: false, do: unquote(x)
    assert Macro.escape(contents, unquote: true) == {:x, [], MacroTest}
  end

  defp eval_escaped(contents) do
    {eval, []} = Code.eval_quoted(Macro.escape(contents, unquote: true))
    eval
  end

  test "escape with remote unquote" do
    contents = quote unquote: false, do: Kernel.unquote(:is_atom)(:ok)
    assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
  end

  test "escape with nested unquote" do
    contents = quote do
      quote do: unquote(x)
    end
    assert eval_escaped(contents) == quote do: (quote do: unquote(x))
  end

  test "escape with alias or no args remote unquote" do
    contents = quote unquote: false, do: Kernel.unquote(:self)
    assert eval_escaped(contents) == quote(do: Kernel.self())

    contents = quote unquote: false, do: x.unquote(Foo)
    assert eval_escaped(contents) == quote(do: x.unquote(Foo))
  end

  test "escape with splicing" do
    contents = quote unquote: false, do: [1, 2, 3, 4, 5]
    assert Macro.escape(contents, unquote: true) == [1, 2, 3, 4, 5]

    contents = quote unquote: false, do: [1, 2, unquote_splicing([3, 4, 5])]
    assert eval_escaped(contents) == [1, 2, 3, 4, 5]

    contents = quote unquote: false, do: [unquote_splicing([1, 2, 3]), 4, 5]
    assert eval_escaped(contents) == [1, 2, 3, 4, 5]

    contents = quote unquote: false, do: [unquote_splicing([1, 2, 3]), unquote_splicing([4, 5])]
    assert eval_escaped(contents) == [1, 2, 3, 4, 5]

    contents = quote unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]), 5]
    assert eval_escaped(contents) == [1, 2, 3, 4, 5]

    contents = quote unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]) | [5]]
    assert eval_escaped(contents) == [1, 2, 3, 4, 5]
  end

  ## Expansion

  test "expand once" do
    assert {:||, _, _} = Macro.expand_once(quote(do: oror(1, false)), __ENV__)
  end

  test "expand once with raw atom" do
    assert Macro.expand_once(quote(do: :foo), __ENV__) == :foo
  end

  test "expand once with current module" do
    assert Macro.expand_once(quote(do: __MODULE__), __ENV__) == __MODULE__
  end

  test "expand once with main" do
    assert Macro.expand_once(quote(do: Elixir), __ENV__) == Elixir
  end

  test "expand once with simple alias" do
    assert Macro.expand_once(quote(do: Foo), __ENV__) == Foo
  end

  test "expand once with current module plus alias" do
    assert Macro.expand_once(quote(do: __MODULE__.Foo), __ENV__) == __MODULE__.Foo
  end

  test "expand once with main plus alias" do
    assert Macro.expand_once(quote(do: Elixir.Foo), __ENV__) == Foo
  end

  test "expand once with custom alias" do
    alias Foo, as: Bar
    assert Macro.expand_once(quote(do: Bar.Baz), __ENV__) == Foo.Baz
  end

  test "expand once with main plus custom alias" do
    alias Foo, as: Bar, warn: false
    assert Macro.expand_once(quote(do: Elixir.Bar.Baz), __ENV__) == Elixir.Bar.Baz
  end

  test "expand once with op" do
    assert Macro.expand_once(quote(do: Foo.bar.Baz), __ENV__) == (quote do
      Foo.bar.Baz
    end)
  end

  test "expand once with Erlang" do
    assert Macro.expand_once(quote(do: :foo), __ENV__) == :foo
  end

  test "expand once env" do
    env = %{__ENV__ | line: 0}
    assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
    assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
    assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
  end

  defmacro local_macro do
    :local_macro
  end

  test "expand once local macro" do
    assert Macro.expand_once(quote(do: local_macro), __ENV__) == :local_macro
  end

  test "expand once checks vars" do
    local_macro = 1
    assert local_macro == 1
    quote = {:local_macro, [], nil}
    assert Macro.expand_once(quote, __ENV__) == quote
  end

  defp expand_once_and_clean(quoted, env) do
    cleaner = &Keyword.drop(&1, [:counter])
    quoted
    |> Macro.expand_once(env)
    |> Macro.prewalk(&Macro.update_meta(&1, cleaner))
  end

  test "expand once with imported macro" do
    temp_var = {:x, [], Kernel}
    assert expand_once_and_clean(quote(do: 1 || false), __ENV__) == (quote context: Kernel do
      case 1 do
        unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
        unquote(temp_var) -> unquote(temp_var)
      end
    end)
  end

  test "expand once with require macro" do
    temp_var = {:x, [], Kernel}
    assert expand_once_and_clean(quote(do: Kernel.||(1, false)), __ENV__) == (quote context: Kernel do
      case 1 do
        unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
        unquote(temp_var) -> unquote(temp_var)
      end
    end)
  end

  test "expand once with not expandable expression" do
    expr = quote(do: other(1, 2, 3))
    assert Macro.expand_once(expr, __ENV__) == expr
  end

  @foo 1

  test "expand once with module attributes" do
    assert_raise ArgumentError, fn ->
      Macro.expand_once(quote(do: @foo), __ENV__)
    end
  end

  defp expand_and_clean(quoted, env) do
    cleaner = &Keyword.drop(&1, [:counter])
    quoted
    |> Macro.expand(env)
    |> Macro.prewalk(&Macro.update_meta(&1, cleaner))
  end

  test "expand" do
    temp_var = {:x, [], Kernel}
    assert expand_and_clean(quote(do: oror(1, false)), __ENV__) == (quote context: Kernel do
      case 1 do
        unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
        unquote(temp_var) -> unquote(temp_var)
      end
    end)
  end

  test "var" do
    assert Macro.var(:foo, nil) == {:foo, [], nil}
    assert Macro.var(:foo, Other) == {:foo, [], Other}
  end

  ## to_string

  test "var to string" do
    assert Macro.to_string(quote do: foo) == "foo"
  end

  test "local call to string" do
    assert Macro.to_string(quote do: foo(1, 2, 3)) == "foo(1, 2, 3)"
    assert Macro.to_string(quote do: foo([1, 2, 3])) == "foo([1, 2, 3])"
  end

  test "remote call to string" do
    assert Macro.to_string(quote do: foo.bar(1, 2, 3)) == "foo.bar(1, 2, 3)"
    assert Macro.to_string(quote do: foo.bar([1, 2, 3])) == "foo.bar([1, 2, 3])"
  end

  test "atom remote call to string" do
    assert Macro.to_string(quote do: :foo.bar(1, 2, 3)) == ":foo.bar(1, 2, 3)"
  end

  test "remote and fun call to string" do
    assert Macro.to_string(quote do: foo.bar.(1, 2, 3)) == "foo.bar().(1, 2, 3)"
    assert Macro.to_string(quote do: foo.bar.([1, 2, 3])) == "foo.bar().([1, 2, 3])"
  end

  test "atom fun call to string" do
    assert Macro.to_string(quote do: :foo.(1, 2, 3)) == ":foo.(1, 2, 3)"
  end

  test "aliases call to string" do
    assert Macro.to_string(quote do: Foo.Bar.baz(1, 2, 3)) == "Foo.Bar.baz(1, 2, 3)"
    assert Macro.to_string(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
    assert Macro.to_string(quote do: Foo.bar(<<>>, [])) == "Foo.bar(<<>>, [])"
  end

  test "sigil call to string" do
    assert Macro.to_string(quote do: ~r"123") == ~s/~r"123"/
    assert Macro.to_string(quote do: ~r"123"u) == ~s/~r"123"u/
    assert Macro.to_string(quote do: ~r"\n123") == ~s/~r"\\\\n123"/

    assert Macro.to_string(quote do: ~r"1#{two}3") == ~S/~r"1#{two}3"/
    assert Macro.to_string(quote do: ~r"1#{two}3"u) == ~S/~r"1#{two}3"u/

    assert Macro.to_string(quote do: ~R"123") == ~s/~R"123"/
    assert Macro.to_string(quote do: ~R"123"u) == ~s/~R"123"u/
    assert Macro.to_string(quote do: ~R"\n123") == ~s/~R"\\\\n123"/
  end

  test "arrow to string" do
    assert Macro.to_string(quote do: foo(1, (2 -> 3))) == "foo(1, (2 -> 3))"
  end

  test "blocks to string" do
    assert Macro.to_string(quote do: (1; 2; (:foo; :bar); 3)) <> "\n" == """
    (
      1
      2
      (
        :foo
        :bar
      )
      3
    )
    """
  end

  test "if else to string" do
    assert Macro.to_string(quote do: (if foo, do: bar, else: baz)) <> "\n" == """
    if(foo) do
      bar
    else
      baz
    end
    """
  end

  test "case to string" do
    assert Macro.to_string(quote do: (case foo do true -> 0; false -> (1; 2) end)) <> "\n" == """
    case(foo) do
      true ->
        0
      false ->
        1
        2
    end
    """
  end

  test "fn to string" do
    assert Macro.to_string(quote do: (fn -> 1 + 2 end)) == "fn -> 1 + 2 end"
    assert Macro.to_string(quote do: (fn(x) -> x + 1 end)) == "fn x -> x + 1 end"

    assert Macro.to_string(quote do: (fn(x) -> y = x + 1; y end)) <> "\n" == """
    fn x ->
      y = x + 1
      y
    end
    """

    assert Macro.to_string(quote do: (fn(x) -> y = x + 1; y; (z) -> z end)) <> "\n" == """
    fn
      x ->
        y = x + 1
        y
      z ->
        z
    end
    """
  end

  test "range to string" do
    assert Macro.to_string(quote do: unquote(-1..+2)) == "-1..2"
    assert Macro.to_string(quote do: Foo.integer..3) == "Foo.integer()..3"
  end

  test "when to string" do
    assert Macro.to_string(quote do: (() -> x)) == "(() -> x)"
    assert Macro.to_string(quote do: (x when y -> z)) == "(x when y -> z)"
    assert Macro.to_string(quote do: (x, y when z -> w)) == "((x, y) when z -> w)"
    assert Macro.to_string(quote do: ((x, y) when z -> w)) == "((x, y) when z -> w)"
  end

  test "nested to string" do
    assert Macro.to_string(quote do: (defmodule Foo do def foo do 1 + 1 end end)) <> "\n" == """
    defmodule(Foo) do
      def(foo) do
        1 + 1
      end
    end
    """
  end

  test "op precedence to string" do
    assert Macro.to_string(quote do: (1 + 2) * (3 - 4)) == "(1 + 2) * (3 - 4)"
    assert Macro.to_string(quote do: ((1 + 2) * 3) - 4) == "(1 + 2) * 3 - 4"
    assert Macro.to_string(quote do: (1 + 2 + 3) == "(1 + 2 + 3)")
    assert Macro.to_string(quote do: (1 + 2 - 3) == "(1 + 2 - 3)")
  end

  test "capture op to string" do
    assert Macro.to_string(quote do: &foo/0) == "&foo/0"
    assert Macro.to_string(quote do: &Foo.foo/0) == "&Foo.foo/0"
    assert Macro.to_string(quote do: & &1 + &2) == "&(&1 + &2)"
    assert Macro.to_string(quote do: & &1) == "&(&1)"
    assert Macro.to_string(quote do: &(&1).(:x)) == "&(&1.(:x))"
    assert Macro.to_string(quote do: (&(&1)).(:x)) == "(&(&1)).(:x)"
  end

  test "containers to string" do
    assert Macro.to_string(quote do: {})   == "{}"
    assert Macro.to_string(quote do: [])   == "[]"
    assert Macro.to_string(quote do: {1, 2, 3})   == "{1, 2, 3}"
    assert Macro.to_string(quote do: [ 1, 2, 3 ])   == "[1, 2, 3]"
    assert Macro.to_string(quote do: %{})  == "%{}"
    assert Macro.to_string(quote do: %{:foo => :bar})  == "%{foo: :bar}"
    assert Macro.to_string(quote do: %{{1, 2} => [1, 2, 3]})  == "%{{1, 2} => [1, 2, 3]}"
    assert Macro.to_string(quote do: %{map | "a" => "b"})  == "%{map | \"a\" => \"b\"}"
    assert Macro.to_string(quote do: [ 1, 2, 3 ])   == "[1, 2, 3]"
  end

  test "struct to string" do
    assert Macro.to_string(quote do: %Test{})  == "%Test{}"
    assert Macro.to_string(quote do: %Test{foo: 1, bar: 1})  == "%Test{foo: 1, bar: 1}"
    assert Macro.to_string(quote do: %Test{struct | foo: 2})  == "%Test{struct | foo: 2}"
    assert Macro.to_string(quote do: %Test{} + 1)  == "%Test{} + 1"
  end

  test "binary ops to string" do
    assert Macro.to_string(quote do: 1 + 2)   == "1 + 2"
    assert Macro.to_string(quote do: [ 1, 2 | 3 ]) == "[1, 2 | 3]"
    assert Macro.to_string(quote do: [h | t] = [1, 2, 3]) == "[h | t] = [1, 2, 3]"
    assert Macro.to_string(quote do: (x ++ y) ++ z) == "(x ++ y) ++ z"
  end

  test "unary ops to string" do
    assert Macro.to_string(quote do: not 1) == "not 1"
    assert Macro.to_string(quote do: not foo) == "not foo"
    assert Macro.to_string(quote do: -1) == "-1"
    assert Macro.to_string(quote do: !(foo > bar)) == "!(foo > bar)"
    assert Macro.to_string(quote do: @foo(bar)) == "@foo(bar)"
    assert Macro.to_string(quote do: identity(&1)) == "identity(&1)"
  end

  test "access to string" do
    assert Macro.to_string(quote do: a[b]) == "a[b]"
    assert Macro.to_string(quote do: a[1 + 2]) == "a[1 + 2]"
    assert Macro.to_string(quote do: (a || [a: 1])[:a]) == "(a || [a: 1])[:a]"
    assert Macro.to_string(quote do: Map.put(%{}, :a, 1)[:a]) == "Map.put(%{}, :a, 1)[:a]"
  end

  test "kw list to string" do
    assert Macro.to_string(quote do: [a: a, b: b]) == "[a: a, b: b]"
    assert Macro.to_string(quote do: [a: 1, b: 1 + 2]) == "[a: 1, b: 1 + 2]"
    assert Macro.to_string(quote do: ["a.b": 1, c: 1 + 2]) == "[\"a.b\": 1, c: 1 + 2]"
  end

  test "interpolation to string" do
    assert Macro.to_string(quote do: "foo#{bar}baz") == ~S["foo#{bar}baz"]
  end

  test "bit syntax to string" do
    ast = quote(do: <<69 - 4::bits-size(8 - 4)-unit(1), 65>>)
    assert Macro.to_string(ast) == "<<69 - 4::bits-size(8 - 4)-unit(1), 65>>"
    ast = quote(do: << <<65>>, 65>>)
    assert Macro.to_string(ast) == "<<(<<65>>), 65>>"
    ast = quote(do: <<65, <<65>> >>)
    assert Macro.to_string(ast) == "<<65, (<<65>>)>>"
    ast = quote do: for <<a::4 <- <<1, 2>> >>, do: a
    assert Macro.to_string(ast) == "for(<<(a :: 4 <- <<1, 2>>)>>) do\n  a\nend"
  end

  test "charlist to string" do
    assert Macro.to_string(quote do: []) == "[]"
    assert Macro.to_string(quote do: 'abc') == "'abc'"
  end

  test "last arg kw list to string" do
    assert Macro.to_string(quote do: foo([])) == "foo([])"
    assert Macro.to_string(quote do: foo(x: y)) == "foo(x: y)"
    assert Macro.to_string(quote do: foo(x: 1 + 2)) == "foo(x: 1 + 2)"
    assert Macro.to_string(quote do: foo(x: y, p: q)) == "foo(x: y, p: q)"
    assert Macro.to_string(quote do: foo(a, x: y, p: q)) == "foo(a, x: y, p: q)"

    assert Macro.to_string(quote do: {[]}) == "{[]}"
    assert Macro.to_string(quote do: {[a: b]}) == "{[a: b]}"
    assert Macro.to_string(quote do: {x, a: b}) == "{x, [a: b]}"
    assert Macro.to_string(quote do: foo(else: a)) == "foo(else: a)"
    assert Macro.to_string(quote do: foo(catch: a)) == "foo(catch: a)"
  end

  test "to string with fun" do
    assert Macro.to_string(quote(do: foo(1, 2, 3)), fn _, string -> ":#{string}:" end) ==
           ":foo(:1:, :2:, :3:):"

    assert Macro.to_string(quote(do: Bar.foo(1, 2, 3)), fn _, string -> ":#{string}:" end) ==
           "::Bar:.foo(:1:, :2:, :3:):"
  end

  ## validate

  test "validate" do
    ref = make_ref()

    assert Macro.validate(1) == :ok
    assert Macro.validate(1.0) == :ok
    assert Macro.validate(:foo) == :ok
    assert Macro.validate("bar") == :ok
    assert Macro.validate(self()) == :ok
    assert Macro.validate({1, 2}) == :ok
    assert Macro.validate({:foo, [], :baz}) == :ok
    assert Macro.validate({:foo, [], []}) == :ok
    assert Macro.validate([1, 2, 3]) == :ok

    assert Macro.validate(<<0::4>>) == {:error, <<0::4>>}
    assert Macro.validate(ref) == {:error, ref}
    assert Macro.validate({1, ref}) == {:error, ref}
    assert Macro.validate({ref, 2}) == {:error, ref}
    assert Macro.validate([1, ref, 3]) == {:error, ref}
    assert Macro.validate({:foo, [], 0}) == {:error, {:foo, [], 0}}
    assert Macro.validate({:foo, 0, []}) == {:error, {:foo, 0, []}}
  end

  ## decompose_call

  test "decompose call" do
    assert Macro.decompose_call(quote do: foo)        == {:foo, []}
    assert Macro.decompose_call(quote do: foo())      == {:foo, []}
    assert Macro.decompose_call(quote do: foo(1, 2, 3)) == {:foo, [1, 2, 3]}
    assert Macro.decompose_call(quote do: M.N.foo(1, 2, 3)) ==
           {{:__aliases__, [alias: false], [:M, :N]}, :foo, [1, 2, 3]}
    assert Macro.decompose_call(quote do: :foo.foo(1, 2, 3)) ==
           {:foo, :foo, [1, 2, 3]}
    assert Macro.decompose_call(quote do: 1.(1, 2, 3))  == :error
    assert Macro.decompose_call(quote do: "some string")  == :error
  end

  ## env

  test "env stacktrace" do
    env = %{__ENV__ | file: "foo", line: 12}
    assert Macro.Env.stacktrace(env) ==
           [{__MODULE__, :"test env stacktrace", 1, [file: "foo", line: 12]}]

    env = %{env | function: nil}
    assert Macro.Env.stacktrace(env) ==
           [{__MODULE__, :__MODULE__, 0, [file: "foo", line: 12]}]

    env = %{env | module: nil}
    assert Macro.Env.stacktrace(env) ==
           [{:elixir_compiler, :__FILE__, 1, [file: "foo", line: 12]}]
  end

  test "context modules" do
    defmodule Foo.Bar do
      assert __MODULE__ in __ENV__.context_modules
    end
  end

  ## pipe/unpipe

  test "pipe" do
    assert Macro.pipe(1, quote(do: foo), 0) == quote(do: foo(1))
    assert Macro.pipe(1, quote(do: foo(2)), 0) == quote(do: foo(1, 2))
    assert Macro.pipe(1, quote(do: foo), -1) == quote(do: foo(1))
    assert Macro.pipe(2, quote(do: foo(1)), -1) == quote(do: foo(1, 2))

    assert_raise ArgumentError, ~r"cannot pipe 1 into 2", fn ->
      Macro.pipe(1, 2, 0)
    end

    assert_raise ArgumentError, ~r"cannot pipe 1 into {:ok}", fn ->
      Macro.pipe(1, {:ok}, 0)
    end

    assert_raise ArgumentError, ~r"cannot pipe 1 into 1 \+ 1", fn ->
      Macro.pipe(1, quote(do: 1 + 1), 0) == quote(do: foo(1))
    end

    # TODO: restore this test when we drop unary operator support in pipes
    # assert_raise ArgumentError, ~r"cannot pipe 1 into \+1", fn ->
    #   Macro.pipe(1, quote(do: + 1), 0)
    # end

    assert_raise ArgumentError, ~r"cannot pipe Macro into Env", fn ->
      Macro.pipe(Macro, quote(do: Env), 0)
    end

    message = ~r"cannot pipe :foo into an anonymous function without calling"
    assert_raise ArgumentError, message, fn ->
      Macro.pipe(:foo, quote(do: fn x -> x end), 0)
    end
  end

  test "unpipe" do
    assert Macro.unpipe(quote(do: foo)) == quote(do: [{foo, 0}])
    assert Macro.unpipe(quote(do: foo |> bar)) == quote(do: [{foo, 0}, {bar, 0}])
    assert Macro.unpipe(quote(do: foo |> bar |> baz)) == quote(do: [{foo, 0}, {bar, 0}, {baz, 0}])
  end

  ## traverse/pre/postwalk

  test "traverse" do
    assert traverse({:foo, [], nil}) ==
           [{:foo, [], nil}, {:foo, [], nil}]

    assert traverse({:foo, [], [1, 2, 3]}) ==
           [{:foo, [], [1, 2, 3]}, 1, 1, 2, 2, 3, 3, {:foo, [], [1, 2, 3]}]

    assert traverse({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
           [{{:., [], [:foo, :bar]}, [], [1, 2, 3]}, {:., [], [:foo, :bar]}, :foo, :foo, :bar, :bar, {:., [], [:foo, :bar]},
            1, 1, 2, 2, 3, 3, {{:., [], [:foo, :bar]}, [], [1, 2, 3]}]

    assert traverse({[1, 2, 3], [4, 5, 6]}) ==
           [{[1, 2, 3], [4, 5, 6]}, [1, 2, 3], 1, 1, 2, 2, 3, 3, [1, 2, 3],
            [4, 5, 6], 4, 4, 5, 5, 6, 6, [4, 5, 6], {[1, 2, 3], [4, 5, 6]}]
  end

  defp traverse(ast) do
    Macro.traverse(ast, [], &{&1, [&1 | &2]}, &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse
  end

  test "prewalk" do
    assert prewalk({:foo, [], nil}) ==
           [{:foo, [], nil}]

    assert prewalk({:foo, [], [1, 2, 3]}) ==
           [{:foo, [], [1, 2, 3]}, 1, 2, 3]

    assert prewalk({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
           [{{:., [], [:foo, :bar]}, [], [1, 2, 3]}, {:., [], [:foo, :bar]}, :foo, :bar, 1, 2, 3]

    assert prewalk({[1, 2, 3], [4, 5, 6]}) ==
           [{[1, 2, 3], [4, 5, 6]}, [1, 2, 3], 1, 2, 3, [4, 5, 6], 4, 5, 6]
  end

  defp prewalk(ast) do
    Macro.prewalk(ast, [], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse
  end

  test "postwalk" do
    assert postwalk({:foo, [], nil}) ==
           [{:foo, [], nil}]

    assert postwalk({:foo, [], [1, 2, 3]}) ==
           [1, 2, 3, {:foo, [], [1, 2, 3]}]

    assert postwalk({{:., [], [:foo, :bar]}, [], [1, 2, 3]}) ==
           [:foo, :bar, {:., [], [:foo, :bar]}, 1, 2, 3, {{:., [], [:foo, :bar]}, [], [1, 2, 3]}]

    assert postwalk({[1, 2, 3], [4, 5, 6]}) ==
           [1, 2, 3, [1, 2, 3], 4, 5, 6, [4, 5, 6], {[1, 2, 3], [4, 5, 6]}]
  end

  defp postwalk(ast) do
    Macro.postwalk(ast, [], &{&1, [&1 | &2]}) |> elem(1) |> Enum.reverse
  end

  test "underscore" do
    assert Macro.underscore("foo") == "foo"
    assert Macro.underscore("foo_bar") == "foo_bar"
    assert Macro.underscore("Foo") == "foo"
    assert Macro.underscore("FooBar") == "foo_bar"
    assert Macro.underscore("FOOBar") == "foo_bar"
    assert Macro.underscore("FooBAR") == "foo_bar"
    assert Macro.underscore("FoBaZa") == "fo_ba_za"
    assert Macro.underscore("Foo10") == "foo10"
    assert Macro.underscore("10Foo") == "10_foo"
    assert Macro.underscore("FooBar10") == "foo_bar10"
    assert Macro.underscore("Foo10Bar") == "foo10_bar"
    assert Macro.underscore("Foo.Bar") == "foo/bar"
    assert Macro.underscore(Foo.Bar) == "foo/bar"
    assert Macro.underscore("API.V1.User") == "api/v1/user"
    assert Macro.underscore("") == ""
  end

  test "camelize" do
    assert Macro.camelize("Foo") == "Foo"
    assert Macro.camelize("FooBar") == "FooBar"
    assert Macro.camelize("foo") == "Foo"
    assert Macro.camelize("foo_bar") == "FooBar"
    assert Macro.camelize("foo_") == "Foo"
    assert Macro.camelize("_foo") == "Foo"
    assert Macro.camelize("foo10") == "Foo10"
    assert Macro.camelize("_10foo") == "10foo"
    assert Macro.camelize("foo_10") == "Foo10"
    assert Macro.camelize("foo__10") == "Foo10"
    assert Macro.camelize("foo__bar") == "FooBar"
    assert Macro.camelize("foo/bar") == "Foo.Bar"
    assert Macro.camelize("") == ""
  end
end
