Skip to content

Releases: elixir-vibe/reach

v2.5.0

19 May 18:25

Choose a tag to compare

Highlights

  • Added conservative trivial delegate smell detection for pass-through defdelegate calls and hand-written same-argument forwarding functions.
  • Resolves aliases, Erlang module calls, and static generated delegate targets while avoiding documented facades, behaviour adapters, semantic aliases, and ambiguous local calls.
  • Added conversion idiom smells for identity float arithmetic and one-shot List.to_tuple/1 + elem/2 access.
  • Tightened Phoenix/Ecto dead-code checks to avoid reporting compile-time DSL macros and schema/migration declarations as unused results.
  • Improved corpus scan robustness and Elixir frontend handling for unusual pipe-shaped macro heads.

v2.4.0

17 May 20:17

Choose a tag to compare

Highlights

  • Added plugin-provided Phoenix, Ecto, and Oban smell checks for LiveView lifecycle mistakes, Ecto query pitfalls, SQL interpolation, money-like :float fields, and Oban argument shape issues.
  • Added project-local custom smell checks and plugin smell registration.
  • Added security/source smells for unsafe atom creation, unsafe binary_to_term/1, missing @external_resource, Ecto cross joins, and unpinned query values.
  • Added smell profiling and corpus scan scripts for performance and real-world validation.
  • Improved smell performance with inferred source prefilters, shared source runners, selector consolidation, and faster semantic idiom checks.
  • Tuned noisy checks against real-world Elixir projects.

Breaking-ish change

Source-level smell checks now use Reach.Smell.Check.Source instead of Reach.Smell.Check.Pattern. The unified smell/4 DSL also supports AST callback rules with mode: :ast.

Validation

Full CI passes locally.

v2.3.5

17 May 11:28

Choose a tag to compare

New

  • Strict smell gatesmix reach.check --smells --strict now fails when non-baseline smell findings are present. Projects can also enable this with smells: [strict: true] in .reach.exs.
  • Check baselinesmix reach.check supports --baseline PATH, --write-baseline PATH, and checks: [baseline: PATH] to suppress known architecture/smell findings while failing on new ones.

Changed

  • Combined checksmix reach.check --arch --smells now runs all requested check modes and shares one project analysis where possible.
  • Dogfooding — Reach now runs strict smell checking against itself in CI with tuned smell thresholds and an explicit baseline file.

Fixed

  • Optional JavaScript support — compiling Reach without QuickBEAM no longer emits warnings about the unavailable JavaScript frontend.
  • Self-smells — fixed actionable smell findings in Reach's own code and prevented pattern smell declarations from flagging themselves.

v2.3.4

15 May 09:18

Choose a tag to compare

Fixed

  • Docs publishingmix docs and mix hex.publish docs now run in the :docs Mix environment, avoiding dev-only dependency self-conflicts during Hex docs publishing.

v2.3.2

12 May 15:37

Choose a tag to compare

14 new smell patterns from Clippy, RuboCop, and SonarQube, all validated by hand against 200 top Hex packages.

New: case-on-boolean, case→match?, needless bool, manual max/min, @doc false on defp, sort+negative take, cond two-clause, unless/else, redundant assignment, redundant nil default, split→hd, filter→find, Map.new/MapSet.new conversions.

Fixed: ++ in reduce false positives (accumulator variable check), dogfooding across 15 files, CI runs --smells.

See CHANGELOG for details.

v2.3.1

12 May 13:38

Choose a tag to compare

New smell patterns

  • Case-on-booleancase expr do true -> ...; false -> ... end when the subject is a boolean expression. Uses capture guards to avoid false positives on sentinel returns.
  • Cond two-clausecond do ... true -> ... end with exactly two clauses → if/else
  • Unless/elseif with positive case first
  • Redundant assignmentresult = expr; result → just expr
  • Manual max/minif a > b, do: a, else: bKernel.max/2
  • @doc false on defp — redundant, private functions can't have docs
  • Sort then negative takeEnum.sort |> Enum.take(-n)Enum.sort(:desc) |> Enum.take(n)

Fixed

  • ++ in reduce false positives — now checks whether ++ actually references the accumulator variable. 17 fewer false positives across 200 Hex packages.
  • Dogfooding — fixed all actionable smell findings in Reach's own code across 15 files
  • CImix ci now runs reach.check --arch --smells

