API Reference credence v#0.8.0
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.
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).