defmodule OeditusCredo.Check.Warning.CallbackHell do use Credo.Check, base_priority: :normal, category: :warning, explanations: [ check: """ Deeply nested case statements create callback hell and reduce readability. Use `with` statements or pipe operators for better flow control. Bad: case get_user(id) do {:ok, user} -> case get_account(user) do {:ok, account} -> case process(account) do {:ok, result} -> result end end end Good: with {:ok, user} <- get_user(id), {:ok, account} <- get_account(user), {:ok, result} <- process(account) do result end """, params: [max_nesting: "Maximum allowed case statement nesting (default: 2)"] ] @doc false @impl true def run(%SourceFile{} = source_file, params) do issue_meta = IssueMeta.for(source_file, params) max_nesting = Params.get(params, :max_nesting, __MODULE__) source_file |> Credo.Code.prewalk(&traverse(&1, &2, {issue_meta, max_nesting})) end @doc false @impl true def param_defaults do [max_nesting: 2] end defp traverse({:case, meta, _} = ast, issues, {issue_meta, max_nesting}) do nesting_level = count_case_nesting(ast) issues = if nesting_level > max_nesting do [issue_for(issue_meta, meta[:line], nesting_level) | issues] else issues end {ast, issues} end defp traverse(ast, issues, _state) do {ast, issues} end defp count_case_nesting({:case, _, [_expr, [do: clauses]]}) when is_list(clauses) do max_nested = clauses |> Enum.map(&clause_case_nesting/1) |> Enum.max(fn -> 0 end) 1 + max_nested end defp count_case_nesting(_), do: 0 defp clause_case_nesting({:->, _, [_pattern, body]}) do count_nested_cases(body) end defp clause_case_nesting(_), do: 0 defp count_nested_cases({:__block__, _, statements}) when is_list(statements) do statements |> Enum.map(&count_case_nesting/1) |> Enum.max(fn -> 0 end) end defp count_nested_cases({:case, _, _} = ast), do: count_case_nesting(ast) defp count_nested_cases(_), do: 0 defp issue_for(issue_meta, line_no, level) do format_issue( issue_meta, message: "#{level} levels of nested case statements - consider using `with`", trigger: "case", line_no: line_no ) end end