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.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
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.
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:
Supported languages: C, C++, Rust, Go, Java, Python, C#, JavaScript, TypeScript, HTML, CSS, Kotlin, Swift, JSON, YAML, and more.
# 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'
# 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)'
# Run tests for rules
ast-grep test
# Update all snapshots
ast-grep test -U
# 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
Meta variables capture AST nodes and enable flexible pattern matching:
Format: $ + uppercase letters/underscores/digits
$VAR, $META_1, $_TEMP, $A$invalid, $camelCase, $123Usage:
// 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
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
1. Code Patterns (most common)
pattern: console.log($MSG)
2. Kind Patterns (match node types)
kind: function_declaration
3. Regex Patterns
regex: "^test_.*"
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)
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
| 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 |
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 has unique syntax features that require special handling in ast-grep. This section covers patterns specifically for Java development.
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
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
# 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
# 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()
# 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
# 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
# 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
# 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
# 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<>()
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 definitionsStatements:
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 statementsExpressions:
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 arguments <T>type_parameters - Generic type parameterswildcard - Generic wildcards (? extends, ? super)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!
IMPORTANT: This skill is designed for Claude Code's programmatic usage. Claude cannot use interactive mode.
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:
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.
# 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
# Get structured output for Claude to parse
ast-grep run -p 'console.log($A)' -l javascript --json
ast-grep scan --json
ast-grep run with --jsonast-grep scan with rulesast-grep run with --rewrite and -U (after user approval)ast-grep scan with JSON outputInstead of complex regex, use AST relationships:
# Find console.log NOT inside try-catch
rule:
pattern: console.log($A)
not:
inside:
pattern: try { $$$ } catch ($E) { $$$ }
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
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
# ❌ Wrong: lowercase letters
pattern: $myVar = $value
# ✓ Correct: uppercase only
pattern: $MY_VAR = $VALUE
# ❌ 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
# ❌ 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
# ❌ Too broad: matches everything
pattern: $A
# ✓ Specific: matches the structure you want
pattern: if ($COND) { $BODY }
# Without stopBy, search is unlimited (slow & imprecise)
rule:
pattern: Promise.all($A)
has:
pattern: await $_
stopBy: end # Stop at expression boundaries for performance
| 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 |
ast-grep scan --json | jq '.[] | select(.severity == "error")'
git diff --name-only | xargs ast-grep run -p 'pattern'
# Exit with error if issues found
ast-grep scan --json > results.json
if [ $(jq length results.json) -gt 0 ]; then
exit 1
fi
✓ Use ast-grep for:
✗ Don't use ast-grep for:
# 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
ast-grep run -p 'pattern' --json to find all matchesast-grep new rule my-rule-U to apply all changesRemember: ast-grep operates on AST structure, not text. Always think in terms of code syntax, not string patterns.