API Reference credence v#0.8.1

Copy Markdown

Modules

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 a div/2 or rem/2 result that is already bound to a variable by an anchor statement and then re-computed verbatim later in the same function clause body, and rewrites the later occurrences to read the bound variable instead.

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.

Readability rule: flags piping into binary arithmetic functions like rem/2 or div/2. Piping obscures which argument is the dividend — prefer an explicit call for clarity.

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).