API Reference credence v#0.7.0

Copy Markdown

Modules

Credence — Semantic Linter for Elixir.

Safety switches — the promises Credence is allowed to make about your data.

Finds the closest matching defined function for an undefined function call.

Defines the structured issue format for any rule violations.

Pattern phase — detects and fixes anti-patterns in Elixir code.

Performance rule: Detects Enum.count/1 on the result of String.graphemes/1 (without a predicate).

Performance rule: Detects Enum.count/2 with an equality predicate or Enum.sum_by/2 with a counting function on the result of String.graphemes/1.

Performance rule: Detects the use of length/1 on the result of String.graphemes/1.

Fixes calls to guard functions that don't exist in Elixir.

Detects functions where the same positional parameter uses different variable names across clauses.

Readability rule: Detects anonymous functions applied with .() inside a pipeline.

Correctness rule: Detects documentation/spec module attributes (@moduledoc, @doc, @spec, @type, @typep) placed at the top level before a defmodule, and moves them inside that module. A module attribute outside a module does not compile

Detects capture expressions that are immediately applied with .().

Detects a two-clause case that converts a value to a boolean by matching a specific pattern against a trailing wildcard, and rewrites it to match?/2.

Readability rule: Detects a single-clause case used inside a pipeline whose clause head is an irrefutable variable pattern (a bare variable or _, with no guard). That shape is exactly equivalent to then/1 and is non-idiomatic.

Detects a single-parameter function clause whose entire body is a case that dispatches on that parameter, and rewrites it into multi-clause function heads — the idiomatic Elixir way to express this.

Detects case expr do true -> …; false -> … end that should be if/else.

Detects case on a tuple of variables where every clause's pattern is the same tuple of the same variables — all dispatch is via guards. This is a cond in disguise.

Detects Enum.chunk_by(enum, identity_fn) followed by extracting the first element of each chunk, which is a verbose reimplementation of Enum.dedup/1.

Readability & performance rule: Detects String.codepoints(s) |> Enum.reverse() |> IO.iodata_to_binary() (and the Enum.join/0 reassemble variant, and the nested call forms) which is a manual reimplementation of String.reverse/1.

Detects cond with exactly two clauses where the second guard is redundant — either literal true or the logical complement of the first guard. Both patterns are just an if/else in disguise.

Detects a no-op Map.update(key, literal, & &1) |> Map.drop([key]) (and the Map.delete/direct-call variants) where the updated value is immediately thrown away by dropping the same key.

Detects patterns where a list is destructured into individual variables and then immediately reassembled into the same list.

Style rule: Detects @doc false placed before private functions (defp).

Detects two adjacent Enum.filter/2 calls on the same variable whose predicates are exact logical complements, which can be replaced with a single Enum.split_with/2 pass.

Performance rule: Detects sorting the same list twice — once ascending and once descending — when a single sort plus Enum.reverse/1 would suffice.

Performance rule: Detects Enum.with_index/1 passed directly as the enumerable argument to Enum.reduce/3 (or piped into it).

Detects Map.new() called with no arguments and suggests the empty map literal %{} instead.

Detects Enum.at/2 called with a negative integer literal index.

Detects Enum.count/1 (without a predicate) on a provably-list argument and rewrites it to length/1.

Performance rule: Detects Enum.drop(list, -n) where n is a positive integer literal.

Detects Enum.into/2 and Enum.into/3 targeting an empty MapSet.new() and suggests MapSet.new/1 or MapSet.new/2 instead.

Performance rule: Detects Enum.take(list, -n) where n is a positive integer literal.

Flags explicit product-reduction patterns inside Enum.reduce/3.

Flags explicit sum-reduction patterns inside Enum.reduce/3.

Detects Map.update!/3 or Map.update/4 called inside a case Map.fetch/2 {:ok, val} branch on the same map and key, and rewrites the redundant update into a Map.put/3.

Detects Enum.filter/2 piped into length/1 or Enum.count/1, which creates an unnecessary intermediate list.

Performance rule (fixable): Detects Stream.filter/2 piped into Enum.at/2 with a literal 0 index.

