Credence.Pattern.NoGraphemePalindrome (credence v0.7.0)

Copy Markdown

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

This pattern creates an unnecessary intermediate list. Use String.reverse/1 and compare strings directly instead.

Only the String.graphemes/1 form is handled — both it and String.reverse/1 operate in grapheme space, so the rewrite is behaviour-identical for every input. The superficially similar String.to_charlist/1 form is deliberately not rewritten: charlists index codepoints, while String.reverse/1 reverses graphemes, and the two diverge on multi-codepoint graphemes (decomposed accents, ZWJ emoji, flags). See the codepoint↔grapheme policy in CONTEXT.md.

What is flagged / fixed

The fix shape depends on what the decomposed variable was built from and whether it is used anywhere besides the palindrome comparison:

  • built from a bare variable, used only in the comparison → the original string is inlined and the binding dropped: g = String.graphemes(s); g == Enum.reverse(g)s == String.reverse(s)
  • built from a bare variable, but used elsewhere as a grapheme list → the comparison is inlined to s == String.reverse(s) while the String.graphemes/1 binding is kept intact (so the other uses still see a list — rebinding it to the raw string would change their behaviour).
  • built from a larger expression (e.g. a pipe), used only in the comparison → the terminal String.graphemes/1 is stripped from the binding and the comparison uses the variable: g = s |> f() |> String.graphemes(); g == Enum.reverse(g)g = s |> f(); g == String.reverse(g) (inlining would duplicate f).
  • built from a larger expression and used elsewhere → not flagged: there is no behaviour-preserving single-expression rewrite.

Bad

graphemes = String.graphemes(s)
graphemes == Enum.reverse(graphemes)

normalized = s |> String.downcase() |> String.graphemes()
normalized == Enum.reverse(normalized)

Good

s == String.reverse(s)

normalized = s |> String.downcase()
normalized == String.reverse(normalized)