# ast-grep: structural search and rewriting with tree-sitter patterns

> ast-grep matches AST nodes instead of text, so a pattern written like ordinary code finds every syntactically equivalent occurrence. It is a strong fit for codemods and custom lint rules, and a poor fit for plain text search.

**ast-grep/ast-grep** — ⚡A CLI tool for code structural search, lint and rewriting. Written in Rust

- Repository: https://github.com/ast-grep/ast-grep
- Website: https://ast-grep.github.io/
- Stars: 16,087 · Forks: 465
- Language: Rust
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ast-grep-ast-grep

## The problem ast-grep solves: matching syntax, not characters

Text search tools find strings. That breaks down the moment the same construct is written two ways. A regex for a null-coalescing rewrite has to anticipate optional chaining, whitespace, and line breaks, and it will still fire inside comments and string literals. ast-grep takes a different route. It parses each file into an abstract syntax tree produced by tree-sitter, then matches your pattern against nodes in that tree. The README describes the pattern itself as "isomorphic to code": you write the code you want to find, and ast-grep matches every occurrence with the same syntactic structure.

The audience is narrow and specific. The README names three: open-source library authors who want users to absorb breaking changes, tech leads enforcing house rules, and security researchers writing detection rules quickly. If your task is renaming a log message, use grep. If your task is finding every call to a deprecated function regardless of how it is formatted, the AST is the right level of abstraction.

## How the matching engine works: tree-sitter parsing plus $ wildcards

The core is an algorithm that searches and replaces code based on the abstract syntax tree, and the README states the trees come from tree-sitter. The workspace layout confirms the split: crates/core holds the matching engine, crates/language wraps the tree-sitter grammars, crates/config parses the YAML rule format, crates/cli is the binary, and crates/lsp is a language server. Cargo.toml pins tree-sitter at 0.27.0 and the whole workspace at version 0.45.3.

Wildcards are the part that makes patterns usable. A dollar sign followed by upper-case letters, such as $MATCH, matches any single AST node. The README compares it to the regular expression dot, with the qualification that it is not textual: it binds to one node in the tree, so it will not swallow half an expression. Because the engine works on nodes, a pattern for a call expression matches that call whether it sits on one line or is wrapped across five.

Two other capabilities are named in the README. There is a jQuery-like API for AST traversal and manipulation, exposed through the Rust crates rather than the CLI. And there is a YAML configuration format for writing lint rules or code modifications, which is what turns one-off searches into repeatable project rules. The README does not document the YAML schema itself; it points at the website for that.

## Installing ast-grep and running a first rewrite

The README lists npm, pip, cargo, cargo-binstall, homebrew, scoop, mise and MacPorts as installation routes. The three shortest are below. Note that the pip distribution is named ast-grep-cli, not ast-grep, and the npm package is scoped.

```bash
npm install --global @ast-grep/cli
# `pnpm approve-builds` may be needed
pip install ast-grep-cli
brew install ast-grep
```

The README notes that `pnpm approve-builds` may be needed, which matters if you install through pnpm and the postinstall step is blocked. Rust users can install from source instead:

```bash
cargo install ast-grep --locked
```

The CLI takes a pattern, a language and optionally a rewrite. The README gives this exact form:

```bash
ast-grep --pattern 'var code = $PATTERN' --rewrite 'let code = new $PATTERN' --lang ts
```

Here --pattern is the code to find, $PATTERN is the wildcard capturing a single node, --rewrite is the replacement using the same capture, and --lang ts selects the TypeScript grammar. Without --rewrite the command reports matches; with it, ast-grep reports the proposed change. The README's own example rewrites an optional call:

```bash
ast-grep -p '$A && $A()' -l ts -r '$A?.()'
```

The short flags are -p, -l and -r. The README also shows -i used with a Zodios migration example, `ast-grep -p 'new Zodios($URL,  $CONF as const,)' -l ts -r 'new Zodios($URL, $CONF)' -i`, but the README does not state what -i does, so treat it as something to confirm with `ast-grep --help` before relying on it.

## Where ast-grep is the wrong tool

The first limitation is coverage. Matching happens inside the tree-sitter grammar for the target language, so anything the grammar does not model is invisible to a pattern. Comments, string contents and formatting are not the same kind of node as a function call, and a pattern written as code will not reach into them. If your search target is a licence header, a TODO marker or a secret in a config file, ast-grep is the wrong instrument and ripgrep is the right one.

The second is that pattern matching is not semantic analysis. ast-grep matches structure, so it cannot tell whether $A refers to the imported function or a same-named local. The README's own framing, "lightweight static analysis", is accurate and worth taking literally. Type-aware rules belong in a compiler or a type checker.

The third is packaging. The pyproject.toml classifier declares Development Status 3 - Alpha, and the version is 0.45.3, below 1.0. That is a statement about interface stability, not about whether the tool works, but it means the CLI surface and the YAML rule format can change between minor releases. Pin a version in CI rather than tracking latest. The project is not archived and the last push was on 2026-09-20, with 0.45.3 released on 2026-08-31, so releases are still coming; that is also why pinning matters.

## ast-grep compared with Semgrep and ripgrep

The two comparisons that come up most are against ripgrep and against Semgrep, and they fail in different directions.