Detects Enum.find_value/2 where the result is immediately checked against nil to provide a default, instead of using the 3-arity version that accepts a default argument.

Readability & performance rule: Detects the pattern of decomposing a string into graphemes, only to compare it with its own Enum.reverse.

Detects Enum.group_by(enum, key_fn) |> Map.new(fn {k, group} -> {k, length(group)} end) which counts occurrences by key — exactly what Enum.frequencies_by/2 does, but with unnecessary intermediate per-group lists.

Readability rule: Detects guard clauses that compare a parameter to a literal value with == when pattern matching in the function head would be clearer and more idiomatic.

Detects hd(var) / tl(var) calls in a function body where var is already bound to a non-empty list by an anonymous cons pattern (var = [_ | _] or [_ | _] = var) in the function clause head, and rewrites them to destructure [head | tail] in the head, using head / tail directly instead of Kernel.hd/1 / Kernel.tl/1.

Detects Enum._by functions called with an identity function callback, which can be simplified to the non-_by variant.

Flags if Enum.empty?(var), do: default, else: Enum.min(var) (and Enum.max), and the negated form if !Enum.empty?(var), do: Enum.min(var), else: default (also not Enum.empty?(var)).

Detects redundant if/else wrappers around boolean expressions.

Detects is_nil(param) in function guards that can be replaced with pattern matching nil directly in the function head.

Detects def/defp functions with an is_ prefix, which in Elixir is reserved for guard-safe functions defined with defguard or Erlang BIFs.

Detects qualified Kernel.op/2 calls used as steps in a pipeline.

Idiomatic rule: fixes variables that shadow Kernel functions.

Detects Keyword.get(list, integer) where the key is an integer literal.

Detects n = length(list) followed by Enum.at(list, n - K) — a Python list[len(list) - 1] idiom. Elixir's Enum.at/2 natively supports negative indices, so Enum.at(list, -1) is the idiomatic equivalent.

Detects length(list) comparisons with small integers (0–5) that can be replaced with O(1) pattern matching.

Refactoring rule: Detects guards that check list length with a literal comparison that can be replaced by a pattern match in the function head.

Performance rule: Detects acc ++ [expr] passed directly in a recursive tail call, where a matching base case returns the accumulator.

Performance rule: Detects acc ++ [expr] as the return value inside Enum.reduce/3 when the initial accumulator is [].

Detects a list-literal prefix concatenated onto a recursive call with ++ in the return expression of a recursive function, and rewrites the ++ to a cons

Detects List.delete_at(list, length(list) - 1) — computing the length of a list just to index its last element for deletion.

Detects List.delete_at(list, length(list) - 1) where length/1 is computed inline only to build the last index for List.delete_at/2, and rewrites it to the native negative index List.delete_at(list, -1).

Detects Enum.concat(List.duplicate(list, n)) (or the piped list |> List.duplicate(n) |> Enum.concat()) and suggests Enum.flat_map/2 instead.

Detects List.duplicate(string_literal, n) |> Enum.join() (and the nested Enum.join(List.duplicate(string_literal, n))) and suggests String.duplicate/2 instead.

Detects usage of List.foldl/3 and List.foldr/3 and suggests Enum.reduce/3 instead.

Detects List.pop_at(list, 0) |> elem(n) used to extract only the head (n = 0) or only the rest-after-head (n = 1) of a list, and rewrites it to the direct List accessor.

Correctness rule: Detects a multi-element literal list used as an @spec return type — e.g. [pos_integer(), pos_integer()]. This is not valid Elixir: in a typespec [type] means "a list of type", and a list with two or more comma-separated element types raises Kernel.TypespecError at compile time. LLMs translating fixed-size return values from other languages (a Python tuple, say) emit this shape.

Detects hand-rolled recursive counting functions that should use Enum.count/2.

Detects hand-rolled recursive "find first matching element" functions that should use Enum.find/3.

Readability rule: Detects manual frequency counting with Enum.reduce(list, %{}, fn x, acc -> Map.update(acc, KEY, 1, &(&1 + 1)) end).

Detects hand-rolled reimplementations of List.last/1.

