ast-grep
Workflow
Use Tasks to track progress. Create a task for each step below (TaskCreate), mark each in_progress when starting and completed when done (TaskUpdate). Check TaskList after each step.
-
Write a test snippet representing the target code
-
Write the rule (start with pattern , escalate to kind
- has /inside if needed)
-
Test with --stdin before searching the codebase
-
Search the codebase once the rule matches
Critical Gotchas
Always use stopBy: end on relational rules
Without it, has /inside stop at the first non-matching node instead of traversing the full subtree:
WRONG — will miss deeply nested matches
has: pattern: await $EXPR
RIGHT
has: pattern: await $EXPR stopBy: end
Escape metavariables in shell
$VAR gets interpreted by the shell. Either escape or single-quote:
Double-quoted: escape with backslash
ast-grep scan --inline-rules "id: test language: javascript rule: pattern: await $EXPR" .
Single-quoted: no escaping needed
ast-grep scan --inline-rules 'id: test language: javascript rule: pattern: await $EXPR' .
Metavariables must be the sole content of an AST node
These don't work: obj.on$EVENT , "Hello $WORLD" , a $OP b , $jq
Use $$OP for unnamed nodes (operators, punctuation). Use $$$ARGS for zero-or-more nodes.
Testing with --stdin
echo "async function test() { await fetch(); }" | ast-grep scan --inline-rules 'id: test language: javascript rule: kind: function_declaration has: pattern: await $EXPR stopBy: end' --stdin
Debugging with --debug-query
When rules don't match, inspect the AST to find correct kind values:
ast-grep run --pattern 'your code here' --lang javascript --debug-query=cst
Formats: cst (all nodes), ast (named only), pattern (how ast-grep sees your pattern).
Rule syntax
See references/rule_reference.md for the full rule reference (atomic, relational, composite rules, and metavariables).