github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/ginkgo/outline/outline.go (about) 1 package outline 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "go/ast" 7 "go/token" 8 "strings" 9 10 "golang.org/x/tools/go/ast/inspector" 11 ) 12 13 const ( 14 // ginkgoImportPath is the well-known ginkgo import path 15 ginkgoImportPath = "github.com/onsi/ginkgo" 16 ) 17 18 // FromASTFile returns an outline for a Ginkgo test source file 19 func FromASTFile(fset *token.FileSet, src *ast.File) (*outline, error) { 20 ginkgoPackageName := packageNameForImport(src, ginkgoImportPath) 21 if ginkgoPackageName == nil { 22 return nil, fmt.Errorf("file does not import %q", ginkgoImportPath) 23 } 24 25 root := ginkgoNode{} 26 stack := []*ginkgoNode{&root} 27 ispr := inspector.New([]*ast.File{src}) 28 ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool { 29 if push { 30 // Pre-order traversal 31 ce, ok := node.(*ast.CallExpr) 32 if !ok { 33 // Because `Nodes` calls this function only when the node is an 34 // ast.CallExpr, this should never happen 35 panic(fmt.Errorf("node starting at %d, ending at %d is not an *ast.CallExpr", node.Pos(), node.End())) 36 } 37 gn, ok := ginkgoNodeFromCallExpr(fset, ce, ginkgoPackageName) 38 if !ok { 39 // Node is not a Ginkgo spec or container, continue 40 return true 41 } 42 parent := stack[len(stack)-1] 43 parent.Nodes = append(parent.Nodes, gn) 44 stack = append(stack, gn) 45 return true 46 } 47 // Post-order traversal 48 start, end := absoluteOffsetsForNode(fset, node) 49 lastVisitedGinkgoNode := stack[len(stack)-1] 50 if start != lastVisitedGinkgoNode.Start || end != lastVisitedGinkgoNode.End { 51 // Node is not a Ginkgo spec or container, so it was not pushed onto the stack, continue 52 return true 53 } 54 stack = stack[0 : len(stack)-1] 55 return true 56 }) 57 if len(root.Nodes) == 0 { 58 return &outline{[]*ginkgoNode{}}, nil 59 } 60 61 // Derive the final focused property for all nodes. This must be done 62 // _before_ propagating the inherited focused property. 63 root.BackpropagateUnfocus() 64 // Now, propagate inherited properties, including focused and pending. 65 root.PropagateInheritedProperties() 66 67 return &outline{root.Nodes}, nil 68 } 69 70 type outline struct { 71 Nodes []*ginkgoNode `json:"nodes"` 72 } 73 74 func (o *outline) MarshalJSON() ([]byte, error) { 75 return json.Marshal(o.Nodes) 76 } 77 78 // String returns a CSV-formatted outline. Spec or container are output in 79 // depth-first order. 80 func (o *outline) String() string { 81 return o.StringIndent(0) 82 } 83 84 // StringIndent returns a CSV-formated outline, but every line is indented by 85 // one 'width' of spaces for every level of nesting. 86 func (o *outline) StringIndent(width int) string { 87 var b strings.Builder 88 b.WriteString("Name,Text,Start,End,Spec,Focused,Pending\n") 89 90 currentIndent := 0 91 pre := func(n *ginkgoNode) { 92 b.WriteString(fmt.Sprintf("%*s", currentIndent, "")) 93 b.WriteString(fmt.Sprintf("%s,%s,%d,%d,%t,%t,%t\n", n.Name, n.Text, n.Start, n.End, n.Spec, n.Focused, n.Pending)) 94 currentIndent += width 95 } 96 post := func(n *ginkgoNode) { 97 currentIndent -= width 98 } 99 for _, n := range o.Nodes { 100 n.Walk(pre, post) 101 } 102 return b.String() 103 }