API Reference credence v#0.8.1
Copy MarkdownModules
Credence — Semantic Linter for Elixir.
Safety switches — the promises Credence is allowed to make about your data.
Real-world corpus of popular hex packages and beefy application repos, used
by the over-firing test layer (test/corpus/over_firing_test.exs) and the
mix credence.corpus / mix credence.corpus.fetch maintainer tasks.
Resolves every Pattern finding on a corpus package to a stable identity, used for snapshot-based over-fire regression testing.
Lightweight, parallel-safe progress counters for the corpus test layers.
Keeps Pattern rules out of macro blocks that reinterpret plain Elixir AST.
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.
Performance rule: Detects function guards that check length(x) < 2 (or
length(x) <= 1) and rewrites them into two O(1) pattern-matched clauses.
Fixes calls to guard functions that don't exist in Elixir.
Readability rule: Detects anonymous functions applied with .() inside
a pipeline.
Repairs Map.new/2 whose mapper returns a bare (non-{key, value}) value.
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 any @doc annotation 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.
Detects duplicate function clauses with identical argument patterns.
Detects duplicate @spec annotations before function clauses.
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.count/1 (without a predicate) on a provably-list argument
and rewrites it to length/1.
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.map/2 called with an identity function callback, which is just
a verbose Enum.to_list/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 qualified Kernel.op/2 calls used as steps in a pipeline.
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 List.foldl/3 on a provably-list argument and suggests Enum.reduce/3.
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.
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 single plain variable 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 redundant capture of a local function with immediate call.
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 _ = var bindings in pattern position (function heads, case/fn
clause heads, with/for generators) and simplifies them to just var.
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.
Rewrites String.length(s) == 0 (and != 0, and the flipped operand order)
to the O(1) empty-string check s == "" / s != "" — but ONLY when s is
provably a binary.
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 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.
Removes a dead _-prefixed assignment of a total, provably-typed call.
Removes a dead _var = <pure> binding left behind by the unused-variable fix.
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.
Detects Enum.reduce/3 over a range that filters elements into an
accumulator list followed by Enum.reverse/1, and rewrites it as a
for comprehension with a guard.
Detects Enum.flat_map(enumerable, fn x -> x end) which is an identity
flat-map and can be simplified to Enum.concat(enumerable).
Detects nested if/else blocks where the else branch contains another if
(with its own else), and flattens them into a cond for readability.
Detects length(String.codepoints(string)) when a counts map from
Enum.frequencies/1 on the same codepoints already exists.
Prefer Enum.sort(nums, :desc) |> Enum.take(n) |> Enum.reverse()
over Enum.sort(nums) |> Enum.take(-n).
Detects Enum.reduce/3 calls that count elements matching a predicate
(using 0 as initial accumulator and if pred, do: acc + 1, else: acc as body)
and rewrites them to the more concise Enum.count/2.
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 Enum.group_by/1 with an identity function piped into Enum.map/2
that counts group lengths, then piped into Enum.count/2 with a > 1
predicate — a manual "count duplicate occurrences" pattern that
Enum.frequencies/1 followed by Enum.count/2 handles more idiomatically.
Detects single-argument anonymous functions that simply delegate to a
function call and rewrites them using the more concise function capture
syntax (&Module.function/1 or &function/1).
Detects a redundant case inside a guarded function clause that dispatches
on the same list parameter checked by is_list/1 in the guard, and promotes
the case clauses to pattern-matching function heads.
Readability & correctness rule: Detects the pattern
String.to_charlist(s) |> Enum.uniq() |> Enum.count() |> (&(&1 == String.length(s))).().
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.
Detects the anti-pattern of mapping each hex digit (0–15) to its character
representation via 16 separate function clauses, and rewrites it to a single
clause using String.at/2 on the lookup string "0123456789ABCDEF".
Detects MapSet-based intersection of map keys that can be replaced with
Map.intersect/3 (Elixir 1.14+).
Detects Enum.into(enum, %{}) and suggests Map.new(enum) instead.
Detects Enum.map/2 followed by Map.new/0 and suggests combining them
into a single Map.new/2 call.
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.
Detects two String.codepoints/1 (or String.graphemes/1) results that are
each run through Enum.uniq() |> Enum.sort() and then compared with == for
set-equality checking, and rewrites them to compare MapSet.new/1 directly.
Detects a two-argument anonymous function whose single clause is a nested
if/else chain and rewrites it as a multi-clause pattern-matching fn,
turning each if condition into a when guard.
Detects if cond do false else body end and rewrites it to
if !cond do body else false end.
Detects functions with a ? suffix whose @spec declares a non-boolean
return type and renames them to drop the suffix.
Detects byte_size(var) == 0 in function guards that can be replaced with
pattern matching "" directly in the function head.
Readability rule: Detects recursive function clauses that use an if/else
to conditionally count matches, when the same logic is more idiomatically
expressed via multiple pattern-matching function clauses with a guard.
Detects a sequence of MapSet.new/1 assignments followed by nested
MapSet.intersection/2 calls piped into MapSet.to_list/0, and rewrites
them into a single pipeline using |>.
Detects Enum.reduce_while/3 that carries a boolean flag in the accumulator
solely to signal whether to halt, when the halt value itself could convey the
answer directly.
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.
Rewrites a single-quoted charlist literal 'abc' to the ~c"abc" sigil.
Detects the verbose case String.graphemes(str) idiom for removing the
last character from a string, and rewrites it to the idiomatic
String.slice(str, 0..-2//1).
Detects String.split/2 followed by Enum.filter/2 that removes empty strings,
which can be replaced with the :trim option on String.split/3.
Detects immediately-invoked captures (&body).() in pipelines and
rewrites them to use Kernel.then/2 (Elixir 1.12+), which is the
idiomatic replacement.
Detects Enum.map calls that follow Enum.with_index() in a pipe chain
but use a multi-argument anonymous function instead of destructuring the
{element, index} tuple that with_index/1 produces.
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.
Removes a redundant duplicate catch-all clause — a bare catch-all clause (all arguments bare variables / underscores, no guard) that follows an earlier catch-all clause for the same name and arity and is therefore unreachable at runtime.
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.
Removes bare @doc attributes (no arguments) before def declarations.
Fixes compiler errors about undefined types caused by bare names in @spec.
Repairs the common Python→Elixir translation error where & is written as
the bitwise-AND operator.
Fixes compiler warnings about @doc on private functions.
Fixes compiler errors about the undefined type non_negated_integer/0.
Fixes compiler errors caused by using _ in expression position,
such as a tuple key in a for-comprehension body.
Fixes compiler warnings about outdented heredoc lines by re-indenting all body content to match the closing delimiter.
Makes the implicit step of a descending literal range explicit.
Removes a local defp max/2/defp min/2 that exactly re-implements the
auto-imported Kernel.max/2/Kernel.min/2.
Fixes compiler errors caused by unused type variables in @spec clauses.
The canonical "module attributes / code outside a defmodule" rule.
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.
Detects and fixes unclosed @doc heredocs where the LLM jumps
straight into function code without ever closing the triple-quote block.
Repairs an fn closed with ) and a leftover stray end on the next line.
Fixes div and rem used as infix operators (Python // and % style).
Fixes LLM confusions between the do ... end block form and the , do:
one-liner form of definitions.
Fixes @spec declarations where the :: return type separator is
misplaced inside the argument parentheses.
Repairs source whose trailing do blocks were never closed — most commonly a
defmodule (or def) missing its final end.
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.
Fixes truncated binary close delimiters caused by LLM output truncation.
Repairs a documentation attribute written with a stray do block.
Detects and rewrites Python-style else if (two words) chains to idiomatic cond.
Repairs fn(&1 ...) — the fn keyword mistakenly mixed with capture syntax.
Removes the markdown code-fence lines that wrap generated Elixir source.
Repairs an fn clause that is closed with ) instead of end.
Repairs cond -> written in place of cond do.
Fixes @spec declarations where the :: arrow operator is missing
before the return type.
Behaviour for syntax-level rules that fix code which won't parse.
Mix Tasks
Print the Sourceror.parse_string!/1 AST of a code snippet — the exact tree a
Pattern rule's check/2 pattern-matches against — in two views
Runs Credence.Pattern.analyze/1 over the lib/ source of the pinned popular
hex packages in corpus/ (see Credence.Corpus) and reports what Credence
flags — the over-firing signal.
Fetches the source of the pinned popular hex packages (see Credence.Corpus)
into the gitignored corpus/ directory, using mix hex.package fetch — source
only, no transitive deps, no compilation.
Behavioural novelty check (Tunex 07 §3.7): run a snippet through
Credence.fix/2 + Credence.analyze/2 in the default (helpful)
assumptions: mode (the same mode solve's Validator runs — never :strict,
else a switch-gated pattern reads NOVEL here but COVERED in solve), and decide
whether a real rule already engaged
Classify-time behavioural-equivalence pre-check (Tunex 07 §3.11, 08 T1.4).
Deterministic helper for the tunex rule-gen flow (tunex docs/10).
Generate a correctly-shaped rule plus its test files for a type.
Cosmetic cleanup for agent-authored rule tests (tunex docs/10).