lib/recode/task/unused_variable.ex

defmodule Recode.Task.UnusedVariable do
  @moduledoc """
  Prepend unused variables with `_`
  """

  use Recode.Task, correct: true, check: true

  alias Recode.Issue
  alias Rewrite.Source
  alias Sourceror.Zipper

  @impl Recode.Task
  def run(source, opts) do
    {zipper, issues} =
      source
      |> Source.ast()
      |> Zipper.zip()
      |> Zipper.traverse([], fn zipper, issues ->
        prepend_unused_variable_with_underscore(zipper, issues)
      end)

    case opts[:autocorrect] do
      true ->
        Source.update(source, __MODULE__, ast: Zipper.root(zipper))

      false ->
        Source.add_issues(source, issues)
    end
  end

  defp prepend_unused_variable_with_underscore(
         {{:=, _meta, [{var_name, var_meta, _var_value} = var, _other]}, _zipper_meta} = zipper,
         issues
       ) do
    if unused?(zipper, var) do
      zipper = add_underscore(zipper, var)

      issue = Issue.new(__MODULE__, "Unused variable: #{var_name}", var_meta)

      {zipper, [issue | issues]}
    else
      {zipper, issues}
    end
  end

  defp prepend_unused_variable_with_underscore(
         {_ast, _zipper_meta} = zipper,
         issues
       ) do
    {zipper, issues}
  end

  defp add_underscore(zipper, var) do
    zipper
    |> Zipper.find(fn node -> node == var end)
    |> Zipper.update(fn
      {name, meta, value} ->
        {:"_#{name}", meta, value}

      other ->
        other
    end)
  end

  defp unused?(zipper, {name, metadata, _value}) do
    {_zipper, references} =
      Zipper.traverse(zipper, [], fn
        {{^name, ref_metadata, _value} = ref, _zipper_meta} = zipper, references
        when metadata != ref_metadata ->
          {zipper, [ref | references]}

        zipper, references ->
          {zipper, references}
      end)

    references == []
  end
end