Back to skills

Ast Grep

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.

11 stars
0 votes
0 copies
0 views
Added 12/19/2025
developmentjavascripttypescriptpythonrustgojavaswiftkotlinc++c#

Works with

claude codeterminalcliapi

Install via CLI

$openskills install towry/dots
Download Zip
Files
SKILL.md
---
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)

```bash
# 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)

```bash
# 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)

```bash
# Run tests for rules
ast-grep test

# Update all snapshots
ast-grep test -U
```

### 4. ast-grep new (Generate templates)

```bash
# 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
// JavaScript Pattern: $OBJ.$METHOD($$$ARGS)
// Matches: user.save(data)
//          api.call(x, y, z)
//          obj.method()
```

```java
// 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:

```javascript
// Pattern: $X == $X
// Matches: a == a ✓
// Doesn't match: a == b ✗
```

**Non-capturing variables:** Prefix with `_` to match without capturing (performance optimization):

```javascript
// Pattern: $_OBJ.method()
// Matches method calls on any object without storing the object
```

### Multi-Node Matching

Use `$$$` to match zero or more AST nodes:

```javascript
// Pattern: function $FUNC($$$PARAMS) { $$$ }
// Matches any function with any number of parameters
```

**Named multi-node:**

```javascript
// Pattern: someFunc($$$ARGS)
// Captures all arguments as $$$ARGS
```

### Pattern Types

**1. Code Patterns** (most common)

```yaml
pattern: console.log($MSG)
```

**2. Kind Patterns** (match node types)

```yaml
kind: function_declaration
```

**3. Regex Patterns**

```yaml
regex: "^test_.*"
```

## Rule Configuration

Rules are written in YAML with three essential fields:

```yaml
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

```yaml
rule:
  pattern: Promise.all($PROMISES)
```

**2. Relational Rules** - Match node relationships

```yaml
rule:
  pattern: await $EXPR
  inside:
    kind: function_declaration # Not inside async function
    not:
      has:
        kind: async
```

**3. Composite Rules** - Combine multiple rules

```yaml
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

```yaml
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`:

```yaml
# Find all methods with @Deprecated annotation
id: find-deprecated-methods
language: Java
rule:
  kind: method_declaration
  has:
    kind: marker_annotation
    pattern: "@Deprecated"
```

```yaml
# 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!

```yaml
# ❌ 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$
```

```yaml
# 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

```yaml
# 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
```

```yaml
# 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

```yaml
# 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

```yaml
# 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

```yaml
# 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
```

```yaml
# 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

```yaml
# 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

```yaml
# 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
```

```yaml
# 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

```yaml
# 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 definitions
- `interface_declaration` - Interface definitions
- `enum_declaration` - Enum definitions
- `record_declaration` - Record definitions (Java 14+)
- `method_declaration` - Method definitions
- `field_declaration` - Field/member variable definitions
- `constructor_declaration` - Constructor definitions
- `local_variable_declaration` - Local variable definitions

**Statements:**

- `try_statement` - Try-catch blocks
- `try_with_resources_statement` - Try-with-resources
- `if_statement` - If conditionals
- `for_statement` - For loops
- `enhanced_for_statement` - For-each loops
- `while_statement` - While loops
- `synchronized_statement` - Synchronized blocks
- `switch_expression` - Switch expressions (Java 12+)
- `return_statement` - Return statements
- `throw_statement` - Throw statements

**Expressions:**

- `method_invocation` - Method calls
- `object_creation_expression` - New object instantiation
- `lambda_expression` - Lambda expressions
- `method_reference` - Method references (::)
- `field_access` - Field access (obj.field)
- `array_access` - Array indexing
- `cast_expression` - Type casts
- `instanceof_expression` - instanceof checks
- `ternary_expression` - Ternary operator (? :)

**Annotations:**

- `annotation` - Annotations with values
- `marker_annotation` - Annotations without values (@Override)

**Generics:**

- `type_arguments` - Generic type arguments <T>
- `type_parameters` - Generic type parameters
- `wildcard` - Generic wildcards (? extends, ? super)

### Java-Specific Gotchas

**1. Modifier Patterns Don't Work**

```yaml
# ❌ 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**

```java
// 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**

```yaml
# 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**

```yaml
# May need to match both forms
any:
  - pattern: "@Test" # Import org.junit.Test
  - pattern: "@org.junit.Test" # Fully qualified
```

**5. Lambda vs Method Syntax**

```yaml
# 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**

```bash
# 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)**

```bash
# 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

```bash
# 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

```bash
# 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 run` with `--json`
- **Recurring checks:** `ast-grep scan` with rules
- **Code refactoring:** `ast-grep run` with `--rewrite` and `-U` (after user approval)
- **CI/CD integration:** `ast-grep scan` with JSON output

### 3. Leverage Relational Rules

Instead of complex regex, use AST relationships:

```yaml
# 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:

```yaml
# 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:

```yaml
# 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

```bash
# ❌ Wrong: lowercase letters
pattern: $myVar = $value

# ✓ Correct: uppercase only
pattern: $MY_VAR = $VALUE
```

### Pitfall 2: Language Mismatch

```bash
# ❌ 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

```bash
# ❌ 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

```yaml
# ❌ Too broad: matches everything
pattern: $A

# ✓ Specific: matches the structure you want
pattern: if ($COND) { $BODY }
```

### Pitfall 5: Not Using stopBy in Relational Rules

```yaml
# 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)

```bash
ast-grep scan --json | jq '.[] | select(.severity == "error")'
```

### With git (changed files only)

```bash
git diff --name-only | xargs ast-grep run -p 'pattern'
```

### CI/CD Integration

```bash
# 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

```bash
# 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)

1. **Explore:** Use `ast-grep run -p 'pattern' --json` to find all matches
2. **Analyze:** Parse JSON output and present findings to user
3. **Test:** Create a rule with tests: `ast-grep new rule my-rule`
4. **Apply:** After user approval, use `-U` to apply all changes
5. **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.

Attribution

Comments (0)

No comments yet. Be the first to comment!