3.5 KiB
3.5 KiB
name, description
| name | description |
|---|---|
| ast-grep | Structural code search and rewriting with ast-grep (AST-aware grep). Use instead of rg/grep when searching for code *structure* rather than text: find all calls/definitions matching a shape, match across formatting/line breaks, extract matched sub-parts (function names, arguments), or perform safe codemod-style rewrites. Works for Rust, TS/JS, Python, Go, Java, C/C++, and most tree-sitter languages. |
ast-grep: structural search & rewrite
ast-grep matches code by AST shape, not text. Prefer it over rg when the target spans lines, when formatting varies, or when you need to rewrite code safely.
Pattern syntax
Patterns are valid code snippets with meta-variables:
| Syntax | Matches |
|---|---|
$VAR |
exactly one AST node (UPPERCASE names only) |
$$$ / $$$ARGS |
zero or more nodes (args, params, statements) |
$_ |
one node, non-capturing |
repeated $A |
backreference: $A == $A matches x == x, not x == y |
Search
ast-grep -p '$X.unwrap()' -l rust src/ # find all .unwrap() calls
ast-grep -p 'fn $NAME($$$) -> $RET { $$$ }' -l rust . # multi-line shapes ok
ast-grep -p 'await $EXPR' -l ts src/
-l <lang>is required unless searching by file extension is unambiguous; use it anyway.- Always single-quote patterns — double quotes let the shell eat
$VAR. - Pattern must parse as valid code for that language (e.g. a bare
match $E { $$$ }works in Rust because it's a valid expression).
Structured output (for scripting / extraction)
ast-grep -p 'pub fn $F($$$) { $$$ }' -l rust src/ --json=compact
JSON gives file, range, text, and metaVariables.single.F.text — use this to extract e.g. all matching function names instead of parsing grep output.
Rewrite
# Preview as diff (default: prints diff, does NOT modify files)
ast-grep -p '$X.lock().unwrap()' --rewrite '$X.lock().expect("poisoned")' -l rust src/
# Apply all edits
ast-grep -p '...' --rewrite '...' -l rust src/ --update-all
Preview first, then --update-all. The rewrite is a text template: metavars are substituted, unmatched metavars become empty strings.
YAML rules (when patterns aren't enough)
For context-sensitive matches (inside/has/not), use scan --inline-rules:
ast-grep scan --inline-rules 'id: unwrap-in-closure
language: rust
rule:
pattern: $X.unwrap()
inside:
kind: closure_expression
stopBy: end
message: unwrap inside closure' src/
Rule keys:
- Atomic:
pattern,kind(node type, e.g.closure_expression),regex - Relational:
inside,has,follows,precedes— addstopBy: endto search beyond direct parent/child - Composite:
all,any,not - Optional
fix: <template>per rule;constraints:to filter metavars (e.g.X: {regex: '^foo'})
To discover node kinds for kind:, match a sample with --json (each match reports its node) or just use a pattern instead.
Gotchas
$var(lowercase) is not a metavariable; use$VAR.- Single-quote patterns in the shell.
kindcombined withpatterndoesn't re-parse the pattern; if a snippet is ambiguous, use the pattern object form:pattern: {context: 'class A { $F = $V }', selector: field_definition}.all: [{kind: a}, {kind: b}]tests the same node twice — for "contains both", useall: [has: {...}, has: {...}].- Regexes use Rust regex syntax (no backreferences/lookaround).
- Fall back to
rgfor plain-text/comment/string searches — ast-grep only matches code nodes.