API Reference credence v#0.7.1
Copy MarkdownModules
Credence — Semantic Linter for Elixir.
Safety switches — the promises Credence is allowed to make about your data.
Finds the closest matching defined function for an undefined function call.
Defines the structured issue format for any rule violations.
Pattern phase — detects and fixes anti-patterns in Elixir code.
Performance rule: Detects Enum.count/1 on the result of
String.graphemes/1 (without a predicate).
Performance rule: Detects Enum.count/2 with an equality predicate or
Enum.sum_by/2 with a counting function on the result of String.graphemes/1.
Performance rule: Detects the use of length/1 on the result of
String.graphemes/1.
Fixes calls to guard functions that don't exist in Elixir.
Detects functions where the same positional parameter uses different variable names across clauses.
Readability rule: Detects anonymous functions applied with .() inside
a pipeline.
Correctness rule: Detects documentation/spec module attributes
(@moduledoc, @doc, @spec, @type, @typep) placed at the top level
before a defmodule, and moves them inside that module. A module
attribute outside a module does not compile
Detects capture expressions that are immediately applied with .().
Detects a two-clause case that converts a value to a boolean by matching
a specific pattern against a trailing wildcard, and rewrites it to match?/2.
Readability rule: Detects a single-clause case used inside a pipeline
whose clause head is an irrefutable variable pattern (a bare variable
or _, with no guard). That shape is exactly equivalent to then/1 and
is non-idiomatic.
Detects a single-parameter function clause whose entire body is a case
that dispatches on that parameter, and rewrites it into multi-clause
function heads — the idiomatic Elixir way to express this.
Detects case expr do true -> …; false -> … end that should be if/else.
Detects case on a tuple of variables where every clause's pattern
is the same tuple of the same variables — all dispatch is via guards.
This is a cond in disguise.
Detects Enum.chunk_by(enum, identity_fn) followed by extracting the
first element of each chunk, which is a verbose reimplementation of
Enum.dedup/1.
Readability & performance rule: Detects
String.codepoints(s) |> Enum.reverse() |> IO.iodata_to_binary() (and the
Enum.join/0 reassemble variant, and the nested call forms) which is a manual
reimplementation of String.reverse/1.
Detects cond with exactly two clauses where the second guard is
redundant — either literal true or the logical complement of the
first guard. Both patterns are just an if/else in disguise.
Detects a no-op Map.update(key, literal, & &1) |> Map.drop([key]) (and the
Map.delete/direct-call variants) where the updated value is immediately
thrown away by dropping the same key.
Detects patterns where a list is destructured into individual variables and then immediately reassembled into the same list.
Style rule: Detects @doc false placed before private functions (defp).
Detects two adjacent Enum.filter/2 calls on the same variable
whose predicates are exact logical complements, which can be replaced
with a single Enum.split_with/2 pass.
Performance rule: Detects sorting the same list twice — once ascending and
once descending — when a single sort plus Enum.reverse/1 would suffice.
Performance rule: Detects Enum.with_index/1 passed directly as the
enumerable argument to Enum.reduce/3 (or piped into it).
Detects Map.new() called with no arguments and suggests the empty map
literal %{} instead.
Detects Enum.at/2 called with a negative integer literal index.
Detects Enum.count/1 (without a predicate) on a provably-list argument
and rewrites it to length/1.
Performance rule: Detects Enum.drop(list, -n) where n is a positive
integer literal.
Detects Enum.into/2 and Enum.into/3 targeting an empty MapSet.new()
and suggests MapSet.new/1 or MapSet.new/2 instead.
Performance rule: Detects Enum.take(list, -n) where n is a positive
integer literal.
Flags explicit product-reduction patterns inside Enum.reduce/3.
Flags explicit sum-reduction patterns inside Enum.reduce/3.
Detects Map.update!/3 or Map.update/4 called inside a
case Map.fetch/2 {:ok, val} branch on the same map and key, and
rewrites the redundant update into a Map.put/3.
Detects Enum.filter/2 piped into length/1 or Enum.count/1, which
creates an unnecessary intermediate list.
Performance rule (fixable): Detects Stream.filter/2 piped into Enum.at/2
with a literal 0 index.
Detects Enum.find_value/2 where the result is immediately checked
against nil to provide a default, instead of using the 3-arity
version that accepts a default argument.
Readability & performance rule: Detects the pattern of decomposing a string
into graphemes, only to compare it with its own Enum.reverse.
Detects Enum.group_by(enum, key_fn) |> Map.new(fn {k, group} -> {k, length(group)} end)
which counts occurrences by key — exactly what Enum.frequencies_by/2 does, but with
unnecessary intermediate per-group lists.
Readability rule: Detects guard clauses that compare a parameter to a
literal value with == when pattern matching in the function head would
be clearer and more idiomatic.
Detects hd(var) / tl(var) calls in a function body where var is
already bound to a non-empty list by an anonymous cons pattern
(var = [_ | _] or [_ | _] = var) in the function clause head, and
rewrites them to destructure [head | tail] in the head, using head /
tail directly instead of Kernel.hd/1 / Kernel.tl/1.
Detects Enum._by functions called with an identity function callback,
which can be simplified to the non-_by variant.
Flags if Enum.empty?(var), do: default, else: Enum.min(var) (and Enum.max),
and the negated form if !Enum.empty?(var), do: Enum.min(var), else: default
(also not Enum.empty?(var)).
Detects redundant if/else wrappers around boolean expressions.
Detects is_nil(param) in function guards that can be replaced with
pattern matching nil directly in the function head.
Detects def/defp functions with an is_ prefix, which in Elixir
is reserved for guard-safe functions defined with defguard or Erlang BIFs.
Detects qualified Kernel.op/2 calls used as steps in a pipeline.
Idiomatic rule: fixes variables that shadow Kernel functions.
Detects Keyword.get(list, integer) where the key is an integer literal.
Detects n = length(list) followed by Enum.at(list, n - K) — a Python
list[len(list) - 1] idiom. Elixir's Enum.at/2 natively supports
negative indices, so Enum.at(list, -1) is the idiomatic equivalent.
Detects length(list) comparisons with small integers (0–5) that can be
replaced with O(1) pattern matching.
Refactoring rule: Detects guards that check list length with a literal comparison that can be replaced by a pattern match in the function head.
Performance rule: Detects acc ++ [expr] passed directly in a recursive
tail call, where a matching base case returns the accumulator.
Performance rule: Detects acc ++ [expr] as the return value inside
Enum.reduce/3 when the initial accumulator is [].
Detects a list-literal prefix concatenated onto a recursive call with
++ in the return expression of a recursive function, and rewrites the
++ to a cons
Detects List.delete_at(list, length(list) - 1) — computing the length
of a list just to index its last element for deletion.
Detects List.delete_at(list, length(list) - 1) where length/1 is
computed inline only to build the last index for List.delete_at/2,
and rewrites it to the native negative index List.delete_at(list, -1).
Detects Enum.concat(List.duplicate(list, n)) (or the piped
list |> List.duplicate(n) |> Enum.concat()) and suggests
Enum.flat_map/2 instead.
Detects List.duplicate(string_literal, n) |> Enum.join() (and the nested
Enum.join(List.duplicate(string_literal, n))) and suggests
String.duplicate/2 instead.
Detects usage of List.foldl/3 and List.foldr/3 and suggests
Enum.reduce/3 instead.
Detects List.pop_at(list, 0) |> elem(n) used to extract only the head
(n = 0) or only the rest-after-head (n = 1) of a list, and rewrites
it to the direct List accessor.
Correctness rule: Detects a multi-element literal list used as an
@spec return type — e.g. [pos_integer(), pos_integer()]. This is not
valid Elixir: in a typespec [type] means "a list of type", and a list
with two or more comma-separated element types raises
Kernel.TypespecError at compile time. LLMs translating fixed-size return
values from other languages (a Python tuple, say) emit this shape.
Detects hand-rolled recursive counting functions that should use Enum.count/2.
Detects hand-rolled recursive "find first matching element" functions that
should use Enum.find/3.
Readability rule: Detects manual frequency counting with
Enum.reduce(list, %{}, fn x, acc -> Map.update(acc, KEY, 1, &(&1 + 1)) end).
Detects hand-rolled reimplementations of List.last/1.
Detects hand-rolled recursive list-folding functions that should use
Enum.reduce/3.
Detects if expressions that manually reimplement Kernel.max/2.
Detects if expressions that manually reimplement Kernel.min/2.
Readability & performance rule: Detects the pattern
String.graphemes(s) |> Enum.reverse() |> Enum.join() (and the
IO.iodata_to_binary/1 reassemble variant) which is a manual
reimplementation of String.reverse/1.
Detects Map.keys(var) piped into an Enum function whose callback
also looks up values from the same map variable.
Detects x in Map.keys(m) and x not in Map.keys(m) used for membership
testing, and rewrites to Map.has_key?(m, x) / not Map.has_key?(m, x).
Performance rule: Detects Map.values(map) or Map.keys(map) passed
directly into an Enum function, which creates an unnecessary intermediate
list.
Detects Map.put(map, key, Map.get(map, key, 0) + 1) and rewrites
to Map.update(map, key, 1, &(&1 + 1)).
Detects Enum.map/2 immediately followed by Enum.sum/1, which creates an
unnecessary intermediate list, and fuses it into a single Enum.reduce/3.
Performance rule: Detects calling Map.update/4 (or Map.update!/3) on a
map variable and then immediately reading the same key back with
Map.fetch!/2 or Map.get/2.
Detects Logger macro calls without a require Logger in the enclosing module.
Detects Enum.member?/2 calls nested inside another Enum.* traversal
of the same enumerable and rewrites them to use MapSet.member?/2.
Style & correctness rule: Detects rebinding of parameter names inside
anonymous function (fn) bodies.
When a variable from the parameter destructure is rebound inside the body,
readers lose track of which binding is "live" at each point. This is a
common source of subtle bugs, especially in Enum.reduce callbacks where
the accumulator is destructured.
Detects Regex.replace used as a pipe target and replaces it with
String.replace, which accepts the string as its first argument.
Detects a manual Enum.group_by/2 written as Enum.reduce/3 with an
empty-map accumulator that prepends each element onto a per-key list via
Map.update(acc, key, [elem], &[elem | &1]), immediately followed by
|> Map.new(fn {k, v} -> {k, Enum.reverse(v)} end) to restore insertion
order.
Detects Enum.reduce/3 with an empty-map accumulator %{} and a body
consisting solely of Map.put(acc, key, value), and suggests Map.new/2
instead. Also detects Enum.reduce/3 building a MapSet via
MapSet.put/2 and suggests MapSet.new/1.
Detects Enum.reduce_while/3 where every callback clause returns only
{:cont, value} — never :halt or {:halt, value}. This is equivalent
to plain Enum.reduce/3 and the reduce_while adds unnecessary ceremony.
Detects a variable (or tuple/list of plain variables) being assigned and immediately returned as the last two statements of a block.
Detects string literals needlessly wrapped in <<>> binary syntax.
Detects case expressions where a nil clause and a trailing wildcard
clause have identical bodies, and the intermediate guarded clause can be
extended with not is_nil/1 to absorb the nil case.
Detects redundant comparison guards in multi-clause functions.
Detects Enum.dedup/1 or Enum.uniq/1 used before MapSet.new/1,
making the deduplication a redundant intermediate step.
Readability rule: Detects Enum.join("") and Enum.map_join("", mapper)
where the empty-string separator is passed explicitly.
Detects multiple traversals of the same list that could be merged into a single pass.
Detects guard clauses that are logically redundant because a preceding clause of the same function already handles the complementary case.
Detects Enum.to_list/1 used before a function that already accepts
any enumerable, making the conversion a redundant intermediate step.
Detects inefficient patterns where a full sort is performed only to
retrieve the minimum or maximum element via Enum.at(0).
Performance rule (fixable): Detects Enum.sort |> Enum.at(index) where the
index is a literal 0 or -1 and the sort direction can be statically
determined. These are replaced with Enum.min/Enum.max, avoiding the
O(n log n) sort entirely.
Performance & readability rule: Detects the pattern of calling Enum.sort/1,2
followed by Enum.reverse/1 on the result, where the sort direction can be
statically determined.
Performance rule: Detects string concatenation with <> inside
Enum.reduce calls with an empty string initial accumulator that can
be automatically fixed.
Performance rule: Detects String.length(x) == 1 (or != 1) used to
validate that a string is a single character.
String.length/1 traverses the entire string to count grapheme clusters,
making it O(n). For a simple single-character check, pattern matching on
the result of String.graphemes/1 is more expressive and idiomatic.
This rule automatically rewrites the comparison to use match?/2 with
String.graphemes/1, which produces a clean boolean result that works
in any expression context.
Detects Enum.take_while/2 piped into length/1 or Enum.count/1,
which materializes an intermediate list only to count it.
Detects if/else expressions where both branches return the same value.
Detects @doc, @moduledoc, and @typedoc strings that contain a
trailing \n escape sequence.
Detects defp functions whose entire body is a direct call to a standard
library function with the same arguments passed through unchanged, and
inlines the standard library call at every call site, removing the wrapper.
Detects function names that use a leading underscore to indicate privacy, a convention borrowed from Python that is non-idiomatic in Elixir.
Detects Enum.uniq/1 piped into length/1 or Enum.count/1, which
creates an unnecessary intermediate list only to count its size.
Detects unless ... do ... else ... end — a style guide violation.
Detects function clauses where every argument is a wildcard and the
body does nothing but raise.
Detects Enum.zip/2 followed by Enum.map/2 that destructures the
resulting 2-tuples, which can be replaced by Enum.zip_with/3.
Fixes function clauses that are not grouped together.
Prefer Enum.sort(nums, :desc) |> Enum.take(n) |> Enum.reverse()
over Enum.sort(nums) |> Enum.take(-n).
Performance rule: Flags Enum.reverse(list) ++ other_list.
Enum.reverse/1 creates a new list, and ++ traverses that new list
entirely to append the second. This is a 2-pass operation.
Using Enum.reverse/2 performs both actions in a single optimized pass.
Readability rule: flags Enum.drop/2 followed by Enum.take/2 and rewrites it
to Enum.slice/3.
Performance rule: collapses two adjacent assignments that take and drop the
same prefix of the same list variable into a single Enum.split/2, which
produces both results in one pass.
Replaces float-coercion arithmetic tricks with explicit :erlang.float/1.
Detects function clauses whose body is a single if/else with a
guard-eligible condition. In idiomatic Elixir, prefer multi-clause
functions with guards over wrapping the entire body in if/else.
Detects @doc, @moduledoc, and @typedoc strings that contain
escaped newlines (\n) and should use heredoc syntax instead.
Performance and idiomatic code rule: detects an if/unless guard
using Map.has_key?/2 that only calls Map.put/3 when the key is
absent, and suggests Map.put_new/3 instead.
Rewrites a case over Regex.run/2 that only tests whether the regex
matched — never the captured groups — into the idiomatic boolean form built
on Regex.match?/2.
Detects is_list/1 guards on a cons-pattern tail variable
([head | tail] when is_list(tail)) that are redundant under the
proper_lists promise.
Behaviour for pattern-level rules that detect and auto-fix anti-patterns.
Detects the common n-gram generation pipeline that converts a string
to graphemes, creates sliding window chunks of step 1, and joins them
back into strings. This pattern is automatically fixed by replacing it
with String.slice/3 which avoids intermediate list allocations.
Detects Enum.map/2 chained into Enum.join/1 or Enum.join/2,
and suggests Enum.map_join/3 instead.
Shared utilities used by all three Credence phases (Syntax, Semantic, Pattern).
The one place that turns a rule's name into every derived form — snake, Pascal, rule atom, rule module, and the conventional paths/modules of its test files.
Pure templates for a new rule and its test files. files/2 returns
[{path, content}] for a type — no disk I/O — so it is testable in memory and
reused by both the Mix task (mix credence.gen.rule) that writes the files and
the pin (Credence.GeneratorMetaTest) that proves the output passes every
structural gate.
Semantic phase — fixes compiler warnings and errors.
Fixes test modules that are missing use ExUnit.Case.
Fixes compiler warnings about outdented heredoc lines by re-indenting all body content to match the closing delimiter.
Behaviour for semantic-level rules that fix compiler warnings.
Fixes compiler diagnostics about undefined, private, or deprecated functions.
Fixes the common LLM hallucination where String.alphanumeric?/1 is called
as if it were a real Elixir standard library function. It is not.
Fixes compiler warnings about unused variables by adding _ prefix.
Fixes compiler warnings about underscored variables that are referenced after being set, by removing the leading underscore.
Syntax phase — fixes code that won't parse.
Fixes div and rem used as infix operators (Python // and % style).
Fixes @spec declarations where the :: return type separator is
misplaced inside the argument parentheses.
Replaces Python's augmented assignment operators (+=, -=, *=, /=)
with Elixir's rebinding syntax.
Replaces Python's // floor-division operator with Elixir's div/2.
Replaces Python's % modulo operator with Elixir's rem/2.
Fixes Python-style scientific notation that is invalid in Elixir.
Removes non-Elixir access modifier keywords prepended to def/defp/defmacro/defmacrop.
Behaviour for syntax-level rules that fix code which won't parse.
Mix Tasks
Generate a correctly-shaped rule plus its test files for a type.