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