name: ast-grep description: Use when searching or replacing code patterns - use ast-grep instead of text search for semantic accuracy
AST-Grep Code Search
Use ast_grep_search and ast_grep_replace for semantic code search/replace. ast-grep understands code structure, not just text.
When to Use
- Function calls, imports, class methods (structured code)
- Safe replacements across files
- Use LSP first for: definitions/references/types — then scope ast-grep to files discovered by LSP
- Use grep for: partial string patterns, comments, URLs, or after one simplified ast-grep retry still returns zero matches
Golden Rules
- Be specific —
fetchMetrics($ARGS)notfetchMetrics - Scope it — always specify
pathsto relevant files - Retry once on zero matches — simplify the pattern, same
paths, then fall back to grep - Dry-run first —
apply: falsebeforeapply: true - Valid code only —
function $NAME($$$) { $$$ }notfunction $NAME( - Avoid
selectorunless expert — narrows to AST node kind; does not extract metavariables - Metavariables don't work inside strings —
from "$PATH"matches literal"$PATH", not a wildcard
Metavariables
| Syntax | Matches | Named? |
|---|---|---|
$X |
single node | yes — captures the node |
$$$ |
zero or more nodes | no — unnamed wildcard |
$$$ARGS |
zero or more nodes | yes — captures the list |
Use $$$ when you don't need the captured value; $$$NAME when you do.
Quick Reference
Patterns
| Pattern | Matches |
|---|---|
fetchMetrics($ARGS) |
call with any single arg |
fetchMetrics($$$ARGS) |
call with any number of args |
function $NAME($$$) { $$$ } |
function declaration |
import { $NAMES } from $PATH |
named import (no quotes on path) |
const $X = $Y |
variable declaration |
Structural-intent parameters (preferred for cross-context queries)
Use these instead of writing raw YAML:
| Parameter | What it does |
|---|---|
insideKind |
Only match inside an ancestor of this node kind |
hasKind |
Only match nodes that contain a descendant of this kind |
follows |
Only match nodes preceded by a sibling matching this pattern |
precedes |
Only match nodes followed by a sibling matching this pattern |
# console.log only inside functions
ast_grep_search pattern="console.log($MSG)" lang="typescript" insideKind="function_declaration"
# replace var with let, scoped to functions only
ast_grep_replace pattern="var $X" rewrite="let $X" lang="javascript" insideKind="function_declaration"
These synthesize a YAML rule automatically. Use rule: for the full DSL when you need all/any/not, nthChild, regex, or other advanced constraints.
Raw YAML rule (rule: parameter)
Pass a complete ast-grep YAML rule to unlock the full DSL:
ast_grep_search rule="id: my-rule
language: TypeScript
rule:
pattern: console.log($MSG)
inside:
kind: function_declaration
stopBy: end" lang="typescript"
Debugging unknown node kinds — ast_dump
When a pattern returns zero matches and you don't know the correct node kind or field name, use ast_dump to inspect the AST:
ast_dump source="function foo() { return 1; }" lang="typescript"
Returns the full indented AST with node kinds and positions. Then use the correct kind in your pattern or insideKind.
Composite (has/inside) in raw YAML
# console.log inside a class method
pattern: console.log($$$)
inside:
kind: method_definition
stopBy: end
Use kind: directly when you want to match a node type without a pattern:
# any arrow function
kind: arrow_function
Common Gotchas
❌ $VAR inside quotes — matches literal "$VAR", not a metavar
from "$PATH" → use grep for wildcard path matching
from "./utils" → ✅ exact string literal works fine
❌ Trailing comma in objects
{ type: $T, } → use { type: $T }
❌ Shorthand property mismatch
{ runnerId: $RID } → won't match { runnerId }
use { runnerId } or { runnerId, $$$REST }
❌ Unnamed $$$ when you need the value
foo($$$) → captures nothing; use foo($$$ARGS) to inspect matches
❌ Multiple top-level statements — triggers "Multiple AST nodes are detected"
Two shapes, two fixes:
1. Sequence inside a block — wrap in braces:
foo(); bar(); → { foo(); bar(); }
2. Cross-context (module-level + block-level together, e.g. an import AND a call) —
wrapping in {} makes the pattern invalid (imports can't live inside a block).
Use two searches: find files containing the import, then scope the call search
to those paths. Or use a YAML `inside:`/`has:` rule (see Composite section above).
No matches?
- Try
strictness: relaxed— ignores unnamed punctuation (trailing commas, semicolons) thatsmartmode requires - Use
ast_dumpon a sample snippet to verify the correct node kind - Simplify the pattern and retry once
- Fall back to
greporlsp_navigation
Metavar captures appear automatically below each match line:
src/foo.ts:1:1: const x = foo(a, b)
$VAR=x $$$ARGS=a,b
Named captures ($X, $$$NAME) are shown; unnamed wildcards ($$$) are not.
Pagination — use skip: N when results are truncated (next-page hint appears in output).