Credence.Pattern.AvoidGraphemesEnumCountWithPredicate (credence v0.7.0)

Copy Markdown

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.

Splitting a string into a grapheme list just to count occurrences of a specific character is wasteful. String.count/2 performs the same count directly on the string without allocating the intermediate list.

Safety: behind a switch

The rewrite is identical to the original only while every character in the running data is a single codepoint — otherwise String.count/2 (which scans raw text) and the grapheme comparison disagree on a decomposed accent. So this rule needs single_codepoint_graphemes (see Credence.Assumptions) and runs only while that promise is on.

Two guards keep the rewrite honest (decision 6a — shrink first, then promise):

  1. The compared literal is narrowed to a single codepoint (length(String.to_charlist(lit)) == 1), dropping "", "ab", and the two-codepoint form of "é". After that narrowing the only remaining difference between old and new is the rare decomposed-accent case in the data — which is exactly what the promise covers.
  2. A shared single_codepoint?/1 helper gates both check and fix, so they can never disagree on which literals qualify.

Bad

String.graphemes(str) |> Enum.count(&(&1 == "1"))
Enum.count(String.graphemes(str), &(&1 == "1"))
str |> String.graphemes() |> Enum.count(fn c -> c == "1" end)

String.graphemes(str) |> Enum.sum_by(fn "1" -> 1; _ -> 0 end)
Enum.sum_by(String.graphemes(str), fn "1" -> 1; _ -> 0 end)

Good

String.count(str, "1")
str |> String.count("1")