// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ast_test import ( "go/ast" "go/parser" "go/token" "reflect" "slices" "strings" "testing" ) func TestPreorder_Break(t *testing.T) { // This test checks that Preorder correctly handles a break statement while // in the middle of walking a node. Previously, incorrect handling of the // boolean returned by the yield function resulted in the iterator calling // yield for sibling nodes even after yield had returned false. With that // bug, this test failed with a runtime panic. src := "package p\ntype T struct {\n\tF int `json:\"f\"` // a field\n}\n" fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", src, 0) if err != nil { panic(err) } for n := range ast.Preorder(f) { if id, ok := n.(*ast.Ident); ok && id.Name == "F" { break } } } func TestPreorderStack(t *testing.T) { const src = `package a func f() { print("hello") } func g() { print("goodbye") panic("oops") } ` fset := token.NewFileSet() f, _ := parser.ParseFile(fset, "a.go", src, 0) str := func(n ast.Node) string { return strings.TrimPrefix(reflect.TypeOf(n).String(), "*ast.") } var events []string var gotStack []string ast.PreorderStack(f, nil, func(n ast.Node, stack []ast.Node) bool { events = append(events, str(n)) if decl, ok := n.(*ast.FuncDecl); ok && decl.Name.Name == "f" { return false // skip subtree of f() } if lit, ok := n.(*ast.BasicLit); ok && lit.Value == `"oops"` { for _, n := range stack { gotStack = append(gotStack, str(n)) } } return true }) // Check sequence of events. wantEvents := []string{ "File", "Ident", // package a "FuncDecl", // func f() [pruned] "FuncDecl", "Ident", "FuncType", "FieldList", "BlockStmt", // func g() "ExprStmt", "CallExpr", "Ident", "BasicLit", // print... "ExprStmt", "CallExpr", "Ident", "BasicLit", // panic... } if !slices.Equal(events, wantEvents) { t.Errorf("PreorderStack events:\ngot: %s\nwant: %s", events, wantEvents) } // Check captured stack. wantStack := []string{"File", "FuncDecl", "BlockStmt", "ExprStmt", "CallExpr"} if !slices.Equal(gotStack, wantStack) { t.Errorf("PreorderStack stack:\ngot: %s\nwant: %s", gotStack, wantStack) } }