File: outline.go

package info (click to toggle)
golang-github-onsi-ginkgo-v2 2.27.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,340 kB
  • sloc: javascript: 65; makefile: 23; sh: 14
file content (130 lines) | stat: -rw-r--r-- 3,445 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package outline

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"go/ast"
	"go/token"
	"strconv"
	"strings"

	"golang.org/x/tools/go/ast/inspector"
)

const (
	// ginkgoImportPath is the well-known ginkgo import path
	ginkgoImportPath = "github.com/onsi/ginkgo/v2"
)

// FromASTFile returns an outline for a Ginkgo test source file
func FromASTFile(fset *token.FileSet, src *ast.File) (*outline, error) {
	ginkgoPackageName := packageNameForImport(src, ginkgoImportPath)
	if ginkgoPackageName == nil {
		return nil, fmt.Errorf("file does not import %q", ginkgoImportPath)
	}

	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)
			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 bytes.Buffer
	b.WriteString("Name,Text,Start,End,Spec,Focused,Pending,Labels\n")

	csvWriter := csv.NewWriter(&b)

	currentIndent := 0
	pre := func(n *ginkgoNode) {
		b.WriteString(fmt.Sprintf("%*s", currentIndent, ""))
		var labels string
		if len(n.Labels) == 1 {
			labels = n.Labels[0]
		} else {
			labels = strings.Join(n.Labels, ", ")
		}

		row := []string{
			n.Name,
			n.Text,
			strconv.Itoa(n.Start),
			strconv.Itoa(n.End),
			strconv.FormatBool(n.Spec),
			strconv.FormatBool(n.Focused),
			strconv.FormatBool(n.Pending),
			labels,
		}
		csvWriter.Write(row)

		// Ensure we write to `b' before the next `b.WriteString()', which might be adding indentation
		csvWriter.Flush()

		currentIndent += width
	}
	post := func(n *ginkgoNode) {
		currentIndent -= width
	}
	for _, n := range o.Nodes {
		n.Walk(pre, post)
	}

	return b.String()
}