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  }