API Reference credence v#0.8.0

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.

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