SKILL.md

  1---
  2name: ast-grep
  3description: Guide for writing ast-grep rules to perform structural code search and analysis. Use when users need to search codebases using Abstract Syntax Tree (AST) patterns, find specific code structures, or perform complex code queries that go beyond simple text search. This skill should be used when users ask to search for code patterns, find specific language constructs, or locate code with particular structural characteristics.
  4user-invocable: true
  5metadata:
  6  source: https://github.com/ampcode/amp-contrib
  7---
  8
  9This skill helps translate natural language queries into ast-grep rules for structural code search. ast-grep uses Abstract Syntax Tree (AST) patterns to match code based on its structure rather than just text, enabling powerful and precise code search across large codebases.
 10
 11## When to Use This Skill
 12
 13Use this skill when users:
 14
 15- Need to search for code patterns using structural matching (e.g., "find all async functions that don't have error handling")
 16- Want to locate specific language constructs (e.g., "find all function calls with specific parameters")
 17- Request searches that require understanding code structure rather than just text
 18- Ask to search for code with particular AST characteristics
 19- Need to perform complex code queries that traditional text search cannot handle
 20
 21## General Workflow
 22
 23Follow this process to help users write effective ast-grep rules:
 24
 25### Step 1: Understand the Query
 26
 27Clearly understand what the user wants to find. Ask clarifying questions if needed:
 28
 29- What specific code pattern or structure are they looking for?
 30- Which programming language?
 31- Are there specific edge cases or variations to consider?
 32- What should be included or excluded from matches?
 33
 34### Step 2: Create Example Code
 35
 36Write a simple code snippet that represents what the user wants to match. Save this to a temporary file for testing.
 37
 38**Example:**
 39If searching for "async functions that use await", create a test file:
 40
 41```javascript
 42// test_example.js
 43async function example() {
 44	const result = await fetchData();
 45	return result;
 46}
 47```
 48
 49### Step 3: Write the ast-grep Rule
 50
 51Translate the pattern into an ast-grep rule. Start simple and add complexity as needed.
 52
 53**Key principles:**
 54
 55- Always use `stopBy: end` for relational rules (`inside`, `has`) to ensure search goes to the end of the direction
 56- Use `pattern` for simple structures
 57- Use `kind` with `has`/`inside` for complex structures
 58- Break complex queries into smaller sub-rules using `all`, `any`, or `not`
 59
 60**Example rule file (test_rule.yml):**
 61
 62```yaml
 63id: async-with-await
 64language: javascript
 65rule:
 66  kind: function_declaration
 67  has:
 68    pattern: await $EXPR
 69    stopBy: end
 70```
 71
 72See `references/rule_reference.md` for comprehensive rule documentation.
 73
 74### Step 4: Test the Rule
 75
 76Use ast-grep CLI to verify the rule matches the example code. There are two main approaches:
 77
 78**Option A: Test with inline rules (for quick iterations)**
 79
 80```bash
 81echo "async function test() { await fetch(); }" | ast-grep scan --inline-rules "id: test
 82language: javascript
 83rule:
 84  kind: function_declaration
 85  has:
 86    pattern: await \$EXPR
 87    stopBy: end" --stdin
 88```
 89
 90**Option B: Test with rule files (recommended for complex rules)**
 91
 92```bash
 93ast-grep scan --rule test_rule.yml test_example.js
 94```
 95
 96**Debugging if no matches:**
 97
 981. Simplify the rule (remove sub-rules)
 992. Add `stopBy: end` to relational rules if not present