Detects hand-rolled recursive list-folding functions that should use Enum.reduce/3.

Detects if expressions that manually reimplement Kernel.max/2.

Detects if expressions that manually reimplement Kernel.min/2.

Readability & performance rule: Detects the pattern String.graphemes(s) |> Enum.reverse() |> Enum.join() (and the IO.iodata_to_binary/1 reassemble variant) which is a manual reimplementation of String.reverse/1.

Detects Map.keys(var) piped into an Enum function whose callback also looks up values from the same map variable.

Detects x in Map.keys(m) and x not in Map.keys(m) used for membership testing, and rewrites to Map.has_key?(m, x) / not Map.has_key?(m, x).

Performance rule: Detects Map.values(map) or Map.keys(map) passed directly into an Enum function, which creates an unnecessary intermediate list.

Detects Map.put(map, key, Map.get(map, key, 0) + 1) and rewrites to Map.update(map, key, 1, &(&1 + 1)).

Detects Enum.map/2 immediately followed by Enum.sum/1, which creates an unnecessary intermediate list, and fuses it into a single Enum.reduce/3.

Performance rule: Detects calling Map.update/4 (or Map.update!/3) on a map variable and then immediately reading the same key back with Map.fetch!/2 or Map.get/2.

Detects Logger macro calls without a require Logger in the enclosing module.

Detects Enum.member?/2 calls nested inside another Enum.* traversal of the same enumerable and rewrites them to use MapSet.member?/2.

Style & correctness rule: Detects rebinding of parameter names inside anonymous function (fn) bodies. When a variable from the parameter destructure is rebound inside the body, readers lose track of which binding is "live" at each point. This is a common source of subtle bugs, especially in Enum.reduce callbacks where the accumulator is destructured.

Detects Regex.replace used as a pipe target and replaces it with String.replace, which accepts the string as its first argument.

Detects a manual Enum.group_by/2 written as Enum.reduce/3 with an empty-map accumulator that prepends each element onto a per-key list via Map.update(acc, key, [elem], &[elem | &1]), immediately followed by |> Map.new(fn {k, v} -> {k, Enum.reverse(v)} end) to restore insertion order.

Detects Enum.reduce/3 with an empty-map accumulator %{} and a body consisting solely of Map.put(acc, key, value), and suggests Map.new/2 instead. Also detects Enum.reduce/3 building a MapSet via MapSet.put/2 and suggests MapSet.new/1.

Detects Enum.reduce_while/3 where every callback clause returns only {:cont, value} — never :halt or {:halt, value}. This is equivalent to plain Enum.reduce/3 and the reduce_while adds unnecessary ceremony.

Detects a variable (or tuple/list of plain variables) being assigned and immediately returned as the last two statements of a block.

Detects string literals needlessly wrapped in <<>> binary syntax.

Detects case expressions where a nil clause and a trailing wildcard clause have identical bodies, and the intermediate guarded clause can be extended with not is_nil/1 to absorb the nil case.

Detects redundant comparison guards in multi-clause functions.

Detects Enum.dedup/1 or Enum.uniq/1 used before MapSet.new/1, making the deduplication a redundant intermediate step.

Readability rule: Detects Enum.join("") and Enum.map_join("", mapper) where the empty-string separator is passed explicitly.

Detects multiple traversals of the same list that could be merged into a single pass.

Detects guard clauses that are logically redundant because a preceding clause of the same function already handles the complementary case.

Detects Enum.to_list/1 used before a function that already accepts any enumerable, making the conversion a redundant intermediate step.

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

Detects Enum.take_while/2 piped into length/1 or Enum.count/1, which materializes an intermediate list only to count it.

Detects if/else expressions where both branches return the same value.

Detects @doc, @moduledoc, and @typedoc strings that contain a trailing \n escape sequence.

Detects defp functions whose entire body is a direct call to a standard library function with the same arguments passed through unchanged, and inlines the standard library call at every call site, removing the wrapper.

Detects function names that use a leading underscore to indicate privacy, a convention borrowed from Python that is non-idiomatic in Elixir.

