lib/checks/destructure.ex

defmodule TallariumCredo.Checks.Destructure do
  @moduledoc """
  Encourages the use of Destructure
  """

  use Credo.Check,
    base_priority: :normal,
    category: :consistency,
    explanations: [
      check: """
      Use Destructure and the d(%{}) macro to destructure maps.
      """
    ]

  import Destructure

  @doc false
  @impl true
  def run(source_file, params \\ []) do
    issue_meta = IssueMeta.for(source_file, params)

    initial_state = d(%{issue_meta, issues: []})

    d(%{issues}) = Credo.Code.prewalk(source_file, &traverse/2, initial_state)
    issues
  end

  defp traverse({:%{}, meta, args} = ast, state) do
    if is_destructure_expression?(args) do
      issues = state.issues ++ [issue_for(state.issue_meta, meta[:line])]
      {ast, %{state | issues: issues}}
    else
      {ast, state}
    end
  end

  defp traverse(ast, state) do
    {ast, state}
  end

  defp is_destructure_expression?(args) do
    Keyword.keyword?(args) && Enum.count(args, &is_destructure_variable?/1) > 1
  end

  defp is_destructure_variable?(arg) do
    case arg do
      {key, {key, _, nil}} -> true
      _ -> false
    end
  end

  defp issue_for(issue_meta, line_no) do
    format_issue(issue_meta,
      message: "d(%{}) should be used instead of %{}",
      line_no: line_no
    )
  end
end