0.11.1
Added
piped()selector predicate — matches only when the selected node is a pipe expression (|>). Since ExAST normalizes pipes during matching, there was previously no way to distinguish piped from direct call forms. Usewhere(piped())to match only piped calls, orwhere(not piped())for direct calls only.
0.11.0
Added
ExAST.Index.plan/1,ExAST.Index.terms/1, and structural index plan structs for building external candidate indexes while keeping ExAST as the semantic verifier.ExAST.Index.Terms.from_source/1,from_ast/1,from_pattern/1, and term signal classification helpers.ExAST.Selector.requires_source?/1,requires_comments?/1,find_all/3, andmatch?/3for source-aware selector planning and verification.ExAST.Comments.extract/1andExAST.Comments.text/1for comment extraction with source position metadata.ExAST.Symbols.definitions/1andExAST.Symbols.references/1for syntactic definition/reference extraction.- Symbol helpers for stable qualified names and optional BEAM-native MFA tuples:
ExAST.Symbols.qualified_name/1,mfa/1, andmatches?/2. - Indexing and code intelligence guide.
0.10.1
Fixed
- Restored pipe-aware pattern matching after the
0.10.0candidate prefilter optimization. Piped calls such asdata |> Enum.map(fun)now match direct call patterns likeEnum.map(_, _)again.
0.10.0
Added
ExAST.Patcher.find_many/3for running multiple named AST pattern checks in a single traversal where possible, returning matches tagged with:pattern.ExAST.search_many/3for searching files with multiple named patterns while preservingsearch/3options such as:limit.
Changed
- Optimized repeated single-node pattern matching by compiling patterns once, normalizing candidate nodes once per traversal, and using conservative call signature prefilters for common local, remote, piped, and nested call patterns.
0.9.1
Changed
- Restructured documentation: short README with topic-based guides (Getting Started, Pattern Language, Querying, CLI Reference, Diff)
0.9.0
Added
Capture guards —
where/2now accepts^pinsyntax to filter on captured values, similar to Ecto's parameter references:from("Enum.take(_, count)") |> where(match?({:-, _, [_]}, ^count))Supports
match?/2for structural checks, plain comparisons (^event == :click), multi-capture expressions (^left == ^right), and composition with existing structural predicates.Source text in match results —
Patcher.find_all/3now includes a:sourcefield with the matched source snippet.nilfor AST/zipper input.Module attribute pattern matching — attribute names are now captureable:
Patcher.find_all(source, "@name Application.get_env(_, _)")Previously
@env Application.get_env(...)would not match a pattern with a different attribute name, even as a capture variable.
0.8.1
Fixed
- CLI commands now handle closed stdout pipes cleanly, so commands like
mix ex_ast.search ... | headno longer emit EPIPE writer crash messages.
0.8.0
Added
- Comment predicates — queries can now filter by associated source comments
with
comment/1,comment_before/1,comment_after/1,comment_inside/1, andcomment_inline/1. Comment matchers accept strings, regexes, and explicit text matchers likeprefix/2,suffix/2, andtext/2. CLI comment filters detect/.../and~r/.../regex syntax.
0.7.0
Added
- SQL-like query API via
ExAST.Query:from/1,where/2,find/2,find_child/2,contains/1,inside/1, sibling predicates (follows/1,precedes/1,immediately_follows/1,immediately_precedes/1), positional predicates (first/0,last/0,nth/1), and boolean predicate combinators (any/1,all/1). - Selector alternatives — query starts can now accept a list of alternative
patterns, e.g.
from(["def _ do ... end", "defp _ do ... end"]). - Search limits and broad-query guard —
ExAST.search/3andmix ex_ast.searchnow supportlimit: n/--limit nand refuse unboundedfrom("_")searches unlessallow_broad: true/--allow-broadis passed. - Query-style CLI flags —
mix ex_ast.searchandmix ex_ast.replacenow support--contains, sibling filters (--follows,--precedes,--immediately-follows,--immediately-precedes), and position filters (--first,--last,--nth).
Fixed
- Selector negation now works Ecto-style without import hacks —
where(not ...)is rewritten by the selector DSL, so users no longer needimport Kernel, except: [not: 1]. - Search result rendering no longer fails when the current project formatter
config references unavailable
import_deps. - Selector descendant traversal now walks nested AST shapes reliably, fixing missed matches for remote calls nested inside assignments and control flow.
- Alias-aware matching expands local
aliasdirectives so canonical remote-call patterns likeAshPhoenix.Form.for_update(...)match alias-based call sites likeForm.for_update(...). - Grouped alias handling now supports real-world forms such as
alias Phoenix.Socket.{Broadcast, Message, Reply}. - Alias collection no longer misclassifies ordinary variables named
aliasas alias directives.
0.6.0
Added
- CSS-like AST selectors — build relationship-aware selectors with
ExAST.Selector:import ExAST.Selector pattern("defmodule _ do ... end") |> descendant("def _ do ... end") |> child("IO.inspect(_)") - Selector predicates — filter selected nodes with
where/2and Ecto-stylenot/1:parent/1,ancestor/1,has_child/1,has_descendant/1, andhas/1. - CLI relationship filters for
mix ex_ast.searchandmix ex_ast.replace:--parent,--ancestor,--has-child,--has-descendant,--has, and corresponding--not-*flags.
0.5.0
Added
- Ellipsis (
...) — matches zero or more nodes in function args, lists, and block bodies:IO.inspect(...),foo(first, ..., last),def run(_) do ... end ~psigil — compile-time pattern parsing viaimport ExAST.Sigil
0.4.0
Added
- Syntax-aware diff —
ExAST.diff/3,ExAST.diff_files/3,ExAST.apply_diff/1- GumTree-inspired AST matching: functions matched by name/arity, nodes by kind/label/signature
- Edit classification:
:insert,:delete,:update,:move - Function reorder detection reported as
:moveedits - Child suppression: edits covered by a parent update/insert/delete are rolled up
- Inline line-level diffs within updated nodes (Myers algorithm via
List.myers_difference) - Patch application:
ExAST.apply_diff/1produces patched source from a diff result
mix ex_ast.diffCLI task- Colored output with
-/+markers (red/green, auto-detected) --no-color,--no-moves,--summary,--jsonflags- Human-readable labels (
def create/1instead of{:def, :create, 1})
- Colored output with
- AST and zipper input —
Patcher.find_all/3andPatcher.replace_all/4now accept source strings,Sourceror.Zipper, or raw AST as input. Source-string variants return strings, AST/zipper variants return AST. - Quoted expressions as patterns — patterns and replacements can be
strings or quoted expressions:
Patcher.find_all(source, quote(do: IO.inspect(_))) Patcher.replace_all(ast, quote(do: IO.inspect(expr)), quote(do: dbg(expr)))inside/not_insideoptions also accept quoted. - Ellipsis (
...) — matches zero or more nodes in args, lists, and block bodies.IO.inspect(...)matches any arity,foo(first, ..., last)captures surrounding args,def run(_) do ... endmatches any body. ~psigil — compile-time pattern parsing viaimport ExAST.Sigil:~p"IO.inspect(...)"returns parsed AST with no runtime overhead.- ex_dna added to CI checks
0.3.0
- Pipe awareness:
data |> Enum.map(f)matchesEnum.map(data, f) - Where conditions:
--inside,--not-insidefilters - Multi-node patterns:
a = Repo.get!(_, _); Repo.delete(a)
0.2.0
- Initial release with search and replace