Packages

A collection of opinionated Credo checks aimed at improving code quality and catching common mistakes in Elixir, Oban, and LiveView

Current section

Files

Jump to
jump_credo_checks lib jump credo_checks avoid_function_level_else.ex
Raw

lib/jump/credo_checks/avoid_function_level_else.ex

defmodule Jump.CredoChecks.AvoidFunctionLevelElse do
@moduledoc """
Ensures that `else` is not used at the top-level of `def`/`defp` function bodies.
"""
use Credo.Check,
base_priority: :high,
category: :warning,
param_defaults: [],
explanations: [
check: """
Ensures that `else` is not used at the top-level of `def`/`defp` function bodies.
Elixir allows `else` at the function level because the function body acts
as an implicit `try`. However, this is almost always a mistake from a
botched refactor—e.g., removing a `with` block but leaving its `else`
clause behind. The code will compile without warnings, but behaves unexpectedly
(raising `TryClauseError` if no clause matches).
# ❌ Bad — function-level else
def foo(bar) do
something(bar)
else
{:error, reason} -> handle_error(reason)
end
# Good — use with/else or case instead
def foo(bar) do
with {:ok, result} <- something(bar) do
result
else
{:error, reason} -> handle_error(reason)
end
end
"""
]
@doc false
@impl Credo.Check
def run(%SourceFile{} = source_file, params \\ []) do
issue_meta = IssueMeta.for(source_file, params)
source_file
|> Credo.SourceFile.ast()
|> find_function_level_else(issue_meta)
end
defp find_function_level_else(ast, issue_meta) do
{_ast, issues} = Macro.prewalk(ast, [], &traverse(&1, &2, issue_meta))
issues
end
defp traverse({kind, meta, [_name_args, block_kw]} = node, issues, issue_meta)
when kind in [:def, :defp] and is_list(block_kw) do
if Keyword.has_key?(block_kw, :else) do
issue =
format_issue(
issue_meta,
message:
"Function-level `else` is almost always a mistake. " <>
"Use `with`/`else`, `case`, or `try`/`rescue` instead.",
trigger: "#{kind}",
line_no: meta[:line]
)
{node, [issue | issues]}
else
{node, issues}
end
end
defp traverse(node, issues, _issue_meta), do: {node, issues}
end