GoPLS Viewer

Home|gopls/go/ast/inspector/inspector_test.go
1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package inspector_test
6
7import (
8    "go/ast"
9    "go/build"
10    "go/parser"
11    "go/token"
12    "log"
13    "path/filepath"
14    "reflect"
15    "strconv"
16    "strings"
17    "testing"
18
19    "golang.org/x/tools/go/ast/inspector"
20    "golang.org/x/tools/internal/typeparams"
21)
22
23var netFiles []*ast.File
24
25func init() {
26    fileserr := parseNetFiles()
27    if err != nil {
28        log.Fatal(err)
29    }
30    netFiles = files
31}
32
33func parseNetFiles() ([]*ast.Fileerror) {
34    pkgerr := build.Default.Import("net"""0)
35    if err != nil {
36        return nilerr
37    }
38    fset := token.NewFileSet()
39    var files []*ast.File
40    for _filename := range pkg.GoFiles {
41        filename = filepath.Join(pkg.Dirfilename)
42        ferr := parser.ParseFile(fsetfilenamenil0)
43        if err != nil {
44            return nilerr
45        }
46        files = append(filesf)
47    }
48    return filesnil
49}
50
51// TestAllNodes compares Inspector against ast.Inspect.
52func TestInspectAllNodes(t *testing.T) {
53    inspect := inspector.New(netFiles)
54
55    var nodesA []ast.Node
56    inspect.Nodes(nil, func(n ast.Nodepush boolbool {
57        if push {
58            nodesA = append(nodesAn)
59        }
60        return true
61    })
62    var nodesB []ast.Node
63    for _f := range netFiles {
64        ast.Inspect(f, func(n ast.Nodebool {
65            if n != nil {
66                nodesB = append(nodesBn)
67            }
68            return true
69        })
70    }
71    compare(tnodesAnodesB)
72}
73
74func TestInspectGenericNodes(t *testing.T) {
75    if !typeparams.Enabled {
76        t.Skip("type parameters are not supported at this Go version")
77    }
78
79    // src is using the 16 identifiers i0, i1, ... i15 so
80    // we can easily verify that we've found all of them.
81    const src = `package a
82
83type I interface { ~i0|i1 }
84
85type T[i2, i3 interface{ ~i4 }] struct {}
86
87func f[i5, i6 any]() {
88    _ = f[i7, i8]
89    var x T[i9, i10]
90}
91
92func (*T[i11, i12]) m()
93
94var _ i13[i14, i15]
95`
96    fset := token.NewFileSet()
97    f_ := parser.ParseFile(fset"a.go"src0)
98    inspect := inspector.New([]*ast.File{f})
99    found := make([]bool16)
100
101    indexListExprs := make(map[*typeparams.IndexListExpr]bool)
102
103    // Verify that we reach all i* identifiers, and collect IndexListExpr nodes.
104    inspect.Preorder(nil, func(n ast.Node) {
105        switch n := n.(type) {
106        case *ast.Ident:
107            if n.Name[0] == 'i' {
108                indexerr := strconv.Atoi(n.Name[1:])
109                if err != nil {
110                    t.Fatal(err)
111                }
112                found[index] = true
113            }
114        case *typeparams.IndexListExpr:
115            indexListExprs[n] = false
116        }
117    })
118    for iv := range found {
119        if !v {
120            t.Errorf("missed identifier i%d"i)
121        }
122    }
123
124    // Verify that we can filter to IndexListExprs that we found in the first
125    // step.
126    if len(indexListExprs) == 0 {
127        t.Fatal("no index list exprs found")
128    }
129    inspect.Preorder([]ast.Node{&typeparams.IndexListExpr{}}, func(n ast.Node) {
130        ix := n.(*typeparams.IndexListExpr)
131        indexListExprs[ix] = true
132    })
133    for ixv := range indexListExprs {
134        if !v {
135            t.Errorf("inspected node %v not filtered"ix)
136        }
137    }
138}
139
140// TestPruning compares Inspector against ast.Inspect,
141// pruning descent within ast.CallExpr nodes.
142func TestInspectPruning(t *testing.T) {
143    inspect := inspector.New(netFiles)
144
145    var nodesA []ast.Node
146    inspect.Nodes(nil, func(n ast.Nodepush boolbool {
147        if push {
148            nodesA = append(nodesAn)
149            _isCall := n.(*ast.CallExpr)
150            return !isCall // don't descend into function calls
151        }
152        return false
153    })
154    var nodesB []ast.Node
155    for _f := range netFiles {
156        ast.Inspect(f, func(n ast.Nodebool {
157            if n != nil {
158                nodesB = append(nodesBn)
159                _isCall := n.(*ast.CallExpr)
160                return !isCall // don't descend into function calls
161            }
162            return false
163        })
164    }
165    compare(tnodesAnodesB)
166}
167
168func compare(t *testing.TnodesAnodesB []ast.Node) {
169    if len(nodesA) != len(nodesB) {
170        t.Errorf("inconsistent node lists: %d vs %d"len(nodesA), len(nodesB))
171    } else {
172        for i := range nodesA {
173            if ab := nodesA[i], nodesB[i]; a != b {
174                t.Errorf("node %d is inconsistent: %T, %T"iab)
175            }
176        }
177    }
178}
179
180func TestTypeFiltering(t *testing.T) {
181    const src = `package a
182func f() {
183    print("hi")
184    panic("oops")
185}
186`
187    fset := token.NewFileSet()
188    f_ := parser.ParseFile(fset"a.go"src0)
189    inspect := inspector.New([]*ast.File{f})
190
191    var got []string
192    fn := func(n ast.Nodepush boolbool {
193        if push {
194            got = append(gottypeOf(n))
195        }
196        return true
197    }
198
199    // no type filtering
200    inspect.Nodes(nilfn)
201    if want := strings.Fields("File Ident FuncDecl Ident FuncType FieldList BlockStmt ExprStmt CallExpr Ident BasicLit ExprStmt CallExpr Ident BasicLit"); !reflect.DeepEqual(gotwant) {
202        t.Errorf("inspect: got %s, want %s"gotwant)
203    }
204
205    // type filtering
206    nodeTypes := []ast.Node{
207        (*ast.BasicLit)(nil),
208        (*ast.CallExpr)(nil),
209    }
210    got = nil
211    inspect.Nodes(nodeTypesfn)
212    if want := strings.Fields("CallExpr BasicLit CallExpr BasicLit"); !reflect.DeepEqual(gotwant) {
213        t.Errorf("inspect: got %s, want %s"gotwant)
214    }
215
216    // inspect with stack
217    got = nil
218    inspect.WithStack(nodeTypes, func(n ast.Nodepush boolstack []ast.Nodebool {
219        if push {
220            var line []string
221            for _n := range stack {
222                line = append(linetypeOf(n))
223            }
224            got = append(gotstrings.Join(line" "))
225        }
226        return true
227    })
228    want := []string{
229        "File FuncDecl BlockStmt ExprStmt CallExpr",
230        "File FuncDecl BlockStmt ExprStmt CallExpr BasicLit",
231        "File FuncDecl BlockStmt ExprStmt CallExpr",
232        "File FuncDecl BlockStmt ExprStmt CallExpr BasicLit",
233    }
234    if !reflect.DeepEqual(gotwant) {
235        t.Errorf("inspect: got %s, want %s"gotwant)
236    }
237}
238
239func typeOf(n ast.Nodestring {
240    return strings.TrimPrefix(reflect.TypeOf(n).String(), "*ast.")
241}
242
243// The numbers show a marginal improvement (ASTInspect/Inspect) of 3.5x,
244// but a break-even point (NewInspector/(ASTInspect-Inspect)) of about 5
245// traversals.
246//
247// BenchmarkNewInspector   4.5 ms
248// BenchmarkNewInspect       0.33ms
249// BenchmarkASTInspect    1.2  ms
250
251func BenchmarkNewInspector(b *testing.B) {
252    // Measure one-time construction overhead.
253    for i := 0i < b.Ni++ {
254        inspector.New(netFiles)
255    }
256}
257
258func BenchmarkInspect(b *testing.B) {
259    b.StopTimer()
260    inspect := inspector.New(netFiles)
261    b.StartTimer()
262
263    // Measure marginal cost of traversal.
264    var ndeclsnlits int
265    for i := 0i < b.Ni++ {
266        inspect.Preorder(nil, func(n ast.Node) {
267            switch n.(type) {
268            case *ast.FuncDecl:
269                ndecls++
270            case *ast.FuncLit:
271                nlits++
272            }
273        })
274    }
275}
276
277func BenchmarkASTInspect(b *testing.B) {
278    var ndeclsnlits int
279    for i := 0i < b.Ni++ {
280        for _f := range netFiles {
281            ast.Inspect(f, func(n ast.Nodebool {
282                switch n.(type) {
283                case *ast.FuncDecl:
284                    ndecls++
285                case *ast.FuncLit:
286                    nlits++
287                }
288                return true
289            })
290        }
291    }
292}
293
MembersX
TestInspectPruning.nodesB
BenchmarkASTInspect.BlockStmt.RangeStmt_6452.f
TestInspectAllNodes.RangeStmt_1236.f
TestInspectGenericNodes._
TestInspectGenericNodes.RangeStmt_2841.ix
compare
TestTypeFiltering
TestTypeFiltering.fset
TestTypeFiltering.BlockStmt.BlockStmt.RangeStmt_5044.n
typeOf.n
inspector
TestInspectGenericNodes.inspect
BenchmarkInspect.ndecls
BenchmarkNewInspector
BenchmarkInspect
TestInspectAllNodes.inspect
TestInspectPruning.nodesA
BenchmarkASTInspect.b
init.err
parseNetFiles.err
TestInspectAllNodes.t
TestInspectPruning
compare.t
BenchmarkInspect.b
BenchmarkInspect.i
BenchmarkASTInspect
typeparams
init
BenchmarkASTInspect.nlits
TestInspectGenericNodes
TestInspectPruning.RangeStmt_3381.f
TestTypeFiltering.t
TestTypeFiltering.got
reflect
init.files
BenchmarkASTInspect.i
parseNetFiles.RangeStmt_705.filename
TestTypeFiltering.nodeTypes
TestInspectAllNodes.nodesA
BenchmarkNewInspector.b
BenchmarkInspect.nlits
build
filepath
TestInspectGenericNodes.RangeStmt_2450.v
log
netFiles
TestInspectGenericNodes.fset
TestInspectGenericNodes.BlockStmt.BlockStmt.BlockStmt.err
compare.nodesB
TestTypeFiltering.f
typeOf
BenchmarkInspect.inspect
testing
parseNetFiles.fset
compare.nodesA
parseNetFiles
TestInspectGenericNodes.src
TestTypeFiltering.want
parseNetFiles.files
TestInspectGenericNodes.RangeStmt_2450.i
TestInspectGenericNodes.t
TestInspectGenericNodes.f
TestInspectGenericNodes.indexListExprs
TestInspectGenericNodes.BlockStmt.BlockStmt.BlockStmt.index
compare.BlockStmt.RangeStmt_3825.i
TestTypeFiltering.inspect
strings
TestInspectAllNodes
TestTypeFiltering.src
BenchmarkASTInspect.ndecls
parseNetFiles.RangeStmt_705.BlockStmt.f
parseNetFiles.RangeStmt_705.BlockStmt.err
TestInspectAllNodes.nodesB
TestInspectGenericNodes.RangeStmt_2841.v
TestInspectPruning.inspect
TestTypeFiltering._
parser
strconv
TestInspectGenericNodes.found
TestInspectPruning.t
TestTypeFiltering.BlockStmt.BlockStmt.line
BenchmarkNewInspector.i
token
parseNetFiles.pkg
Members
X