v2.3.0

11 May 10:15

Choose a tag to compare

New smell checks

  • Repeated traversal — same variable traversed by 2+ different Enum functions (e.g. Enum.max(list) + Enum.min(list)); suggests Enum.reduce/3.
  • Nested enumEnum.member?/2 nested inside Enum.map/filter/etc on the same variable (O(n²)); suggests MapSet.
  • Multiple Enum.at — 3+ Enum.at/2 calls on the same variable with literal indices; suggests pattern matching.
  • Append in recursion++ [item] in recursive tail calls (O(n²)); suggests prepend + reverse.
  • Piped Regex.replacetext |> Regex.replace(~r/.../, "") where the pipe puts the string in the regex position; uses the new ExAST piped() predicate to avoid false positives on direct calls.

New pattern smells

  • Map.keys/valuesEnum.join/uniq/count/length/member?/sum/max/min
  • List.foldr/3, Enum.min_by/max_by/dedup_by with identity fn
  • Enum.filter |> Enum.filter, Enum.map |> List.flatten/Enum.flat_map
  • Enum.sort/2 |> Enum.reverse, Enum.with_index |> Enum.reduce
  • Enum.map_join(_, "", _) redundant separator
  • Integer.to_string |> String.graphemes, length(String.split) - 1, Enum.at(list, -1)

Other

  • Parser warning suppression via emit_warnings: false
  • ex_ast bumped to ~> 0.11.1
  • Corpus-tested against 200 top Hex packages: 0 crashes, 0 false positives

v2.2.0

06 May 07:20

Choose a tag to compare

New

  • Length comparisons — flags length(list) == 0, 0 == length(list), and length(list) > 0; suggests list pattern matching, == [], or != [].
  • Identity Enum.uniq_by/2 — flags Enum.uniq_by(collection, fn x -> x end); suggests Enum.uniq/1.
  • Identity Enum.sort_by/2 — flags Enum.sort_by(collection, fn x -> x end); suggests Enum.sort/1.
  • length/1 in guards — flags small literal comparisons in guards; suggests list pattern matching.

Fixed

  • Regression coverage for bare literal with clauses — keeps valid clauses such as true in with blocks from regressing.
  • CI — refactored the length-in-guard check to satisfy strict Credo nesting rules.

v2.1.0

06 May 00:01

Choose a tag to compare

New smell checks

  • Enum.at/2 inside loop — flags O(n) indexed access inside loops (O(n²) total)
  • List.delete_at/2 inside loop — same O(n²) concern
  • Enum.count/1 without predicate — suggests length/1 (avoids protocol dispatch)
  • Map.put with variable key and boolean — suggests MapSet for membership tracking
  • Map.values → Enum.all?/any?/find/filter/map — iterate the map directly
  • Enum.map → Enum.max/min/sum — allocates intermediate list; use *_by or reduce
  • List.foldl/3 — non-idiomatic; use Enum.reduce/3
  • String.graphemes → Enum.reverse → Enum.join — use String.reverse/1
  • Redundant negated guardwhen x != y after when x == y on same variables
  • Destructure then reconstruct[a, b, c] pattern reassembled as [a, b, c] in body

Fixed

  • Frontend crash on import Mod, only: :macros — atom values are now handled correctly
  • Frontend crash on macro-generated AST — bare atoms in with clauses, non-list else clauses, and non-list handler clauses no longer crash
  • Enum.count/1 false positive — piped predicate form (|> Enum.count(&pred)) no longer flagged
  • Dogfooded own smells — fixed 50 findings in Reach's own code

Improved

  • Stress-tested on 4500+ Hex packages with zero crashes

v2.0.1

05 May 17:03

Choose a tag to compare

Fixed

  • ex_ast dependencyex_ast is now a regular dependency instead of only: [:dev, :test]. Pattern smell checks import ExAST at compile time, so the previous declaration made Reach uninstallable from Hex. (Fixes #14)

Improved

  • Smell false positive reduction — narrowed loop antipatterns to accumulator loops and recursive operands, excluded compile-time constructs and formatting functions from redundant computation, removed debatable patterns (chained String.replace, Enum.take with negative count, sequential Enum.filter). Validated on 19 Hex packages: 63% fewer findings, all remaining verified as true positives.
  • Module documentation — all public modules now have @moduledoc descriptions.