Files
upstream-mirror 0f4df23290
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
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

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`.