79 lines
2.9 KiB
Markdown
79 lines
2.9 KiB
Markdown
# AST-Grep (sg) — Structural Code Search & Transform
|
|||
|
|
|
||
|
|
## Core Concept
|
||
|
|
- AST-grep searches/transforms code using Abstract Syntax Tree patterns, not regex.
|
||
|
|
- Pattern matches structural meaning, ignoring whitespace, comments, formatting.
|
||
|
|
- Works across: Rust, Go, Python, JS/TS, Java, C, C++, Ruby, Kotlin, Lua, CSS, HTML.
|
||
|
|
|
||
|
|
## CLI Usage
|
||
|
|
- `sg --pattern 'unwrap()' -l rust` — find all `.unwrap()` calls in Rust files.
|
||
|
|
- `sg --pattern 'println!($$$ARGS)' -l rust` — find all println macros with any args.
|
||
|
|
- `sg --pattern '$A.unwrap()' --rewrite '$A.expect("TODO")' -l rust` — rewrite unwrap to expect.
|
||
|
|
- `sg scan` — run lint rules from `sgconfig.yml`.
|
||
|
|
- `sg test` — test rules against fixtures.
|
||
|
|
|
||
|
|
## Pattern Syntax
|
||
|
|
- `$VAR` matches single AST node (identifier, expression, etc).
|
||
|
|
- `$$$VARS` matches zero or more nodes (variadic).
|
||
|
|
- `$$VAR` matches zero or one node (optional).
|
||
|
|
- Literal code matches itself: `if true { $$$BODY }` matches any `if true` block.
|
||
|
|
|
||
|
|
## Meta Variables
|
||
|
|
- `$A` in pattern captures node, available in `--rewrite` as `$A`.
|
||
|
|
- Named captures: same name must match same content. `$A == $A` matches `x == x` but not `x == y`.
|
||
|
|
- `$_` is anonymous — matches anything without capturing.
|
||
|
|
|
||
|
|
## Rule YAML Format
|
||
|
|
```yaml
|
||
|
|
id: no-unwrap
|
||
|
|
language: rust
|
||
|
|
rule:
|
||
|
|
pattern: $A.unwrap()
|
||
|
|
not:
|
||
|
|
inside:
|
||
|
|
kind: test_function
|
||
|
|
fix: $A.expect("handle error")
|
||
|
|
message: "Use .expect() instead of .unwrap() in production code"
|
||
|
|
severity: warning
|
||
|
|
```
|
||
|
|
|
||
|
|
## Composite Rules
|
||
|
|
- `all: [rule1, rule2]` — both must match.
|
||
|
|
- `any: [rule1, rule2]` — either matches.
|
||
|
|
- `not: rule` — negation.
|
||
|
|
- `matches: rule-id` — reference another rule.
|
||
|
|
- `inside: { kind: function_item }` — must be inside a function.
|
||
|
|
- `has: { pattern: $EXPR }` — must contain sub-pattern.
|
||
|
|
- `follows: { pattern: ... }` — must follow another pattern.
|
||
|
|
- `precedes: { pattern: ... }` — must precede another pattern.
|
||
|
|
|
||
|
|
## Kind Selectors
|
||
|
|
- `kind: function_item` — match AST node type directly.
|
||
|
|
- `kind: call_expression` — match function calls.
|
||
|
|
- Use `sg --debug-query='println!("hello")'` to see AST node kinds.
|
||
|
|
|
||
|
|
## Configuration (sgconfig.yml)
|
||
|
|
```yaml
|
||
|
|
ruleDirs:
|
||
|
|
- rules/
|
||
|
|
testConfigs:
|
||
|
|
- rules/tests/
|
||
|
|
```
|
||
|
|
|
||
|
|
## Advanced Patterns
|
||
|
|
- Find unused variables: `let $VAR = $EXPR;` where `$VAR` not referenced later.
|
||
|
|
- Find API migrations: `old_function($$$ARGS)` → `new_function($$$ARGS)`.
|
||
|
|
- Enforce patterns: ensure all error handling uses `?` not `.unwrap()`.
|
||
|
|
- Security: find `eval($EXPR)`, SQL injection patterns, hardcoded secrets.
|
||
|
|
|
||
|
|
## Integration
|
||
|
|
- CI/CD: `sg scan --json` for machine-readable output.
|
||
|
|
- Pre-commit hooks: `sg scan --rule rules/` on staged files.
|
||
|
|
- Editor: VSCode extension, LSP support.
|
||
|
|
- Programmatic: `@ast-grep/napi` Node.js binding for custom tools.
|
||
|
|
|
||
|
|
## vs Regex
|
||
|
|
- Regex: `unwrap\(\)` matches in comments, strings, docs. AST-grep: only actual code.
|
||
|
|
- Regex can't match nested structures. AST-grep handles `if { if { unwrap() } }`.
|
||
|
|
- AST-grep understands scope, types, structure. Regex is text-level.
|