Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
123 lines
3.1 KiB
Markdown
123 lines
3.1 KiB
Markdown
# AST Utilities Package - Claude Code Guide
|
|
|
|
This package provides utilities for traversing and transforming AST nodes.
|
|
|
|
## Key Functions
|
|
|
|
### Walk
|
|
`Walk(f Visitor, node ast.Node)` traverses the AST depth-first, calling `f.Visit()` on each node.
|
|
|
|
```go
|
|
type Visitor interface {
|
|
Visit(node ast.Node) Visitor
|
|
}
|
|
```
|
|
|
|
**Important**: When adding new AST node types, you MUST add a case to the switch statement in `walk.go`, otherwise you'll get a panic:
|
|
```
|
|
panic: walk: unexpected node type *ast.YourNewType
|
|
```
|
|
|
|
### Apply (Rewrite)
|
|
`Apply(root ast.Node, pre, post ApplyFunc) ast.Node` traverses and optionally transforms the AST.
|
|
|
|
```go
|
|
type ApplyFunc func(*Cursor) bool
|
|
```
|
|
|
|
The `Cursor` provides:
|
|
- `Node()` - current node
|
|
- `Parent()` - parent node
|
|
- `Name()` - field name in parent
|
|
- `Index()` - index if in a list
|
|
- `Replace(node)` - replace current node
|
|
|
|
**Important**: When adding new AST node types, you MUST add a case to the switch statement in `rewrite.go`, otherwise you'll get a panic:
|
|
```
|
|
panic: Apply: unexpected node type *ast.YourNewType
|
|
```
|
|
|
|
### Search
|
|
`Search(root ast.Node, fn func(ast.Node) bool) *ast.List` finds all nodes matching a predicate.
|
|
|
|
### Join
|
|
`Join(list *ast.List, sep string) string` joins string nodes with a separator.
|
|
|
|
## Adding Support for New AST Nodes
|
|
|
|
When you create a new AST node type, you must update BOTH `walk.go` and `rewrite.go`:
|
|
|
|
### In walk.go
|
|
Add a case that walks all child nodes:
|
|
```go
|
|
case *ast.YourNewType:
|
|
if n.ChildField != nil {
|
|
Walk(f, n.ChildField)
|
|
}
|
|
if n.ChildList != nil {
|
|
Walk(f, n.ChildList)
|
|
}
|
|
```
|
|
|
|
For leaf nodes with no children:
|
|
```go
|
|
case *ast.YourNewType:
|
|
// Leaf node - no children to traverse
|
|
```
|
|
|
|
### In rewrite.go
|
|
Add a case that applies to all child nodes:
|
|
```go
|
|
case *ast.YourNewType:
|
|
a.apply(n, "ChildField", nil, n.ChildField)
|
|
a.apply(n, "ChildList", nil, n.ChildList)
|
|
```
|
|
|
|
For leaf nodes:
|
|
```go
|
|
case *ast.YourNewType:
|
|
// Leaf node - no children to traverse
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Finding All Tables in a Statement
|
|
```go
|
|
var tv tableVisitor
|
|
astutils.Walk(&tv, stmt.FromClause)
|
|
// tv.list now contains all RangeVar nodes
|
|
```
|
|
|
|
### Rewriting Nodes In Place
|
|
Apply replaces a node with a new one as the tree is walked:
|
|
```go
|
|
astutils.Apply(root, func(cr *astutils.Cursor) bool {
|
|
if _, ok := cr.Node().(*ast.SomeType); ok {
|
|
cr.Replace(&ast.OtherType{})
|
|
}
|
|
return true
|
|
}, nil)
|
|
```
|
|
|
|
Note that sqlc syntax (`sqlc.arg()`, `@name`) is *not* handled here. It is
|
|
rewritten to native SQL by `internal/sql/preprocess` before the engine parses a
|
|
query, so a node type missing from walk.go/rewrite.go can no longer silently
|
|
drop a parameter.
|
|
|
|
## Node Types That Must Be Handled
|
|
|
|
All node types in `internal/sql/ast/` must have cases in both walk.go and rewrite.go. Key MySQL-specific nodes:
|
|
- `IntervalExpr` - INTERVAL expressions
|
|
- `OnDuplicateKeyUpdate` - MySQL ON DUPLICATE KEY UPDATE
|
|
- `ParenExpr` - Parenthesized expressions
|
|
- `VariableExpr` - MySQL user variables (@var)
|
|
|
|
## Debugging Tips
|
|
|
|
If you see a panic like:
|
|
```
|
|
panic: walk: unexpected node type *ast.SomeType
|
|
```
|
|
|
|
Check that `SomeType` has a case in both `walk.go` and `rewrite.go`.
|