claude ast-grep skill

This commit is contained in:
2026-08-04 11:44:21 +02:00
parent 43d7fb0a7b
commit 1bee24c168

View File

@@ -0,0 +1,82 @@
---
name: ast-grep
description: "Structural code search and rewriting with ast-grep (AST-aware grep). Use instead of rg/grep when searching for code *structure* rather than text: find all calls/definitions matching a shape, match across formatting/line breaks, extract matched sub-parts (function names, arguments), or perform safe codemod-style rewrites. Works for Rust, TS/JS, Python, Go, Java, C/C++, and most tree-sitter languages."
---
# ast-grep: structural search & rewrite
ast-grep matches code by AST shape, not text. Prefer it over `rg` when the target spans lines, when formatting varies, or when you need to rewrite code safely.
## Pattern syntax
Patterns are **valid code snippets** with meta-variables:
| Syntax | Matches |
|---|---|
| `$VAR` | exactly one AST node (UPPERCASE names only) |
| `$$$` / `$$$ARGS` | zero or more nodes (args, params, statements) |
| `$_` | one node, non-capturing |
| repeated `$A` | backreference: `$A == $A` matches `x == x`, not `x == y` |
## Search
```bash
ast-grep -p '$X.unwrap()' -l rust src/ # find all .unwrap() calls
ast-grep -p 'fn $NAME($$$) -> $RET { $$$ }' -l rust . # multi-line shapes ok
ast-grep -p 'await $EXPR' -l ts src/
```
- `-l <lang>` is required unless searching by file extension is unambiguous; use it anyway.
- **Always single-quote patterns** — double quotes let the shell eat `$VAR`.
- Pattern must parse as valid code for that language (e.g. a bare `match $E { $$$ }` works in Rust because it's a valid expression).
## Structured output (for scripting / extraction)
```bash
ast-grep -p 'pub fn $F($$$) { $$$ }' -l rust src/ --json=compact
```
JSON gives `file`, `range`, `text`, and `metaVariables.single.F.text` — use this to extract e.g. all matching function names instead of parsing grep output.
## Rewrite
```bash
# Preview as diff (default: prints diff, does NOT modify files)
ast-grep -p '$X.lock().unwrap()' --rewrite '$X.lock().expect("poisoned")' -l rust src/
# Apply all edits
ast-grep -p '...' --rewrite '...' -l rust src/ --update-all
```
Preview first, then `--update-all`. The rewrite is a text template: metavars are substituted, unmatched metavars become empty strings.
## YAML rules (when patterns aren't enough)
For context-sensitive matches (inside/has/not), use `scan --inline-rules`:
```bash
ast-grep scan --inline-rules 'id: unwrap-in-closure
language: rust
rule:
pattern: $X.unwrap()
inside:
kind: closure_expression
stopBy: end
message: unwrap inside closure' src/
```
Rule keys:
- **Atomic:** `pattern`, `kind` (node type, e.g. `closure_expression`), `regex`
- **Relational:** `inside`, `has`, `follows`, `precedes` — add `stopBy: end` to search beyond direct parent/child
- **Composite:** `all`, `any`, `not`
- Optional `fix: <template>` per rule; `constraints:` to filter metavars (e.g. `X: {regex: '^foo'}`)
To discover node kinds for `kind:`, match a sample with `--json` (each match reports its node) or just use a pattern instead.
## Gotchas
- `$var` (lowercase) is not a metavariable; use `$VAR`.
- Single-quote patterns in the shell.
- `kind` combined with `pattern` doesn't re-parse the pattern; if a snippet is ambiguous, use the pattern object form: `pattern: {context: 'class A { $F = $V }', selector: field_definition}`.
- `all: [{kind: a}, {kind: b}]` tests the *same* node twice — for "contains both", use `all: [has: {...}, has: {...}]`.
- Regexes use Rust regex syntax (no backreferences/lookaround).
- Fall back to `rg` for plain-text/comment/string searches — ast-grep only matches code nodes.