| created | "2025-12-16T00:00:00.000Z" |
| modified | "2025-12-16T00:00:00.000Z" |
| reviewed | "2025-12-16T00:00:00.000Z" |
| name | ast-grep Search |
| description | AST-based code search using ast-grep for structural pattern matching. Use when searching for code patterns, refactoring, or performing semantic code analysis across multiple languages. |
| allowed-tools | Bash, Read, Grep, Glob |
ast-grep Search
Expert knowledge for using ast-grep as a powerful AST-based code search and refactoring tool with structural pattern matching across 20+ programming languages.
Core Expertise
ast-grep Advantages
- AST-based matching (more precise than text-based tools)
- Extremely fast (written in Rust, multi-core processing)
- Supports 20+ languages via tree-sitter
- Pattern code syntax (write code to match code)
- Built-in rewriting capabilities
- Interactive mode for safe transformations
- Language server protocol support
- YAML-based rule configuration for custom linting
Supported Languages
JavaScript, TypeScript, Python, Java, Go, Rust, C++, C, C#, Ruby, PHP, Swift, Kotlin, Scala, HTML, CSS, YAML, JSON, and more.
Basic Usage
Simple Pattern Search
ast-grep -p 'console.log($MSG)' --lang js
ast-grep -p 'function $NAME($$$ARGS) { $$$ }' --lang js
ast-grep -p 'def $FUNC($$$): $$$' --lang py
ast-grep -p 'import $PKG' src/
ast-grep -p 'class $NAME:' tests/ --lang py
Pattern Syntax
Meta Variables (Wildcards)
$VAR - Match any single AST node (named node)
$$VAR - Match any single unnamed node
$$$ARGS - Match zero or more nodes (e.g., function arguments)
$_ - Match single node without capturing
Naming Rules
- Must start with
$
- Followed by uppercase letters, underscores, or digits
- Valid:
$MATCH, $META_VAR, $VAR1, $_, $_123
- Invalid:
$invalid, $Svalue, $KEBAB-CASE
Language Selection
ast-grep -p 'pattern' --lang js
ast-grep -p 'pattern' --lang py
ast-grep -p 'pattern' --lang rs
ast-grep -p 'pattern' --lang go
Advanced Pattern Matching
Capturing and Reusing Variables
ast-grep -p '$A == $A' --lang js
ast-grep -p 'if ($COND) { $$$ } else if ($COND) { $$$ }' --lang js
ast-grep -p '$_VAR == $_VAR' --lang js
Multi-node Matching
ast-grep -p 'console.log($$$)' --lang js
ast-grep -p 'function $NAME($$$PARAMS) { $$$BODY }' --lang js
ast-grep -p 'try { $$$ } catch ($ERR) { $$$ }' --lang js
Nested Patterns
ast-grep -p 'React.useState($$$)' --lang jsx
ast-grep -p '$OBJ.$METHOD1().$METHOD2()' --lang js
ast-grep -p "import { $$$IMPORTS } from '$PKG'" --lang js
Code Search and Rewrite
Search and Replace
ast-grep -p 'var $VAR = $VAL' -r 'let $VAR = $VAL' --lang js
ast-grep -p 'function($$$ARGS) { $$$BODY }' \
-r '($$$ARGS) => { $$$BODY }' --lang js
ast-grep -p 'oldAPI($$$ARGS)' -r 'newAPI($$$ARGS)' --lang py
Interactive Mode
ast-grep -p 'var $V = $X' -r 'let $V = $X' -i --lang js
ast-grep -p 'console.log($$$)' -r '// removed log' -U --lang js
Dry Run and Preview
ast-grep -p 'pattern' -r 'replacement' --lang js
ast-grep -p 'pattern' -r 'replacement' -U --lang js
Command-Line Options
Main Commands
ast-grep run - One-time search or rewrite (default)
ast-grep run -p 'pattern' [PATHS]
ast-grep -p 'pattern' -r 'rewrite' -l js src/
ast-grep scan - Scan using YAML configuration
ast-grep scan
ast-grep scan -c sgconfig.yml
ast-grep scan -r rule-name
ast-grep scan --filter 'console'
ast-grep scan --inline-rules 'rule.yml'
ast-grep test - Test ast-grep rules
ast-grep test
ast-grep test -c custom-config.yml
ast-grep test --snapshot-dir snapshots/
ast-grep new - Create new project/rules/tests
ast-grep new project my-linter
ast-grep new rule no-console-log
ast-grep new test test-suite
ast-grep new util common-patterns
ast-grep lsp - Language server for editor integration
ast-grep lsp
Common Flags
| Flag | Purpose | Example |
|---|
-p, --pattern | Search pattern | ast-grep -p 'console.log($MSG)' |
-r, --rewrite | Replacement pattern | ast-grep -p 'var $V' -r 'let $V' |
-l, --lang | Target language | ast-grep -p 'pattern' -l js |
-i, --interactive | Interactive mode | ast-grep -p 'old' -r 'new' -i |
-U, --update-all | Auto-apply all changes | ast-grep -p 'old' -r 'new' -U |
--json | JSON output | ast-grep -p 'pattern' --json |
-A, -B, -C | Context lines | ast-grep -p 'pattern' -A 3 |
--color | Color output | ast-grep -p 'pattern' --color always |
--heading | Group by file | ast-grep -p 'pattern' --heading |
--debug-query | Debug pattern parsing | ast-grep -p 'pattern' --debug-query |
Output Formats
ast-grep -p 'pattern'
ast-grep -p 'pattern' --json
ast-grep -p 'pattern' --json=pretty
ast-grep -p 'pattern' --json=stream
ast-grep -p 'pattern' --json=compact
YAML Rule Configuration
Rule File Structure
A complete ast-grep rule file contains these sections:
id: rule-identifier
language: JavaScript
rule:
pattern: console.log($$$)
---
id: no-await-in-promise-all
language: TypeScript
severity: error
message: Avoid await inside Promise.all
note: |
Using await inside Promise.all defeats the purpose of parallel execution.
Extract async operations before Promise.all.
url: https://docs.example.com/no-await-promise-all
rule:
pattern: Promise.all($ARGS)
has:
pattern: await $_
stopBy: end
constraints:
ARGS:
regex: '^\[.*\]$'
utils:
is-promise:
pattern: Promise.$METHOD($$$)
transform:
VAR:
substring:
source: $ARG
startChar: 0
endChar: -1
fix: |
const results = await Promise.all($ARGS)
labels:
- label: problematic await
source: await $_
files:
- '**/*.ts'
- '**/*.tsx'
ignores:
- '**/*.test.ts'
- '**/node_modules/**'
metadata:
category: async
tags: [performance, best-practices]
Rule Types
Atomic Rules - Match individual AST nodes
rule:
pattern: console.log($$$)
rule:
kind: function_declaration
has:
field: name
regex: '^test_'
rule:
regex: 'TODO|FIXME|XXX'
Relational Rules - Match node relationships
rule:
pattern: Promise.all($ARGS)
has:
pattern: await $_
stopBy: end
rule:
pattern: await $_
inside:
pattern: Promise.all($$$)
rule:
pattern: $A
follows:
pattern: $B
rule:
pattern: $A
precedes:
pattern: $B
Composite Rules - Combine multiple rules
rule:
all:
- pattern: function $NAME($$$) { $$$ }
- not:
has:
pattern: return $$$
- inside:
kind: class_declaration
rule:
any:
- pattern: var $VAR = $$$
- pattern: let $VAR = $$$
rule:
pattern: function $NAME($$$) { $$$ }
not:
has:
pattern: return $$$
rule:
pattern: $CALL($$$)
matches: is-console-method
Utility Rules - Reusable patterns
utils:
is-console-method:
kind: call_expression
has:
field: function
pattern: console.$METHOD
is-async-function:
any:
- pattern: async function $NAME($$$) { $$$ }
- pattern: async ($$$) => $$$
has-side-effect:
any:
- matches: is-console-method
- pattern: $OBJ.$MUTATE($$$)
- pattern: $VAR = $$$
rule:
pattern: $EXPR
matches: has-side-effect
Constraints and Transformations
Constraints - Filter meta-variables by conditions
rule:
pattern: if ($COND) { $$$ }
constraints:
COND:
regex: '^true$|^false$'
COND:
kind: binary_expression
COND:
pattern: $A == $B
Transformations - Manipulate captured variables
transform:
NEW_NAME:
replace:
source: $OLD_NAME
replace: 'Test'
by: 'Spec'
TRIMMED:
substring:
source: $TEXT
startChar: 1
endChar: -1
UPPER:
convert:
source: $NAME
toCase: upperCase
LOWER:
convert:
source: $NAME
toCase: lowerCase
fix: |
describe($NEW_NAME, () => {
$$$TESTS
})
File Globbing
Control which files rules apply to:
files:
- 'src/**/*.ts'
- 'src/**/*.tsx'
- '!src/**/*.test.ts'
ignores:
- '**/node_modules/**'
- '**/dist/**'
- '**/*.min.js'
- '**/coverage/**'
Multiple Rules in One File
Separate rules with ---:
id: no-var
language: JavaScript
severity: error
message: Use let or const instead of var
rule:
pattern: var $VAR = $$$
fix: const $VAR = $$$
---
id: no-console
language: JavaScript
severity: warning
message: Remove console statements
rule:
pattern: console.$METHOD($$$)
Rule Testing
Test File Structure
Create test files in the same directory as rules:
id: no-console-test
testCases:
- id: no-console-in-code
valid:
- console.error('error')
- const x = 'console.log'
- console.log('test')
- console.log(x, y, z)
- id: test-with-fix
input: |
var x = 1;
var y = 2;
output: |
const x = 1;
const y = 2;
Running Tests
ast-grep test
ast-grep test -c sgconfig.yml
ast-grep test --update-all
ast-grep test --test-dir tests/
ast-grep test --snapshot-dir snapshots/
Test Workflow
ast-grep new rule no-console-log
cat > rules/no-console-log.yml <<EOF
id: no-console-log
language: JavaScript
message: Remove console.log statements
severity: warning
rule:
pattern: console.log(\$\$\$)
fix: ''
EOF
cat > rules/no-console-log-test.yml <<EOF
id: no-console-log-test
testCases:
- id: basic-test
valid:
- console.error('error')
- const log = console.log
invalid:
- console.log('test')
- console.log(x, y)
EOF
ast-grep test
ast-grep scan -r no-console-log
Project Configuration
Initialize a Project
ast-grep new project my-linter
Project Config (sgconfig.yml)
ruleDirs:
- rules
- custom-rules
utilDirs:
- utils
testConfigs:
testDir: rules
snapshotDir: __snapshots__
languageGlobs:
- language: TypeScript
extensions: [ts, tsx, cts, mts]
- language: JavaScript
extensions: [js, jsx, cjs, mjs]
customLanguages:
- libraryPath: ./my-parser.so
language: MyLanguage
extensions: [mylang]
Language-Specific Patterns
JavaScript/TypeScript
ast-grep -p 'const [$STATE, $SETTER] = useState($INIT)' --lang jsx
ast-grep -p 'async function $NAME($$$) { $$$ }' --lang js
ast-grep -p 'class $CLASS { $METHOD($$$) { $$$ } }' --lang ts
ast-grep -p "import $NAME from '$PKG'" --lang js
ast-grep -p 'await $PROMISE' --lang ts
ast-grep -p '$EXPR as $TYPE' --lang ts
Python
ast-grep -p 'class $NAME($$$BASES): $$$' --lang py
ast-grep -p '@$DECORATOR\ndef $FUNC($$$): $$$' --lang py
ast-grep -p '[$EXPR for $VAR in $ITER]' --lang py
ast-grep -p 'with $RESOURCE as $VAR: $$$' --lang py
ast-grep -p 'f"$$$"' --lang py
ast-grep -p 'def $NAME($$$) -> $TYPE: $$$' --lang py
Rust
ast-grep -p 'fn $NAME($$$) -> $RET { $$$ }' --lang rs
ast-grep -p 'struct $NAME { $$$FIELDS }' --lang rs
ast-grep -p 'impl $TRAIT for $TYPE { $$$ }' --lang rs
ast-grep -p 'unsafe { $$$ }' --lang rs
ast-grep -p '$MACRO!($$$)' --lang rs
ast-grep -p "fn $NAME<'$LIFE>($$$) { $$$ }" --lang rs
Go
ast-grep -p 'func $NAME($$$) $RET { $$$ }' --lang go
ast-grep -p 'type $NAME struct { $$$FIELDS }' --lang go
ast-grep -p 'defer $FUNC($$$)' --lang go
ast-grep -p 'go $FUNC($$$)' --lang go
ast-grep -p 'type $NAME interface { $$$METHODS }' --lang go
ast-grep -p 'if err != nil { $$$ }' --lang go
Practical Use Cases
Code Quality and Linting
ast-grep -p 'console.log($$$)' --lang js
ast-grep -p '// TODO: $$$' --lang js
ast-grep -p 'if ($VAR > 100)' --lang py
ast-grep -p 'function $NAME($A, $B, $C, $D, $E, $$$)' --lang js
ast-grep -p 'try { $$$ } catch ($E) { }' --lang js
ast-grep -p 'import $NAME from $PKG' --lang js
Security Analysis
ast-grep -p 'eval($$$)' --lang js
ast-grep -p '"SELECT * FROM " + $VAR' --lang py
ast-grep -p 'password = $$$' --lang py
ast-grep -p 'os.system($$$)' --lang py
ast-grep -p '$ELEM.innerHTML = $$$' --lang js
ast-grep -p 'document.write($$$)' --lang js
Refactoring Patterns
ast-grep -p 'var $V = $X' -r 'const $V = $X' --lang js -U
ast-grep -p 'function($$$ARGS) { return $EXPR }' \
-r '($$$ARGS) => $EXPR' --lang js -i
ast-grep -p "import $NAME from '@old/$PATH'" \
-r "import $NAME from '@new/$PATH'" --lang ts -U
ast-grep -p 'oldAPI.$METHOD($$$)' \
-r 'newAPI.$METHOD($$$)' --lang py -i
ast-grep -p '$FUNC($$$, function($ERR, $DATA) { $$$ })' --lang js
ast-grep -p 'class $NAME extends React.Component { $$$ }' --lang jsx
Finding Definitions
ast-grep -p 'function $NAME($$$) { $$$ }' --lang js
ast-grep -p 'def $NAME($$$): $$$' --lang py
ast-grep -p 'fn $NAME($$$) { $$$ }' --lang rs
ast-grep -p 'class $NAME { $$$ }' --lang js
ast-grep -p 'class $NAME: $$$' --lang py
ast-grep -p 'struct $NAME { $$$ }' --lang rs
ast-grep -p 'interface $NAME { $$$FIELDS }' --lang ts
ast-grep -p 'export function $NAME($$$) { $$$ }' --lang js
ast-grep -p 'pub fn $NAME($$$) { $$$ }' --lang rs
Finding Usage
ast-grep -p '$FUNC($$$)' --lang js
ast-grep -p '$OBJ.$METHOD($$$)' --lang py
ast-grep -p 'requests.get($$$)' --lang py
ast-grep -p 'axios.post($$$)' --lang js
ast-grep -p '<$COMPONENT $$$>$$$</$COMPONENT>' --lang jsx
ast-grep -p 'use$HOOK($$$)' --lang jsx
Migration and Deprecation
ast-grep -p 'React.createClass($$$)' --lang jsx
ast-grep -p 'componentWillMount($$$) { $$$ }' --lang jsx
ast-grep -p '$PROMISE.done($$$)' --lang js
ast-grep -p "import _ from 'lodash'" --lang js
ast-grep -p 'React.findDOMNode($$$)' \
-r 'ref.current' --lang jsx -i
Advanced YAML Rule Examples
Complex Linting Rule
id: no-nested-ternary
language: JavaScript
severity: warning
message: Avoid nested ternary expressions
note: |
Nested ternary operators reduce code readability.
Consider using if-else statements or early returns.
rule:
pattern: $A ? $B : $C
any:
- has:
pattern: $X ? $Y : $Z
field: consequent
- has:
pattern: $X ? $Y : $Z
field: alternate
files:
- 'src/**/*.js'
- 'src/**/*.ts'
ignores:
- '**/*.test.js'
Security Rule with Fix
id: no-eval
language: JavaScript
severity: error
message: Never use eval() - it's a security risk
url: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
rule:
any:
- pattern: eval($CODE)
- pattern: new Function($$$ARGS, $CODE)
- pattern: setTimeout($STRING, $$$)
- pattern: setInterval($STRING, $$$)
constraints:
CODE:
kind: string
STRING:
kind: string
labels:
- label: dangerous eval usage
source: eval($CODE)
fix: |
// FIXME: Replace eval with safe alternative
$CODE
Refactoring Rule with Transformation
id: modernize-var-declarations
language: JavaScript
severity: info
message: Use const for immutable variables
rule:
pattern: var $VAR = $INIT
constraints:
VAR:
regex: '^[A-Z_]+$'
transform:
CONST_NAME:
convert:
source: $VAR
toCase: upperCase
fix: const $VAR = $INIT
utils:
is-reassigned:
pattern: $VAR = $$$
inside:
kind: block
Performance Rule
id: no-array-in-loop
language: JavaScript
severity: warning
message: Avoid creating arrays inside loops
note: Creating arrays in loops can cause performance issues
rule:
all:
- pattern: '[$$$]'
- inside:
any:
- kind: for_statement
- kind: while_statement
- kind: do_statement
- not:
inside:
kind: function_declaration
stopBy: neighbor
files:
- '**/*.js'
ignores:
- '**/*.test.js'
Integration with Other Tools
Pipe to other commands
ast-grep -p 'pattern' --json=stream | wc -l
ast-grep -p 'pattern' --json=stream | jq -r '.file' | sort -u
ast-grep -p 'TODO' --json=stream | jq -r '.file' | xargs nvim
ast-grep -p 'function $NAME($$$) { $$$ }' --lang js | grep -i 'async'
ast-grep -p 'pattern' --json=stream | \
jq '{file: .file, line: .range.start.line, match: .text}'
Combine with fd/find
fd -e js -e ts | xargs ast-grep -p 'pattern'
git ls-files '*.py' | xargs ast-grep -p 'pattern'
git diff --name-only | xargs ast-grep -p 'console.log($$$)' --lang js
CI/CD Integration
- name: Lint with ast-grep
run: |
ast-grep scan --json > results.json
if [ -s results.json ]; then
echo "Linting errors found"
cat results.json | jq '.'
exit 1
fi
ast-grep scan --json > /tmp/ast-grep-results.json
if [ -s /tmp/ast-grep-results.json ]; then
echo "ast-grep violations found:"
cat /tmp/ast-grep-results.json | jq -r '.[] | "\(.file):\(.range.start.line) - \(.message)"'
exit 1
fi
Editor Integration
ast-grep lsp
let g:ale_linters = {'javascript': ['ast-grep']}
ast-grep -p 'pattern' -r 'replacement' -U --lang js %
Performance Tips
Fast Searches
- Specify language explicitly with
-l/--lang flag
- Narrow search paths (e.g.,
src/ instead of .)
- Use simpler patterns when possible
- Leverage multi-core processing (default)
- Use
files and ignores in YAML rules
Large Codebases
- Use
--json=stream for incremental processing
- Combine with
fd to filter files first
- Use configuration files instead of CLI for complex rules
- Consider running on subsets and combining results
- Cache results when possible
Rule Optimization
- Use
kind matching when possible (faster than pattern)
- Place most specific rules first in
any blocks
- Use
stopBy in relational rules to limit search depth
- Use specific patterns instead of broad
$$$ wildcards
- Use utility rules to centralize common patterns
Best Practices
When to Use ast-grep
- Structural code refactoring across files
- Finding complex code patterns and anti-patterns
- Multi-language code analysis
- Precise code transformations
- Custom linting rules
- Semantic code search
- Migration and deprecation tasks
When to Use grep/ripgrep Instead
- Simple text search
- Searching non-code files (logs, docs)
- Literal string matching
- When language is unknown
- Quick one-off searches without structural requirements
Rule Writing Best Practices
- Start with simple patterns, add complexity incrementally
- Test rules with
ast-grep test before deploying
- Use descriptive IDs and helpful messages
- Provide documentation URLs for complex rules
- Use utility rules for common patterns
- Test fix patterns in interactive mode first
- Add constraints to reduce false positives
- Use the playground for pattern development
Common Mistakes to Avoid
- Forgetting to specify language (
--lang)
- Using too generic patterns that match unintended code
- Not testing rewrites in interactive mode first
- Forgetting that
$VAR matches single nodes (use $$$ for multiple)
- Not escaping special characters in patterns
- Modifying files without backup (
-U without testing)
- Writing patterns that match comments as code
- Not using
stopBy in relational rules (too broad matching)
Testing and Validation
- Always create test cases for rules
- Test both valid and invalid code
- Use snapshot testing for complex transformations
- Run tests in CI/CD pipeline
- Validate rules on real codebases before deploying
- Use
--debug-query to understand pattern parsing
Debugging and Development
Debug Pattern Matching
ast-grep -p 'pattern' --debug-query --lang js
ast-grep -p '.' --debug-query --lang js file.js
ast-grep -p 'pattern' -vv --lang js
Interactive Playground
Use the online playground for rapid development:
Local Development Workflow
ast-grep new project my-rules
ast-grep new rule my-rule
$EDITOR rules/my-rule.yml
ast-grep test -u
ast-grep test
ast-grep scan -r my-rule src/
ast-grep scan -r my-rule -i
Quick Reference
Essential Pattern Syntax
| Pattern | Matches | Example |
|---|
$VAR | Single named AST node | console.log($MSG) |
$$VAR | Single unnamed node | $$EXPR |
$$$ARGS | Zero or more nodes | func($$$ARGS) |
$_ | Unnamed capture | $_ == $_ |
$A == $A | Identical captures | x == x (not x == y) |
$_VAR | Non-capturing (any match) | $_A == $_A matches x == y |
Common Language Codes
| Code | Language |
|---|
js | JavaScript |
ts | TypeScript |
jsx | JavaScript (JSX) |
tsx | TypeScript (JSX) |
py | Python |
rs | Rust |
go | Go |
java | Java |
cpp | C++ |
c | C |
rb | Ruby |
php | PHP |
cs | C# |
swift | Swift |
kt | Kotlin |
Rule Composition Cheat Sheet
rule:
pattern: code_pattern
kind: node_type
regex: text_pattern
rule:
has:
pattern: child_pattern
inside:
pattern: parent_pattern
follows:
pattern: previous_pattern
precedes:
pattern: next_pattern
rule:
all: [rule1, rule2]
any: [rule1, rule2]
not: rule
matches: util_rule
constraints:
VAR:
regex: pattern
kind: node_type
pattern: structure
Common Workflow Commands
ast-grep -p 'pattern' --lang js src/
ast-grep -p 'old' -r 'new' --lang py
ast-grep -p 'old' -r 'new' -i --lang js
ast-grep -p 'old' -r 'new' -U --lang ts
ast-grep scan -c sgconfig.yml
ast-grep test
ast-grep new rule rule-name
ast-grep -p 'pattern' --debug-query --lang js
Resources and Links
This makes ast-grep an invaluable tool for precise, AST-based code search and transformation across polyglot codebases, with powerful YAML-based rule configuration for custom linting and refactoring workflows.