lib/checks/ecto_only_macro_syntax.ex

defmodule TallariumCredo.Checks.EctoOnlyMacroSyntax do
  @moduledoc """
  Permit only the use of the macro syntax from the Ecto.Query module
  """

  use Credo.Check,
    base_priority: :high,
    category: :consistency,
    explanations: [
      check: """
      Ecto.Query provides two syntaxes for building queries: the macro syntax and
      the keyword syntax. The macro syntax is preferred as it is more flexible.
      """
    ]

  import Destructure

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

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

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

  defp traverse(
         {:import, _meta, [{:__aliases__, _aliases_meta, [:Ecto, :Query]}]} = ast,
         state
       ) do
    {ast, %{state | has_ecto_query_import?: true}}
  end

  defp traverse(
         {:from, meta, [{:in, _meta, _children} | _]} = ast,
         d(%{has_ecto_query_import?}) = state
       )
       when has_ecto_query_import? do
    issues = state.issues ++ [issue_for(state.issue_meta, meta[:line])]
    {ast, %{state | issues: issues}}
  end

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

  defp issue_for(issue_meta, line_no) do
    format_issue(issue_meta,
      message: "Only the macro syntax from Ecto.Query is permitted",
      line_no: line_no
    )
  end
end