ExAST.Patcher (ExAST v0.11.0)

Copy Markdown View Source

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

match()

@type match() :: %{
  node: Macro.t(),
  range: Sourceror.Range.t() | nil,
  captures: ExAST.Pattern.captures(),
  source: String.t() | nil
}

named_pattern()

@type named_pattern() ::
  {pattern_name(), ExAST.Pattern.pattern() | ExAST.Selector.t()}

pattern_name()

@type pattern_name() :: term()

tagged_match()

@type tagged_match() :: %{:pattern => pattern_name(), optional(atom()) => term()}

Functions

find_all(input, pattern, opts \\ [])

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 — a Sourceror.Range.t() with line/column positions, or nil
  • :captures — a map of captured names to AST nodes
  • :source — the matched source text, or nil for 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

find_many(input, patterns, opts \\ [])

@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.

replace_all(input, pattern, replacement, opts \\ [])

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.