| // Copyright 2018 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 inspector provides helper functions for traversal over the | 
 | // syntax trees of a package, including node filtering by type, and | 
 | // materialization of the traversal stack. | 
 | // | 
 | // During construction, the inspector does a complete traversal and | 
 | // builds a list of push/pop events and their node type. Subsequent | 
 | // method calls that request a traversal scan this list, rather than walk | 
 | // the AST, and perform type filtering using efficient bit sets. | 
 | // | 
 | // Experiments suggest the inspector's traversals are about 2.5x faster | 
 | // than ast.Inspect, but it may take around 5 traversals for this | 
 | // benefit to amortize the inspector's construction cost. | 
 | // If efficiency is the primary concern, do not use Inspector for | 
 | // one-off traversals. | 
 | package inspector | 
 |  | 
 | // There are four orthogonal features in a traversal: | 
 | //  1 type filtering | 
 | //  2 pruning | 
 | //  3 postorder calls to f | 
 | //  4 stack | 
 | // Rather than offer all of them in the API, | 
 | // only a few combinations are exposed: | 
 | // - Preorder is the fastest and has fewest features, | 
 | //   but is the most commonly needed traversal. | 
 | // - Nodes and WithStack both provide pruning and postorder calls, | 
 | //   even though few clients need it, because supporting two versions | 
 | //   is not justified. | 
 | // More combinations could be supported by expressing them as | 
 | // wrappers around a more generic traversal, but this was measured | 
 | // and found to degrade performance significantly (30%). | 
 |  | 
 | import ( | 
 | 	"go/ast" | 
 | ) | 
 |  | 
 | // An Inspector provides methods for inspecting | 
 | // (traversing) the syntax trees of a package. | 
 | type Inspector struct { | 
 | 	events []event | 
 | } | 
 |  | 
 | // New returns an Inspector for the specified syntax trees. | 
 | func New(files []*ast.File) *Inspector { | 
 | 	return &Inspector{traverse(files)} | 
 | } | 
 |  | 
 | // An event represents a push or a pop | 
 | // of an ast.Node during a traversal. | 
 | type event struct { | 
 | 	node  ast.Node | 
 | 	typ   uint64 // typeOf(node) | 
 | 	index int    // 1 + index of corresponding pop event, or 0 if this is a pop | 
 | } | 
 |  | 
 | // Preorder visits all the nodes of the files supplied to New in | 
 | // depth-first order. It calls f(n) for each node n before it visits | 
 | // n's children. | 
 | // | 
 | // The types argument, if non-empty, enables type-based filtering of | 
 | // events. The function f if is called only for nodes whose type | 
 | // matches an element of the types slice. | 
 | func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { | 
 | 	// Because it avoids postorder calls to f, and the pruning | 
 | 	// check, Preorder is almost twice as fast as Nodes. The two | 
 | 	// features seem to contribute similar slowdowns (~1.4x each). | 
 |  | 
 | 	mask := maskOf(types) | 
 | 	for i := 0; i < len(in.events); { | 
 | 		ev := in.events[i] | 
 | 		if ev.typ&mask != 0 { | 
 | 			if ev.index > 0 { | 
 | 				f(ev.node) | 
 | 			} | 
 | 		} | 
 | 		i++ | 
 | 	} | 
 | } | 
 |  | 
 | // Nodes visits the nodes of the files supplied to New in depth-first | 
 | // order. It calls f(n, true) for each node n before it visits n's | 
 | // children. If f returns true, Nodes invokes f recursively for each | 
 | // of the non-nil children of the node, followed by a call of | 
 | // f(n, false). | 
 | // | 
 | // The types argument, if non-empty, enables type-based filtering of | 
 | // events. The function f if is called only for nodes whose type | 
 | // matches an element of the types slice. | 
 | func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) { | 
 | 	mask := maskOf(types) | 
 | 	for i := 0; i < len(in.events); { | 
 | 		ev := in.events[i] | 
 | 		if ev.typ&mask != 0 { | 
 | 			if ev.index > 0 { | 
 | 				// push | 
 | 				if !f(ev.node, true) { | 
 | 					i = ev.index // jump to corresponding pop + 1 | 
 | 					continue | 
 | 				} | 
 | 			} else { | 
 | 				// pop | 
 | 				f(ev.node, false) | 
 | 			} | 
 | 		} | 
 | 		i++ | 
 | 	} | 
 | } | 
 |  | 
 | // WithStack visits nodes in a similar manner to Nodes, but it | 
 | // supplies each call to f an additional argument, the current | 
 | // traversal stack. The stack's first element is the outermost node, | 
 | // an *ast.File; its last is the innermost, n. | 
 | func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) { | 
 | 	mask := maskOf(types) | 
 | 	var stack []ast.Node | 
 | 	for i := 0; i < len(in.events); { | 
 | 		ev := in.events[i] | 
 | 		if ev.index > 0 { | 
 | 			// push | 
 | 			stack = append(stack, ev.node) | 
 | 			if ev.typ&mask != 0 { | 
 | 				if !f(ev.node, true, stack) { | 
 | 					i = ev.index | 
 | 					stack = stack[:len(stack)-1] | 
 | 					continue | 
 | 				} | 
 | 			} | 
 | 		} else { | 
 | 			// pop | 
 | 			if ev.typ&mask != 0 { | 
 | 				f(ev.node, false, stack) | 
 | 			} | 
 | 			stack = stack[:len(stack)-1] | 
 | 		} | 
 | 		i++ | 
 | 	} | 
 | } | 
 |  | 
 | // traverse builds the table of events representing a traversal. | 
 | func traverse(files []*ast.File) []event { | 
 | 	// Preallocate approximate number of events | 
 | 	// based on source file extent. | 
 | 	// This makes traverse faster by 4x (!). | 
 | 	var extent int | 
 | 	for _, f := range files { | 
 | 		extent += int(f.End() - f.Pos()) | 
 | 	} | 
 | 	// This estimate is based on the net/http package. | 
 | 	capacity := extent * 33 / 100 | 
 | 	if capacity > 1e6 { | 
 | 		capacity = 1e6 // impose some reasonable maximum | 
 | 	} | 
 | 	events := make([]event, 0, capacity) | 
 |  | 
 | 	var stack []event | 
 | 	for _, f := range files { | 
 | 		ast.Inspect(f, func(n ast.Node) bool { | 
 | 			if n != nil { | 
 | 				// push | 
 | 				ev := event{ | 
 | 					node:  n, | 
 | 					typ:   typeOf(n), | 
 | 					index: len(events), // push event temporarily holds own index | 
 | 				} | 
 | 				stack = append(stack, ev) | 
 | 				events = append(events, ev) | 
 | 			} else { | 
 | 				// pop | 
 | 				ev := stack[len(stack)-1] | 
 | 				stack = stack[:len(stack)-1] | 
 |  | 
 | 				events[ev.index].index = len(events) + 1 // make push refer to pop | 
 |  | 
 | 				ev.index = 0 // turn ev into a pop event | 
 | 				events = append(events, ev) | 
 | 			} | 
 | 			return true | 
 | 		}) | 
 | 	} | 
 |  | 
 | 	return events | 
 | } |