Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
# CLI reference — `sg` / `ast-grep`
|
||||
|
||||
Compact reference for the underlying `sg` binary that the helper wraps. Use this when the helper isn't enough or when you want to invoke `sg` directly.
|
||||
|
||||
> **Binary name on Linux**: prefer `ast-grep` over `sg` because `sg` collides with `setgroups` from `util-linux`.
|
||||
|
||||
---
|
||||
|
||||
## `sg run` — one-shot search/rewrite
|
||||
|
||||
The default subcommand. `sg -p 'foo'` is shorthand for `sg run -p 'foo'`.
|
||||
|
||||
```bash
|
||||
sg run [OPTIONS] --pattern <PATTERN> [PATHS...]
|
||||
```
|
||||
|
||||
| Flag | Purpose |
|
||||
|---|---|
|
||||
| `-p, --pattern <P>` | AST pattern to match. **Always single-quote** in shell to prevent `$VAR` expansion. |
|
||||
| `-r, --rewrite <R>` | Replacement pattern. Used with `-U` to apply. |
|
||||
| `-l, --lang <LANG>` | Language. Inferred from path extension if omitted. |
|
||||
| `--selector <KIND>` | When the pattern is ambiguous, extract only this AST kind. |
|
||||
| `--strictness <S>` | `cst` \| `smart` (default) \| `ast` \| `relaxed` \| `signature` |
|
||||
| `--debug-query[=<F>]` | Print parsed pattern. F: `pattern` \| `ast` \| `cst` \| `sexp` |
|
||||
| `--stdin` | Read code from stdin instead of files. Lang must be set. |
|
||||
| `--globs <G>` | Include/exclude glob (repeatable; prefix `!` to exclude). |
|
||||
| `--follow` | Follow symlinks. |
|
||||
| `--no-ignore <T>` | Disable a class of ignore: `hidden`, `dot`, `exclude`, `global`, `parent`, `vcs`. |
|
||||
| `-i, --interactive` | Step through matches and confirm each rewrite. |
|
||||
| `-U, --update-all` | Apply all rewrites without confirmation. **Mutually exclusive with `--json`** (silently). |
|
||||
| `--json[=<S>]` | Emit JSON. S: `pretty` \| `stream` \| `compact` (compact is best for piping). |
|
||||
| `--color <W>` | `auto` \| `always` \| `ansi` \| `never` |
|
||||
| `--inspect <G>` | Detail level: `nothing` \| `summary` \| `entity` |
|
||||
| `-A, -B, -C <N>` | Context lines after / before / around each match. |
|
||||
| `-j, --threads <N>` | Thread count (default: heuristic; `0` = auto). |
|
||||
|
||||
### `--update-all` + `--json` — the trap
|
||||
|
||||
`sg` silently ignores `--update-all` when `--json` is set. To preview AND apply, run **two passes**:
|
||||
|
||||
```bash
|
||||
# Pass 1: preview
|
||||
sg run -p 'foo()' -r 'bar()' --json=compact src/
|
||||
|
||||
# Pass 2: apply
|
||||
sg run -p 'foo()' -r 'bar()' --update-all src/
|
||||
```
|
||||
|
||||
The `ast_grep_helper.py replace --apply` subcommand does this automatically.
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Basic search
|
||||
sg run -p 'console.log($MSG)' --lang ts src/
|
||||
|
||||
# Search with context lines
|
||||
sg run -p 'eval($CODE)' --lang js -C 3 .
|
||||
|
||||
# Rewrite, dry-run preview as JSON
|
||||
sg run -p 'console.log($MSG)' -r 'logger.info($MSG)' --json=compact --lang ts src/
|
||||
|
||||
# Rewrite, apply
|
||||
sg run -p 'console.log($MSG)' -r 'logger.info($MSG)' --update-all --lang ts src/
|
||||
|
||||
# Pattern from stdin
|
||||
echo 'console.log("x")' | sg run -p 'console.log($MSG)' --lang js --stdin
|
||||
|
||||
# Limit to specific files
|
||||
sg run -p 'foo()' --lang ts --globs 'src/**/*.ts' --globs '!**/*.test.ts' .
|
||||
|
||||
# Debug a pattern that returns 0 matches
|
||||
sg run -p 'def $F($$$):' --lang py --debug-query=ast --stdin <<< 'def foo(): pass'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `sg scan` — YAML rule scanner
|
||||
|
||||
Run a configuration of YAML rules across files. Used for project-wide lints and codemods.
|
||||
|
||||
```bash
|
||||
sg scan [OPTIONS] [PATHS...]
|
||||
```
|
||||
|
||||
| Flag | Purpose |
|
||||
|---|---|
|
||||
| `-c, --config <C>` | Path to `sgconfig.yml` (default: walk up from cwd looking for one). |
|
||||
| `-r, --rule <F>` | Run a **single** rule file. Mutually exclusive with `--config`. |
|
||||
| `--inline-rules <Y>` | Pass YAML rule text inline. Use `---` to separate multiple rules. |
|
||||
| `--filter <RE>` | Only run rules whose `id` matches this regex. |
|
||||
| `--include-metadata` | Include rule `metadata` field in JSON output. |
|
||||
| `-U, --update-all` | Apply fixes from `fix:` automatically. |
|
||||
| `--report-style <S>` | `rich` \| `medium` \| `short` |
|
||||
| `--format <F>` | `github` \| `sarif` (CI-friendly outputs). |
|
||||
| `--error[=ID]`, `--warning[=ID]`, `--info[=ID]`, `--hint[=ID]`, `--off[=ID]` | Promote/demote severity. |
|
||||
| `-i, --interactive` | Confirm each fix interactively. |
|
||||
| `--json[=<S>]` | JSON output. |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Run all rules in sgconfig.yml-discovered ruleDirs
|
||||
sg scan src/
|
||||
|
||||
# Run a single rule file (no sgconfig.yml needed)
|
||||
sg scan -r rules/no-console.yml src/
|
||||
|
||||
# Inline rule (great for one-offs and CI)
|
||||
sg scan --inline-rules '
|
||||
id: no-todo
|
||||
language: TypeScript
|
||||
severity: warning
|
||||
rule: { pattern: TODO }' src/
|
||||
|
||||
# Apply all auto-fixes
|
||||
sg scan -U src/
|
||||
|
||||
# CI-friendly GitHub annotations
|
||||
sg scan --format github src/
|
||||
|
||||
# SARIF for security scanners
|
||||
sg scan --format sarif src/ > sarif.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `sg test` — run rule snapshot tests
|
||||
|
||||
```bash
|
||||
sg test [OPTIONS]
|
||||
```
|
||||
|
||||
| Flag | Purpose |
|
||||
|---|---|
|
||||
| `-c, --config <C>` | Path to `sgconfig.yml`. |
|
||||
| `-t, --test-dir <D>` | Test directory. |
|
||||
| `--snapshot-dir <D>` | Snapshot directory (default: `__snapshots__`). |
|
||||
| `--skip-snapshot-tests` | Validate test code parses; don't compare snapshots. |
|
||||
| `-U, --update-all` | Update all changed snapshots. |
|
||||
| `-f, --filter <G>` | Filter test cases by glob on rule id. |
|
||||
| `--include-off` | Include rules with severity `off`. |
|
||||
| `-i, --interactive` | Step through changed snapshots and accept/reject each. |
|
||||
|
||||
A test directory looks like:
|
||||
|
||||
```
|
||||
test/
|
||||
├── no-console.yml # `valid:` and `invalid:` snippets
|
||||
└── no-console-test.yml # alternative test file format
|
||||
__snapshots__/
|
||||
└── no-console-snapshot.yml # expected match locations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `sg new` — scaffold
|
||||
|
||||
```bash
|
||||
sg new <COMMAND> [NAME] [OPTIONS]
|
||||
```
|
||||
|
||||
| Subcommand | Creates |
|
||||
|---|---|
|
||||
| `project` | `sgconfig.yml`, `rules/`, `utils/`, `__snapshots__/` directory tree |
|
||||
| `rule` | A new YAML rule file in the first `ruleDirs` entry |
|
||||
| `test` | A new test file in `testConfigs[0].testDir` |
|
||||
| `util` | A new utility rule in the first `utilDirs` entry |
|
||||
|
||||
```bash
|
||||
# New project in current dir
|
||||
sg new project --yes
|
||||
|
||||
# New rule
|
||||
sg new rule no-console --lang typescript
|
||||
|
||||
# New test
|
||||
sg new test no-console --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `sg lsp` — language server
|
||||
|
||||
```bash
|
||||
sg lsp -c sgconfig.yml
|
||||
```
|
||||
|
||||
Speak LSP over stdin/stdout. Configure your editor (VS Code extension, Neovim `nvim-lspconfig`, Helix `languages.toml`) to spawn this command for live diagnostics.
|
||||
|
||||
---
|
||||
|
||||
## `sg completions` — shell completions
|
||||
|
||||
```bash
|
||||
sg completions bash >> ~/.bashrc
|
||||
sg completions zsh > "${fpath[1]}/_sg"
|
||||
sg completions fish > ~/.config/fish/completions/sg.fish
|
||||
sg completions powershell >> $PROFILE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Useful one-liners
|
||||
|
||||
```bash
|
||||
# Count matches per file
|
||||
sg run -p 'console.log($_)' --lang ts --json=compact . \
|
||||
| jq -r '.[].file' | sort | uniq -c | sort -rn
|
||||
|
||||
# Find all unique kinds in a file (great for figuring out kind names)
|
||||
sg run -p '$_' --lang ts --debug-query=cst src/foo.ts \
|
||||
| grep -oE 'kind: [a-z_]+' | sort -u
|
||||
|
||||
# Rewrite only in a subset of files
|
||||
sg run -p 'foo()' -r 'bar()' --update-all --globs 'src/**/*.ts' --globs '!src/legacy/**' .
|
||||
|
||||
# Apply fixes from many rules but only ones matching a pattern in their id
|
||||
sg scan --filter 'no-' -U src/
|
||||
|
||||
# Use ast-grep as a linter in pre-commit
|
||||
sg scan --format github src/ || exit 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `references/yaml-rules.md` — rule schema (`pattern`, `kind`, `regex`, `inside`, `has`, `all`, `any`, `not`, `matches`, `transform`, `fix`).
|
||||
- `references/sgconfig.md` — project configuration.
|
||||
- Official: <https://ast-grep.github.io/reference/cli.html>
|
||||
@@ -0,0 +1,166 @@
|
||||
# Install ast-grep
|
||||
|
||||
The skill ships an `install.sh` (POSIX) and `install.ps1` (Windows) that try every reasonable method in priority order and fall back to a GitHub release download as a last resort. **You usually do not need to read this page.** Run the installer:
|
||||
|
||||
```bash
|
||||
bash install.sh # macOS / Linux / WSL / Git Bash
|
||||
pwsh -File install.ps1 # Windows PowerShell
|
||||
```
|
||||
|
||||
This page exists for the (rare) case the installer cannot find a working method, or you want to install ast-grep manually.
|
||||
|
||||
---
|
||||
|
||||
## Per-OS install commands (verbatim, copy-paste)
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
brew install ast-grep # Homebrew - the primary path
|
||||
sudo port install ast-grep # MacPorts
|
||||
npm install -g @ast-grep/cli # if you have Node already
|
||||
cargo install ast-grep --locked # if you have Rust already
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
# Universal (works on every distro)
|
||||
npm install -g @ast-grep/cli
|
||||
cargo install ast-grep --locked
|
||||
pip install ast-grep-cli
|
||||
|
||||
# Distro-specific
|
||||
nix-env -iA nixpkgs.ast-grep # NixOS / Nix
|
||||
brew install ast-grep # Linuxbrew
|
||||
|
||||
# NixOS shell.nix
|
||||
nix-shell -p ast-grep
|
||||
```
|
||||
|
||||
> **Linux gotcha**: the binary is named `sg`, but on most Linux systems `sg` is also the **`setgroups` command** from `util-linux`. The shell sees `setgroups` first and ignores ast-grep. Two options:
|
||||
>
|
||||
> 1. Always invoke `ast-grep` (full name).
|
||||
> 2. Add an alias: `alias sg=ast-grep` in your `~/.bashrc` / `~/.zshrc`.
|
||||
>
|
||||
> The `ast_grep_helper.py` script in `scripts/` already handles this — when it sees `sg` on PATH on Linux, it runs `--version` and rejects the binary if it isn't ast-grep.
|
||||
|
||||
### Windows
|
||||
|
||||
```powershell
|
||||
scoop install main/ast-grep # Scoop (most common on dev machines)
|
||||
winget install --id ast-grep.ast-grep # Winget (Microsoft built-in)
|
||||
choco install ast-grep # Chocolatey
|
||||
npm install -g @ast-grep/cli # any OS with Node
|
||||
cargo install ast-grep --locked # any OS with Rust
|
||||
```
|
||||
|
||||
### WSL / Git Bash on Windows
|
||||
|
||||
Treat as Linux. Use `npm`, `cargo`, `pip`, or `bash install.sh`.
|
||||
|
||||
---
|
||||
|
||||
## Cross-platform / language-ecosystem methods
|
||||
|
||||
These work on every OS:
|
||||
|
||||
| Method | Command | Pros | Cons |
|
||||
|---|---|---|---|
|
||||
| **npm** | `npm install -g @ast-grep/cli` | Fast, prebuilt platform binaries | Needs Node 18+ |
|
||||
| **cargo** | `cargo install ast-grep --locked` | Always builds latest from source | Slow (~3-5 min compile) |
|
||||
| **cargo binstall** | `cargo binstall ast-grep` | Fast (downloads release binary) | Needs `cargo-binstall` first |
|
||||
| **pip** | `pip install ast-grep-cli` | Works in any Python venv | Needs Python 3.8+ |
|
||||
| **pipx** | `pipx install ast-grep-cli` | Isolated install | Needs pipx |
|
||||
| **mise** | `mise use -g ast-grep` | asdf successor, version-pinning | Needs mise |
|
||||
| **GitHub release** | manual download | Pure binary, no toolchain | Manual PATH setup |
|
||||
|
||||
---
|
||||
|
||||
## GitHub release manual install
|
||||
|
||||
If every package manager fails:
|
||||
|
||||
```bash
|
||||
# 1. Pick the right asset for your OS+arch from the latest release:
|
||||
# https://github.com/ast-grep/ast-grep/releases/latest
|
||||
#
|
||||
# Naming pattern:
|
||||
# app-aarch64-apple-darwin.zip macOS Apple Silicon
|
||||
# app-x86_64-apple-darwin.zip macOS Intel
|
||||
# app-aarch64-unknown-linux-gnu.zip Linux ARM64 (glibc)
|
||||
# app-x86_64-unknown-linux-gnu.zip Linux x86_64 (glibc)
|
||||
# app-x86_64-pc-windows-msvc.zip Windows x86_64
|
||||
# app-aarch64-pc-windows-msvc.zip Windows ARM64
|
||||
|
||||
# 2. Download and extract:
|
||||
VERSION=0.45.0
|
||||
TRIPLE=aarch64-apple-darwin
|
||||
curl -fsSL "https://github.com/ast-grep/ast-grep/releases/download/${VERSION}/app-${TRIPLE}.zip" -o /tmp/ast-grep.zip
|
||||
unzip /tmp/ast-grep.zip -d /tmp/ast-grep
|
||||
sudo mv /tmp/ast-grep/ast-grep /usr/local/bin/sg
|
||||
sudo chmod +x /usr/local/bin/sg
|
||||
|
||||
# 3. Verify:
|
||||
sg --version
|
||||
```
|
||||
|
||||
The skill's `install.sh` does steps 1-3 automatically and drops the binary in `<skill_root>/bin/sg` so you can use it without sudo.
|
||||
|
||||
---
|
||||
|
||||
## Build from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ast-grep/ast-grep.git
|
||||
cd ast-grep
|
||||
cargo install --path ./crates/cli --locked
|
||||
```
|
||||
|
||||
Requires Rust 1.74+. Slowest path; only useful when you need a specific commit or unreleased fix.
|
||||
|
||||
---
|
||||
|
||||
## Verifying the install
|
||||
|
||||
```bash
|
||||
ast-grep --version # or `sg --version`
|
||||
# ast-grep 0.45.0
|
||||
```
|
||||
|
||||
Then sanity-check a real query:
|
||||
|
||||
```bash
|
||||
echo 'console.log("hello")' | sg run -p 'console.log($MSG)' --lang js --stdin
|
||||
```
|
||||
|
||||
Expected: a single match with the `console.log("hello")` call highlighted.
|
||||
|
||||
---
|
||||
|
||||
## Editor integration
|
||||
|
||||
After installing the CLI, set up your editor:
|
||||
|
||||
- **VS Code**: install the [`ast-grep`](https://marketplace.visualstudio.com/items?itemName=ast-grep.ast-grep-vscode) extension. Requires `sgconfig.yml` in workspace root for live diagnostics.
|
||||
- **Neovim**: configure `nvim-lspconfig` with `ast_grep` server, or install [`telescope-ast-grep.nvim`](https://github.com/ray-x/telescope-ast-grep.nvim).
|
||||
- **Helix**: add `ast-grep lsp` as a language server in `languages.toml`.
|
||||
- **Emacs**: install [`ast-grep.el`](https://github.com/SunskyXH/ast-grep.el).
|
||||
|
||||
See `references/cli.md` for `ast-grep lsp` flags.
|
||||
|
||||
---
|
||||
|
||||
## Uninstall
|
||||
|
||||
| Method | Command |
|
||||
|---|---|
|
||||
| brew | `brew uninstall ast-grep` |
|
||||
| npm | `npm uninstall -g @ast-grep/cli` |
|
||||
| cargo | `cargo uninstall ast-grep` |
|
||||
| pip | `pip uninstall ast-grep-cli` |
|
||||
| pipx | `pipx uninstall ast-grep-cli` |
|
||||
| scoop | `scoop uninstall ast-grep` |
|
||||
| winget | `winget uninstall --id ast-grep.ast-grep` |
|
||||
| choco | `choco uninstall ast-grep` |
|
||||
| GitHub binary | `rm <skill_root>/bin/sg` |
|
||||
@@ -0,0 +1,147 @@
|
||||
# Pattern syntax — meta-variables and how patterns parse
|
||||
|
||||
ast-grep is **not regex**. Patterns are written in the **same syntax as the target language** (TypeScript, Python, Go, etc.), and ast-grep matches them against the AST of every file. The wildcards are called **meta-variables**.
|
||||
|
||||
This page is the canonical primer. If a pattern fails, 90% of the time it is one of the issues on this page.
|
||||
|
||||
---
|
||||
|
||||
## The three meta-variables
|
||||
|
||||
| Syntax | Matches | Capture |
|
||||
|---|---|---|
|
||||
| `$VAR` | exactly **one** AST node | yes, by name |
|
||||
| `$$$` | **zero or more** AST nodes (a list) | no (anonymous) |
|
||||
| `$$$VAR` | zero or more AST nodes | yes, by name |
|
||||
| `$_` | one AST node | no (anonymous) |
|
||||
|
||||
A meta-variable always replaces a **whole AST node**, never a substring of a node. `$VAR` cannot match the first three characters of an identifier, only an entire identifier (or expression, or statement, depending on context).
|
||||
|
||||
### Naming rules
|
||||
|
||||
- Must start with `$`.
|
||||
- Then uppercase letters `A-Z`, digits, or underscores.
|
||||
- **Valid**: `$X`, `$VAR`, `$VAR_1`, `$_`, `$_VAR`, `$ARG1`.
|
||||
- **Invalid**: `$lower`, `$kebab-case`, `$1` (digit first), `$$single` (use `$_` for anonymous).
|
||||
|
||||
### Same-name = same content
|
||||
|
||||
Two occurrences of the same metavariable in a pattern must capture **identical text**:
|
||||
|
||||
```ts
|
||||
// Pattern
|
||||
$X === $X
|
||||
|
||||
// Matches
|
||||
a === a
|
||||
foo.bar === foo.bar
|
||||
|
||||
// Does NOT match
|
||||
a === b
|
||||
foo === foo.bar
|
||||
```
|
||||
|
||||
Useful for finding redundant comparisons, double assignments, etc.
|
||||
|
||||
### `$$$` is greedy
|
||||
|
||||
When you write `foo($$$A, b, $$$C)`, the matcher does **not** backtrack or try every possible split. It greedily fills `$$$A` until the pattern can match `b`, then everything left goes into `$$$C`.
|
||||
|
||||
```ts
|
||||
// Pattern
|
||||
foo($$$A, b, $$$C)
|
||||
|
||||
// Input
|
||||
foo(a, c, b, b, c)
|
||||
|
||||
// Capture
|
||||
$$$A = [a, c]
|
||||
$$$C = [b, c]
|
||||
```
|
||||
|
||||
If you need a different split, restructure the pattern (e.g. add a constraint).
|
||||
|
||||
---
|
||||
|
||||
## Patterns must be valid code
|
||||
|
||||
The pattern itself must parse with the target language's grammar. ast-grep treats `$VAR` and `$$$` as identifiers/argument lists during parsing, then matches structurally.
|
||||
|
||||
### What goes wrong
|
||||
|
||||
| Bad pattern | Why it fails | Fix |
|
||||
|---|---|---|
|
||||
| `function $NAME` | Function declaration without body — not a valid AST node in JS/TS/Go/Rust. | `function $NAME($$$) { $$$ }` |
|
||||
| `def $FN($$$):` | Trailing colon. ast-grep parses as a complete function definition; the colon makes it a statement. | `def $FN($$$)` |
|
||||
| `class Foo:` | Same — Python class without body. | `class Foo($$$)` |
|
||||
| `fn $NAME` | Rust fn without signature. | `fn $NAME($$$) -> $RET { $$$ }` |
|
||||
| `if x` | Incomplete `if` — most languages require the body. | `if x { $$$ }` (curly-brace languages) or `if x: $$$` (Python uses `pattern.context`/`selector` instead) |
|
||||
| `"key": "$VAL"` | JSON pattern — a key/value pair on its own isn't valid JSON. | Use `pattern: { context: '{"key": "$VAL"}', selector: pair }` |
|
||||
|
||||
### When a sub-expression isn't valid on its own
|
||||
|
||||
Sometimes you want to match an *expression* that the language only allows inside a larger context. Use the `pattern` object form:
|
||||
|
||||
```yaml
|
||||
pattern:
|
||||
context: 'class A { $FIELD = $INIT }'
|
||||
selector: field_definition
|
||||
```
|
||||
|
||||
This says: parse `class A { $FIELD = $INIT }` as a whole, then keep only the `field_definition` sub-tree as the actual pattern.
|
||||
|
||||
---
|
||||
|
||||
## Strictness levels
|
||||
|
||||
When CST nodes don't match exactly (extra whitespace, different unnamed punctuation), ast-grep can be more or less forgiving. Pass `--strictness <LEVEL>` on the CLI, or set it in a YAML rule:
|
||||
|
||||
| Level | Matches |
|
||||
|---|---|
|
||||
| `cst` | Every node, including unnamed (commas, parens, etc.) |
|
||||
| `smart` (default) | All except unnamed nodes in the **target** that aren't in the pattern |
|
||||
| `ast` | Only named AST nodes |
|
||||
| `relaxed` | Named AST nodes, ignoring comments |
|
||||
| `signature` | Only node kinds — text and unnamed nodes ignored |
|
||||
|
||||
`smart` is almost always what you want. Reach for `signature` when you want to match "any function called `foo`" regardless of arguments.
|
||||
|
||||
---
|
||||
|
||||
## Testing a pattern
|
||||
|
||||
Two tools help you confirm a pattern parses the way you expect:
|
||||
|
||||
```bash
|
||||
# Print the AST of the pattern itself
|
||||
sg run -p 'console.log($MSG)' --lang ts --debug-query=ast
|
||||
|
||||
# Print the parsed CST of a file (great for figuring out kind names)
|
||||
sg run -p '$_' --lang ts --debug-query=cst src/example.ts | head -40
|
||||
```
|
||||
|
||||
`--debug-query=ast` shows the named AST nodes only (cleaner). `--debug-query=cst` shows everything including punctuation. Both go to stderr, so they don't interfere with stdout JSON.
|
||||
|
||||
The web playground is also fast: <https://ast-grep.github.io/playground.html>.
|
||||
|
||||
---
|
||||
|
||||
## When ast-grep is the wrong tool
|
||||
|
||||
If your pattern is fundamentally text-shaped, switch to `grep` / `rg`:
|
||||
|
||||
- Match across multiple files **for any text** → `rg`
|
||||
- Cross-language regex with alternation → `rg -e foo -e bar`
|
||||
- Match comments only → `rg --type ts '^\s*//.*TODO'`
|
||||
- Match URLs, emails, license headers → `rg`
|
||||
|
||||
ast-grep is for **code structure**: function shapes, call patterns, control flow, type annotations, imports, error handling. If your "pattern" only depends on the bytes of the file and not on the syntax, regex is the right tool.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `references/pitfalls.md` — concrete regex anti-patterns and language-specific traps.
|
||||
- `references/recipes.md` — copy-paste-ready patterns for TS/JS/Py/Go/Rust.
|
||||
- `references/yaml-rules.md` — `kind`, `regex`, `inside`, `has`, `all`, `any`, `not`, `matches`.
|
||||
- Official: <https://ast-grep.github.io/guide/pattern-syntax.html>
|
||||
@@ -0,0 +1,303 @@
|
||||
# Pitfalls — what breaks patterns and how to fix them
|
||||
|
||||
This is the failure-mode field guide. The `scripts/ast_grep_helper.py validate` subcommand mechanically checks for the items in §1 before calling `sg`; the rest are lower-frequency but still common.
|
||||
|
||||
---
|
||||
|
||||
## 1. Regex syntax does not work
|
||||
|
||||
ast-grep does **not** interpret regex inside patterns. The following all fail:
|
||||
|
||||
| Bad | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `foo\|bar` | `\|` is regex alternation. ast-grep does not alternate. | Two separate calls, OR `any: [pattern: foo, pattern: bar]` in a YAML rule, OR `rg -e foo -e bar`. |
|
||||
| `foo.*bar` | `.*` is a regex wildcard. | `foo($$$) bar` if the gap is a list of nodes; otherwise switch to `rg`. |
|
||||
| `\w+`, `\d+`, `\s` | Regex character classes. | `$VAR` to capture any identifier. For digits-only, use `kind: number_literal`. |
|
||||
| `[a-z]+` | Regex character class. | No AST equivalent — switch to `rg`. |
|
||||
| `^foo$` | Regex anchors. | Anchor by AST: use `kind: program > expression_statement` or use `inside`/`not has`. |
|
||||
|
||||
**Why this happens**: LLMs default to regex thinking. The mental switch is "ast-grep patterns are *code*, not *strings*."
|
||||
|
||||
When you genuinely need regex, use the `regex` rule field in YAML (matches node text with Rust regex):
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
all:
|
||||
- kind: identifier
|
||||
- regex: '^[A-Z][a-z]+$' # CamelCase identifiers only
|
||||
```
|
||||
|
||||
Note: `regex` matches the **whole node text** — no partial matches. Combine with `kind` or `pattern` for performance.
|
||||
|
||||
---
|
||||
|
||||
## 2. Incomplete AST nodes
|
||||
|
||||
Patterns must be valid code that the parser accepts as a complete node. Common mistakes:
|
||||
|
||||
```text
|
||||
# JS/TS
|
||||
function foo ❌ no params, no body
|
||||
function $NAME($$$) { $$$ } ✅
|
||||
|
||||
async function $NAME ❌
|
||||
async function $NAME($$$) { $$$ } ✅
|
||||
|
||||
# Python
|
||||
def foo: ❌ trailing colon makes it a statement
|
||||
def $FN($$$) ✅
|
||||
class Foo: ❌
|
||||
class $C($$$) ✅
|
||||
|
||||
# Go
|
||||
func foo ❌
|
||||
func $NAME($$$) { $$$ } ✅
|
||||
|
||||
# Rust
|
||||
fn foo ❌
|
||||
fn $NAME($$$) -> $RET { $$$ } ✅
|
||||
fn $NAME($$$) { $$$ } ✅ (-> () inferred)
|
||||
|
||||
# Java
|
||||
public void foo ❌
|
||||
public void $NAME($$$) { $$$ } ✅
|
||||
```
|
||||
|
||||
If a pattern returns 0 matches and looks correct, run `sg run -p '<pattern>' --lang <lang> --debug-query=ast --stdin <<< 'echo'` and see what the parser thinks the pattern is. If it returns an `ERROR` node, the pattern is malformed.
|
||||
|
||||
---
|
||||
|
||||
## 3. Pattern parses as the wrong kind
|
||||
|
||||
A class field initializer `a = 123` *also* parses as an assignment expression. If you want only field definitions, you must disambiguate:
|
||||
|
||||
```yaml
|
||||
# WRONG — pattern parses as assignment_expression, not field_definition
|
||||
pattern: a = 123
|
||||
kind: field_definition
|
||||
|
||||
# CORRECT — use pattern object with context + selector
|
||||
pattern:
|
||||
context: 'class C { a = 123 }'
|
||||
selector: field_definition
|
||||
```
|
||||
|
||||
`kind` and `pattern` are **independent constraints**, not modifiers of each other. ast-grep does not change *how* it parses based on `kind`.
|
||||
|
||||
---
|
||||
|
||||
## 4. The `|` ambiguity
|
||||
|
||||
A bare `|` in a pattern is interpreted as bitwise-or in most languages, **not** alternation. So:
|
||||
|
||||
```yaml
|
||||
pattern: foo | bar # parses as: foo bitwise-or'd with bar
|
||||
```
|
||||
|
||||
…matches expressions like `x | y`, not "either foo or bar". To get alternation, use `any`:
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
any:
|
||||
- pattern: foo
|
||||
- pattern: bar
|
||||
```
|
||||
|
||||
In TypeScript union types (`A | B`), `|` is part of the type syntax — `pattern: A | B` correctly parses as a union type and matches that.
|
||||
|
||||
---
|
||||
|
||||
## 5. Same-name metavars collide
|
||||
|
||||
```ts
|
||||
// Pattern: $X = $X
|
||||
// Captures only when both sides are TEXTUALLY identical.
|
||||
|
||||
// Matches:
|
||||
a = a
|
||||
foo.bar = foo.bar
|
||||
|
||||
// Does NOT match:
|
||||
a = b
|
||||
let x = compute() // because $X needs to bind once and re-use
|
||||
```
|
||||
|
||||
If you actually want two independent captures, name them differently: `$X = $Y`.
|
||||
|
||||
---
|
||||
|
||||
## 6. `$$$` is greedy then commits
|
||||
|
||||
`$$$` does **not** backtrack. It captures as much as possible, then commits. If your pattern needs a non-greedy match, structure it differently:
|
||||
|
||||
```ts
|
||||
// You want "match foo($X), where $X is any single arg"
|
||||
// BAD: foo($$$X) // matches foo(a), foo(a, b), foo(a, b, c) - too broad
|
||||
// GOOD: foo($X) // matches only single-arg calls
|
||||
|
||||
// You want "match foo() with at least one arg"
|
||||
// BAD: foo($$$X) // also matches foo()
|
||||
// GOOD: foo($X, $$$REST) // forces at least one arg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. `kind` names depend on tree-sitter grammar
|
||||
|
||||
`kind: function_declaration` works for JavaScript, but Python uses `function_definition`, Rust uses `function_item`, Go uses `function_declaration` (same as JS by coincidence). To find the right name, parse a known-good file:
|
||||
|
||||
```bash
|
||||
sg run -p '$_' --lang python --debug-query=cst path/to/example.py | grep -i function
|
||||
```
|
||||
|
||||
Or open <https://ast-grep.github.io/playground.html> and click on a node to see its `kind`.
|
||||
|
||||
---
|
||||
|
||||
## 8. `inside` / `has` defaults to `stopBy: neighbor`
|
||||
|
||||
```yaml
|
||||
inside:
|
||||
kind: function_declaration # only checks the IMMEDIATE parent
|
||||
```
|
||||
|
||||
If you want "anywhere inside a function (any depth)":
|
||||
|
||||
```yaml
|
||||
inside:
|
||||
kind: function_declaration
|
||||
stopBy: end # walks up to the file root
|
||||
```
|
||||
|
||||
Same for `has` (descendants):
|
||||
|
||||
```yaml
|
||||
has:
|
||||
kind: return_statement
|
||||
stopBy: end # walks down the whole subtree
|
||||
```
|
||||
|
||||
Without `stopBy: end`, `has` only matches direct children.
|
||||
|
||||
---
|
||||
|
||||
## 9. CLI silently ignores `--update-all` when `--json` is set
|
||||
|
||||
This is the single biggest gotcha when scripting ast-grep. If you run:
|
||||
|
||||
```bash
|
||||
sg run -p 'foo()' -r 'bar()' --json=compact --update-all .
|
||||
```
|
||||
|
||||
…you get the JSON output but **no files are mutated**. ast-grep silently drops `--update-all` when `--json` is on. To both preview and apply, run **two passes**:
|
||||
|
||||
```bash
|
||||
# Pass 1: preview as JSON
|
||||
sg run -p 'foo()' -r 'bar()' --json=compact .
|
||||
|
||||
# Pass 2: actually apply
|
||||
sg run -p 'foo()' -r 'bar()' --update-all .
|
||||
```
|
||||
|
||||
`scripts/ast_grep_helper.py replace` does this automatically when `--apply` is set.
|
||||
|
||||
---
|
||||
|
||||
## 10. Composite rules apply to a single node
|
||||
|
||||
`all` and `any` evaluate against **one target node** at a time:
|
||||
|
||||
```yaml
|
||||
# WRONG — wants "node has BOTH a number child AND a string child"
|
||||
has:
|
||||
all:
|
||||
- kind: number # impossible: one node cannot be both at once
|
||||
- kind: string
|
||||
|
||||
# CORRECT
|
||||
all:
|
||||
- has: { kind: number }
|
||||
- has: { kind: string }
|
||||
```
|
||||
|
||||
Lift relational rules out of composites when the relation is "the surrounding node has X children matching Y."
|
||||
|
||||
---
|
||||
|
||||
## 11. Field order is not guaranteed
|
||||
|
||||
When a rule object has multiple fields:
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
pattern: $X = compute()
|
||||
has: { kind: number }
|
||||
```
|
||||
|
||||
…ast-grep evaluates them as an implicit `all`, but the **order** in which metavariables are captured is not guaranteed. If your `transform` or `fix` depends on capture order, use an explicit `all` array:
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
all:
|
||||
- pattern: function $F() { $$$ }
|
||||
- has: { pattern: $F() } # $F captured by pattern first; here we just check
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. `regex` without `kind` is slow
|
||||
|
||||
`regex` alone scans every node text in the file. On large repos this is noticeably slow. Always combine:
|
||||
|
||||
```yaml
|
||||
# Slow
|
||||
rule:
|
||||
regex: '^TODO'
|
||||
|
||||
# Fast
|
||||
rule:
|
||||
all:
|
||||
- kind: comment
|
||||
- regex: '^//\s*TODO'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. No scope / type / data-flow analysis
|
||||
|
||||
ast-grep is a **structural** matcher. It does NOT know:
|
||||
|
||||
- Whether two `foo` references point to the same variable.
|
||||
- Whether a variable is shadowed.
|
||||
- Whether a function is async, throws, returns a Promise.
|
||||
- Whether a value flows from input to output.
|
||||
|
||||
For those questions, use a real type-aware tool: TypeScript LSP, Pyright, Semgrep with type inference, CodeQL, etc.
|
||||
|
||||
ast-grep is great when *the syntactic shape* is what you care about: "find every call to `eval(...)`", "find every `as any`", "find every empty catch block." It is weak for "find every variable that's never used."
|
||||
|
||||
---
|
||||
|
||||
## 14. Pattern testing is the fastest debugger
|
||||
|
||||
When a pattern returns 0 matches and you can't see why:
|
||||
|
||||
1. Open <https://ast-grep.github.io/playground.html>.
|
||||
2. Paste your code into the left pane, your pattern into the top-right.
|
||||
3. The bottom-right shows the parsed AST and which nodes matched (highlighted) or failed.
|
||||
|
||||
Or locally:
|
||||
|
||||
```bash
|
||||
sg run -p '<pattern>' --lang <lang> --debug-query=ast --stdin <<< '<sample-code>'
|
||||
```
|
||||
|
||||
stderr shows the parsed pattern; stdout shows the JSON match result. If the pattern shows up as `ERROR (XXX)`, it doesn't parse.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `references/patterns.md` — meta-variables, strictness, naming rules.
|
||||
- `references/recipes.md` — known-good patterns by language.
|
||||
- `references/cli.md` — `--debug-query`, `--strictness`, `--update-all`.
|
||||
@@ -0,0 +1,402 @@
|
||||
# Recipes — copy-paste patterns by language
|
||||
|
||||
Every pattern in this file has been verified against the canonical syntax. They are starting points; tweak metavariable names and constraints to fit your case.
|
||||
|
||||
Use them with the helper:
|
||||
|
||||
```bash
|
||||
ast-grep-helper search '<PATTERN>' --lang <LANG> [path]
|
||||
ast-grep-helper replace '<PATTERN>' '<REWRITE>' --lang <LANG> [path] # dry-run
|
||||
ast-grep-helper replace '<PATTERN>' '<REWRITE>' --lang <LANG> [path] --apply
|
||||
```
|
||||
|
||||
Or directly:
|
||||
|
||||
```bash
|
||||
sg run -p '<PATTERN>' --lang <LANG> [path]
|
||||
sg run -p '<PATTERN>' -r '<REWRITE>' --update-all --lang <LANG> [path]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript / TSX / JavaScript
|
||||
|
||||
### Find structural patterns
|
||||
|
||||
```typescript
|
||||
// Every function declaration
|
||||
function $NAME($$$PARAMS) { $$$BODY }
|
||||
|
||||
// Every async function
|
||||
async function $NAME($$$PARAMS) { $$$BODY }
|
||||
|
||||
// Every arrow function (any param shape)
|
||||
($$$PARAMS) => $$$BODY
|
||||
|
||||
// Every method on a class
|
||||
class $C { $$$ $METHOD($$$P) { $$$B } $$$ }
|
||||
|
||||
// Every import statement
|
||||
import { $$$NAMES } from '$MOD'
|
||||
import $DEFAULT from '$MOD'
|
||||
import * as $NS from '$MOD'
|
||||
|
||||
// Every console.* call
|
||||
console.$METHOD($$$ARGS)
|
||||
|
||||
// Every JSX element of a given name
|
||||
<MyComponent $$$PROPS>$$$CHILDREN</MyComponent>
|
||||
|
||||
// Every try/catch
|
||||
try { $$$BODY } catch ($E) { $$$HANDLER }
|
||||
|
||||
// Every throw
|
||||
throw $EXPR
|
||||
|
||||
// Every new expression
|
||||
new $CLASS($$$ARGS)
|
||||
|
||||
// Every type assertion to any (anti-pattern!)
|
||||
$EXPR as any
|
||||
$EXPR as unknown as $T
|
||||
```
|
||||
|
||||
### Common rewrites
|
||||
|
||||
```bash
|
||||
# console.log -> logger.info
|
||||
sg run -p 'console.log($$$A)' -r 'logger.info($$$A)' --lang ts --update-all .
|
||||
|
||||
# require -> import (one-arg case)
|
||||
sg run -p 'const $V = require($M)' -r 'import $V from $M' --lang ts --update-all .
|
||||
|
||||
# .then(callback) -> await on the same line (use with caution; needs async function context)
|
||||
sg run -p '$P.then($CB)' -r 'const $TMP = await $P; $CB($TMP)' --lang ts --update-all .
|
||||
|
||||
# Strip `as any`
|
||||
sg run -p '$E as any' -r '$E' --lang ts --update-all .
|
||||
|
||||
# Rename a function call site
|
||||
sg run -p 'oldName($$$A)' -r 'newName($$$A)' --lang ts --update-all .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python
|
||||
|
||||
> **Reminder**: never end a Python pattern with `:`. Patterns parse as a complete statement, so `def foo($$$):` is invalid.
|
||||
|
||||
```python
|
||||
# Every function definition
|
||||
def $FN($$$PARAMS)
|
||||
|
||||
# Every class definition
|
||||
class $C($$$BASES)
|
||||
|
||||
# Every decorator usage
|
||||
@$DEC
|
||||
def $FN($$$P)
|
||||
|
||||
# Every print call (Python 3)
|
||||
print($$$ARGS)
|
||||
|
||||
# Every f-string
|
||||
f"$STR"
|
||||
|
||||
# Every with-statement
|
||||
with $CTX as $VAR: $$$BODY
|
||||
|
||||
# Every try/except
|
||||
try: $$$BODY
|
||||
except $EXC: $$$HANDLER
|
||||
|
||||
# Every list comprehension
|
||||
[$EXPR for $VAR in $ITER]
|
||||
|
||||
# Every async def
|
||||
async def $FN($$$PARAMS)
|
||||
|
||||
# Type hints — Optional[X]
|
||||
Optional[$T]
|
||||
|
||||
# Type hints — X | None (PEP 604)
|
||||
$T | None
|
||||
```
|
||||
|
||||
### Common rewrites
|
||||
|
||||
```bash
|
||||
# print(...) -> logger.info(...)
|
||||
sg run -p 'print($$$A)' -r 'logger.info($$$A)' --lang py --update-all .
|
||||
|
||||
# Optional[X] -> X | None
|
||||
sg run -p 'Optional[$T]' -r '$T | None' --lang py --update-all .
|
||||
|
||||
# from typing import List -> remove (built-in list works in 3.9+)
|
||||
sg run -p 'from typing import List' -r 'from typing import List # TODO: remove, use list' --lang py --update-all .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go
|
||||
|
||||
```go
|
||||
// Every function
|
||||
func $NAME($$$PARAMS) $$$RET { $$$BODY }
|
||||
|
||||
// Every method
|
||||
func ($RECV $TYPE) $NAME($$$PARAMS) $$$RET { $$$BODY }
|
||||
|
||||
// The classic err nil-check
|
||||
if err != nil { $$$BODY }
|
||||
|
||||
// Every fmt.Println / fmt.Printf / fmt.Sprintf
|
||||
fmt.$METHOD($$$ARGS)
|
||||
|
||||
// Every defer
|
||||
defer $EXPR
|
||||
|
||||
// Every goroutine
|
||||
go $EXPR
|
||||
|
||||
// Every channel send/recv
|
||||
$CH <- $VAL
|
||||
$VAL := <-$CH
|
||||
|
||||
// Every type assertion
|
||||
$EXPR.($TYPE)
|
||||
```
|
||||
|
||||
### Common rewrites
|
||||
|
||||
```bash
|
||||
# fmt.Println -> log.Println
|
||||
sg run -p 'fmt.Println($$$A)' -r 'log.Println($$$A)' --lang go --update-all .
|
||||
|
||||
# Add error wrapping
|
||||
sg run -p 'return $ERR' -r 'return fmt.Errorf("operation failed: %w", $ERR)' --lang go --update-all .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rust
|
||||
|
||||
```rust
|
||||
// Every fn
|
||||
fn $NAME($$$PARAMS) -> $RET { $$$BODY }
|
||||
fn $NAME($$$PARAMS) { $$$BODY } // no return type
|
||||
|
||||
// Every async fn
|
||||
async fn $NAME($$$PARAMS) -> $RET { $$$BODY }
|
||||
|
||||
// Every method on impl
|
||||
impl $TYPE { fn $METHOD($$$P) -> $R { $$$B } }
|
||||
|
||||
// Every trait impl
|
||||
impl $TRAIT for $TYPE { $$$ITEMS }
|
||||
|
||||
// Every match expression
|
||||
match $EXPR { $$$ARMS }
|
||||
|
||||
// Every Result-returning fn that uses ?
|
||||
fn $N($$$P) -> Result<$T, $E> { $$$ }
|
||||
|
||||
// .unwrap() / .expect() (anti-patterns)
|
||||
$EXPR.unwrap()
|
||||
$EXPR.expect($MSG)
|
||||
|
||||
// Every println!/eprintln!/format!
|
||||
println!($$$ARGS)
|
||||
format!($$$ARGS)
|
||||
```
|
||||
|
||||
### Common rewrites
|
||||
|
||||
```bash
|
||||
# unwrap() -> ? in Result-returning fns (caution: needs context)
|
||||
sg run -p '$E.unwrap()' -r '$E?' --lang rust --update-all .
|
||||
|
||||
# eprintln! -> log::error!
|
||||
sg run -p 'eprintln!($$$A)' -r 'log::error!($$$A)' --lang rust --update-all .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Java
|
||||
|
||||
```java
|
||||
// Every public class
|
||||
public class $NAME { $$$BODY }
|
||||
|
||||
// Every method (any modifier)
|
||||
$$$MOD $RET $NAME($$$P) { $$$BODY }
|
||||
|
||||
// Every System.out.println / System.err.println
|
||||
System.$STREAM.println($$$ARGS)
|
||||
|
||||
// Every try-with-resources
|
||||
try ($$$RES) { $$$BODY } catch ($EXC $E) { $$$HANDLER }
|
||||
|
||||
// Every annotation usage
|
||||
@$ANNOTATION
|
||||
$DECL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## C / C++
|
||||
|
||||
```cpp
|
||||
// Every printf-family call
|
||||
printf($$$ARGS)
|
||||
sprintf($$$ARGS)
|
||||
fprintf($$$ARGS)
|
||||
|
||||
// Every malloc / free pair (find-only — pairing requires data flow)
|
||||
malloc($SIZE)
|
||||
free($PTR)
|
||||
|
||||
// Every for-loop
|
||||
for ($INIT; $COND; $POST) { $$$BODY }
|
||||
|
||||
// C++ smart pointer make
|
||||
std::make_shared<$T>($$$ARGS)
|
||||
std::make_unique<$T>($$$ARGS)
|
||||
```
|
||||
|
||||
### Rewrites
|
||||
|
||||
```bash
|
||||
# malloc(N * sizeof(T)) -> calloc(N, sizeof(T)) - safer
|
||||
sg run -p 'malloc($N * sizeof($T))' -r 'calloc($N, sizeof($T))' --lang c --update-all .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS
|
||||
|
||||
```css
|
||||
/* Every rule with a specific property */
|
||||
{ $$$ color: $VAL; $$$ }
|
||||
|
||||
/* Every @media query */
|
||||
@media $QUERY { $$$BODY }
|
||||
|
||||
/* Every var() reference */
|
||||
var($NAME)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTML
|
||||
|
||||
```html
|
||||
<!-- Every img without alt -->
|
||||
<img $$$ />
|
||||
|
||||
<!-- Every script tag -->
|
||||
<script $$$>$$$BODY</script>
|
||||
|
||||
<!-- Every link to stylesheet -->
|
||||
<link rel="stylesheet" href=$URL />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bash / Shell
|
||||
|
||||
```bash
|
||||
# Every for-loop
|
||||
for $VAR in $$$LIST; do $$$BODY; done
|
||||
|
||||
# Every if-statement
|
||||
if $$$COND; then $$$BODY; fi
|
||||
|
||||
# Every function definition
|
||||
$NAME() { $$$BODY }
|
||||
|
||||
# Every subshell call
|
||||
$( $$$CMD )
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## YAML rule recipes (for `sg scan`)
|
||||
|
||||
These are full YAML rules you can drop in `rules/*.yml` and run via `sg scan`. See `references/yaml-rules.md` for the full schema.
|
||||
|
||||
### no-console (TypeScript)
|
||||
|
||||
```yaml
|
||||
id: no-console
|
||||
language: TypeScript
|
||||
severity: warning
|
||||
message: "Avoid console.* in production"
|
||||
rule:
|
||||
pattern: console.$METHOD($$$ARGS)
|
||||
fix: logger.$METHOD($$$ARGS)
|
||||
```
|
||||
|
||||
### no-as-any (TypeScript)
|
||||
|
||||
```yaml
|
||||
id: no-as-any
|
||||
language: TypeScript
|
||||
severity: error
|
||||
message: "`as any` defeats type safety. Use a proper type."
|
||||
rule:
|
||||
pattern: $EXPR as any
|
||||
fix: $EXPR
|
||||
```
|
||||
|
||||
### empty-catch (JavaScript)
|
||||
|
||||
```yaml
|
||||
id: empty-catch
|
||||
language: JavaScript
|
||||
severity: error
|
||||
message: "Empty catch swallows errors silently."
|
||||
rule:
|
||||
all:
|
||||
- pattern: try { $$$T } catch ($E) { $$$H }
|
||||
- has:
|
||||
kind: catch_clause
|
||||
has:
|
||||
kind: statement_block
|
||||
not:
|
||||
has:
|
||||
kind: statement
|
||||
stopBy: end
|
||||
```
|
||||
|
||||
### print-to-logger (Python)
|
||||
|
||||
```yaml
|
||||
id: print-to-logger
|
||||
language: Python
|
||||
severity: hint
|
||||
message: "Use logger.info instead of print"
|
||||
rule:
|
||||
pattern: print($$$ARGS)
|
||||
fix: logger.info($$$ARGS)
|
||||
```
|
||||
|
||||
### no-unwrap (Rust)
|
||||
|
||||
```yaml
|
||||
id: no-unwrap
|
||||
language: Rust
|
||||
severity: warning
|
||||
message: "Avoid .unwrap() in production code; propagate or handle the error."
|
||||
rule:
|
||||
pattern: $EXPR.unwrap()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `references/patterns.md` — meta-variable rules.
|
||||
- `references/yaml-rules.md` — full YAML rule schema (atomic / relational / composite / transform / fix).
|
||||
- `references/cli.md` — `sg run`, `sg scan`, `sg test`, `sg new`.
|
||||
- Official catalog: <https://ast-grep.github.io/catalog/> (community-maintained, browse by language).
|
||||
@@ -0,0 +1,248 @@
|
||||
# sgconfig.yml — project configuration
|
||||
|
||||
`sgconfig.yml` lives at your project root (the same place as `package.json`, `Cargo.toml`, `pyproject.toml`, etc.) and tells `sg scan`/`sg test` where to find rules and tests.
|
||||
|
||||
`sg` walks **upward** from the current directory until it finds an `sgconfig.yml`. You can also pass `--config <path>` explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Minimal project layout
|
||||
|
||||
```
|
||||
my-project/
|
||||
├── sgconfig.yml
|
||||
├── rules/
|
||||
│ ├── no-console.yml
|
||||
│ └── no-as-any.yml
|
||||
├── utils/
|
||||
│ └── is-literal.yml
|
||||
├── tests/
|
||||
│ ├── no-console.yml
|
||||
│ └── __snapshots__/
|
||||
│ └── no-console-snapshot.yml
|
||||
└── src/
|
||||
└── ...
|
||||
```
|
||||
|
||||
```yaml
|
||||
# sgconfig.yml
|
||||
ruleDirs:
|
||||
- rules
|
||||
|
||||
testConfigs:
|
||||
- testDir: tests
|
||||
snapshotDir: __snapshots__
|
||||
|
||||
utilDirs:
|
||||
- utils
|
||||
```
|
||||
|
||||
That's it. `sg scan src/` will load every `.yml` in `rules/`, find every `.ts`/`.py`/whatever matching the rule's `language`, and report violations.
|
||||
|
||||
---
|
||||
|
||||
## Full schema
|
||||
|
||||
```yaml
|
||||
# Rule directories — required
|
||||
ruleDirs:
|
||||
- rules
|
||||
- team-rules
|
||||
- vendor/sg-rules
|
||||
|
||||
# Test directories — optional
|
||||
testConfigs:
|
||||
- testDir: tests
|
||||
snapshotDir: __snapshots__
|
||||
- testDir: integration-tests
|
||||
|
||||
# Utility rule directories — optional
|
||||
# Files here become global utilities accessible via `matches: <id>` from any rule.
|
||||
utilDirs:
|
||||
- utils
|
||||
- team-utils
|
||||
|
||||
# Override file-extension -> language mapping — optional
|
||||
# Useful when your code uses non-standard extensions.
|
||||
languageGlobs:
|
||||
html:
|
||||
- '*.vue'
|
||||
- '*.svelte'
|
||||
- '*.astro'
|
||||
json:
|
||||
- '.eslintrc'
|
||||
- '.prettierrc'
|
||||
cpp:
|
||||
- '*.c' # treat C as C++
|
||||
tsx:
|
||||
- '*.ts' # treat all .ts as TSX (so TSX rules work everywhere)
|
||||
|
||||
# Custom tree-sitter languages (experimental) — optional
|
||||
customLanguages:
|
||||
mojo:
|
||||
libraryPath: tree-sitter-mojo.so
|
||||
extensions: [mojo, '🔥']
|
||||
expandoChar: _ # Replace $ in patterns when language uses $ syntactically
|
||||
languageSymbol: tree_sitter_mojo
|
||||
|
||||
# Language injection — embedded code in another language (experimental) — optional
|
||||
# Example: CSS inside styled-components template literals.
|
||||
languageInjections:
|
||||
- hostLanguage: js
|
||||
rule:
|
||||
pattern: 'styled.$TAG`$CONTENT`'
|
||||
injected: css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field-by-field
|
||||
|
||||
### `ruleDirs` (required)
|
||||
|
||||
`Array<string>` — directories containing rule YAML files. Resolved relative to `sgconfig.yml`.
|
||||
|
||||
Each `.yml`/`.yaml` file in these directories is loaded as a rule. One file can contain multiple rules separated by `---`.
|
||||
|
||||
### `testConfigs`
|
||||
|
||||
`Array<TestConfig>` where each entry has:
|
||||
|
||||
- `testDir` (required): directory of test YAML files.
|
||||
- `snapshotDir` (optional, default `__snapshots__`): directory for snapshots.
|
||||
|
||||
Each test file looks like:
|
||||
|
||||
```yaml
|
||||
id: no-console
|
||||
valid:
|
||||
- 'logger.info("hi")'
|
||||
invalid:
|
||||
- 'console.log("hi")'
|
||||
```
|
||||
|
||||
`sg test` runs every test, compares matches against the snapshot, and fails on diff. Snapshots are created on first run with `-U`.
|
||||
|
||||
### `utilDirs`
|
||||
|
||||
`Array<string>` — directories with global utility rules. Each util file must have `id` and `language`. Utils become referenceable via `matches: <id>` from any rule in the project.
|
||||
|
||||
### `languageGlobs`
|
||||
|
||||
`HashMap<string, Array<string>>` — override which extensions map to which language. Takes precedence over the built-in defaults.
|
||||
|
||||
Useful for:
|
||||
|
||||
- Custom file extensions (`.eslintrc` is JSON).
|
||||
- Force-treating `.ts` files as TSX (so JSX-shaped patterns work).
|
||||
- Vue/Svelte/Astro files (HTML host language).
|
||||
|
||||
### `customLanguages` (experimental)
|
||||
|
||||
Register a tree-sitter parser that ast-grep doesn't ship with. Requires:
|
||||
|
||||
- `libraryPath`: path to a built `.so` / `.dylib` / `.dll` containing the grammar.
|
||||
- `extensions`: file extensions to recognize.
|
||||
- `languageSymbol`: the C symbol exported by the grammar (typically `tree_sitter_<name>`).
|
||||
- `expandoChar` (optional): character to substitute for `$` in patterns when the host language uses `$` syntactically (PHP, jQuery, etc.).
|
||||
|
||||
This is **rarely needed** — ast-grep already supports 25 languages out of the box.
|
||||
|
||||
### `languageInjections` (experimental)
|
||||
|
||||
Match patterns inside embedded languages. Example: CSS inside JS template literals (styled-components, emotion).
|
||||
|
||||
```yaml
|
||||
languageInjections:
|
||||
- hostLanguage: js
|
||||
rule:
|
||||
pattern: 'styled.$TAG`$CONTENT`'
|
||||
injected: css
|
||||
```
|
||||
|
||||
After this, a `css` rule with pattern `color: $C` will match `$CONTENT` strings.
|
||||
|
||||
---
|
||||
|
||||
## Common configurations
|
||||
|
||||
### Monorepo with shared rules
|
||||
|
||||
```
|
||||
monorepo/
|
||||
├── sgconfig.yml # root config — applies to entire monorepo
|
||||
├── shared-rules/
|
||||
│ ├── no-todo.yml
|
||||
│ └── no-as-any.yml
|
||||
└── packages/
|
||||
├── frontend/
|
||||
│ ├── sgconfig.yml # extends root with frontend-specific rules
|
||||
│ └── rules/
|
||||
└── backend/
|
||||
├── sgconfig.yml # extends root with backend-specific rules
|
||||
└── rules/
|
||||
```
|
||||
|
||||
Each package's `sgconfig.yml` references both the package-local rules and the shared ones:
|
||||
|
||||
```yaml
|
||||
# packages/frontend/sgconfig.yml
|
||||
ruleDirs:
|
||||
- rules
|
||||
- ../../shared-rules
|
||||
```
|
||||
|
||||
### Single rule file (no project)
|
||||
|
||||
For one-offs, skip `sgconfig.yml` entirely:
|
||||
|
||||
```bash
|
||||
sg scan -r path/to/single-rule.yml src/
|
||||
```
|
||||
|
||||
### Inline rule (no file)
|
||||
|
||||
```bash
|
||||
sg scan --inline-rules '
|
||||
id: no-todo
|
||||
language: TypeScript
|
||||
severity: warning
|
||||
rule: { pattern: TODO }' src/
|
||||
```
|
||||
|
||||
Multiple rules separated by `---`:
|
||||
|
||||
```bash
|
||||
sg scan --inline-rules '
|
||||
id: no-todo
|
||||
language: TypeScript
|
||||
rule: { pattern: TODO }
|
||||
---
|
||||
id: no-fixme
|
||||
language: TypeScript
|
||||
rule: { pattern: FIXME }' src/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Editor integration
|
||||
|
||||
VS Code / Neovim / Helix detect `sgconfig.yml` automatically and surface diagnostics from every rule. Without `sgconfig.yml`, the LSP runs without any rules loaded.
|
||||
|
||||
To enable schema validation in your editor, add a header to each rule file:
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/ast-grep/ast-grep/main/schemas/rule.json
|
||||
id: no-console
|
||||
language: TypeScript
|
||||
rule:
|
||||
pattern: console.log($_)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `references/yaml-rules.md` — rule schema (atomic / relational / composite / transform / fix).
|
||||
- `references/cli.md` — `sg scan`, `sg test`, `sg new project`.
|
||||
- Official: <https://ast-grep.github.io/reference/sgconfig.html>, <https://ast-grep.github.io/guide/project/project-config.html>
|
||||
@@ -0,0 +1,509 @@
|
||||
# YAML rule reference — atomic, relational, composite, transform, fix
|
||||
|
||||
Use this when you outgrow inline `sg run -p ...` patterns and need a reusable, testable rule. A YAML rule is the unit of work for `sg scan`. Drop one or more files in `ruleDirs/` (configured via `sgconfig.yml`) and they get loaded automatically.
|
||||
|
||||
This page is the practical reference. The full upstream docs live at:
|
||||
|
||||
- <https://ast-grep.github.io/reference/yaml.html>
|
||||
- <https://ast-grep.github.io/reference/rule.html>
|
||||
- <https://ast-grep.github.io/cheatsheet/rule.html>
|
||||
|
||||
---
|
||||
|
||||
## Skeleton
|
||||
|
||||
A single YAML file can hold multiple rules separated by `---`.
|
||||
|
||||
```yaml
|
||||
id: no-console
|
||||
language: TypeScript
|
||||
severity: warning
|
||||
message: "Avoid console.* in production"
|
||||
note: |
|
||||
Use a proper logger so we can route logs to stderr in production
|
||||
and silence them in tests.
|
||||
url: https://internal.docs/rules/no-console
|
||||
|
||||
rule:
|
||||
pattern: console.$METHOD($$$ARGS)
|
||||
|
||||
fix: logger.$METHOD($$$ARGS)
|
||||
|
||||
constraints:
|
||||
METHOD:
|
||||
not:
|
||||
regex: '^(error|warn)$'
|
||||
|
||||
files:
|
||||
- 'src/**/*.ts'
|
||||
ignores:
|
||||
- 'src/**/*.test.ts'
|
||||
|
||||
metadata:
|
||||
category: logging
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Top-level fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|---|---|---|
|
||||
| `id` | yes | Unique identifier. Use `kebab-case`. |
|
||||
| `language` | yes | One of: `Bash`, `C`, `Cpp`, `CSharp`, `Css`, `Elixir`, `Go`, `Haskell`, `Html`, `Java`, `JavaScript`, `Json`, `Kotlin`, `Lua`, `Nix`, `Php`, `Python`, `Ruby`, `Rust`, `Scala`, `Solidity`, `Swift`, `TypeScript`, `Tsx`, `Yaml`. **Capitalized PascalCase** is canonical, but lowercase often works. |
|
||||
| `rule` | yes | The matching logic. Object containing one or more atomic / relational / composite rules. |
|
||||
| `constraints` | no | Filter on captured single-metavariables (`$VAR`, not `$$$`). |
|
||||
| `utils` | no | Local utility rules referenced by `matches:` in this file. |
|
||||
| `transform` | no | Manipulate metavariable strings before `fix`. |
|
||||
| `fix` | no | String or `FixConfig` for auto-rewrite. |
|
||||
| `rewriters` | no | Rewriter rules for the `rewrite` transform. |
|
||||
| `severity` | no | `hint` \| `info` \| `warning` \| `error` \| `off` (default: `hint`). |
|
||||
| `message` | no | Concise lint message. May reference `$VAR` capture text. |
|
||||
| `note` | no | Detailed markdown explanation (no `$VAR` interpolation). |
|
||||
| `labels` | no | Custom diagnostic highlighting per-metavariable. |
|
||||
| `files` | no | Glob include list. |
|
||||
| `ignores` | no | Glob exclude list. |
|
||||
| `url` | no | Doc link shown in editor diagnostics. |
|
||||
| `metadata` | no | Free-form data ignored by `sg`, useful for external tooling. |
|
||||
|
||||
---
|
||||
|
||||
## Atomic rules — match a single node
|
||||
|
||||
### `pattern`
|
||||
|
||||
Match by structural pattern. The most common rule.
|
||||
|
||||
```yaml
|
||||
# String form
|
||||
rule:
|
||||
pattern: console.log($MSG)
|
||||
|
||||
# Object form (when context is needed)
|
||||
rule:
|
||||
pattern:
|
||||
context: 'class C { $FIELD = $INIT }'
|
||||
selector: field_definition
|
||||
strictness: relaxed # optional, default: smart
|
||||
```
|
||||
|
||||
### `kind`
|
||||
|
||||
Match by AST node type name. Tree-sitter grammar-specific.
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
kind: call_expression
|
||||
```
|
||||
|
||||
ast-grep 0.39+ supports limited ESQuery selectors:
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
kind: call_expression > identifier # direct child
|
||||
kind: call_expression + identifier # next sibling
|
||||
kind: call_expression ~ identifier # following sibling
|
||||
kind: call_expression identifier # descendant
|
||||
```
|
||||
|
||||
To find the right `kind`, parse a known-good file:
|
||||
|
||||
```bash
|
||||
sg run -p '$_' --lang ts --debug-query=cst src/foo.ts | head -40
|
||||
```
|
||||
|
||||
### `regex`
|
||||
|
||||
Match node text against a Rust regex. Whole-text match (no partial). Always combine with `kind` or `pattern` for performance.
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
all:
|
||||
- kind: identifier
|
||||
- regex: '^[A-Z][a-z]+$' # PascalCase
|
||||
```
|
||||
|
||||
Inline flags work: `(?i)apple`, `(?m)^foo`. No look-around, no backreferences.
|
||||
|
||||
### `nthChild`
|
||||
|
||||
Match by 1-based index among **named** siblings. Inspired by CSS `:nth-child`.
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
nthChild: 1 # first sibling
|
||||
|
||||
# Functional form
|
||||
rule:
|
||||
nthChild: 2n+1 # odd siblings
|
||||
|
||||
# With reverse and ofRule
|
||||
rule:
|
||||
nthChild:
|
||||
position: 1
|
||||
reverse: true # last
|
||||
ofRule:
|
||||
kind: function_declaration
|
||||
```
|
||||
|
||||
### `range`
|
||||
|
||||
Match by character range. Useful for tooling that pinpoints a known location.
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
range:
|
||||
start: { line: 0, column: 0 }
|
||||
end: { line: 0, column: 11 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relational rules — match by relation to other nodes
|
||||
|
||||
All four take a sub-rule object plus optional `stopBy` and (for `inside`/`has`) `field`.
|
||||
|
||||
### `inside` — target is inside parent/ancestor matching sub-rule
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
pattern: this.$PROP
|
||||
inside:
|
||||
kind: class_body
|
||||
stopBy: end # walk up to file root, default: neighbor
|
||||
```
|
||||
|
||||
### `has` — target has child/descendant matching sub-rule
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
kind: function_declaration
|
||||
has:
|
||||
kind: throw_statement
|
||||
stopBy: end
|
||||
```
|
||||
|
||||
### `precedes` — target appears before sibling matching sub-rule
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
kind: import_statement
|
||||
precedes:
|
||||
kind: function_declaration
|
||||
```
|
||||
|
||||
### `follows` — target appears after sibling matching sub-rule
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
pattern: super($$$)
|
||||
follows:
|
||||
pattern: $X = $Y
|
||||
```
|
||||
|
||||
### `stopBy`
|
||||
|
||||
| Value | Behavior |
|
||||
|---|---|
|
||||
| `"neighbor"` (default) | Stop at immediate parent/child/sibling. |
|
||||
| `"end"` | Walk all the way to root / leaf / sequence boundary. |
|
||||
| Rule object | Stop when sub-rule matches (inclusive). |
|
||||
|
||||
### `field`
|
||||
|
||||
Specify the semantic role of the target inside its parent (e.g. `name`, `body`, `value`, `key`).
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
kind: pair
|
||||
has:
|
||||
field: key
|
||||
regex: '^password$'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Composite rules — combine sub-rules
|
||||
|
||||
| Rule | Meaning |
|
||||
|---|---|
|
||||
| `all` | All sub-rules must match the same target node. Metavariables from all sub-rules merge. |
|
||||
| `any` | At least one sub-rule must match. Only metavars from the matched branch survive. |
|
||||
| `not` | Inverse: target must NOT match the sub-rule. |
|
||||
| `matches` | Reference a utility rule by id. |
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
all:
|
||||
- kind: call_expression
|
||||
- pattern: $FN($$$ARGS)
|
||||
- inside:
|
||||
kind: function_declaration
|
||||
stopBy: end
|
||||
|
||||
rule:
|
||||
any:
|
||||
- pattern: console.log($X)
|
||||
- pattern: console.warn($X)
|
||||
- pattern: console.error($X)
|
||||
|
||||
rule:
|
||||
all:
|
||||
- pattern: $E.unwrap()
|
||||
- not:
|
||||
inside:
|
||||
kind: function_item
|
||||
has:
|
||||
kind: result_type
|
||||
stopBy: end
|
||||
|
||||
rule:
|
||||
matches: is-react-component
|
||||
```
|
||||
|
||||
> Composites apply to a **single** target. To express "node X has BOTH a number child AND a string child," use two relational rules at the top level, not `all` inside `has`. See `references/pitfalls.md` §10.
|
||||
|
||||
---
|
||||
|
||||
## Implicit `all` — multiple rule fields
|
||||
|
||||
A rule object with multiple fields is treated as an implicit `all`:
|
||||
|
||||
```yaml
|
||||
# These two are equivalent
|
||||
rule:
|
||||
pattern: this.$PROP
|
||||
inside: { kind: class_body }
|
||||
|
||||
rule:
|
||||
all:
|
||||
- pattern: this.$PROP
|
||||
- inside: { kind: class_body }
|
||||
```
|
||||
|
||||
Use the explicit `all` array when capture order matters (rare, but possible with downstream `transform`).
|
||||
|
||||
---
|
||||
|
||||
## `constraints` — post-match metavariable filtering
|
||||
|
||||
After the main `rule` matches, additional checks on captured single metavariables:
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
pattern: function $NAME($$$P) { $$$B }
|
||||
|
||||
constraints:
|
||||
NAME:
|
||||
regex: '^[a-z][a-zA-Z0-9]*$' # camelCase only
|
||||
not:
|
||||
regex: '^_' # not starting with _
|
||||
```
|
||||
|
||||
Constraints **only apply to single metavars** (`$VAR`), not multi (`$$$VAR`).
|
||||
|
||||
---
|
||||
|
||||
## `utils` — local reusable sub-rules
|
||||
|
||||
```yaml
|
||||
utils:
|
||||
is-literal:
|
||||
any:
|
||||
- kind: number
|
||||
- kind: string
|
||||
- kind: 'true'
|
||||
- kind: 'false'
|
||||
|
||||
rule:
|
||||
all:
|
||||
- pattern: $X = $Y
|
||||
- has:
|
||||
matches: is-literal # references utils.is-literal
|
||||
```
|
||||
|
||||
For utils accessible across multiple rule files, use `utilDirs` in `sgconfig.yml` and put each util in its own YAML file with `id` and `language`.
|
||||
|
||||
---
|
||||
|
||||
## `transform` — manipulate captures before `fix`
|
||||
|
||||
Operations: `replace`, `substring`, `convert`, `rewrite`.
|
||||
|
||||
### `replace` — regex search/replace on a captured string
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
pattern: $OLD_FN($$$A)
|
||||
constraints:
|
||||
OLD_FN:
|
||||
regex: '^debug_'
|
||||
transform:
|
||||
NEW_FN:
|
||||
replace:
|
||||
source: $OLD_FN
|
||||
replace: '^debug_'
|
||||
by: 'release_'
|
||||
fix: $NEW_FN($$$A)
|
||||
```
|
||||
|
||||
### `substring` — character slicing (negative indices supported)
|
||||
|
||||
```yaml
|
||||
transform:
|
||||
INNER:
|
||||
substring:
|
||||
source: $WRAPPED
|
||||
startChar: 1
|
||||
endChar: -1
|
||||
```
|
||||
|
||||
### `convert` — case conversion
|
||||
|
||||
```yaml
|
||||
transform:
|
||||
KEBAB:
|
||||
convert:
|
||||
source: $CAMEL
|
||||
toCase: kebabCase # camelCase | snakeCase | kebabCase | pascalCase | upperCase | lowerCase | capitalize
|
||||
separatedBy: [underscore] # optional: dash | dot | space | slash | underscore | caseChange
|
||||
```
|
||||
|
||||
### `rewrite` — apply other rewriter rules (experimental)
|
||||
|
||||
```yaml
|
||||
rewriters:
|
||||
- id: stringify
|
||||
rule: { pattern: "'' + $A" }
|
||||
fix: "String($A)"
|
||||
|
||||
rule:
|
||||
pattern: stringify-all($EXPR)
|
||||
transform:
|
||||
REWRITTEN:
|
||||
rewrite:
|
||||
source: $EXPR
|
||||
rewriters: [stringify]
|
||||
joinBy: "\n"
|
||||
fix: $REWRITTEN
|
||||
```
|
||||
|
||||
### Transforms can chain
|
||||
|
||||
Later transforms can reference variables produced by earlier ones:
|
||||
|
||||
```yaml
|
||||
transform:
|
||||
KEBABED:
|
||||
convert: { source: $X, toCase: kebabCase }
|
||||
PREFIXED:
|
||||
replace:
|
||||
source: $KEBABED
|
||||
replace: '^'
|
||||
by: 'css-'
|
||||
fix: $PREFIXED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `fix` — auto-rewrite
|
||||
|
||||
### String form
|
||||
|
||||
```yaml
|
||||
fix: logger.log($$$ARGS)
|
||||
|
||||
# Empty string deletes the match
|
||||
fix: ""
|
||||
```
|
||||
|
||||
### FixConfig form (for list-item deletion that needs to expand the range)
|
||||
|
||||
When deleting one item from a comma-separated list, you also need to remove the trailing comma. Use `expandEnd`:
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
kind: pair
|
||||
has:
|
||||
field: key
|
||||
regex: '^password$'
|
||||
|
||||
fix:
|
||||
template: ''
|
||||
expandEnd:
|
||||
regex: ','
|
||||
```
|
||||
|
||||
`expandStart` and `expandEnd` accept `regex` matching characters that should be absorbed into the rewrite range.
|
||||
|
||||
---
|
||||
|
||||
## `rewriters` — sub-rule library for `rewrite` transform
|
||||
|
||||
Top-level field defining one or more named rewriters:
|
||||
|
||||
```yaml
|
||||
rewriters:
|
||||
- id: nullable-to-optional
|
||||
rule: { pattern: $X | null }
|
||||
fix: '$X | undefined'
|
||||
|
||||
- id: stringify
|
||||
rule: { pattern: "'' + $A" }
|
||||
fix: 'String($A)'
|
||||
```
|
||||
|
||||
Used inside `transform` via the `rewrite` operation (see above).
|
||||
|
||||
---
|
||||
|
||||
## `labels` — custom diagnostic highlighting
|
||||
|
||||
```yaml
|
||||
rule:
|
||||
pattern: $FN($$$ARGS)
|
||||
|
||||
labels:
|
||||
FN:
|
||||
style: primary
|
||||
message: "this function shouldn't be called"
|
||||
ARGS:
|
||||
style: secondary
|
||||
message: "with these arguments"
|
||||
```
|
||||
|
||||
Editor extensions render the diagnostic with these labels. Defaults are usually fine.
|
||||
|
||||
---
|
||||
|
||||
## `files` and `ignores` — file selection per-rule
|
||||
|
||||
```yaml
|
||||
files:
|
||||
- 'src/**/*.ts'
|
||||
- 'lib/**/*.ts'
|
||||
|
||||
ignores:
|
||||
- 'src/**/*.test.ts'
|
||||
- '**/__generated__/**'
|
||||
```
|
||||
|
||||
If omitted, the rule runs on every file matching its `language`. These globs override `sgconfig.yml`-level globs for this rule only.
|
||||
|
||||
Object form (rare):
|
||||
|
||||
```yaml
|
||||
files:
|
||||
- pattern: 'src/**/*.ts'
|
||||
case_sensitive: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `references/recipes.md` — copy-paste rules by language.
|
||||
- `references/cli.md` — `sg scan`, `sg test`.
|
||||
- `references/sgconfig.md` — project-level configuration.
|
||||
- Official rule reference: <https://ast-grep.github.io/reference/rule.html>
|
||||
- Cheat sheets: <https://ast-grep.github.io/cheatsheet/rule.html>, <https://ast-grep.github.io/cheatsheet/yaml.html>
|
||||
Reference in New Issue
Block a user