Skip to content

Instantly share code, notes, and snippets.

@s3cur3
Created September 21, 2023 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s3cur3/a83b361c40ae21fc00efd81c421cb4b0 to your computer and use it in GitHub Desktop.
Save s3cur3/a83b361c40ae21fc00efd81c421cb4b0 to your computer and use it in GitHub Desktop.
A custom Credo check to disallow using the `unless` macro in Elixir
# To use this check:
# 1. Add it to your project at `/lib/credo/disallow_unless.ex`
# 2. Add it to the list of `requires` in `.credo.exs`:
#
# requires: ["lib/credo/disallow_unless.ex"], # <-- add file here
#
# Add the module to the list of `checks` within `.credo.exs`:
#
# checks: [
# {Credo.DisallowUnless}, # <-- add check here
# ]
#
# If you do not have a `.credo.exs` file yet, use `mix credo gen.config`.
defmodule Credo.DisallowUnless do
use Credo.Check,
base_priority: :high,
explanations: [
check: """
We consider `unless` to be unidiomatic.
Unlike languages like Swift or Ruby, where `unless` (or `guard`) can be used to
signal an early return from a function, Elixir doesn't have a concept of early
returns. Worse, `unless` can give new devs false confidence that it behaves like
a guard. Consider:
unless allowed? do
conn
|> put_status(:forbidden)
|> halt()
end
do_sensitive_thing()
In testing, you'll see that disallowed users correctly get a 403, but the
sensitive action is still performed!
""",
]
@doc false
@impl Credo.Check
def run(%SourceFile{} = source_file, params) do
issue_meta = IssueMeta.for(source_file, params)
Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
end
defp traverse({:@, meta, [{:unless, _, _}]} = ast, issues, issue_meta) do
new_issue = issue_for(issue_meta, meta[:line], "unless")
{ast, issues ++ List.wrap(new_issue)}
end
defp traverse({:unless, meta, _arguments} = ast, issues, issue_meta) do
new_issue = issue_for(issue_meta, meta[:line], "unless")
{ast, issues ++ List.wrap(new_issue)}
end
defp traverse(ast, issues, _issue_meta), do: {ast, issues}
defp issue_for(issue_meta, line_no, trigger) do
format_issue(
issue_meta,
message: "Unless conditions are unidiomatic",
trigger: trigger,
line_no: line_no
)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment