Packages
Custom Credo checks for common Elixir/Phoenix anti-patterns and CWE Top 25 security vulnerabilities. Provides 40 comprehensive checks covering code quality, error handling, performance, change risk, telemetry, and security (SQL injection, XSS, path traversal, CSRF, SSRF, hardcoded credentials, etc).
Current section
Files
Jump to
Current section
Files
lib/credo/check/warning/callback_hell.ex
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