ast-grep
Structural code search. You understand code's abstract syntax tree, not just its text.
When to Run
| Task | Tool |
|---|---|
| Find a string literal | rg |
| Find a comment | rg |
| Find all exports | rg (fast enough) |
| Find all async functions that await outside try/catch | ast-grep |
Refactor foo(bar) → foo.method(bar) everywhere |
ast-grep |
| Rename a class across a TypeScript codebase | ast-grep (catches shorthand, destructuring, types) |
| Find components that receive a specific prop | ast-grep |
Default to rg for text; escalate to ast-grep when the pattern needs grammar awareness.
Installation
brew install ast-grep # macOS
cargo install ast-grep # cross-platform
npm install -g @ast-grep/cli # Node-friendly
Verify: ast-grep --version (should print a 0.x.x or later).
Basic Usage
Find pattern
# Find all console.log calls inside class methods
ast-grep --lang ts --pattern 'class $_ { $$ { console.log($$$) } $$ }'
# Find React components using useEffect with empty deps
ast-grep --lang tsx --pattern 'useEffect($_, [])'
# Find async functions without try/catch
ast-grep --lang ts --pattern 'async function $NAME($$) { $$ }' \
| grep -v 'try {'
Rewrite in place
# Convert var → let (demonstrative; use at your own risk)
ast-grep --lang js \
--pattern 'var $NAME = $VAL' \
--rewrite 'let $NAME = $VAL' \
--update-all
Rule files
For complex patterns, write a YAML rule in .ast-grep/rules/:
# .ast-grep/rules/no-console-in-prod.yml
id: no-console-in-prod
language: typescript
rule:
pattern: console.log($$$)
not:
inside:
kind: if_statement
has:
pattern: process.env.NODE_ENV === 'development'
message: "console.log outside dev-mode guard"
severity: error
Run: ast-grep scan.
Agent Protocol
When the user asks for a code search that depends on structure:
- Decide the right tool. Is this pattern purely textual? Use
rg. Does it depend on AST context (inside class, function, expression)? Useast-grep. - Write the pattern. Use metavariables
$_(wildcard),$NAME(named),$$$(multiple). Test on a single file first. - Scope the search. Always pass
--lang <lang>and a path argument — not the whole repo if you can avoid it. - Preview before rewriting. Never pass
--update-allwithout first running without it and reading the output. - For refactors > 10 files, write a rule file in
.ast-grep/rules/and commit it. Reproducibility > one-off commands.
Language Support
ast-grep ships first-class support for: TypeScript, TSX, JavaScript, JSX, Python, Rust, Go, Java, Kotlin, Swift, C, C++, C#, Ruby, PHP, HTML, CSS, YAML, JSON, Bash.
If your language is missing, check the ast-grep playground for tree-sitter grammar coverage.
Common Patterns Cheat Sheet
| Intent | Pattern |
|---|---|
| Any function with > N args | function $NAME($A, $B, $C, $D, $$$) |
| React class component | class $_ extends React.Component { $$ } |
| Async function | async function $_($$) { $$ } |
| Arrow function | ($$) => $_ |
| Call with specific first arg | $FN("specific-arg", $$) |
| TypeScript generic call | $FN<$T>($$) |
Never
- Never use ast-grep for plain string/text search —
rgis 10x faster and the right tool - Never run
--update-allwithout first running the same pattern without it and reading the diff - Never leave complex patterns as one-off commands — commit them as rule files in
.ast-grep/rules/ - Never run against
node_modules/,vendor/,dist/, or build output — always scope with a path argument - Never trust a rewrite across a codebase without running the test suite after
Credits
Reference from the official ast-grep AI docs, prompting pattern originally from Kieran Klaassen on X.