# `ExAST`
[🔗](https://github.com/elixir-vibe/ex_ast/blob/v0.11.0/lib/ex_ast.ex#L1)

Search, replace, and diff Elixir code by AST pattern.

Patterns are valid Elixir syntax:
- Variables (`name`, `expr`) capture matched nodes
- `_` and `_name` are wildcards
- Structs/maps match partially
- Pipes are normalized (`data |> Enum.map(f)` matches `Enum.map(data, f)`)
- Everything else matches literally
- CSS-like selectors can be built with `ExAST.Selector`

## Options

  * `:inside` — only match nodes inside an ancestor matching this pattern
  * `:not_inside` — reject nodes inside an ancestor matching this pattern

## Examples

    # Find all IO.inspect calls
    ExAST.search("lib/**/*.ex", "IO.inspect(_)")

    # Find IO.inspect only inside test blocks
    ExAST.search("test/", "IO.inspect(_)", inside: "test _ do _ end")

    # Replace dbg with the expression itself
    ExAST.replace("lib/**/*.ex", "dbg(expr)", "expr")

    # Match piped and direct calls interchangeably
    ExAST.search("lib/", "Enum.map(_, _)")  # also finds `data |> Enum.map(f)`

    # Relationship-aware queries
    import ExAST.Query

    query =
      from("def _ do ... end")
      |> where(contains("Repo.transaction(_)"))
      |> where(not contains("IO.inspect(...)"))

    ExAST.search("lib/", query)

    # Capture guards — filter on captured values with ^pin
    import ExAST.Query

    query =
      from("Enum.take(_, count)")
      |> where(match?({:-, _, [_]}, ^count))

    ExAST.search("lib/", query)

    # Syntax-aware diff
    result = ExAST.diff(old_source, new_source)
    result.edits  #=> [%ExAST.Diff.Edit{op: :update, kind: :function, ...}]
    ExAST.diff_files("lib/old.ex", "lib/new.ex")

# `diff_result`

```elixir
@type diff_result() :: ExAST.Diff.Result.t()
```

# `match`

```elixir
@type match() :: %{
  file: String.t(),
  line: pos_integer(),
  source: String.t(),
  captures: ExAST.Pattern.captures()
}
```

# `named_pattern`

```elixir
@type named_pattern() :: ExAST.Patcher.named_pattern()
```

# `pattern_name`

```elixir
@type pattern_name() :: ExAST.Patcher.pattern_name()
```

# `tagged_match`

```elixir
@type tagged_match() :: %{:pattern =&gt; pattern_name(), optional(atom()) =&gt; term()}
```

# `apply_diff`

```elixir
@spec apply_diff(diff_result()) :: String.t()
```

Applies a diff result to produce the patched source.

# `diff`

```elixir
@spec diff(String.t(), String.t(), keyword()) :: diff_result()
```

Computes a syntax-aware diff between two Elixir source strings.

# `diff_files`

```elixir
@spec diff_files(String.t(), String.t(), keyword()) :: diff_result()
```

Computes a syntax-aware diff between two Elixir files.

# `replace`

```elixir
@spec replace(
  String.t() | [String.t()],
  String.t() | ExAST.Selector.t(),
  String.t(),
  keyword()
) :: [
  {String.t(), pos_integer()}
]
```

Replaces AST pattern matches in files.

Options:
- `:dry_run` — return changes without writing (default: `false`)
- `:inside` — only replace inside ancestors matching this pattern
- `:not_inside` — skip replacements inside ancestors matching this pattern

Returns a list of `{file, count}` tuples for modified files.

# `search`

```elixir
@spec search(String.t() | [String.t()], String.t() | ExAST.Selector.t(), keyword()) ::
  [match()]
```

Searches files for AST pattern matches.

Returns a list of match maps with `:file`, `:line`, `:source`, and `:captures`.
Accepts `:inside` and `:not_inside` options to filter by context.

Options:
  * `:limit` — stop after returning this many matches
  * `:allow_broad` — allow unbounded broad searches like `from("_")`

# `search_many`

```elixir
@spec search_many(String.t() | [String.t()], [named_pattern()] | map(), keyword()) ::
  [tagged_match()]
```

Searches files for multiple named AST patterns.

`patterns` may be a keyword list or a map. Returned matches include a
`:pattern` field with the matching pattern name. This is more efficient than
calling `search/3` repeatedly for analyzers that run many checks over the same
files.

Options are the same as `search/3`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
