Back
54/65
Lesson 54
7 min
ast-grep: Structural Search
Find and replace code by AST shape, not string matching — powers DCG and UBS
Ready to level up?
Mark complete to track your learning progress.
Find and replace code by AST shape, not string matching — powers DCG and UBS
Mark complete to track your learning progress.
Master structural code search with ast-grep — find patterns by AST shape, not string matching, for refactors, policy checks, and code analysis.
Traditional regex-based grep searches treat code as flat text. That means matches inside comments, strings, and unrelated contexts all show up as false positives. Renaming a function with sed can silently break imports, type annotations, and documentation in one pass.
ast-grep takes a different approach: it parses your code into an Abstract Syntax Tree (AST) and matches against the structural shape of the code. A pattern like useState($INIT) only matches actual function calls — never a comment that mentions useState or a string containing the word.
Rename APIs and change import forms with structural precision
Enforce coding patterns consistently across repositories
DCG and UBS use it internally for command and bug analysis
Understands 20+ languages including TypeScript, Rust, Python, and Go
ast-grep patterns use $NAME to capture a single AST node and $$$ARGS to capture zero or more nodes (variadic). The rest of the pattern is matched literally against the code structure.
# Find all function declarationsast-grep run -l TypeScript -p 'function $NAME($$$ARGS) { $$$BODY }'# Find useState callsast-grep run -l TypeScript -p 'useState($INIT)'# Find console.log statementsast-grep run -l JavaScript -p 'console.log($$$ARGS)'# Find async functionsast-grep run -l TypeScript -p 'async function $NAME($$$ARGS) { $$$BODY }'
ast-grep can do structural rewrites using the -r flag. Captured metavariables from the pattern are available in the replacement, so transformations preserve the original structure.
# Replace var with const (only where safe)ast-grep run -l JavaScript -p 'var $NAME = $VALUE' -r 'const $NAME = $VALUE'# Rename an API function across the codebaseast-grep run -l TypeScript -p 'oldApiName($$$ARGS)' -r 'newApiName($$$ARGS)'# Convert require to importast-grep run -l JavaScript -p 'const $NAME = require("$MOD")' -r 'import $NAME from "$MOD"'# Preview changes before applyingast-grep run -l TypeScript -p 'oldFn($$$)' -r 'newFn($$$)' --interactive
The power combo: use rg for speed to narrow candidate files, then pipe into ast-grep for structural precision. ripgrep scans millions of lines per second; ast-grep ensures only real matches survive.
# Step 1: ripgrep narrows candidate files (instant)rg -l -t ts 'useState' src/# Step 2: ast-grep matches structural pattern (precise)rg -l -t ts 'useState' src/ | xargs ast-grep run -l TypeScript -p 'useState($INIT)' --json# Real-world: find all React components using deprecated APIrg -l 'componentWillMount' src/ | xargs ast-grep run -l TypeScript -p 'componentWillMount() { $$$BODY }'# Fleet-wide: combine with ru for multi-repo searchru list --paths | xargs -I{} ast-grep run -l Rust -p 'unwrap()' {}
DCG uses ast-grep's Rust crates internally to parse commands and match destructive patterns at the AST level. UBS uses AST helpers for cross-language bug detection, including resource lifecycle analysis and type narrowing checks.
# DCG internally matches patterns like:# "rm -rf /" → filesystem.recursive_delete# "git push --force" → git.force_push# "DROP TABLE" → sql.destructive_ddl# Test what DCG would catch:dcg test "rm -rf /tmp/build" --explain# → ALLOWED: scoped to /tmp/build (not root)dcg test "rm -rf /" --explain# → BLOCKED: filesystem.recursive_delete (root path)# UBS uses AST analysis for deeper checks:ubs src/api.ts# → Detects unclosed database connections via AST lifecycle analysis
For reusable checks, define rules in YAML files and run them with ast-grep scan. A sgconfig.yml at your project root tells ast-grep where to find rule definitions.
# sgconfig.yml — project-level ast-grep rulesruleDirs:- ./rules# rules/no-console-log.ymlid: no-console-logmessage: Remove console.log before committingseverity: warninglanguage: TypeScriptrule:pattern: console.log($$$ARGS)# Run all project rulesast-grep scan# Output as JSON for CI integrationast-grep scan --json