paulk_asert
10/22/2026, 10:46 PMvisit(Expression) isn't a traversal hook. It's a default convenience method on GroovyCodeVisitor that just does if (expr != null) expr.visit(this). The traversal itself goes through double dispatch: each node calls its specific visitXxx method (visitBinaryExpression, visitMethodCallExpression, ...), and the CodeVisitorSupport implementation of each one visits the children. Nothing ever routes back through visit(Expression), so CodeVisitorSupport has no single catch-all.
To see every expression with one override, use ClassCodeExpressionTransformer and override transform(Expression). super.transform(e) calls e.transformExpression(this), which sends each child back through transform, so you get every expression, nested ones included:
def t = new ClassCodeExpressionTransformer() {
SourceUnit getSourceUnit() { sourceUnit }
Expression transform(Expression e) {
// your per-expression logic here
if (e instanceof ClosureExpression) { e.code.visit(this); return e } // closures aren't descended into by default
super.transform(e)
}
}
classNode.visitContents(t) // or methodNode.code.visit(t), module.statementBlock.visit(t)
Two caveats:
• Closure bodies are skipped unless you handle them yourself, as in the line above (this is documented on the class).
• It's a transformer: transformExpression builds copies of many node types and writes them back into the tree. So use it for read-only walking only if node identity doesn't matter to you (e.g. you're not keeping references or metadata keyed on the original nodes). Otherwise you're stuck overriding the specific visitXxx methods of CodeVisitorSupport / ClassCodeVisitorSupport, or overriding a few common ones and calling super.