108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package outline
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/ast/inspector"
|
|
)
|
|
|
|
const (
|
|
// ginkgoImportPath is the well-known ginkgo import path
|
|
ginkgoImportPath = "github.com/onsi/ginkgo"
|
|
|
|
// tableImportPath is the well-known table extension import path
|
|
tableImportPath = "github.com/onsi/ginkgo/extensions/table"
|
|
)
|
|
|
|
// FromASTFile returns an outline for a Ginkgo test source file
|
|
func FromASTFile(fset *token.FileSet, src *ast.File) (*outline, error) {
|
|
ginkgoPackageName := packageNameForImport(src, ginkgoImportPath)
|
|
tablePackageName := packageNameForImport(src, tableImportPath)
|
|
if ginkgoPackageName == nil && tablePackageName == nil {
|
|
return nil, fmt.Errorf("file does not import %q or %q", ginkgoImportPath, tableImportPath)
|
|
}
|
|
|
|
root := ginkgoNode{}
|
|
stack := []*ginkgoNode{&root}
|
|
ispr := inspector.New([]*ast.File{src})
|
|
ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool {
|
|
if push {
|
|
// Pre-order traversal
|
|
ce, ok := node.(*ast.CallExpr)
|
|
if !ok {
|
|
// Because `Nodes` calls this function only when the node is an
|
|
// ast.CallExpr, this should never happen
|
|
panic(fmt.Errorf("node starting at %d, ending at %d is not an *ast.CallExpr", node.Pos(), node.End()))
|
|
}
|
|
gn, ok := ginkgoNodeFromCallExpr(fset, ce, ginkgoPackageName, tablePackageName)
|
|
if !ok {
|
|
// Node is not a Ginkgo spec or container, continue
|
|
return true
|
|
}
|
|
parent := stack[len(stack)-1]
|
|
parent.Nodes = append(parent.Nodes, gn)
|
|
stack = append(stack, gn)
|
|
return true
|
|
}
|
|
// Post-order traversal
|
|
start, end := absoluteOffsetsForNode(fset, node)
|
|
lastVisitedGinkgoNode := stack[len(stack)-1]
|
|
if start != lastVisitedGinkgoNode.Start || end != lastVisitedGinkgoNode.End {
|
|
// Node is not a Ginkgo spec or container, so it was not pushed onto the stack, continue
|
|
return true
|
|
}
|
|
stack = stack[0 : len(stack)-1]
|
|
return true
|
|
})
|
|
if len(root.Nodes) == 0 {
|
|
return &outline{[]*ginkgoNode{}}, nil
|
|
}
|
|
|
|
// Derive the final focused property for all nodes. This must be done
|
|
// _before_ propagating the inherited focused property.
|
|
root.BackpropagateUnfocus()
|
|
// Now, propagate inherited properties, including focused and pending.
|
|
root.PropagateInheritedProperties()
|
|
|
|
return &outline{root.Nodes}, nil
|
|
}
|
|
|
|
type outline struct {
|
|
Nodes []*ginkgoNode `json:"nodes"`
|
|
}
|
|
|
|
func (o *outline) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(o.Nodes)
|
|
}
|
|
|
|
// String returns a CSV-formatted outline. Spec or container are output in
|
|
// depth-first order.
|
|
func (o *outline) String() string {
|
|
return o.StringIndent(0)
|
|
}
|
|
|
|
// StringIndent returns a CSV-formated outline, but every line is indented by
|
|
// one 'width' of spaces for every level of nesting.
|
|
func (o *outline) StringIndent(width int) string {
|
|
var b strings.Builder
|
|
b.WriteString("Name,Text,Start,End,Spec,Focused,Pending\n")
|
|
|
|
currentIndent := 0
|
|
pre := func(n *ginkgoNode) {
|
|
b.WriteString(fmt.Sprintf("%*s", currentIndent, ""))
|
|
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))
|
|
currentIndent += width
|
|
}
|
|
post := func(n *ginkgoNode) {
|
|
currentIndent -= width
|
|
}
|
|
for _, n := range o.Nodes {
|
|
n.Walk(pre, post)
|
|
}
|
|
return b.String()
|
|
}
|