lib/bylaw/credo/check/elixir/no_then.ex

defmodule Bylaw.Credo.Check.Elixir.NoThen do
  @moduledoc """
  Prefer explicit control flow over `then/2`.

  ## Examples

  Avoid:

        value
        |> transform()
        |> then(&{:ok, &1})

        then(value, &{:ok, &1})
  Prefer:

        value =
          value
          |> transform()

        {:ok, value}

  ## Notes

  This check uses static AST analysis, so it favors clear source-level patterns over runtime behavior.

  ## Options

  This check has no check-specific options. Configure it with an empty option list.

  ## Usage

  Add this check to Credo's `checks:` list in `.credo.exs`:

  ```elixir
  %{
    configs: [
      %{
        name: "default",
        checks: [
          {Bylaw.Credo.Check.Elixir.NoThen, []}
        ]
      }
    ]
  }
  ```
  """

  use Credo.Check,
    base_priority: :high,
    category: :readability,
    explanations: [
      check: @moduledoc
    ]

  @doc false
  @impl Credo.Check
  def run(source_file, params \\ []) do
    ctx = Context.build(source_file, params, __MODULE__)
    Credo.Code.prewalk(source_file, &walk/2, ctx).issues
  end

  defp walk({:then, meta, [_value, _fun]} = ast, ctx) do
    {ast, put_issue(ctx, issue_for(ctx, meta, "then"))}
  end

  defp walk(
         {{:., meta, [{:__aliases__, _aliases_meta, [:Kernel]}, :then]}, _call_meta,
          [_value, _fun]} = ast,
         ctx
       ) do
    {ast, put_issue(ctx, issue_for(ctx, meta, "Kernel.then"))}
  end

  defp walk({:|>, _pipe_meta, [_value, {:then, meta, [_fun]}]} = ast, ctx) do
    {ast, put_issue(ctx, issue_for(ctx, meta, "then"))}
  end

  defp walk(
         {:|>, _pipe_meta,
          [
            _value,
            {{:., meta, [{:__aliases__, _aliases_meta, [:Kernel]}, :then]}, _call_meta, [_fun]}
          ]} =
           ast,
         ctx
       ) do
    {ast, put_issue(ctx, issue_for(ctx, meta, "Kernel.then"))}
  end

  defp walk(ast, ctx), do: {ast, ctx}

  defp issue_for(ctx, meta, trigger) do
    format_issue(
      ctx,
      message: "Avoid `#{trigger}/2`; prefer explicit control flow instead of `then`.",
      trigger: trigger,
      line_no: meta[:line]
    )
  end
end