golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.41k stars 17.71k forks source link

proposal: text/template/parse: traversal utility #56404

Open tylermmorton opened 2 years ago

tylermmorton commented 2 years ago

When using the package text/template/parse to analyze a template ParseTree, it is common to want to visit all Nodes in a tree, even when they are nested in other nodes.

I've implemented a Traverse function that works recursively from a tree root:

package traverse

import (
    "text/template/parse"
)

func Traverse(cur parse.Node, visitor func(parse.Node)) {
    switch node := cur.(type) {
    case *parse.ActionNode:
        if node.Pipe != nil {
            Traverse(node.Pipe, visitor)
        }
    case *parse.BoolNode:
    case *parse.BranchNode:
        if node.Pipe != nil {
            Traverse(node.Pipe, visitor)
        }
        if node.List != nil {
            Traverse(node.List, visitor)
        }
        if node.ElseList != nil {
            Traverse(node.ElseList, visitor)
        }
    case *parse.BreakNode:
    case *parse.ChainNode:
    case *parse.CommandNode:
        if node.Args != nil {
            for _, arg := range node.Args {
                Traverse(arg, visitor)
            }
        }
    case *parse.CommentNode:
    case *parse.ContinueNode:
    case *parse.DotNode:
    case *parse.FieldNode:
    case *parse.IdentifierNode:
    case *parse.IfNode:
        Traverse(&node.BranchNode, visitor)
    case *parse.ListNode:
        if node.Nodes != nil {
            for _, child := range node.Nodes {
                Traverse(child, visitor)
            }
        }
    case *parse.NilNode:
    case *parse.NumberNode:
    case *parse.PipeNode:
        if node.Cmds != nil {
            for _, cmd := range node.Cmds {
                Traverse(cmd, visitor)
            }
        }
        if node.Decl != nil {
            for _, decl := range node.Decl {
                Traverse(decl, visitor)
            }
        }
    case *parse.RangeNode:
        Traverse(&node.BranchNode, visitor)
    case *parse.StringNode:
    case *parse.TemplateNode:
        if node.Pipe != nil {
            Traverse(node.Pipe, visitor)
        }
    case *parse.TextNode:
    case *parse.VariableNode:
    case *parse.WithNode:
        Traverse(&node.BranchNode, visitor)
    }
    visitor(cur)
}

Is this something that could be added to text/template/parse as a utility function? I believe that a cleaned up version of this or something similar to the ast.Walk interface could increase the utility of the parse package and open the door for static analysis tooling around go templates.

seankhliao commented 2 years ago

cc @robpike

zokrezyl commented 4 months ago

This could have saved me couple of days of research. Thanks a lot Some additional features could be: