ast-grep — Structural Code Search, Lint, and Rewrite
Content verified against ast-grep 0.45.1. Flags can drift between versions — when a command fails, check ast-grep <cmd> --help.
Overview
ast-grep is a fast, Rust-based CLI tool. It parses code with tree-sitter and matches the syntax tree, not the text. You write patterns that look like the code you search for. Metavariables ($NAME, $$$ARGS) act as wildcards for AST nodes.
Three main modes:
| Mode |
Command |
Use for |
| Search / rewrite |
ast-grep run -p '<pattern>' (default command) |
One-time queries and codemods |
| Lint |
ast-grep scan |
YAML rule sets, CI checks, complex rules |
| Explore |
ast-grep outline |
Symbols, imports, exports of files |
It supports 25+ languages: JavaScript, TypeScript, TSX, Python, Rust, Go, Java, C, C++, C#, Kotlin, Swift, Ruby, PHP, and more. See the language table in references/cli-reference.md.
Installation
Check first: which ast-grep. If it is not installed, run the bundled helper:
bash <SKILL_DIR>/scripts/install.sh
The script tries brew, cargo, npm, pip3, and MacPorts in order, and verifies the result. For manual installation see https://ast-grep.github.io/guide/quick-start.html.
Note: some systems have a different sg binary (shell group). Always use the full name ast-grep.
Tool Selection: grep vs ast-grep vs LSP
| The question is about... |
Use |
| Text: strings, error messages, config values, file names |
grep / rg / fd |
| Syntax: "all calls that look like X", pattern rewrites, codemods, lint rules |
ast-grep |
| Semantics: go-to-definition, find-references across scopes, types, scope-aware rename |
LSP / compiler tooling |
| Graphs: transitive callers, circular imports, dead-code detection across a whole codebase |
A graph script over ast-grep --json output; ast-grep alone sees only per-file matches |
ast-grep matches syntax, not semantics. It cannot tell two same-named symbols in different scopes apart. A rename with ast-grep is an exact syntactic replace (it skips comments, strings, and oldNameHelper), but it hits every scope. For a true semantic rename, prefer an LSP rename; use ast-grep when no LSP is available and verify the diff.
There is no runtime conflict with LSP tools: the ast-grep CLI is stateless — it reads files, matches, and exits. It starts no server and builds no index.
Also do NOT use ast-grep when:
- The pattern spans comments or string contents — patterns cannot match inside them.
- The codebase is tiny (< 5 files) — just read the files.
Files with syntax errors are usually fine: tree-sitter error recovery still matches the valid regions.
Quick Start
# Find all console.log calls in TypeScript
ast-grep -p 'console.log($$$ARGS)' -l ts src/
# Rewrite: var -> const, interactive review per match
ast-grep -p 'var $N = $V' -r 'const $N = $V' -l js --interactive src/
# Apply all rewrites without confirmation
ast-grep -p '$P && $P()' -r '$P?.()' -l ts -U src/
# Only list files that contain a match
ast-grep -p 'eval($$$A)' -l js --files-with-matches src/
# JSON output for scripts (note: --json requires `=` for its value)
ast-grep -p 'function $NAME($$$P) { $$$B }' -l ts --json=compact src/ > matches.json
# Run one inline YAML rule without a file
ast-grep scan --inline-rules 'id: no-eval
language: typescript
severity: error
rule: {pattern: "eval($$$A)"}' src/
# Show the structure of a file
ast-grep outline src/parser.ts --type class,function
# Test a pattern without files, via stdin
echo 'var x = 1' | ast-grep -p 'var $N = $V' -l js --stdin
Always put patterns in single quotes — double quotes let the shell expand $NAME first. For interactive pattern experiments there is an online playground: https://ast-grep.github.io/playground.html
Metavariables — Minimal Reminder
$VAR matches exactly one AST node. $$$ARGS matches zero or more.
- Default to
$$$ for arguments, parameters, and bodies. foo($ARG) matches only one-argument calls — this is the most common cause of missed matches.
- Repeated names must match identical content:
$A == $A matches x == x, not x == y.
- Names use
A-Z, _, digits. $var is invalid. $_NAME is non-capturing.
Full syntax, pattern objects, strictness, and language pitfalls (TypeScript generics, keywords, statement-vs-expression): references/pattern-syntax.md.
Standard Workflow: Rewrite
- Search:
ast-grep -p '<pattern>' -l <lang> <path> — check the matches.
- Inspect: add
--json=compact if you must verify match boundaries.
- Preview: add
-r '<rewrite>' — the diff prints to stdout, nothing is written.
- Apply: add
--interactive for per-match review, or -U to apply all.
- Verify: run the search again and expect exit code 1 (no matches). Then run the project tests.
A rename needs multiple passes (definition, call sites, imports are distinct node kinds) — see references/pattern-syntax.md.
Exit Codes
run: 0 = at least one match, 1 = no match. In scripts, "no matches" is not an error.
scan: 0 = clean, 1 = diagnostics with error severity found. Use this as a CI gate.
- Other non-zero codes signal usage or config errors (e.g.
2 bad flag, 8 invalid pattern/rule).
- Pipelines mask exit codes.
ast-grep ... | other-tool reports the last command's status. Set set -o pipefail (bash/zsh) when the ast-grep status matters.
Reference Files
Read these on demand. Do not load them all at once.
- references/cli-reference.md — All subcommands and flags (verified against 0.45.1), flag syntax rules, language table.
- references/pattern-syntax.md — Full pattern syntax, pattern objects, strictness levels, pitfalls with examples.
- references/rule-yaml.md — YAML rule files, rule object (atomic/relational/composite),
sgconfig.yml project setup, rule testing, CI.
- references/pattern-cookbook.md — Ready-to-use patterns for JS/TS/Python, rewrite recipes with semantic warnings, task recipes (find callers, dead exports), YAML rule scenarios.
Bundled Scripts
Replace <SKILL_DIR> with the absolute directory of this SKILL.md file.
scripts/install.sh — Try available package managers in order and install ast-grep.
scripts/json-summary.py — Summarize ast-grep --json output: match counts per file plus a compact match list. Usage:
set -o pipefail # keep ast-grep's exit code visible
ast-grep -p '<pattern>' --json=compact <path> | python3 <SKILL_DIR>/scripts/json-summary.py
1---2name: ast-grep3description: Use this skill ANY time you must search, lint, or rewrite code by its syntax structure. ast-grep is an AST-based CLI tool. It matches code by syntax tree, not by text. ALWAYS prefer it over grep/ripgrep when the query is about code structure. Trigger categories: structural code search (find all calls to X, functions without Y), bulk code rewrite/codemod (replace API calls, migrate deprecated patterns), custom lint rules (enforce project patterns), multi-pass symbol renaming, code outline/structure exploration, ast-grep rule authoring. Trigger keywords: ast-grep, sg, structural search, codemod, rewrite pattern, metavariable, tree-sitter pattern, lint rule, sgconfig, find all calls, refactor across files, AST. Do NOT trigger for: plain string or regex searches, file-name lookups, log/config searches, single-file edits you can do by hand.4---56# ast-grep — Structural Code Search, Lint, and Rewrite78Content verified against ast-grep 0.45.1. Flags can drift between versions — when a command fails, check `ast-grep <cmd> --help`.910## Overview1112ast-grep is a fast, Rust-based CLI tool. It parses code with tree-sitter and matches the syntax tree, not the text. You write patterns that look like the code you search for. Metavariables (`$NAME`, `$$$ARGS`) act as wildcards for AST nodes.1314Three main modes:1516| Mode | Command | Use for |17| --- | --- | --- |18| Search / rewrite | `ast-grep run -p '<pattern>'` (default command) | One-time queries and codemods |19| Lint | `ast-grep scan` | YAML rule sets, CI checks, complex rules |20| Explore | `ast-grep outline` | Symbols, imports, exports of files |2122It supports 25+ languages: JavaScript, TypeScript, TSX, Python, Rust, Go, Java, C, C++, C#, Kotlin, Swift, Ruby, PHP, and more. See the language table in [references/cli-reference.md](references/cli-reference.md).2324## Installation2526Check first: `which ast-grep`. If it is not installed, run the bundled helper:2728```bash29bash <SKILL_DIR>/scripts/install.sh30```3132The script tries brew, cargo, npm, pip3, and MacPorts in order, and verifies the result. For manual installation see <https://ast-grep.github.io/guide/quick-start.html>.3334Note: some systems have a different `sg` binary (shell group). Always use the full name `ast-grep`.3536## Tool Selection: grep vs ast-grep vs LSP3738| The question is about... | Use |39| --- | --- |40| Text: strings, error messages, config values, file names | `grep` / `rg` / `fd` |41| Syntax: "all calls that look like X", pattern rewrites, codemods, lint rules | **ast-grep** |42| Semantics: go-to-definition, find-references across scopes, types, scope-aware rename | LSP / compiler tooling |43| Graphs: transitive callers, circular imports, dead-code detection across a whole codebase | A graph script over `ast-grep --json` output; ast-grep alone sees only per-file matches |4445ast-grep matches syntax, not semantics. It cannot tell two same-named symbols in different scopes apart. A rename with ast-grep is an **exact syntactic** replace (it skips comments, strings, and `oldNameHelper`), but it hits *every* scope. For a true semantic rename, prefer an LSP rename; use ast-grep when no LSP is available and verify the diff.4647There is no runtime conflict with LSP tools: the ast-grep CLI is stateless — it reads files, matches, and exits. It starts no server and builds no index.4849Also do NOT use ast-grep when:5051- The pattern spans comments or string contents — patterns cannot match inside them.52- The codebase is tiny (< 5 files) — just read the files.5354Files with syntax errors are usually fine: tree-sitter error recovery still matches the valid regions.5556## Quick Start5758```bash59# Find all console.log calls in TypeScript60ast-grep -p 'console.log($$$ARGS)' -l ts src/6162# Rewrite: var -> const, interactive review per match63ast-grep -p 'var $N = $V' -r 'const $N = $V' -l js --interactive src/6465# Apply all rewrites without confirmation66ast-grep -p '$P && $P()' -r '$P?.()' -l ts -U src/6768# Only list files that contain a match69ast-grep -p 'eval($$$A)' -l js --files-with-matches src/7071# JSON output for scripts (note: --json requires `=` for its value)72ast-grep -p 'function $NAME($$$P) { $$$B }' -l ts --json=compact src/ > matches.json7374# Run one inline YAML rule without a file75ast-grep scan --inline-rules 'id: no-eval76language: typescript77severity: error78rule: {pattern: "eval($$$A)"}' src/7980# Show the structure of a file81ast-grep outline src/parser.ts --type class,function8283# Test a pattern without files, via stdin84echo 'var x = 1' | ast-grep -p 'var $N = $V' -l js --stdin85```8687Always put patterns in **single quotes** — double quotes let the shell expand `$NAME` first. For interactive pattern experiments there is an online playground: <https://ast-grep.github.io/playground.html>8889## Metavariables — Minimal Reminder9091- `$VAR` matches exactly **one** AST node. `$$$ARGS` matches **zero or more**.92- **Default to `$$$` for arguments, parameters, and bodies.** `foo($ARG)` matches only one-argument calls — this is the most common cause of missed matches.93- Repeated names must match identical content: `$A == $A` matches `x == x`, not `x == y`.94- Names use `A-Z`, `_`, digits. `$var` is invalid. `$_NAME` is non-capturing.9596Full syntax, pattern objects, strictness, and language pitfalls (TypeScript generics, keywords, statement-vs-expression): [references/pattern-syntax.md](references/pattern-syntax.md).9798## Standard Workflow: Rewrite991001. **Search:** `ast-grep -p '<pattern>' -l <lang> <path>` — check the matches.1012. **Inspect:** add `--json=compact` if you must verify match boundaries.1023. **Preview:** add `-r '<rewrite>'` — the diff prints to stdout, nothing is written.1034. **Apply:** add `--interactive` for per-match review, or `-U` to apply all.1045. **Verify:** run the search again and expect exit code 1 (no matches). Then run the project tests.105106A rename needs multiple passes (definition, call sites, imports are distinct node kinds) — see [references/pattern-syntax.md](references/pattern-syntax.md).107108## Exit Codes109110- `run`: `0` = at least one match, `1` = no match. In scripts, "no matches" is not an error.111- `scan`: `0` = clean, `1` = diagnostics with `error` severity found. Use this as a CI gate.112- Other non-zero codes signal usage or config errors (e.g. `2` bad flag, `8` invalid pattern/rule).113- **Pipelines mask exit codes.** `ast-grep ... | other-tool` reports the last command's status. Set `set -o pipefail` (bash/zsh) when the ast-grep status matters.114115## Reference Files116117Read these on demand. Do not load them all at once.118119- [references/cli-reference.md](references/cli-reference.md) — All subcommands and flags (verified against 0.45.1), flag syntax rules, language table.120- [references/pattern-syntax.md](references/pattern-syntax.md) — Full pattern syntax, pattern objects, strictness levels, pitfalls with examples.121- [references/rule-yaml.md](references/rule-yaml.md) — YAML rule files, rule object (atomic/relational/composite), `sgconfig.yml` project setup, rule testing, CI.122- [references/pattern-cookbook.md](references/pattern-cookbook.md) — Ready-to-use patterns for JS/TS/Python, rewrite recipes with semantic warnings, task recipes (find callers, dead exports), YAML rule scenarios.123124## Bundled Scripts125126Replace `<SKILL_DIR>` with the absolute directory of this SKILL.md file.127128- `scripts/install.sh` — Try available package managers in order and install ast-grep.129- `scripts/json-summary.py` — Summarize `ast-grep --json` output: match counts per file plus a compact match list. Usage:130131 ```bash132 set -o pipefail # keep ast-grep's exit code visible133 ast-grep -p '<pattern>' --json=compact <path> | python3 <SKILL_DIR>/scripts/json-summary.py134 ```