Detects Enum.uniq/1 piped into length/1 or Enum.count/1, which creates an unnecessary intermediate list only to count its size.

Detects unless ... do ... else ... end — a style guide violation.

Detects function clauses where every argument is a wildcard and the body does nothing but raise.

Detects Enum.zip/2 followed by Enum.map/2 that destructures the resulting 2-tuples, which can be replaced by Enum.zip_with/3.

Fixes function clauses that are not grouped together.

Prefer Enum.sort(nums, :desc) |> Enum.take(n) |> Enum.reverse() over Enum.sort(nums) |> Enum.take(-n).

Performance rule: Flags Enum.reverse(list) ++ other_list. Enum.reverse/1 creates a new list, and ++ traverses that new list entirely to append the second. This is a 2-pass operation. Using Enum.reverse/2 performs both actions in a single optimized pass.

Readability rule: flags Enum.drop/2 followed by Enum.take/2 and rewrites it to Enum.slice/3.

Performance rule: collapses two adjacent assignments that take and drop the same prefix of the same list variable into a single Enum.split/2, which produces both results in one pass.

Replaces float-coercion arithmetic tricks with explicit :erlang.float/1.

Detects function clauses whose body is a single if/else with a guard-eligible condition. In idiomatic Elixir, prefer multi-clause functions with guards over wrapping the entire body in if/else.

Detects @doc, @moduledoc, and @typedoc strings that contain escaped newlines (\n) and should use heredoc syntax instead.

Performance and idiomatic code rule: detects an if/unless guard using Map.has_key?/2 that only calls Map.put/3 when the key is absent, and suggests Map.put_new/3 instead.

Rewrites a case over Regex.run/2 that only tests whether the regex matched — never the captured groups — into the idiomatic boolean form built on Regex.match?/2.

Detects is_list/1 guards on a cons-pattern tail variable ([head | tail] when is_list(tail)) that are redundant under the proper_lists promise.

Behaviour for pattern-level rules that detect and auto-fix anti-patterns.

Detects the common n-gram generation pipeline that converts a string to graphemes, creates sliding window chunks of step 1, and joins them back into strings. This pattern is automatically fixed by replacing it with String.slice/3 which avoids intermediate list allocations.

Detects Enum.map/2 chained into Enum.join/1 or Enum.join/2, and suggests Enum.map_join/3 instead.

Shared utilities used by all three Credence phases (Syntax, Semantic, Pattern).

The one place that turns a rule's name into every derived form — snake, Pascal, rule atom, rule module, and the conventional paths/modules of its test files.

Pure templates for a new rule and its test files. files/2 returns [{path, content}] for a type — no disk I/O — so it is testable in memory and reused by both the Mix task (mix credence.gen.rule) that writes the files and the pin (Credence.GeneratorMetaTest) that proves the output passes every structural gate.

Semantic phase — fixes compiler warnings and errors.

Fixes test modules that are missing use ExUnit.Case.

Fixes compiler warnings about outdented heredoc lines by re-indenting all body content to match the closing delimiter.

Behaviour for semantic-level rules that fix compiler warnings.

Fixes compiler diagnostics about undefined, private, or deprecated functions.

Fixes the common LLM hallucination where String.alphanumeric?/1 is called as if it were a real Elixir standard library function. It is not.

Fixes compiler warnings about unused variables by adding _ prefix.

Fixes compiler warnings about underscored variables that are referenced after being set, by removing the leading underscore.

Syntax phase — fixes code that won't parse.

Fixes div and rem used as infix operators (Python // and % style).

Fixes @spec declarations where the :: return type separator is misplaced inside the argument parentheses.

Replaces Python's augmented assignment operators (+=, -=, *=, /=) with Elixir's rebinding syntax.

Replaces Python's // floor-division operator with Elixir's div/2.

Replaces Python's % modulo operator with Elixir's rem/2.

Fixes Python-style scientific notation that is invalid in Elixir.

Removes non-Elixir access modifier keywords prepended to def/defp/defmacro/defmacrop.

Behaviour for syntax-level rules that fix code which won't parse.

Mix Tasks

Generate a correctly-shaped rule plus its test files for a type.