ripgrep is a text search tool. It reads lines and matches regular expressions against bytes, which makes it fast, dependency-free and indifferent to language. ast-grep has to parse the file first, which costs more per file, and it needs a grammar for the language. The payoff is that ast-grep will not match a pattern inside a comment or a string, and it will match a call that ripgrep's line-based regex misses because the arguments are split across lines. If your query is genuinely textual, ripgrep wins on every axis. If your query is a code shape, ripgrep cannot express it reliably.

Semgrep also does structural matching over parsed code, and it also ships a YAML rule format, so the two occupy overlapping ground. The README does not make a comparison, so the honest difference is architectural: ast-grep is written in Rust, uses tree-sitter grammars, and is distributed as a single compiled binary with no runtime dependency on Python or a hosted service. Semgrep's rule ecosystem and its cross-file taint analysis are outside what ast-grep's README claims. If you need dataflow tracking across files, ast-grep's node-level matching is not that.

## Maintenance, licensing and the cost of upgrading

The repository is MIT licensed, stated in Cargo.toml as `license = "MIT"` and in pyproject.toml as an OSI-approved MIT classifier. MIT is permissive: it allows commercial use, modification and redistribution, and it requires that the copyright notice and permission notice be preserved. The LICENSE file sits at the repository root. Whether that notice needs to travel with a binary you ship is a question for your own legal review; the licence text is short and readable.

The upgrade cost is concentrated in the YAML rule format. Because the version is pre-1.0, a rule written against 0.45.x may need edits when the schema changes. The workspace pins every internal crate to the same version string, so the CLI, config parser and language bindings move together. Building from source requires Rust 1.88.0, the rust-version declared in Cargo.toml, and Cargo.lock is committed, so a `cargo install --locked` build is reproducible. The README's source build path is:

```bash
cargo install --path ./crates/cli --locked
```

If you write rules for a team, keep them in the repository and run them in CI so a version bump surfaces breakage as a failing job rather than a silent change in what gets rewritten.

## What to check before you commit to it

Two things are under-documented in the README and worth resolving first. The YAML rule schema is described only as existing; the README directs readers to the website, so budget time to read the rule documentation before promising a rule set to anyone. And the -i flag appears in one example without explanation, so verify its behaviour against `ast-grep --help` rather than inferring it.

On the language side, the crates/language crate is where tree-sitter grammars live, and the README does not enumerate which languages are covered. If your codebase is in a language outside the common set, check that grammar before evaluating the tool at all. The npm and pip packages are thin wrappers around the compiled binary, so a language that the crates do not support will not be fixed by installing a different package manager's build.

Finally, run your intended codemod against a fixture directory before pointing it at a working tree. The rewrite path edits files, and the README does not document a dry-run flag or a rollback mechanism; search the CLI help for one instead of assuming the rewrite is reversible.

## Conclusion

Adopt ast-grep if you maintain a library with breaking changes, enforce team-specific code rules, or need one-off codemods across a large repository. Do not adopt it as a replacement for ripgrep when your target is text rather than syntax, and do not expect the YAML rule format to be documented exhaustively: the README points to the website for rule details. Before committing to it in CI, verify rule behaviour on your own fixtures, confirm the binary installs through your package manager, and check that the language you target has tree-sitter support in the crates/language crate.

## FAQ

### What is ast-grep?

ast-grep is a CLI tool for code structural search, lint and rewriting, written in Rust. It parses code with tree-sitter and matches patterns against AST nodes rather than text, so a pattern written like ordinary code finds every syntactically equivalent occurrence.

### How do I install ast-grep?

The README lists npm, pip, cargo, cargo-binstall, homebrew, scoop, mise and MacPorts. The pip distribution is named ast-grep-cli and the npm package is scoped as @ast-grep/cli; Rust users can run cargo install ast-grep --locked or build from source with cargo install --path ./crates/cli --locked.

### What are some alternatives to ast-grep?

ripgrep searches text with regular expressions and cannot express a code shape, while Semgrep also matches over parsed code and ships a YAML rule format. ast-grep's README does not make these comparisons; the difference is that ast-grep is a single Rust binary using tree-sitter grammars.

### How do I use ast-grep?

Pass a pattern, a language and optionally a rewrite. The README gives ast-grep --pattern 'var code = $PATTERN' --rewrite 'let code = new $PATTERN' --lang ts, where $PATTERN is a wildcard matching any single AST node. Short flags -p, -l and -r work the same way.

### Is ast-grep good?

It depends on the task. For finding and rewriting a code shape across a large codebase it is well suited, and the README describes it as lightweight static analysis. It cannot resolve which symbol a wildcard refers to, so type-aware checks belong elsewhere.

### How does ast-grep differ from ripgrep?

ripgrep matches regular expressions against lines of text, so it will fire inside comments and strings and can miss a call split across lines. ast-grep parses the file into a tree-sitter AST first and matches nodes, which costs more per file and requires a grammar for the language.

## Sources

- [ast-grep/ast-grep on GitHub](https://github.com/ast-grep/ast-grep)
- [License: MIT](https://github.com/ast-grep/ast-grep/blob/main/LICENSE)
- [Project website](https://ast-grep.github.io/)
- [README](https://github.com/ast-grep/ast-grep/blob/main/README.md)
- [Releases](https://github.com/ast-grep/ast-grep/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ast-grep-ast-grep
