+++ /dev/null
-// 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
-}