Patterns are valid Elixir expressions given as strings or quote blocks.

Rules

SyntaxMeaning
_ or _nameWildcard — matches any node, not captured
name, expr, xCapture — matches any node, bound by name
...Ellipsis — matches zero or more nodes (args, list items, block body)
Everything elseLiteral — must match exactly

Repeated variable names require the same value at every position:

# Only matches when both arguments are the same
source = """
{x, x}
{x, y}
"""

ExAST.Patcher.find_all(source, "{a, a}")
#=> matches line 1 only

Pipes

Pipes are desugared before matching — write the pattern in either form:

# Both patterns match both forms:
source = """
Enum.map(users, fn u -> u.name end)
users |> Enum.map(fn u -> u.name end)
"""

ExAST.Patcher.find_all(source, "Enum.map(_, _)")
#=> 2 matches

ExAST.Patcher.find_all(source, "_ |> Enum.map(_)")
#=> 2 matches (same results)

Ellipsis

... matches zero or more nodes in three positions:

# Any arity
"IO.inspect(...)"

# 1+ args, capture first
"foo(first, ...)"

# Any function body
"def run(_) do ... end"

Structs and maps

Partial matching — only specified keys must be present:

source = "%User{name: \"Alice\", age: 30, role: :admin}"

ExAST.Patcher.find_all(source, "%User{role: role}")
#=> [%{captures: %{role: :admin}}]

Multi-node patterns

Separate statements with ; to match contiguous sequences within a block:

source = """
a = Repo.get!(User, 1)
Logger.info(a)
Repo.delete(a)
"""

ExAST.Patcher.find_all(source, "a = Repo.get!(_, _); Repo.delete(a)")
#=> matches lines 1 and 3 — captures are consistent across statements

Module attributes

Attribute names are captureable — the @name inside @name(expr) matches like a variable, not a literal:

source = """
@env Application.get_env(:app, :key)
@port 4000
@db_url Application.get_env(:app, :db_url)
"""

ExAST.Patcher.find_all(source, "@name Application.get_env(_, _)")
#=> [%{captures: %{name: :env}}, %{captures: %{name: :db_url}}]

Use @_ to wildcard the name.

Recipes

Common patterns that solve real problems without needing queries or guards:

# Negative literal — flag potential bugs
"Enum.take(_, -_)"

# Specific atom in a tuple
"{:ok, val}"

# Same value in two positions (always-true comparison)
"{a, a}"

# String literal
"Logger.info(\"starting\")"

# Any module attribute read at compile time
"@_ Application.get_env(_, _)"

# Pipe chain (matches both pipe and direct form)
"Enum.filter(_, _) |> Enum.map(_)"