Finds and replaces AST patterns in source code.
Accepts source strings, AST nodes, or Sourceror zippers as input. Patterns and replacements can be strings or quoted expressions.
Source-string input preserves formatting via Sourceror.patch_string/2.
AST/zipper input returns modified AST trees.
# All equivalent
Patcher.find_all(source, "IO.inspect(_)")
Patcher.find_all(ast, quote(do: IO.inspect(_)))
Patcher.find_all(zipper, quote(do: IO.inspect(_)))
import ExAST.Selector
Patcher.find_all(source, pattern("defmodule _ do ... end") |> descendant("IO.inspect(_)"))
Summary
Functions
Finds all occurrences of pattern.
Finds matches for multiple named patterns in a single pass where possible.
Replaces all occurrences of pattern with replacement.
Types
@type match() :: %{ node: Macro.t(), range: Sourceror.Range.t() | nil, captures: ExAST.Pattern.captures(), source: String.t() | nil }
@type named_pattern() :: {pattern_name(), ExAST.Pattern.pattern() | ExAST.Selector.t()}
@type pattern_name() :: term()
@type tagged_match() :: %{:pattern => pattern_name(), optional(atom()) => term()}
Functions
@spec find_all( String.t() | Sourceror.Zipper.t() | Macro.t(), ExAST.Pattern.pattern() | ExAST.Selector.t(), keyword() ) :: [match()]
Finds all occurrences of pattern.
The first argument can be a source string, a Sourceror.Zipper, or a raw AST.
The pattern can be a string or a quoted expression.
Returns a list of match maps with:
:node— the matched AST node:range— aSourceror.Range.t()with line/column positions, ornil:captures— a map of captured names to AST nodes:source— the matched source text, ornilfor AST/zipper input
Range fields are accessed as keyword lists:
match.range.start[:line] #=> line number (1-based)
match.range.start[:column] #=> column number (1-based)
match.range.end[:line]
match.range.end[:column]Options
:inside— only match nodes nested within an ancestor matching this pattern:not_inside— reject nodes nested within an ancestor matching this pattern
@spec find_many( String.t() | Sourceror.Zipper.t() | Macro.t(), [named_pattern()] | %{required(pattern_name()) => ExAST.Pattern.pattern() | ExAST.Selector.t()}, keyword() ) :: [tagged_match()]
Finds matches for multiple named patterns in a single pass where possible.
patterns may be a keyword list or a map. Returned matches include a
:pattern field with the matching pattern name:
Patcher.find_many(source,
inspect_call: "IO.inspect(expr)",
debug_call: "dbg(expr)"
)This is useful for analyzers that run many independent pattern checks over the same source tree. Single-node patterns are compiled once and scanned together; selectors and multi-node sequence patterns fall back to the regular matcher while keeping the same tagged result shape.
@spec replace_all( String.t(), ExAST.Pattern.pattern() | ExAST.Selector.t(), ExAST.Pattern.pattern(), keyword() ) :: String.t()
@spec replace_all( Sourceror.Zipper.t() | Macro.t(), ExAST.Pattern.pattern() | ExAST.Selector.t(), ExAST.Pattern.pattern(), keyword() ) :: Macro.t()
Replaces all occurrences of pattern with replacement.
When given a source string, returns a modified source string with formatting preserved. When given a zipper or AST, returns modified AST.
Pattern and replacement can be strings or quoted expressions.
Captures from the pattern are substituted into the replacement template.
Accepts the same :inside / :not_inside options as find_all/3.