3.1 KiB
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.
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.
type ApplyFunc func(*Cursor) bool
The Cursor provides:
Node()- current nodeParent()- parent nodeName()- field name in parentIndex()- index if in a listReplace(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:
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:
case *ast.YourNewType:
// Leaf node - no children to traverse
In rewrite.go
Add a case that applies to all child nodes:
case *ast.YourNewType:
a.apply(n, "ChildField", nil, n.ChildField)
a.apply(n, "ChildList", nil, n.ChildList)
For leaf nodes:
case *ast.YourNewType:
// Leaf node - no children to traverse
Common Patterns
Finding All Tables in a Statement
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:
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 expressionsOnDuplicateKeyUpdate- MySQL ON DUPLICATE KEY UPDATEParenExpr- Parenthesized expressionsVariableExpr- 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.