lib/ex_factor/neighbors.ex

defmodule ExFactor.Neighbors do
  @moduledoc """
  Documentation for `ExFactor.Neighbors`.
  """
  @excluded_neighbors ~w(alias use import require)a

  @doc """
  Walk the AST and find all the elements before the target function and the previous function.
  Find all the instances of the target function. Return after evaluating all the block-level
  AST elements. Ignore certain elements, such as :alias.
  """
  def walk(block, func_name, arity \\ :unmatched)

  def walk(block, func_name, arity) when is_binary(func_name) do
    # func_name_atom = String.to_atom(func_name)
    walk(block, String.to_atom(func_name), arity)
  end

  def walk(block, func_name, arity) do
    block
    |> Enum.reduce({[], []}, fn el, acc ->
      eval_elem(el, acc, func_name, arity)
    end)
    |> elem(1)
  end

  defp eval_elem({type, _, [{name, _, args} | _]} = el, {pending, acc}, name, arity)
       when type in [:def, :defp] do
    cond do
      arity == :unmatched ->
        {[], acc ++ pending ++ [el]}

      length(args) == arity ->
        {[], acc ++ pending ++ [el]}

      true ->
        {[], acc}
    end
  end

  defp eval_elem({type, _, _}, {_pending, acc}, _name, _arity) when type in [:def, :defp] do
    {[], acc}
  end

  defp eval_elem({type, _, _}, {pending, acc}, _name, _artiy) when type in @excluded_neighbors do
    {pending, acc}
  end

  defp eval_elem(el, {pending, acc}, _name, _artiy) do
    {pending ++ [el], acc}
  end
end