
Ast Grep
- 279 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
ast-grep is a Claude Code skill that teaches YAML ast-grep rule authoring and debugging for developers who need structural code search, linting, and automated rewrites.
About
ast-grep is a pproenca/dot-skills package (version 1.0.1) that codifies community best practices for writing, reviewing, and optimizing ast-grep YAML rules across large repositories. The skill walks developers from a natural-language query through example code, pattern or relational rule design, meta-variable constraints, and CLI test commands before deployment. It flags pitfalls such as missing stopBy:end on relational rules, improper meta reuse, and risky rewrite semantics. ast-grep triggers on tasks involving pattern syntax, kind/has/inside queries, all/any/not composition, and code transformation pipelines. Developers reach for ast-grep when ripgrep text search is too brittle to find language constructs like async functions missing error handling or calls with specific parameter shapes.
- ast-grep
Ast Grep by the numbers
- 279 all-time installs (skills.sh)
- +7 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,394 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill ast-grepAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 279 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do you write ast-grep YAML rules correctly?
Use ast-grep for development tasks
Who is it for?
Developers authoring or reviewing ast-grep lint rules who need AST-accurate search and codemods beyond text grep.
Skip if: Teams that only need simple string search with ripgrep and have no structural refactor or lint automation requirements.
When should I use this skill?
A developer is writing, debugging, or reviewing ast-grep YAML rules for code search, linting, or transformation.
What you get
Validated ast-grep YAML rules, tested pattern matches, and documented rewrite fixes ready for sg scan or sg test.
- ast-grep YAML rule files
- tested sg scan results
By the numbers
- Packages version 1.0.1 with 46 rules across 8 prioritized categories
Files
ast-grep Community Best Practices
Comprehensive best practices guide for ast-grep rule writing and usage, maintained by the ast-grep community. Contains 46 rules across 8 categories, prioritized by impact to guide automated rule generation and code transformation.
When to Apply
Reference these guidelines when:
- Writing new ast-grep rules for linting or search
- Debugging patterns that don't match expected code
- Optimizing rule performance for large codebases
- Setting up ast-grep projects with proper organization
- Reviewing ast-grep rules for correctness and maintainability
General Workflow
Follow this workflow when creating ast-grep rules for code search:
Step 1: Understand the Query
Clarify what you want to find:
- Target programming language
- Edge cases to handle
- What to include vs exclude
Step 2: Create Example Code
Write a sample code snippet representing the desired match pattern.
Step 3: Write the ast-grep Rule
Choose the right approach:
- Use
patternfor simple structures - Use
kindwithhas/insidefor complex structures - Combine with
all,any, ornotfor compound queries - Always use `stopBy: end` for relational rules (
inside,has) to ensure complete search
Step 4: Test the Rule
# Inspect AST structure
ast-grep run --pattern '[code]' --lang [language] --debug-query=ast
# Test inline rule
echo "[code]" | ast-grep scan --inline-rules "[rule]" --stdin
# Test from file
ast-grep scan --rule [file.yml] [path]Step 5: Search the Codebase
Deploy the validated rule:
# Search with pattern (simple matches)
ast-grep run --pattern '[pattern]' --lang [language] [path]
# Search with rule file (complex queries)
ast-grep scan --rule [file.yml] [path]
# Apply fixes interactively
ast-grep scan --rule [file.yml] --interactive [path]Quick Tips
1. Always use `stopBy: end` - Ensures complete subtree traversal for relational rules 2. Start simple, add complexity - Begin with patterns, progress to kinds, then relational rules 3. Debug with AST inspection - Use --debug-query=ast to verify structure matching 4. Escape in inline rules - Use \$VAR or single quotes for shell commands 5. Test in playground first - Use https://ast-grep.github.io/playground.html for rapid iteration
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Pattern Correctness | CRITICAL | pattern- |
| 2 | Meta Variable Usage | CRITICAL | meta- |
| 3 | Rule Composition | HIGH | compose- |
| 4 | Constraint Design | HIGH | const- |
| 5 | Rewrite Correctness | MEDIUM-HIGH | rewrite- |
| 6 | Project Organization | MEDIUM | org- |
| 7 | Performance Optimization | MEDIUM | perf- |
| 8 | Testing & Debugging | LOW-MEDIUM | test- |
Quick Reference
1. Pattern Correctness (CRITICAL)
- `pattern-valid-syntax` - Use valid parseable code as patterns
- `pattern-language-aware` - Account for language-specific syntax differences
- `pattern-context-selector` - Use context and selector for code fragments
- `pattern-avoid-comments-strings` - Avoid matching inside comments and strings
- `pattern-strictness-levels` - Configure pattern strictness appropriately
- `pattern-kind-vs-pattern` - Choose kind or pattern based on specificity needs
- `pattern-debug-ast` - Use debug query to inspect AST structure
- `pattern-nthchild-matching` - Use nthChild for index-based positional matching
- `pattern-range-matching` - Use range for character position matching
2. Meta Variable Usage (CRITICAL)
- `meta-naming-convention` - Follow meta variable naming conventions
- `meta-single-node` - Match single AST nodes with meta variables
- `meta-reuse-binding` - Reuse meta variables to enforce equality
- `meta-underscore-noncapture` - Use underscore prefix for non-capturing matches
- `meta-named-vs-unnamed` - Use double dollar for unnamed node matching
- `meta-multi-match-lazy` - Understand multi-match variables are lazy
3. Rule Composition (HIGH)
- `compose-all-for-and-logic` - Use all for AND logic between rules
- `compose-any-for-or-logic` - Use any for OR logic between rules
- `compose-not-for-exclusion` - Use not for exclusion patterns
- `compose-inside-for-context` - Use inside for contextual matching
- `compose-has-for-children` - Use has for child node requirements
- `compose-matches-for-reuse` - Use matches for rule reusability
- `compose-precedes-follows` - Use precedes and follows for sequential positioning
- `compose-field-targeting` - Use field to target specific sub-nodes
4. Constraint Design (HIGH)
- `const-kind-filter` - Use kind constraints to filter meta variables
- `const-regex-filter` - Use regex constraints for text patterns
- `const-not-inside-not` - Avoid constraints inside not rules
- `const-pattern-constraint` - Use pattern constraints for structural filtering
- `const-post-match-timing` - Understand constraints apply after matching
5. Rewrite Correctness (MEDIUM-HIGH)
- `rewrite-preserve-semantics` - Preserve program semantics in rewrites
- `rewrite-meta-variable-reference` - Reference all necessary meta variables in fix
- `rewrite-transform-operations` - Use transform for complex rewrites
- `rewrite-test-before-deploy` - Test rewrites on representative code
- `rewrite-syntax-validity` - Ensure fix templates produce valid syntax
6. Project Organization (MEDIUM)
- `org-project-structure` - Use standard project directory structure
- `org-unique-rule-ids` - Use unique descriptive rule IDs
- `org-severity-levels` - Assign appropriate severity levels
- `org-file-filtering` - Use file filtering for targeted rules
- `org-message-clarity` - Write clear actionable messages
7. Performance Optimization (MEDIUM)
- `perf-specific-patterns` - Use specific patterns over generic ones
- `perf-stopby-boundaries` - Use stopBy to limit search depth
- `perf-thread-parallelism` - Leverage parallel scanning with threads
- `perf-avoid-regex-heavy` - Avoid heavy regex in hot paths
8. Testing & Debugging (LOW-MEDIUM)
- `test-valid-invalid-cases` - Write both valid and invalid test cases
- `test-snapshot-updates` - Use snapshot testing for fix verification
- `test-playground-first` - Test patterns in playground first
- `test-edge-cases` - Test edge cases and boundary conditions
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Full Compiled Document
- AGENTS.md - Complete compiled guide with all rules
Reference Files
| File | Description |
|---|---|
| AGENTS.md | Complete compiled guide with all rules |
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
Rule Title in Imperative Form
Brief explanation (1-3 sentences) of WHY this matters. Focus on the problem being solved and its impact.
Incorrect (description of the problem):
id: example-rule
language: javascript
rule:
pattern: bad_pattern($ARG)
# Comment explaining the cost/problemCorrect (description of the solution):
id: example-rule
language: javascript
rule:
pattern: good_pattern($ARG)
# Comment explaining the benefitWhen NOT to use this pattern:
- Exception 1
- Exception 2
Benefits:
- Benefit 1
- Benefit 2
Reference: Reference Title
{
"version": "1.1.6",
"organization": "ast-grep Community",
"technology": "ast-grep",
"date": "January 2026",
"abstract": "Comprehensive best practices guide for ast-grep rule writing and usage, designed for AI agents and LLMs. Contains 46 rules across 8 categories, prioritized by impact from critical (pattern correctness, meta variable usage) to incremental (testing and debugging). Each rule includes detailed explanations, YAML examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated rule generation and code transformation. Includes workflow guidance for systematic rule development.",
"references": [
"https://ast-grep.github.io/",
"https://ast-grep.github.io/guide/rule-config.html",
"https://ast-grep.github.io/reference/cli.html",
"https://github.com/ast-grep/ast-grep",
"https://github.com/ast-grep/agent-skill",
"https://github.com/coderabbitai/ast-grep-essentials"
]
}
ast-grep Best Practices Skill
Comprehensive best practices guide for ast-grep rule writing and usage, designed for AI agents and LLMs.
Overview
This skill contains 42 rules across 8 categories, covering pattern syntax, meta variables, rule composition, constraints, rewrites, project organization, performance, and testing.
Structure
ast-grep/
├── SKILL.md # Entry point with quick reference
├── AGENTS.md # Compiled comprehensive guide
├── metadata.json # Version, org, references
├── README.md # This file
├── references/
│ ├── _sections.md # Category definitions
│ ├── pattern-*.md # Pattern correctness rules (7)
│ ├── meta-*.md # Meta variable rules (6)
│ ├── compose-*.md # Rule composition rules (6)
│ ├── const-*.md # Constraint design rules (5)
│ ├── rewrite-*.md # Rewrite correctness rules (5)
│ ├── org-*.md # Project organization rules (5)
│ ├── perf-*.md # Performance optimization rules (4)
│ └── test-*.md # Testing & debugging rules (4)
└── assets/
└── templates/
└── _template.md # Rule template for extensionsGetting Started
# Install dependencies (from skill directory)
pnpm install
# Build AGENTS.md from references
pnpm build
# Validate skill structure and content
pnpm validateCreating a New Rule
1. Choose the appropriate category based on the rule's focus:
| Category | Prefix | Use For |
|---|---|---|
| Pattern Correctness | pattern- | Pattern syntax issues |
| Meta Variable Usage | meta- | Meta variable problems |
| Rule Composition | compose- | Combining rules |
| Constraint Design | const- | Filtering matches |
| Rewrite Correctness | rewrite- | Code transformation |
| Project Organization | org- | Project structure |
| Performance Optimization | perf- | Speed improvements |
| Testing & Debugging | test- | Rule validation |
2. Create a new file: references/{prefix}-{descriptive-name}.md
3. Follow the template in assets/templates/_template.md
4. Rebuild AGENTS.md: pnpm build
Rule File Structure
---
title: Rule Title in Imperative Form
impact: CRITICAL|HIGH|MEDIUM-HIGH|MEDIUM|LOW-MEDIUM|LOW
impactDescription: quantified impact
tags: category-prefix, technique, concepts
---
## Rule Title in Imperative Form
Why this matters (1-3 sentences).
**Incorrect (problem description):**
\`\`\`yaml
# Bad example
\`\`\`
**Correct (solution description):**
\`\`\`yaml
# Good example
\`\`\`
Reference: [Link](url)File Naming Convention
Files follow the pattern: {prefix}-{description}.md
- prefix: Category identifier (pattern, meta, compose, etc.)
- description: Kebab-case description of the rule
Examples:
pattern-valid-syntax.mdmeta-naming-convention.mdcompose-all-for-and-logic.md
Impact Levels
| Level | Description |
|---|---|
| CRITICAL | Causes failures or silent bugs |
| HIGH | Leads to significant issues |
| MEDIUM-HIGH | Important for correctness |
| MEDIUM | Affects maintainability |
| LOW-MEDIUM | Nice to have improvements |
| LOW | Edge case optimizations |
Scripts
| Script | Description |
|---|---|
pnpm build | Compile references into AGENTS.md |
pnpm validate | Check skill structure and content |
Contributing
1. Read existing rules for style consistency 2. Use the template for new rules 3. Include both incorrect and correct examples 4. Provide quantified impact descriptions 5. Reference official documentation 6. Run validation before submitting
Acknowledgments
- ast-grep - The fast and polyglot tool for code structural search
- ast-grep-essentials - Community rules collection
- Tree-sitter - Underlying parser technology
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group rules.
---
1. Pattern Correctness (pattern)
Impact: CRITICAL Description: Invalid patterns cause parse failures or silent mismatches. Patterns must be valid, parseable code that tree-sitter can process.
2. Meta Variable Usage (meta)
Impact: CRITICAL Description: Incorrect meta variable syntax causes capture failures and unexpected matches. Meta variables are the foundation of pattern flexibility.
3. Rule Composition (compose)
Impact: HIGH Description: Poor rule composition leads to missed matches or over-matching. Combining atomic, relational, and composite rules requires understanding execution semantics.
4. Constraint Design (const)
Impact: HIGH Description: Missing or incorrect constraints cause false positives or negatives. Constraints filter matches after pattern matching.
5. Rewrite Correctness (rewrite)
Impact: MEDIUM-HIGH Description: Incorrect rewrite patterns can introduce bugs into codebases. Rewrites must preserve program semantics while making intended changes.
6. Project Organization (org)
Impact: MEDIUM Description: Poor organization leads to maintenance burden and rule conflicts. Well-structured projects enable team collaboration and rule reuse.
7. Performance Optimization (perf)
Impact: MEDIUM Description: Inefficient rules slow down scans on large codebases. Pattern specificity and rule structure affect matching performance.
8. Testing & Debugging (test)
Impact: LOW-MEDIUM Description: Lack of testing leads to regressions and hard-to-diagnose issues. Testing rules before deployment prevents production surprises.
Use All for AND Logic Between Rules
The all composite rule requires a node to satisfy every sub-rule. Use it to combine multiple conditions that must all be true.
Incorrect (relies on implicit YAML field merging):
id: find-async-arrow
language: javascript
rule:
kind: arrow_function
pattern: async () => $BODY # YAML fields don't AND togetherCorrect (explicit all for AND):
id: find-async-arrow
language: javascript
rule:
all:
- kind: arrow_function
- has:
pattern: asyncKey behaviors:
- All sub-rules must match the same single node
- Meta variables from all sub-rules are merged into final match
- Order in
allarray can matter for relational rules
Common AND patterns:
# Match console.log inside async functions
rule:
all:
- pattern: console.log($MSG)
- inside:
kind: function_declaration
has:
pattern: async
# Match identifier that's a function parameter
rule:
all:
- kind: identifier
- inside:
kind: formal_parametersReference: Composite Rules
Use Any for OR Logic Between Rules
The any composite rule matches if at least one sub-rule succeeds. Use it for alternative patterns that should trigger the same action.
Incorrect (multiple separate rules for variations):
# Rule 1
id: find-console-log
rule:
pattern: console.log($MSG)
---
# Rule 2
id: find-console-warn
rule:
pattern: console.warn($MSG)Correct (single rule with any):
id: find-console-usage
language: javascript
rule:
any:
- pattern: console.log($MSG)
- pattern: console.warn($MSG)
- pattern: console.error($MSG)
message: Avoid console statements in productionKey behaviors:
- First matching sub-rule determines the captured variables
- Meta variables from non-matching sub-rules are not available
- Use consistent variable names across alternatives for uniform rewrites
Common OR patterns:
# Match various loop types
rule:
any:
- kind: for_statement
- kind: while_statement
- kind: do_statement
# Match function declarations or expressions
rule:
any:
- kind: function_declaration
- kind: function_expression
- kind: arrow_functionReference: Composite Rules
Use Field to Target Specific Sub-Nodes
The field option in relational rules (inside, has) restricts matching to specific named children of AST nodes. Use it to distinguish between function bodies, parameters, conditions, and other structural elements.
Incorrect (matches anywhere in function):
id: find-return-in-function
language: javascript
rule:
pattern: return $VAL
inside:
kind: function_declaration
# Matches returns in body AND nested functionsCorrect (field targets specific child):
id: find-direct-return
language: javascript
rule:
kind: function_declaration
has:
field: body # Only check the body field
has:
pattern: return $VALCommon AST fields by node type:
# function_declaration fields
- name: identifier
- parameters: formal_parameters
- body: statement_block
# if_statement fields
- condition: parenthesized_expression
- consequence: statement_block
- alternative: else_clause
# for_statement fields
- initializer: variable_declaration
- condition: expression
- increment: update_expression
- body: statement_block
# call_expression fields
- function: identifier/member_expression
- arguments: argumentsTargeting function parameters vs body:
# Match only in parameters (not in body)
id: find-destructured-param
language: javascript
rule:
kind: function_declaration
has:
field: parameters
has:
kind: object_pattern
# Match only in body (not in parameters)
id: find-await-in-body
language: javascript
rule:
kind: function_declaration
has:
field: body
has:
pattern: await $EXPRTargeting if statement parts:
# Match in condition only
id: find-assignment-in-condition
language: javascript
rule:
kind: if_statement
has:
field: condition
has:
pattern: $VAR = $VAL # Assignment, not comparison
# Match in consequence only
id: find-return-in-if-body
language: javascript
rule:
kind: if_statement
has:
field: consequence
has:
pattern: return $VALCombining field with stopBy:
# Match return directly in function body, not nested
id: find-top-level-return
language: javascript
rule:
kind: function_declaration
has:
field: body
has:
pattern: return $VAL
stopBy: neighbor # Direct child of body onlyWhen to use field:
- Distinguishing function parameters from body
- Targeting if/while conditions vs their bodies
- Matching loop initializers, conditions, or increments separately
- Any case where position within parent matters
Tip: Use --debug-query=ast to discover field names for your target language.
Reference: Relational Rules
Use Has for Child Node Requirements
The has relational rule confirms that a matched node contains specific descendant nodes. Use it to filter parent nodes by their content.
Incorrect (pattern can't express child requirements):
id: find-function-with-await
language: javascript
rule:
pattern: function $NAME() { $$$BODY } # Can't require await in bodyCorrect (has checks for descendants):
id: find-function-with-await
language: javascript
rule:
all:
- kind: function_declaration
- has:
pattern: await $EXPRSpecifying which children via field:
# Only check function body, not parameters
rule:
kind: function_declaration
has:
field: body
pattern: return $VAL
# Only check condition, not consequent
rule:
kind: if_statement
has:
field: condition
pattern: $A === nullControlling search depth:
# Avoids matching nested returns in inner functions
rule:
kind: block_statement
has:
kind: return_statement
stopBy: neighbor
# Searches entire function body including nested callbacks
rule:
kind: function_declaration
has:
pattern: console.log($MSG)Reference: Relational Rules
Use Inside for Contextual Matching
The inside relational rule verifies that matched nodes appear within a specific parent structure. Use it to limit pattern matches to relevant contexts.
Incorrect (matches everywhere):
id: find-this-usage
language: javascript
rule:
pattern: this.$PROP
# Matches in classes, objects, functions - too broadCorrect (scoped to class methods):
id: find-this-in-class
language: javascript
rule:
all:
- pattern: this.$PROP
- inside:
kind: class_declarationControlling search depth with stopBy:
# Match only direct children (immediate parent)
rule:
pattern: $EXPR
inside:
kind: if_statement
stopBy: neighbor # Only immediate parent
# Match anywhere in function (default)
rule:
pattern: $EXPR
inside:
kind: function_declaration
stopBy: end # Search to root
# Stop at specific boundary
rule:
pattern: await $EXPR
inside:
kind: function_declaration
stopBy:
kind: arrow_function # Don't cross nested arrow functionsReference: Relational Rules
Use Matches for Rule Reusability
The matches rule references utility rules by ID. Define common patterns once and reuse them across multiple rules.
Incorrect (duplicates pattern logic):
id: rule-1
rule:
all:
- pattern: console.log($MSG)
- inside:
any:
- kind: function_declaration
- kind: arrow_function
---
id: rule-2
rule:
all:
- pattern: console.warn($MSG)
- inside:
any:
- kind: function_declaration
- kind: arrow_functionCorrect (utility rule for reuse):
# utils/inside-function.yml
id: inside-function
language: javascript
rule:
inside:
any:
- kind: function_declaration
- kind: arrow_function
---
# rules/no-console-log.yml
id: no-console-log
rule:
all:
- pattern: console.log($MSG)
- matches: inside-function
---
# rules/no-console-warn.yml
id: no-console-warn
rule:
all:
- pattern: console.warn($MSG)
- matches: inside-functionUtility rule best practices:
- Place in
utils/directory - Use descriptive IDs:
inside-function,is-exported,has-type-annotation - Reference with
matches: rule-id - Configure
utilsDirsinsgconfig.yml
Reference: Utility Rules
Use Not for Exclusion Patterns
The not rule inverts matching logic. Use it to exclude specific patterns from broader matches.
Incorrect (relies on post-processing to filter):
id: find-all-functions
language: javascript
rule:
kind: function_declaration
# Then manually filter out async functionsCorrect (not excludes at match time):
id: find-sync-functions
language: javascript
rule:
all:
- kind: function_declaration
- not:
has:
pattern: asyncKey behaviors:
notsucceeds when its sub-rule fails to match- Cannot capture meta variables (nothing matched)
- Often combined with
allfor filter patterns
Common exclusion patterns:
# Match console.log but not inside catch blocks
rule:
all:
- pattern: console.log($MSG)
- not:
inside:
kind: catch_clause
# Match await but not inside try blocks
rule:
all:
- pattern: await $EXPR
- not:
inside:
kind: try_statement
# Match identifiers that aren't parameters
rule:
all:
- kind: identifier
- not:
inside:
kind: formal_parametersReference: Composite Rules
Use Precedes and Follows for Sequential Positioning
The precedes and follows relational rules match nodes based on their order among siblings. Use them to find patterns that depend on statement sequence.
Incorrect (pattern can't express order):
id: find-ordered-calls
language: javascript
rule:
all:
- pattern: setup()
- pattern: execute()
# Matches both but doesn't verify orderCorrect (precedes enforces order):
id: find-setup-before-execute
language: javascript
rule:
pattern: setup()
precedes:
pattern: execute()Understanding precedes vs follows:
# "A precedes B" means A comes before B
rule:
pattern: $FIRST
precedes:
pattern: $SECOND
# "A follows B" means A comes after B
rule:
pattern: $SECOND
follows:
pattern: $FIRST
# Both match the same relationship, different anchor nodeControlling search depth with stopBy:
# Match immediate next sibling only
rule:
pattern: const $VAR = $VAL
precedes:
pattern: console.log($VAR)
stopBy: neighbor # Must be immediate next statement
# Match anywhere after in same block
rule:
pattern: const $VAR = $VAL
precedes:
pattern: console.log($VAR)
stopBy: end # Can have statements betweenPractical examples:
# Find variable declaration followed by its usage
id: find-immediate-use
language: javascript
rule:
pattern: const $VAR = $VAL
precedes:
pattern: $VAR
stopBy: neighbor
# Find return not at end of function
id: find-early-return
language: javascript
rule:
pattern: return $VAL
precedes:
kind: expression_statement
stopBy: end
inside:
kind: statement_block
# Find missing cleanup after resource allocation
id: find-unclosed-resource
language: javascript
rule:
pattern: const $HANDLE = open($PATH)
not:
precedes:
pattern: $HANDLE.close()
stopBy: endKey behaviors:
precedesanchors on the first node, searches forwardfollowsanchors on the second node, searches backward- Both operate only among siblings (same parent)
- Use
stopBy: neighborfor immediate adjacency - Use
stopBy: endfor anywhere in sequence
Reference: Relational Rules
Use Kind Constraints to Filter Meta Variables
Constraints filter captured meta variables after pattern matching. Use kind constraints to ensure variables match expected node types.
Incorrect (accepts any node type):
id: find-function-call
language: javascript
rule:
pattern: $FN($ARGS)
# Matches: getUser(), obj.method(), new Class() - too broadCorrect (constrain to identifiers only):
id: find-simple-call
language: javascript
rule:
pattern: $FN($ARGS)
constraints:
FN:
kind: identifier
# Only matches: getUser(), validate() - not obj.method()Multiple kind constraints:
id: find-callable-usage
language: javascript
rule:
pattern: $FN($ARGS)
constraints:
FN:
any:
- kind: identifier
- kind: member_expressionImportant: Constraints apply after pattern matching. The pattern must first match, then constraints filter the results.
When to use kind constraints:
- Filter function calls by callee type
- Distinguish variable declarations from parameters
- Separate property access from subscript access
Reference: Lint Rules
Avoid Constraints Inside Not Rules
Constraints cannot be used inside not rules because not succeeds when nothing matches, leaving no node to constrain.
Incorrect (constraint inside not):
id: find-non-private
language: javascript
rule:
all:
- kind: identifier
- not:
pattern: $NAME
constraints:
NAME:
regex: ^_ # This won't work as expected!Correct (constrain outside not):
id: find-non-private
language: javascript
rule:
all:
- kind: identifier
- pattern: $NAME
- not:
regex: ^_
constraints:
NAME:
kind: identifierAlternative (use regex directly in not):
id: find-non-private
language: javascript
rule:
all:
- kind: identifier
- not:
regex: ^_Why this happens: Constraints filter captured meta variables. When not succeeds, nothing was captured, so there's nothing to constrain.
Workaround strategies: 1. Move constraint to outer rule 2. Use regex directly in not (atomic rule) 3. Restructure as positive match with constraint
Reference: FAQ
Use Pattern Constraints for Structural Filtering
Use pattern constraints to verify that captured meta variables match a specific code structure, not just kind or text.
Incorrect (kind constraint too broad):
id: find-null-check
language: javascript
rule:
pattern: if ($COND) { $$$BODY }
constraints:
COND:
kind: binary_expression # Matches any binary expressionCorrect (pattern constraint for specific structure):
id: find-null-check
language: javascript
rule:
pattern: if ($COND) { $$$BODY }
constraints:
COND:
pattern: $VAR === nullCombining multiple constraint types:
id: find-hook-call-with-deps
language: javascript
rule:
pattern: $HOOK($CALLBACK, $DEPS)
constraints:
HOOK:
regex: ^use(Effect|Callback|Memo)$
DEPS:
pattern: '[$$$ITEMS]' # Must be array literalStructural constraint use cases:
- Verify function arguments have specific shape
- Match only certain binary operators
- Filter by nested structure depth
Reference: Lint Rules
Understand Constraints Apply After Matching
Constraints filter results after pattern matching completes. A pattern must first match successfully before constraints are evaluated.
Incorrect (expects constraint to guide matching):
id: find-specific-call
language: javascript
rule:
pattern: $FN()
constraints:
FN:
pattern: console.log # Constraints don't make pattern more specificCorrect (use specific pattern, constrain captures):
id: find-console-log
language: javascript
rule:
pattern: console.log() # Specific patternAlternative (pattern then filter):
id: find-console-methods
language: javascript
rule:
pattern: console.$METHOD($$$ARGS)
constraints:
METHOD:
regex: ^(log|warn|error)$ # Filter which methodsExecution flow: 1. Pattern matches against code → captures meta variables 2. Constraints evaluate against captured values 3. Match succeeds only if both pass
When constraints are useful:
- Pattern is necessarily generic (can't express specificity)
- Need to filter by properties pattern can't express
- Want different rules for same pattern, different constraints
Reference: Lint Rules
Use Regex Constraints for Text Patterns
Use regex constraints to filter meta variables by their text content. The regex must match the entire node text.
Incorrect (pattern can't filter by name):
id: find-hook-usage
language: javascript
rule:
pattern: use$HOOK() # Invalid! Can't mix text and meta varsCorrect (use regex constraint):
id: find-hook-usage
language: javascript
rule:
pattern: $HOOK()
constraints:
HOOK:
regex: ^use[A-Z]Common regex patterns:
# Match React hooks (useEffect, useState, etc.)
constraints:
FN:
regex: ^use[A-Z]\w*$
# Match private fields (_name, _value)
constraints:
PROP:
regex: ^_
# Match test functions (test_*, *_test)
constraints:
NAME:
regex: (^test_|_test$)
# Match specific prefixes
constraints:
VAR:
regex: ^(temp|tmp|_)Important: Regex matches against the full text representation of the captured node, including nested content.
Reference: Lint Rules
Understand Multi-Match Variables Are Lazy
The $$$ multi-match operator is lazy, not greedy. It stops at the first valid match to ensure linear-time performance.
Incorrect (expects greedy matching):
id: extract-all-but-last
language: javascript
rule:
pattern: fn($$$FIRST, $LAST)
# For fn(a, b, c): $$$FIRST = a, $LAST = b, c is unmatched!Correct (understand lazy semantics):
id: match-any-call
language: javascript
rule:
pattern: fn($$$ARGS) # Matches entire argument listWorking with multi-match:
# Match calls with at least 2 args
id: find-two-plus-args
rule:
pattern: fn($FIRST, $$$REST) # $FIRST is required, $$$REST is 0+
# Match calls with at least 3 args
id: find-three-plus-args
rule:
pattern: fn($A, $B, $$$REST)Important: Multi-match variables cannot be used in rewrites directly because they represent multiple nodes, not a single value.
Reference: FAQ
Use Double Dollar for Unnamed Node Matching
Single $VAR only matches named AST nodes. Use $$VAR to match unnamed nodes like operators and punctuation.
Incorrect (single dollar misses operators):
id: find-binary-operator
language: javascript
rule:
pattern: $LEFT $OP $RIGHT # $OP won't match + or -Correct (double dollar matches unnamed nodes):
id: find-binary-operator
language: javascript
rule:
pattern: $LEFT $$OP $RIGHT # $$OP matches +, -, *, etc.Named vs Unnamed nodes:
- Named nodes: Have a
kindproperty (e.g.,identifier,call_expression) - Unnamed nodes: Operators (
+,-), punctuation (;,,), keywords
Common use cases for `$$VAR`:
# Match any binary expression
pattern: $A $$OP $B
# Match any assignment operator
pattern: $TARGET $$ASSIGN $VALUE # =, +=, -=, etc.
# Match array access brackets
pattern: $ARR$$OPEN$IDX$$CLOSETip: Use --debug-query to see which nodes are named vs unnamed.
Reference: Core Concepts
Follow Meta Variable Naming Conventions
Meta variables must start with $ followed by uppercase letters, underscores, or digits. Invalid names silently fail to capture.
Incorrect (lowercase, kebab-case, numbers first):
id: find-function-calls
language: javascript
rule:
pattern: $func($args) # lowercase fails
# pattern: $KEBAB-CASE # hyphens invalid
# pattern: $123ABC # numbers first invalidCorrect (uppercase with underscores):
id: find-function-calls
language: javascript
rule:
pattern: $FUNC($ARGS)Valid meta variable formats:
$META- basic uppercase$META_VAR- with underscores$META_VAR1- with trailing digits$_- single underscore wildcard$_123- underscore prefix with digits
Invalid formats:
$invalid- lowercase letters$Svalue- mixed case$123- starts with digit$KEBAB-CASE- contains hyphen
Reference: Pattern Syntax
Reuse Meta Variables to Enforce Equality
When the same meta variable name appears multiple times in a pattern, all occurrences must match identical code. Use different names for independent captures.
Incorrect (reuses $VAR for independent values):
id: find-assignment
language: javascript
rule:
pattern: $VAR = $VAR # Only matches self-assignment like x = x
# Won't match: x = yCorrect (uses distinct names for different captures):
id: find-assignment
language: javascript
rule:
pattern: $TARGET = $VALUE # Matches any assignmentIntentional reuse for equality checks:
id: find-self-comparison
language: javascript
rule:
pattern: $EXPR === $EXPR
message: Comparing expression to itself is always true
# Matches: x === x, foo.bar === foo.barPractical use cases:
- Detect redundant comparisons:
$A == $A - Find self-assignment bugs:
$X = $X - Match symmetric operations:
$A + $A
Reference: Pattern Syntax
Match Single AST Nodes with Meta Variables
A single $VAR matches exactly one AST node. It cannot match multiple consecutive nodes like function arguments or statements.
Incorrect (expects $ARGS to match multiple arguments):
id: find-multi-arg-call
language: javascript
rule:
pattern: console.log($ARGS) # Only matches single-arg calls
# Won't match: console.log(a, b, c)Correct (use $$$ for multiple nodes):
id: find-any-log-call
language: javascript
rule:
pattern: console.log($$$ARGS) # Matches zero or more argsUnderstanding node boundaries:
$VAR= exactly one node (like regex.)$$$or$$$VAR= zero or more nodes (like regex.*)- Each meta variable captures the entire subtree of its matched node
Common mistake:
# This only matches: fn(singleArg)
pattern: fn($ARG)
# This matches: fn(), fn(a), fn(a, b, c)
pattern: fn($$$ARGS)Reference: Pattern Syntax
Use Underscore Prefix for Non-Capturing Matches
Prefix meta variables with underscore ($_) when you don't need their captured value. This avoids HashMap creation during matching, improving performance.
Incorrect (captures values never used):
id: find-console-usage
language: javascript
rule:
pattern: console.$METHOD($ARGS)
# $METHOD and $ARGS captured but never referencedCorrect (underscore signals no capture needed):
id: find-console-usage
language: javascript
rule:
pattern: console.$_METHOD($_ARGS)
message: Avoid console statements in productionWhen to capture vs non-capture:
# Need the value for rewrite - capture it
id: migrate-console-to-logger
rule:
pattern: console.log($MSG)
fix: logger.info($MSG) # $MSG referenced in fix
# Just detecting presence - don't capture
id: find-console
rule:
pattern: console.$_METHOD($_ARGS)Performance note: Non-capturing variables can match different content at each occurrence in the same pattern, unlike capturing variables which enforce equality.
Reference: Pattern Syntax
Use File Filtering for Targeted Rules
Use files and ignores globs to apply rules only to relevant files. This reduces false positives and improves scan performance.
Incorrect (applies to all files):
id: react-hooks-rules
language: tsx
rule:
pattern: useEffect($$$ARGS)
# Runs on all .tsx files including non-React codeCorrect (targeted to React components):
id: react-hooks-rules
language: tsx
files:
- 'src/components/**/*.tsx'
- 'src/hooks/**/*.tsx'
ignores:
- '**/*.test.tsx'
- '**/*.stories.tsx'
rule:
pattern: useEffect($$$ARGS)Glob pattern tips:
- Paths are relative to
sgconfig.ymllocation - Don't use
./prefix - Use
**for recursive matching - Use
*for single directory level
Common filtering patterns:
# Only source files, not tests
files:
- 'src/**/*.ts'
ignores:
- '**/*.test.ts'
- '**/*.spec.ts'
- '**/__tests__/**'
# Only test files
files:
- '**/*.test.ts'
- '**/*.spec.ts'
# Specific directories
files:
- 'packages/core/**/*.ts'
ignores:
- '**/node_modules/**'
- '**/dist/**'Reference: Lint Rules
Write Clear Actionable Messages
Write messages that explain what's wrong and how to fix it. Use note for detailed guidance and labels for precise highlighting.
Incorrect (vague, unhelpful message):
id: bad-code
message: Bad code detectedCorrect (specific, actionable):
id: no-console-in-production
language: javascript
rule:
pattern: console.$METHOD($$$ARGS)
message: Remove console.$METHOD() before deploying to production
note: |
Console statements slow down execution and expose debugging
information in production. Use a logging library instead:
- logger.info() for informational messages
- logger.error() for error tracking
Or wrap in environment check: if (process.env.NODE_ENV === 'development')Using labels for precision:
id: missing-await
rule:
pattern: $PROMISE
inside:
kind: expression_statement
has:
kind: call_expression
labels:
- source: $PROMISE
style: primary
message: This promise is not awaitedMessage writing guidelines:
- State the problem clearly
- Reference captured variables:
$METHOD - Provide the fix in
note - Include code examples in
notewhen helpful - Use labels to highlight specific code regions
Reference: Lint Rules
Use Standard Project Directory Structure
Organize ast-grep projects with standard directories for rules, utilities, and tests. The sgconfig.yml file defines project root and directories.
Incorrect (flat structure, no config):
project/
├── rule1.yml
├── rule2.yml
├── test1.yml
└── some-code.jsCorrect (organized with sgconfig.yml):
project/
├── sgconfig.yml
├── rules/
│ ├── security/
│ │ └── no-eval.yml
│ └── style/
│ └── prefer-const.yml
├── utils/
│ └── inside-function.yml
└── tests/
├── no-eval-test.yml
└── prefer-const-test.ymlsgconfig.yml example:
ruleDirs:
- rules
utilsDirs:
- utils
testConfigs:
- testDir: testsDirectory purposes:
rules/- Lint rules that produce diagnosticsutils/- Reusable rule fragments (utility rules)tests/- Test cases for rule validation
Initialize with:
ast-grep new project # Creates sgconfig.yml
ast-grep new rule # Scaffolds rule file
ast-grep new test # Creates test fileReference: Tooling Overview
Assign Appropriate Severity Levels
Set severity levels that reflect the actual impact of rule violations. This enables developers to prioritize fixes and configure CI appropriately.
Incorrect (everything is error):
id: prefer-const
severity: error # Style preference shouldn't block CI
id: sql-injection
severity: warning # Security issue should be errorCorrect (severity matches impact):
id: prefer-const
language: javascript
severity: hint # Style suggestion
rule:
pattern: let $VAR = $VAL
message: Consider using const for variables that are never reassigned
---
id: sql-injection
language: javascript
severity: error # Security vulnerability
rule:
pattern: db.query($QUERY)
message: Potential SQL injection vulnerabilitySeverity level guidelines:
| Severity | Use Case | CI Behavior |
|---|---|---|
| error | Security, correctness bugs | Fail build |
| warning | Likely bugs, deprecated usage | Warn, may fail |
| hint | Style preferences | Informational |
| off | Temporarily disabled | Skipped |
Note field for context:
severity: error
message: Avoid eval() for security
note: |
eval() executes arbitrary code, enabling code injection attacks.
Use JSON.parse() for data or Function constructor for dynamic code.Reference: Lint Rules
Use Unique Descriptive Rule IDs
Rule IDs must be unique across the entire project. Use descriptive names that indicate the rule's purpose and enable targeted suppression.
Incorrect (vague IDs that may conflict):
id: rule1
# or
id: no-bad-code
# or
id: checkCorrect (descriptive, namespaced IDs):
id: security/no-eval-usage
# or
id: react-hooks/exhaustive-deps
# or
id: typescript/no-explicit-anyID naming conventions:
- Use lowercase with hyphens:
no-console-log - Add category prefix:
security/,style/,react/ - Be specific:
no-dynamic-requirenotno-require - Match file name to ID when possible
Why unique IDs matter:
// Developers suppress by ID
// ast-grep-ignore: security/no-eval-usage
eval(userInput)
// Vague IDs make suppression dangerous
// ast-grep-ignore: check // What does this suppress?Validation:
# ast-grep test will fail on duplicate IDs
ast-grep test -c sgconfig.ymlReference: Lint Rules
Avoid Matching Inside Comments and Strings
ast-grep matches AST nodes, not text. Patterns will never match content inside comments or string literals because those are leaf nodes without children matching code structure.
Incorrect (expects to match commented code):
id: find-todo-console
language: javascript
rule:
pattern: console.log($MSG)
# Will NOT match: // console.log("debugging")Correct (use regex for comment/string content):
id: find-todo-comments
language: javascript
rule:
kind: comment
regex: 'TODO|FIXME'For matching inside strings:
id: find-sql-injection-risk
language: javascript
rule:
kind: string
regex: 'SELECT.*FROM.*WHERE'Note: Comments and strings are terminal AST nodes - their content is not parsed into sub-nodes.
Reference: Core Concepts
Use Context and Selector for Code Fragments
Code fragments like object keys or function parameters cannot be parsed standalone. Use context to provide surrounding structure and selector to target the specific node.
Incorrect (fragment cannot be parsed):
id: find-json-key
language: json
rule:
pattern: '"name"' # Invalid standalone JSONCorrect (context provides structure, selector targets node):
id: find-json-key
language: json
rule:
pattern:
context: '{"name": $VAL}'
selector: pairCommon use cases:
- Object keys:
context: '{key: $VAL}'withselector: pair - Function parameters:
context: 'function($PARAM) {}'withselector: formal_parameters - Array elements:
context: '[$ELEM]'withselector: array
Reference: Rule Configuration
Use Debug Query to Inspect AST Structure
When patterns don't match expected code, use --debug-query to inspect the actual AST structure. Misunderstanding node types is the most common pattern failure.
Incorrect (assumes wrong AST structure):
id: find-arrow-function
language: javascript
rule:
kind: function # Wrong! Arrow functions are arrow_functionCorrect (verified with debug-query):
# First, inspect the AST
ast-grep run --debug-query '() => {}' -l javascript
# Output shows: arrow_function
# Then use correct kindid: find-arrow-function
language: javascript
rule:
kind: arrow_functionDebugging workflow: 1. Write minimal code example containing the pattern 2. Run ast-grep run --debug-query 'your code' -l language 3. Examine node kinds and structure in output 4. Adjust pattern to match actual AST
Tip: Use the playground's AST viewer tab for interactive exploration.
Reference: CLI Reference
Choose Kind or Pattern Based on Specificity Needs
Use kind for broad node-type matching and pattern for specific code structures. Combining them incorrectly causes unexpected results.
Incorrect (kind + pattern together fails):
id: find-specific-call
language: javascript
rule:
kind: call_expression
pattern: console.log($MSG) # These don't compose directlyCorrect (use pattern object or all):
id: find-specific-call
language: javascript
rule:
all:
- kind: call_expression
- pattern: console.log($MSG)Alternative (pattern with kind constraint):
id: find-identifier-usage
language: javascript
rule:
pattern: $VAR
constraints:
VAR:
kind: identifierWhen to use each:
kindalone: Match all nodes of a type (all function declarations)patternalone: Match specific code structure (console.log calls)allwith both: Filter pattern matches by kind- Constraints: Filter captured meta variables by kind
Reference: Atomic Rules
Account for Language-Specific Syntax Differences
Identical pattern strings parse differently across languages. Single quotes denote strings in JavaScript but character literals in C/Java.
Incorrect (assumes JavaScript semantics in C):
id: find-char-literal
language: c
rule:
pattern: 'a' # Matches char literal, not string
message: Found character literalCorrect (uses language-appropriate syntax):
id: find-string-literal
language: c
rule:
pattern: '"hello"' # C strings use double quotes
message: Found string literalWhen working with multiple languages:
- Test each pattern in language-specific playground
- Create separate rules for similar languages (TypeScript vs JavaScript)
- Use
languageGlobsonly for true supersets
Reference: Pattern Syntax
Use nthChild for Index-Based Positional Matching
The nthChild atomic rule matches nodes by their position among siblings. Use it to target specific elements in arrays, function parameters, or statement sequences.
Incorrect (pattern can't express position):
id: find-first-param
language: javascript
rule:
pattern: function $NAME($FIRST, $$$REST) {}
# Only works if function has 2+ params
# Can't match first param of single-param functionCorrect (nthChild targets by position):
id: find-first-param
language: javascript
rule:
kind: formal_parameters
has:
kind: identifier
nthChild: 1 # 1-based index, matches first childIndex patterns (An+B formula):
# Match first element
nthChild: 1
# Match last element
nthChild:
position: 1
reverse: true
# Match every other element (2nd, 4th, 6th...)
nthChild: 2n
# Match odd elements (1st, 3rd, 5th...)
nthChild: 2n+1
# Match first three elements
nthChild:
position: -n+3Common use cases:
# Match second argument in function calls
id: find-second-arg
rule:
kind: arguments
has:
nthChild: 2
# Match last statement in block
id: find-last-statement
rule:
kind: statement_block
has:
kind: expression_statement
nthChild:
position: 1
reverse: trueNote: nthChild uses 1-based indexing (first element is 1, not 0). Use reverse: true to count from the end.
Reference: Atomic Rules
Use Range for Character Position Matching
The range atomic rule matches nodes by their character position in the source file. Use it for precise location-based targeting when other methods are insufficient.
Incorrect (trying to match by line number with pattern):
id: find-at-line
language: javascript
rule:
pattern: $EXPR # No way to filter by locationCorrect (range targets by position):
id: find-in-range
language: javascript
rule:
kind: expression_statement
range:
start:
line: 10
column: 0
end:
line: 20
column: 0Range specification:
# Match node starting at specific position
range:
start:
line: 5 # 0-based line number
column: 4 # 0-based column number
# Match node within range
range:
start:
line: 10
column: 0
end:
line: 50
column: 0Practical use cases:
# Match code in specific function (by known position)
id: audit-function
rule:
kind: function_declaration
range:
start:
line: 100
end:
line: 150
# Match imports at top of file
id: find-early-imports
rule:
kind: import_statement
range:
end:
line: 20 # First 20 linesWhen NOT to use range:
- For structural matching (use
patternorkindinstead) - For context-based matching (use
insideinstead) - When code positions may change (range is brittle)
When to use range:
- Auditing specific code regions
- Generating reports with location context
- Combining with other rules for precise targeting
Note: Line and column numbers are 0-based. Range matching is fragile to code changes - prefer structural patterns when possible.
Reference: Atomic Rules
Configure Pattern Strictness Appropriately
The strictness parameter controls how precisely patterns must match AST structure. Looser settings match more variations but risk false positives.
Incorrect (default strictness misses valid variations):
id: find-await-fetch
language: typescript
rule:
pattern: await fetch($URL)
# Misses: await (fetch(url))
# Misses: await fetch(url, options)Correct (relaxed strictness catches variations):
id: find-await-fetch
language: typescript
rule:
pattern:
context: await fetch($URL)
strictness: relaxedStrictness levels:
cst: Exact match including punctuation (most strict)smart: Ignores unnamed nodes like parentheses (default)ast: Ignores node kinds, focuses on structurerelaxed: Matches if pattern is subtree (most lenient)signature: Ignores non-essential nodes like async/visibility
When to adjust:
- Use
relaxedwhen matching expressions that may be wrapped in parens - Use
signaturefor function signatures with optional modifiers - Use
cstwhen punctuation matters (template literals, regex)
Reference: Pattern Strictness
Use Valid Parseable Code as Patterns
Patterns must be syntactically valid code that tree-sitter can parse. Invalid patterns silently fail to match anything, wasting debugging time.
Incorrect (incomplete expression, unparseable):
id: find-console-log
language: javascript
rule:
pattern: console.log( # Missing closing parenCorrect (valid, complete expression):
id: find-console-log
language: javascript
rule:
pattern: console.log($ARG)Note: Test patterns in the ast-grep playground to verify parseability before deployment.
Reference: Pattern Syntax
Avoid Heavy Regex in Hot Paths
Regex matching is slower than AST pattern matching. Use patterns and kind filters first, then apply regex only when necessary.
Incorrect (regex as primary filter):
id: find-variables
language: javascript
rule:
kind: identifier
regex: '.*' # Matches all identifiers, then filters
constraints:
# Complex regex on every identifierCorrect (pattern first, regex to refine):
id: find-prefixed-variables
language: javascript
rule:
pattern: $VAR
inside:
kind: variable_declarator
constraints:
VAR:
regex: ^(cache|pending|_)Regex optimization tips:
# Anchor patterns for faster matching
regex: ^prefix # Faster than: .*prefix
regex: suffix$ # Faster than: suffix.*
# Use character classes efficiently
regex: ^[a-z_][a-zA-Z0-9_]*$ # Identifier pattern
# Avoid catastrophic backtracking
# Bad: (a+)+b
# Good: a+bWhen to use regex vs pattern:
| Use Case | Prefer |
|---|---|
| Structural matching | pattern |
| Node type filtering | kind |
| Text content filtering | regex (in constraints) |
| Partial name matching | regex |
Performance hierarchy: 1. kind - fastest (direct node type check) 2. pattern - fast (tree comparison) 3. regex - slower (string matching)
Reference: Atomic Rules
Use Specific Patterns Over Generic Ones
More specific patterns match fewer nodes, improving scan performance. Avoid overly generic patterns that match most of the codebase.
Incorrect (matches every expression):
id: find-issues
language: javascript
rule:
pattern: $EXPR # Matches every expression in codebase
# Then filters with complex constraints
constraints:
EXPR:
kind: call_expression
has:
pattern: consoleCorrect (specific pattern, fewer matches):
id: find-console-calls
language: javascript
rule:
pattern: console.$METHOD($$$ARGS)Pattern specificity hierarchy (fastest to slowest): 1. Literal patterns: console.log("debug") 2. Partial literals: console.log($MSG) 3. Kind-specific: kind: call_expression 4. Generic captures: $EXPR
Balancing specificity:
# Too specific - misses variations
pattern: console.log($MSG)
# Misses: console.log(a, b)
# Right balance - specific enough, flexible
pattern: console.log($$$ARGS)
# Too generic - matches too much
pattern: $FUNC($$$ARGS)When generic patterns are unavoidable:
- Add
kindto narrow node types - Use
insideto limit search scope - Apply file filtering to reduce scan area
Reference: Core Concepts
Use StopBy to Limit Search Depth
Relational rules like inside and has search the entire tree by default. Use stopBy to limit search depth and improve performance.
Incorrect (searches to root):
id: find-nested-await
language: javascript
rule:
pattern: await $EXPR
inside:
kind: function_declaration
# Searches all the way up the tree - expensiveCorrect (bounded search):
id: find-await-in-function
language: javascript
rule:
pattern: await $EXPR
inside:
kind: function_declaration
stopBy:
kind: arrow_function # Don't cross nested functionsStopBy options:
# Stop at immediate parent only
inside:
kind: block_statement
stopBy: neighbor
# Stop at end (default - searches entire tree)
inside:
kind: function_declaration
stopBy: end
# Stop at specific node type
inside:
kind: class_declaration
stopBy:
kind: function_declarationPerformance impact:
stopBy: neighbor- O(1), checks parent onlystopBy: {kind: X}- O(depth to X)stopBy: end- O(tree height), slowest
Common boundaries:
- Functions:
stopBy: { any: [{ kind: function_declaration }, { kind: arrow_function }] } - Classes:
stopBy: { kind: class_declaration } - Blocks:
stopBy: { kind: block_statement }
Reference: Relational Rules
Leverage Parallel Scanning with Threads
ast-grep scans files in parallel by default. Tune thread count for optimal performance on your hardware and codebase size.
Incorrect (single-threaded on large codebase):
ast-grep scan -c sgconfig.yml -j 1 # Forces single threadCorrect (use optimal thread count):
# Let ast-grep choose (default - usually optimal)
ast-grep scan -c sgconfig.yml
# Explicit for CPU-bound machines
ast-grep scan -c sgconfig.yml -j 8 # 8 threads
# Check available cores
ast-grep scan -c sgconfig.yml -j $(nproc)Thread tuning guidelines:
- Default heuristic works well for most cases
- I/O-bound (SSD): More threads than cores can help
- CPU-bound (complex rules): Match thread count to cores
- Memory-constrained: Reduce threads to lower peak memory
CI/CD optimization:
# GitHub Actions example
- name: Lint with ast-grep
run: |
ast-grep scan -c sgconfig.yml -j 4 --json > results.jsonMeasuring performance:
# Time the scan
time ast-grep scan -c sgconfig.yml
# Compare thread counts
time ast-grep scan -c sgconfig.yml -j 1
time ast-grep scan -c sgconfig.yml -j 4
time ast-grep scan -c sgconfig.yml -j 8Reference: CLI Reference
Reference All Necessary Meta Variables in Fix
The fix template replaces matched code textually. Any captured meta variable not referenced in the fix is lost.
Incorrect (loses the message argument):
id: migrate-console
language: javascript
rule:
pattern: console.log($MSG, $$$REST)
fix: logger.info() # $MSG and $$$REST are lost!Correct (preserves all captures):
id: migrate-console
language: javascript
rule:
pattern: console.log($MSG, $$$REST)
fix: logger.info($MSG, $$$REST)Intentional omission for cleanup:
# Remove debug statements entirely (intentional loss)
id: remove-debug
language: javascript
rule:
pattern: console.debug($$$ARGS)
fix: '' # Empty fix removes the statementChecking for completeness:
# Before deploying, verify fix includes:
# 1. All single meta variables ($VAR)
# 2. All multi-match variables ($$$VAR) if needed
# 3. Proper syntax around insertions
rule:
pattern: old($A, $B, $C)
fix: new($A, $B, $C) # All three preservedReference: Lint Rules
Preserve Program Semantics in Rewrites
Rewrites replace matched code textually. Ensure the replacement maintains identical behavior to avoid introducing subtle bugs.
Incorrect (changes evaluation order):
id: simplify-ternary
language: javascript
rule:
pattern: $COND ? $THEN : $ELSE
fix: $COND && $THEN || $ELSE
# Bug: if $THEN is falsy, $ELSE executes even when $COND is trueCorrect (semantically equivalent transformation):
id: convert-to-if
language: javascript
rule:
pattern: $COND ? true : false
fix: Boolean($COND)Semantic preservation checklist:
- Does the replacement evaluate the same expressions?
- Are side effects executed in the same order?
- Are variables captured in the same scope?
- Does short-circuit evaluation change?
Safe transformations:
# Safe: !! to Boolean()
pattern: '!!$EXPR'
fix: Boolean($EXPR)
# Safe: array spread for concat
pattern: $ARR.concat($ITEM)
fix: '[...$ARR, $ITEM]'
# Unsafe: may change behavior
pattern: $A || $B
fix: $A ?? $B # Different for falsy vs nullish!Reference: Transformation
Ensure Fix Templates Produce Valid Syntax
Fix templates are inserted textually without parsing. Ensure meta variable substitutions produce syntactically valid code for all possible matches.
Incorrect (fix may produce invalid syntax):
id: wrap-in-array
language: javascript
rule:
pattern: $EXPR
fix: '[$EXPR]'
# If $EXPR is "a, b", produces "[a, b]" - different meaning!Correct (account for expression types):
id: wrap-single-value
language: javascript
rule:
pattern: $EXPR
constraints:
EXPR:
not:
kind: sequence_expression # Exclude comma expressions
fix: '[$EXPR]'Syntax validation checklist:
- Parentheses balance after substitution
- Quote escaping in string contexts
- Comma placement for multi-match variables
- Statement terminators (semicolons)
Handling edge cases:
# Multi-match needs comma handling
id: spread-args
rule:
pattern: fn($$$ARGS)
fix: newFn(...[$$$ARGS]) # Commas preserved from original
# Statement vs expression context
id: add-return
rule:
pattern: $EXPR
inside:
kind: arrow_function
field: body
fix: '{ return $EXPR; }' # Add braces and semicolonReference: Lint Rules
Test Rewrites on Representative Code
Always test rewrites with --interactive mode on representative samples before running on entire codebase. Edge cases in real code often break assumptions.
Incorrect (deploys untested rewrite):
# Dangerous: applies to entire codebase without verification
ast-grep scan --rule migrate.yml --update-allCorrect (interactive testing first):
# Step 1: Test on sample file
ast-grep run -p 'console.log($MSG)' -r 'logger.info($MSG)' sample.js
# Step 2: Interactive review on subset
ast-grep scan --rule migrate.yml --interactive src/module/
# Step 3: Full deployment after confidence
ast-grep scan --rule migrate.yml --update-allTesting workflow:
# 1. Create test file for rule
# tests/migrate-console-test.yml
id: migrate-console
valid:
- 'logger.info(msg)'
- 'console.debug(msg)' # Should not match
invalid:
- 'console.log(msg)'
- 'console.log("test", data)'# 2. Run rule tests
ast-grep test -c sgconfig.yml
# 3. Dry run on codebase
ast-grep scan --rule migrate.yml # View matches only
# 4. Interactive apply
ast-grep scan --rule migrate.yml --interactiveReference: CLI Reference
Use Transform for Complex Rewrites
The transform section modifies captured meta variables before inserting into the fix. Use it for case conversion, substring extraction, and regex replacement.
Incorrect (manual string manipulation not possible):
id: convert-case
language: javascript
rule:
pattern: const $VAR = $VAL
fix: const $VAR_UPPER = $VAL # Can't uppercase in fix templateCorrect (use transform):
id: convert-to-screaming-case
language: javascript
rule:
pattern: const $VAR = $VAL
transform:
UPPER_VAR:
convert:
source: $VAR
toCase: upperCase
fix: const $UPPER_VAR = $VALTransform operations:
# Replace with regex
transform:
NEW_NAME:
replace:
source: $NAME
replace: 'old'
by: 'new'
# Extract substring
transform:
PREFIX:
substring:
source: $VAR
startChar: 0
endChar: 3
# Case conversion
transform:
CAMEL:
convert:
source: $VAR
toCase: camelCase
# Options: lowerCase, upperCase, capitalize,
# camelCase, snakeCase, kebabCase, pascalCaseChaining transforms: Create intermediate variables to chain multiple operations.
Reference: Transformation
Test Edge Cases and Boundary Conditions
Include edge cases that stress pattern boundaries. Real codebases contain unusual but valid code that breaks naive patterns.
Incorrect (only happy path):
id: no-console-log
valid:
- logger.info(msg)
invalid:
- console.log(msg)
# Missing: multiline, nested, chained, commentedCorrect (comprehensive edge cases):
id: no-console-log
valid:
# Similar patterns that shouldn't match
- logger.info(msg)
- window.console # Just property access
- 'const console = mock' # Shadowed
# Commented code (doesn't match)
- '// console.log(debug)'
invalid:
# Basic cases
- console.log(msg)
- console.log()
- console.log(a, b, c)
# Multiline
- |
console.log(
longMessage
)
# Chained
- console.log(msg) || fallback
# Nested
- fn(console.log(msg))
# In expressions
- 'x = console.log(y)'
# Template literals
- 'console.log(`template ${var}`)'Edge case categories:
| Category | Examples |
|---|---|
| Whitespace | Multiline, extra spaces |
| Nesting | Inside functions, callbacks |
| Chaining | Method chains, pipelines |
| Context | Different statement types |
| Literals | Strings, templates, regex |
| Comments | Near matches (shouldn't match) |
Reference: Testing Rules
Test Patterns in Playground First
Use the ast-grep playground for rapid pattern iteration. The interactive environment shows matches immediately and displays AST structure.
Incorrect (edit-deploy-test cycle):
# Slow feedback loop
vim rule.yml
ast-grep scan --rule rule.yml src/
# No matches... why?
vim rule.yml
# Repeat...Correct (playground iteration):
1. Open https://ast-grep.github.io/playground.html
2. Select target language
3. Paste sample code in Code panel
4. Write pattern in Pattern panel
5. See immediate match highlighting
6. Refine until pattern works
7. Export to YAML rule filePlayground features:
- Real-time match highlighting
- AST viewer tab shows node structure
- Share patterns via URL
- Multiple language support
- Pattern object support (context/selector)
Debugging workflow:
# Pattern not matching?
1. Check AST tab - verify node types
2. Simplify pattern to minimum
3. Add complexity incrementally
4. Use --debug-query for CLI comparison:
ast-grep run --debug-query 'your pattern' -l javascriptWhen playground differs from CLI:
- Parser versions may differ
- UTF-8 vs UTF-16 encoding differences
- Use
--debug-queryfor authoritative AST
Reference: Playground
Use Snapshot Testing for Fix Verification
Snapshot testing captures expected rewrite output. Use --update-all to regenerate snapshots when intentionally changing fixes.
Incorrect (no fix verification):
id: migrate-import
rule:
pattern: require($PATH)
fix: import $PATH # Fix applied but never tested!Correct (snapshot test for fix):
# tests/migrate-import-test.yml
id: migrate-import
valid:
- import 'lodash'
invalid:
- require('lodash')
# Run test to generate snapshot
# ast-grep test -c sgconfig.ymlGenerated snapshot file:
# __snapshots__/migrate-import-test.yml.snap
id: migrate-import
snapshots:
require('lodash'):
fixed: import 'lodash'
labels: []Snapshot workflow:
# 1. Write rule with fix
# 2. Write test cases
# 3. Generate initial snapshots
ast-grep test -c sgconfig.yml -U
# 4. Review snapshots in __snapshots__/
# 5. Commit both test and snapshot files
# 6. CI runs tests against snapshots
ast-grep test -c sgconfig.ymlUpdating after intentional changes:
# After modifying fix template
ast-grep test -c sgconfig.yml --update-all
# Review changes, then commitReference: Testing Rules
Write Both Valid and Invalid Test Cases
Test files must include both valid (should not match) and invalid (should match) cases. This verifies both precision and recall.
Incorrect (only invalid cases):
id: no-console
valid: [] # No valid cases - doesn't test for false positives
invalid:
- console.log(msg)
- console.warn(msg)Correct (both valid and invalid):
id: no-console
valid:
- logger.info(msg) # Alternative approach
- console # Just the identifier, not a call
- 'const console = {}' # Shadowed variable
invalid:
- console.log(msg)
- console.warn(msg)
- console.error(a, b, c)Test case categories:
valid:
# Similar but different patterns
- logger.log(msg)
# Edge cases that shouldn't match
- 'obj.console.log(msg)'
# Already fixed code
- 'if (DEBUG) console.log(msg)'
invalid:
# Basic case
- console.log(msg)
# Variations
- console.log()
- console.log(a, b)
# Edge cases that should match
- 'console.log("literal")'Running tests:
ast-grep test -c sgconfig.yml
# Or for specific test file
ast-grep test -t tests/no-console-test.ymlReference: Testing Rules
Related skills
How it compares
Pick ast-grep over plain grep or ripgrep skills when queries must match code structure such as function signatures, imports, or nested language constructs.
FAQ
When should developers invoke the ast-grep skill?
The ast-grep skill triggers when writing, reviewing, or debugging YAML rules for code search, linting, or transformation. It applies to pattern syntax, meta variables, relational has/inside queries, constraints, and rewrite testing with the ast-grep CLI.
What ast-grep pitfalls does the skill address?
The ast-grep skill warns about missing stopBy:end on relational rules, improper meta-variable reuse, overly broad pattern matches, and unsafe rewrite semantics. It recommends starting from a clear query and example code before authoring rules and testing with sg test.