1003. Use `--debug-query` to understand the AST structure (see below)
1014. Check if `kind` values are correct for the language
102
103### Step 5: Search the Codebase
104
105Once the rule matches the example code correctly, search the actual codebase:
106
107**For simple pattern searches:**
108
109```bash
110ast-grep run --pattern 'console.log($ARG)' --lang javascript /path/to/project
111```
112
113**For complex rule-based searches:**
114
115```bash
116ast-grep scan --rule my_rule.yml /path/to/project
117```
118
119**For inline rules (without creating files):**
120
121```bash
122ast-grep scan --inline-rules "id: my-rule
123language: javascript
124rule:
125  pattern: \$PATTERN" /path/to/project
126```
127
128## Rewriting Code with ast-grep
129
130ast-grep is a powerful AST-based tool that can search for code patterns and transform them into new code. It works like a syntax-aware sed/grep that understands code structure rather than just text.
131
132### Method 1: Command Line with `--rewrite`
133
134The simplest approach is using the `--rewrite` (or `-r`) flag directly in your terminal:
135
136```bash
137ast-grep run --pattern 'foo' --rewrite 'bar' --lang python
138```
139
140This finds all occurrences of `foo` and replaces them with `bar`. A practical example:
141
142```bash
143# Convert old-style property checks to optional chaining
144ast-grep -p '$PROP && $PROP()' --rewrite '$PROP?.()' --interactive -l ts ./src
145```
146
147**Key flags:**
148
149- `--interactive` or `-i`: Review each change before applying
150- `--update-all` or `-U`: Apply all changes without confirmation
151
152### Method 2: YAML Rules with `fix`
153
154For more complex transformations, use YAML rule files with the `fix` field:
155
156```yaml
157id: change_def
158language: Python
159rule:
160  pattern: |
161    def foo($X):
162      $$$S
163fix: |-
164  def baz($X):
165    $$$S
166```
167
168Run with: `ast-grep scan -r rule.yml ./src`
169
170### Meta-Variables
171
172Meta-variables are the key to powerful rewrites. They act like capture groups in regex:
173
174| Meta-variable | Matches                                            |
175| ------------- | -------------------------------------------------- |
176| `$NAME`       | Any single AST node (expression, identifier, etc.) |
177| `$$$ITEMS`    | Multiple nodes (like function arguments)           |
178
179**Example** — Swapping assignment sides:
180
181```yaml
182rule:
183  pattern: $X = $Y
184fix: $Y = $X
185```
186
187Transforms `a = b` into `b = a`.
188
189### Indentation Sensitivity
190
191ast-grep preserves indentation in rewrites. If your fix template has indentation, it's maintained relative to the original code position:
192
193```yaml
194rule:
195  pattern: "$B = lambda: $R"
196fix: |-
197  def $B():
198    return $R
199```
200
201### Expanding the Match Range with FixConfig
202
203Sometimes you need to delete surrounding characters (like commas). Use `FixConfig` with `expandStart` and `expandEnd`:
204
205```yaml
206rule:
207  kind: pair
208  has:
209    field: key
210    regex: Remove
211fix:
212  template: ""
213  expandEnd: { regex: "," } # Also deletes trailing comma
214```
215
216This removes the matched node _plus_ any trailing comma.
217
218### Advanced Features: Rewriters
219
220For complex multi-node transformations, use `rewriters` to process lists of matched nodes:
221
222```yaml
223id: barrel-to-single
224language: JavaScript
225rule:
226  pattern: import {$$$IDENTS} from './module'
227rewriters:
228  - id: rewrite-identifier
229    rule:
230      pattern: $IDENT
231      kind: identifier
232    transform:
233      LIB: { convert: { source: $IDENT, toCase: lowerCase } }
234    fix: import $IDENT from './module/$LIB'
235transform:
236  IMPORTS:
237    rewrite:
238      rewriters: [rewrite-identifier]
239      source: $$$IDENTS
240      joinBy: "\n"
241fix: $IMPORTS
242```
243
244This converts barrel imports like `import { A, B } from './module'` into individual imports.
245
246### Workflow Summary
247
2481. **Find**: Use patterns to match AST nodes
2492. **Capture**: Meta-variables (`$VAR`, `$$$ARGS`) capture matched content
2503. **Transform**: Optionally process captured content (case conversion, regex replacement)
2514. **Patch**: Replace matched nodes with the `fix` template
252
253### Tips
254
255- Use single quotes on command line to prevent shell expansion of `$`
256- Non-matched meta-variables become empty strings in the fix
257
258## ast-grep CLI Commands
259
260### Inspect Code Structure (--debug-query)
261
262Dump the AST structure to understand how code is parsed:
263
264```bash
265ast-grep run --pattern 'async function example() { await fetch(); }' \
266  --lang javascript \
267  --debug-query=cst
268```
269
270**Available formats:**
271
272- `cst`: Concrete Syntax Tree (shows all nodes including punctuation)
273- `ast`: Abstract Syntax Tree (shows only named nodes)
274- `pattern`: Shows how ast-grep interprets your pattern
275
276**Use this to:**
277
278- Find the correct `kind` values for nodes
279- Understand the structure of code you want to match
280- Debug why patterns aren't matching
281
282**Example:**
283
284```bash
285# See the structure of your target code
286ast-grep run --pattern 'class User { constructor() {} }' \
287  --lang javascript \
288  --debug-query=cst
289
290# See how ast-grep interprets your pattern
291ast-grep run --pattern 'class $NAME { $$$BODY }' \
292  --lang javascript \
293  --debug-query=pattern
294```
295
296### Test Rules (scan with --stdin)
297
298Test a rule against code snippet without creating files:
299
300```bash
301echo "const x = await fetch();" | ast-grep scan --inline-rules "id: test
302language: javascript
303rule:
304  pattern: await \$EXPR" --stdin
305```
306
307**Add --json for structured output:**
308
309```bash
310echo "const x = await fetch();" | ast-grep scan --inline-rules "..." --stdin --json
311```
312
313### Search with Patterns (run)
314
315Simple pattern-based search for single AST node matches:
316
317```bash
318# Basic pattern search
319ast-grep run --pattern 'console.log($ARG)' --lang javascript .
320
321# Search specific files
322ast-grep run --pattern 'class $NAME' --lang python /path/to/project
323
324# JSON output for programmatic use
325ast-grep run --pattern 'function $NAME($$$)' --lang javascript --json .
326```
327
328**When to use:**
329
330- Simple, single-node matches
331- Quick searches without complex logic
332- When you don't need relational rules (inside/has)
333
334### Search with Rules (scan)
335
336YAML rule-based search for complex structural queries:
337
338```bash
339# With rule file
340ast-grep scan --rule my_rule.yml /path/to/project
341
342# With inline rules
343ast-grep scan --inline-rules "id: find-async
344language: javascript
345rule:
346  kind: function_declaration
347  has:
348    pattern: await \$EXPR
349    stopBy: end" /path/to/project
350
351# JSON output
352ast-grep scan --rule my_rule.yml --json /path/to/project
353```
354
355**When to use:**
356
357- Complex structural searches
358- Relational rules (inside, has, precedes, follows)
359- Composite logic (all, any, not)
360- When you need the power of full YAML rules
361
362**Tip:** For relational rules (inside/has), always add `stopBy: end` to ensure complete traversal.
363
364## Tips for Writing Effective Rules
365
366### Always Use stopBy: end
367
368For relational rules, always use `stopBy: end` unless there's a specific reason not to:
369
370```yaml
371has:
372  pattern: await $EXPR
373  stopBy: end
374```
375
376This ensures the search traverses the entire subtree rather than stopping at the first non-matching node.
377
378### Start Simple, Then Add Complexity
379
380Begin with the simplest rule that could work:
381
3821. Try a `pattern` first
3832. If that doesn't work, try `kind` to match the node type
3843. Add relational rules (`has`, `inside`) as needed
3854. Combine with composite rules (`all`, `any`, `not`) for complex logic
386
387### Use the Right Rule Type
388
389- **Pattern**: For simple, direct code matching (e.g., `console.log($ARG)`)
390- **Kind + Relational**: For complex structures (e.g., "function containing await")
391- **Composite**: For logical combinations (e.g., "function with await but not in try-catch")
392
393### Debug with AST Inspection
394
395When rules don't match:
396
3971. Use `--debug-query=cst` to see the actual AST structure
3982. Check if metavariables are being detected correctly
3993. Verify the node `kind` matches what you expect
4004. Ensure relational rules are searching in the right direction
401
402### Escaping in Inline Rules
403
404When using `--inline-rules`, escape metavariables in shell commands:
405
406- Use `\$VAR` instead of `$VAR` (shell interprets `$` as variable)
407- Or use single quotes: `'$VAR'` works in most shells
408
409**Example:**
410
411```bash
412# Correct: escaped $
413ast-grep scan --inline-rules "rule: {pattern: 'console.log(\$ARG)'}" .
414
415# Or use single quotes
416ast-grep scan --inline-rules 'rule: {pattern: "console.log($ARG)"}' .
417```
418
419## Common Use Cases
420
421### Find Functions with Specific Content
422
423Find async functions that use await:
424
425```bash
426ast-grep scan --inline-rules "id: async-await
427language: javascript
428rule:
429  all:
430    - kind: function_declaration
431    - has:
432        pattern: await \$EXPR
433        stopBy: end" /path/to/project
434```
435
436### Find Code Inside Specific Contexts
437
438Find console.log inside class methods:
439
440```bash
441ast-grep scan --inline-rules "id: console-in-class
442language: javascript
443rule:
444  pattern: console.log(\$\$\$)
445  inside:
446    kind: method_definition
447    stopBy: end" /path/to/project
448```
449
450### Find Code Missing Expected Patterns
451
452Find async functions without try-catch:
453
454```bash
455ast-grep scan --inline-rules "id: async-no-trycatch
456language: javascript
457rule:
458  all:
459    - kind: function_declaration
460    - has:
461        pattern: await \$EXPR
462        stopBy: end
463    - not:
464        has:
465          pattern: try { \$\$\$ } catch (\$E) { \$\$\$ }
466          stopBy: end" /path/to/project
467```
468
469## Resources
470
471### references/
472
473Contains detailed documentation for ast-grep rule syntax:
474
475- `rule_reference.md`: Comprehensive ast-grep rule documentation covering atomic rules, relational rules, composite rules, and metavariables
476
477Load these references when detailed rule syntax information is needed.