| name | ast-grep |
| description | Use this skill when working with ast-grep, an AST-based code search, lint, and rewrite tool. Activate when the user asks to search code patterns, refactor code structurally, create linting rules, or perform AST-based code analysis. Helps with pattern syntax, meta-variables, rule configuration, and CLI commands for Java, JavaScript, TypeScript, Python, Rust, Go, and 20+ other languages. Includes Java-specific patterns for annotations, null checks, exception handling, and Stream API usage. |
ast-grep Skill
You now have access to ast-grep, a powerful AST-based code search, lint, and rewrite tool. Use this skill when working with code analysis, refactoring, or pattern matching tasks.
What is ast-grep?
ast-grep is a command-line tool that searches, lints, and rewrites code using Abstract Syntax Trees (ASTs) rather than plain text. Think of it as "a hybrid of grep, eslint, and codemod."
Key advantages over text-based tools:
- Structurally aware: matches code patterns, not just text
- Language-aware: understands syntax across 20+ languages
- Precise: avoids false positives from string/comment matches
- Fast: written in Rust with multi-core support
Supported languages: C, C++, Rust, Go, Java, Python, C#, JavaScript, TypeScript, HTML, CSS, Kotlin, Swift, JSON, YAML, and more.
Core Commands
1. ast-grep run (Quick searches & rewrites)
# Basic pattern search (JavaScript)
ast-grep run -p 'console.log($MSG)' -l javascript
# Basic pattern search (Java)
ast-grep run -p 'System.out.println($MSG)' -l java
# Search and rewrite (JavaScript)
ast-grep run -p 'var $VAR = $VAL' -r 'let $VAR = $VAL' -l javascript
# Search and rewrite (Java)
ast-grep run -p 'new Date()' -r 'LocalDate.now()' -l java
# Apply all changes (use after user approval)
ast-grep run -p 'PATTERN' -r 'REWRITE' -U
# Get JSON output for analysis
ast-grep run -p 'PATTERN' --json
# From stdin
echo "var x = 1" | ast-grep run --stdin -l javascript -p 'var $V = $VAL'
2. ast-grep scan (Rule-based linting)
# Scan with all rules in project
ast-grep scan
# Use specific rule file
ast-grep scan -r path/to/rule.yml
# Filter rules by regex
ast-grep scan --filter 'no-console'
# Inline rule
ast-grep scan --inline-rules 'id: test
language: JavaScript
rule:
pattern: console.log($A)'
3. ast-grep test (Validate rules)
# Run tests for rules
ast-grep test
# Update all snapshots
ast-grep test -U
4. ast-grep new (Generate templates)
# Create new project structure
ast-grep new project
# Create new rule
ast-grep new rule my-rule
# Create new test
ast-grep new test my-test
Pattern Syntax
Meta Variables
Meta variables capture AST nodes and enable flexible pattern matching:
Format: $ + uppercase letters/underscores/digits
- Valid:
$VAR,$META_1,$_TEMP,$A - Invalid:
$invalid,$camelCase,$123
Usage:
// JavaScript Pattern: $OBJ.$METHOD($$$ARGS)
// Matches: user.save(data)
// api.call(x, y, z)
// obj.method()
// Java Pattern: $OBJ.$METHOD($$$ARGS)
// Matches: user.getName()
// list.add(item)
// stream.collect(Collectors.toList())
Capturing constraints: Reusing the same variable name ensures matched code is identical:
// Pattern: $X == $X
// Matches: a == a ✓
// Doesn't match: a == b ✗
Non-capturing variables: Prefix with _ to match without capturing (performance optimization):
// Pattern: $_OBJ.method()
// Matches method calls on any object without storing the object
Multi-Node Matching
Use $$$ to match zero or more AST nodes:
// Pattern: function $FUNC($$$PARAMS) { $$$ }
// Matches any function with any number of parameters
Named multi-node:
// Pattern: someFunc($$$ARGS)
// Captures all arguments as $$$ARGS
Pattern Types
1. Code Patterns (most common)
pattern: console.log($MSG)
2. Kind Patterns (match node types)
kind: function_declaration
3. Regex Patterns
regex: "^test_.*"
Rule Configuration
Rules are written in YAML with three essential fields:
id: unique-rule-identifier # Required
language: JavaScript # Required: determines which files to scan
rule: # Required: matching logic
pattern: console.log($A)
Rule Categories
1. Atomic Rules - Match single node properties
rule:
pattern: Promise.all($PROMISES)
2. Relational Rules - Match node relationships
rule:
pattern: await $EXPR
inside:
kind: function_declaration # Not inside async function
not:
has:
kind: async
3. Composite Rules - Combine multiple rules
rule:
all: # All conditions must match
- pattern: $VAR = $VAL
- not:
inside:
kind: function_declaration
any: # At least one must match
- pattern: var $V
- pattern: let $V
Rule Object Fields
| Field | Category | Purpose |
|---|---|---|
pattern |
Atomic | Match code patterns |
kind |
Atomic | Match AST node types |
regex |
Atomic | Match with regex |
inside |
Relational | Node must be inside matched pattern |
has |
Relational | Node must contain matched pattern |
follows |
Relational | Node must come after pattern |
precedes |
Relational | Node must come before pattern |
all |
Composite | All sub-rules must match (AND) |
any |
Composite | At least one sub-rule must match (OR) |
not |
Composite | Pattern must NOT match |
matches |
Utility | Reference other rules by ID |
Advanced Rule Example
id: no-await-in-promise-all
language: TypeScript
message: Avoid await inside Promise.all
note: Use Promise.all to parallelize async operations
severity: warning
rule:
pattern: Promise.all($PROMISES)
has:
pattern: await $_
stopBy: end # Stop searching at expression boundaries
fix: |
Remove await from $PROMISES
Java-Specific Patterns
Java has unique syntax features that require special handling in ast-grep. This section covers patterns specifically for Java development.
Working with Annotations
Annotations are a core Java feature but complicate simple pattern matching. Use structural rules with kind and has:
# Find all methods with @Deprecated annotation
id: find-deprecated-methods
language: Java
rule:
kind: method_declaration
has:
kind: marker_annotation
pattern: "@Deprecated"
# Find JUnit test methods without assertions
id: test-without-assertions
language: Java
rule:
kind: method_declaration
has:
kind: marker_annotation
pattern: "@Test"
not:
has:
any:
- pattern: assert$$$($$$)
- pattern: assertEquals($$$)
- pattern: assertTrue($$$)
message: Test method lacks assertions
Field Declarations with Modifiers
Critical Gotcha: Cannot use meta-variables in modifier positions!
# ❌ FAILS - Cannot parse $MOD as valid syntax
pattern: $MOD String $FIELD;
# ✓ CORRECT - Use structural targeting
id: find-string-fields
language: Java
rule:
kind: field_declaration
has:
field: type
regex: ^String$
# Find fields of specific type regardless of modifiers
id: find-list-fields
language: Java
rule:
kind: field_declaration
has:
pattern: List<$T> $NAME
Exception Handling
# Detect empty catch blocks
id: empty-catch-block
language: Java
rule:
kind: catch_clause
has:
pattern: |
catch ($E) {
}
message: Empty catch block - handle or log exception
severity: warning
# Find try blocks without catch or finally
id: incomplete-try
language: Java
rule:
kind: try_statement
not:
any:
- has:
kind: catch_clause
- has:
kind: finally_clause
message: Try statement must have catch or finally
severity: error
Null Safety Patterns
# Find potential NullPointerException risks
id: missing-null-check
language: Java
rule:
pattern: $OBJ.$METHOD($$$)
not:
any:
- inside:
pattern: if ($OBJ != null) { $$$ }
- inside:
pattern: if (Objects.nonNull($OBJ)) { $$$ }
- inside:
pattern: if (Objects.requireNonNull($OBJ)) { $$$ }
message: Potential NullPointerException - add null check
note: Consider using Optional or Objects.requireNonNull()
Optional Anti-patterns
# Detect Optional.get() without isPresent() check
id: optional-get-without-check
language: Java
rule:
pattern: $OPT.get()
not:
inside:
any:
- pattern: if ($OPT.isPresent()) { $$$ }
- pattern: $OPT.orElse($$$)
- pattern: $OPT.orElseGet($$$)
- pattern: $OPT.orElseThrow($$$)
message: Optional.get() called without isPresent() check
note: Use orElse(), orElseGet(), or orElseThrow() instead
severity: warning
Stream API Patterns
# Detect streams created but not consumed
id: stream-without-terminal
language: Java
rule:
pattern: $LIST.stream().$$$OPS
not:
has:
regex: "\\.(collect|forEach|reduce|count|findFirst|findAny|allMatch|anyMatch|noneMatch|toArray)\\("
message: Stream created but not consumed with terminal operation
# Warn about sequential operations that could be parallel
id: sequential-stream-on-large-collection
language: Java
rule:
all:
- pattern: $LARGE_LIST.stream().$$$
- has:
regex: "(filter|map|flatMap)"
message: Consider parallelStream() for large collections
note: Profile first to ensure parallel processing benefits outweigh overhead
severity: info
Resource Management
# Enforce try-with-resources for AutoCloseable
id: use-try-with-resources
language: Java
rule:
all:
- kind: local_variable_declaration
- has:
regex: "(Stream|Connection|Statement|Reader|Writer|InputStream|OutputStream|Scanner|BufferedReader)"
- not:
inside:
kind: resource_specification
message: Resource should be managed with try-with-resources
note: Ensures resources are closed even if exceptions occur
severity: warning
Security Patterns
# Detect potential SQL injection via string concatenation
id: sql-injection-risk
language: Java
rule:
all:
- pattern: $QUERY + $INPUT
- has:
kind: identifier
regex: "(?i)(query|sql|select|insert|update|delete)"
message: Potential SQL injection - use PreparedStatement
note: Never concatenate user input into SQL queries
severity: error
# Find hardcoded passwords or credentials
id: hardcoded-credentials
language: Java
rule:
kind: variable_declarator
has:
pattern: $VAR = "$VALUE"
has:
kind: identifier
regex: "(?i)(password|passwd|pwd|secret|key|token|credential)"
message: Hardcoded credential detected
note: Use environment variables or secure configuration
severity: error
Generics and Type Matching
# Find raw type usage (missing generics)
id: raw-type-usage
language: Java
rule:
pattern: List $VAR = new ArrayList()
message: Use generic types - List<Type> instead of raw List
fix: List<Object> $VAR = new ArrayList<>()
Java AST Node Types Reference
Common node types for structural rules:
Declarations:
class_declaration- Class definitionsinterface_declaration- Interface definitionsenum_declaration- Enum definitionsrecord_declaration- Record definitions (Java 14+)method_declaration- Method definitionsfield_declaration- Field/member variable definitionsconstructor_declaration- Constructor definitionslocal_variable_declaration- Local variable definitions
Statements:
try_statement- Try-catch blockstry_with_resources_statement- Try-with-resourcesif_statement- If conditionalsfor_statement- For loopsenhanced_for_statement- For-each loopswhile_statement- While loopssynchronized_statement- Synchronized blocksswitch_expression- Switch expressions (Java 12+)return_statement- Return statementsthrow_statement- Throw statements
Expressions:
method_invocation- Method callsobject_creation_expression- New object instantiationlambda_expression- Lambda expressionsmethod_reference- Method references (::)field_access- Field access (obj.field)array_access- Array indexingcast_expression- Type castsinstanceof_expression- instanceof checksternary_expression- Ternary operator (? :)
Annotations:
annotation- Annotations with valuesmarker_annotation- Annotations without values (@Override)
Generics:
type_arguments- Generic type argumentstype_parameters- Generic type parameterswildcard- Generic wildcards (? extends, ? super)
Java-Specific Gotchas
1. Modifier Patterns Don't Work
# ❌ Fails - ERROR node in AST
pattern: public static $TYPE $METHOD($$$)
# ✓ Use structural approach
rule:
kind: method_declaration
regex: "public.*static"
2. Annotations Break Simple Patterns
// Pattern: String $FIELD;
// Won't match: @NotNull String field;
// Reason: Annotation changes AST structure
// Solution: Use kind: field_declaration with has constraints
3. Generic Type Complexity
# Simple pattern may fail with complex generics
pattern: Map<String, List<Integer>> $VAR
# More reliable: Use kind + regex on type field
rule:
kind: local_variable_declaration
has:
field: type
regex: "^Map<"
4. Import Handling
# May need to match both forms
any:
- pattern: "@Test" # Import org.junit.Test
- pattern: "@org.junit.Test" # Fully qualified
5. Lambda vs Method Syntax
# Different AST structures
- kind: lambda_expression # () -> expr
- kind: method_reference # Class::method
# Must match separately!
Using ast-grep with Claude Code
IMPORTANT: This skill is designed for Claude Code's programmatic usage. Claude cannot use interactive mode.
Recommended Workflow
When using ast-grep through Claude Code, follow this pattern:
1. Search and Analyze
# Use --json to get structured output for analysis
ast-grep run -p 'console.log($A)' -l javascript --json
2. Present Findings to User Claude should review the JSON output and present findings to the user with:
- What was found
- Locations (file paths and line numbers)
- Proposed changes (if applicable)
3. Apply Changes (Only After User Approval)
# Use -U to apply all changes automatically
ast-grep run -p 'console.log($A)' -r 'logger.info($A)' -l javascript -U
DO NOT use --interactive flag - it requires human input and will fail in Claude Code.
Example Claude Code Workflow
# Step 1: Find all matches
ast-grep run -p 'var $VAR = $VAL' -l javascript --json
# Claude analyzes JSON, shows user: "Found 15 var declarations in 8 files"
# User says: "Please update them to let"
# Step 2: Apply changes with user approval
ast-grep run -p 'var $VAR = $VAL' -r 'let $VAR = $VAL' -l javascript -U
Best Practices
1. Always Use --json for Analysis
# Get structured output for Claude to parse
ast-grep run -p 'console.log($A)' -l javascript --json
ast-grep scan --json
2. Use the Right Tool for the Job
- Quick one-off searches:
ast-grep runwith--json - Recurring checks:
ast-grep scanwith rules - Code refactoring:
ast-grep runwith--rewriteand-U(after user approval) - CI/CD integration:
ast-grep scanwith JSON output
3. Leverage Relational Rules
Instead of complex regex, use AST relationships:
# Find console.log NOT inside try-catch
rule:
pattern: console.log($A)
not:
inside:
pattern: try { $$$ } catch ($E) { $$$ }
4. Test Rules Before Deploying
Always create tests for your rules:
# In rule-test.yml
id: no-console-log
testCases:
- id: should-match
match: console.log("test")
- id: should-not-match
match: logger.info("test")
Run: ast-grep test
5. Understand Language-Specific Patterns
Patterns must be valid code in the target language:
# JavaScript: snake_case and camelCase are different
pattern: my_function() # Won't match myFunction()
# Python: indentation matters for blocks
pattern: |
if $COND:
$BODY
Common Pitfalls & Solutions
Pitfall 1: Invalid Meta Variable Names
# ❌ Wrong: lowercase letters
pattern: $myVar = $value
# ✓ Correct: uppercase only
pattern: $MY_VAR = $VALUE
Pitfall 2: Language Mismatch
# ❌ Wrong: using Python syntax for JavaScript
ast-grep run -p 'print($A)' -l javascript
# ✓ Correct: use console.log for JavaScript
ast-grep run -p 'console.log($A)' -l javascript
Pitfall 3: Forgetting --lang with stdin
# ❌ Wrong: ast-grep can't infer language
echo "code" | ast-grep run -p 'pattern'
# ✓ Correct: specify language
echo "code" | ast-grep run -p 'pattern' --stdin -l python
Pitfall 4: Overly Broad Patterns
# ❌ Too broad: matches everything
pattern: $A
# ✓ Specific: matches the structure you want
pattern: if ($COND) { $BODY }
Pitfall 5: Not Using stopBy in Relational Rules
# Without stopBy, search is unlimited (slow & imprecise)
rule:
pattern: Promise.all($A)
has:
pattern: await $_
stopBy: end # Stop at expression boundaries for performance
Useful Flags
| Flag | Purpose | Example | Claude Code Usage |
|---|---|---|---|
-p, --pattern |
Search pattern | -p 'console.log($A)' |
✓ Use |
-r, --rewrite |
Replacement code | -r 'logger.info($A)' |
✓ Use |
-l, --lang |
Specify language | -l javascript |
✓ Use |
--json |
Machine-readable output | --json |
✓ Always use for analysis |
-U, --update-all |
Apply all changes | -U |
✓ Use after user approval |
--stdin |
Read from stdin | --stdin |
✓ Use when needed |
--debug-query |
Debug pattern matching | --debug-query |
✓ Use for troubleshooting |
-j, --threads |
Control parallelization | -j 4 |
✓ Use |
-i, --interactive |
Manual review of each change | --interactive |
✗ DO NOT USE - requires human input |
Integration Examples
With jq (JSON processing)
ast-grep scan --json | jq '.[] | select(.severity == "error")'
With git (changed files only)
git diff --name-only | xargs ast-grep run -p 'pattern'
CI/CD Integration
# Exit with error if issues found
ast-grep scan --json > results.json
if [ $(jq length results.json) -gt 0 ]; then
exit 1
fi
When to Use ast-grep
✓ Use ast-grep for:
- Code refactoring across multiple files
- Finding complex code patterns (nested structures, specific contexts)
- Enforcing code standards (custom linting rules)
- Language-aware code search
- Safe automated code transformations
✗ Don't use ast-grep for:
- Simple string searches (use grep/ripgrep)
- Comment/documentation searches
- Binary file searches
- When exact character positions matter more than syntax
Quick Reference
# Search for a pattern
ast-grep run -p 'PATTERN' -l LANG [FILES]
# Search and replace
ast-grep run -p 'PATTERN' -r 'REPLACEMENT' -l LANG
# Scan with rules
ast-grep scan [--rule RULE_FILE]
# Test rules
ast-grep test
# Generate completions
ast-grep completions bash > ~/.bash_completion.d/ast-grep
Additional Resources
- Online Playground: https://ast-grep.github.io/playground.html
- Documentation: https://ast-grep.github.io/
- Pattern Catalog: Explore pre-built patterns for common tasks
- Language Reference: Check language-specific node types and syntax
Workflow for Complex Refactoring (Claude Code)
- Explore: Use
ast-grep run -p 'pattern' --jsonto find all matches - Analyze: Parse JSON output and present findings to user
- Test: Create a rule with tests:
ast-grep new rule my-rule - Apply: After user approval, use
-Uto apply all changes - Validate: Run tests and build to ensure correctness
Remember: ast-grep operates on AST structure, not text. Always think in terms of code syntax, not string patterns.