Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / go / analysis / passes / tests / tests.go
1 // Copyright 2015 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
5 // Package tests defines an Analyzer that checks for common mistaken
6 // usages of tests and examples.
7 package tests
8
9 import (
10         "go/ast"
11         "go/types"
12         "strings"
13         "unicode"
14         "unicode/utf8"
15
16         "golang.org/x/tools/go/analysis"
17 )
18
19 const Doc = `check for common mistaken usages of tests and examples
20
21 The tests checker walks Test, Benchmark and Example functions checking
22 malformed names, wrong signatures and examples documenting non-existent
23 identifiers.
24
25 Please see the documentation for package testing in golang.org/pkg/testing
26 for the conventions that are enforced for Tests, Benchmarks, and Examples.`
27
28 var Analyzer = &analysis.Analyzer{
29         Name: "tests",
30         Doc:  Doc,
31         Run:  run,
32 }
33
34 func run(pass *analysis.Pass) (interface{}, error) {
35         for _, f := range pass.Files {
36                 if !strings.HasSuffix(pass.Fset.File(f.Pos()).Name(), "_test.go") {
37                         continue
38                 }
39                 for _, decl := range f.Decls {
40                         fn, ok := decl.(*ast.FuncDecl)
41                         if !ok || fn.Recv != nil {
42                                 // Ignore non-functions or functions with receivers.
43                                 continue
44                         }
45
46                         switch {
47                         case strings.HasPrefix(fn.Name.Name, "Example"):
48                                 checkExample(pass, fn)
49                         case strings.HasPrefix(fn.Name.Name, "Test"):
50                                 checkTest(pass, fn, "Test")
51                         case strings.HasPrefix(fn.Name.Name, "Benchmark"):
52                                 checkTest(pass, fn, "Benchmark")
53                         }
54                 }
55         }
56         return nil, nil
57 }
58
59 func isExampleSuffix(s string) bool {
60         r, size := utf8.DecodeRuneInString(s)
61         return size > 0 && unicode.IsLower(r)
62 }
63
64 func isTestSuffix(name string) bool {
65         if len(name) == 0 {
66                 // "Test" is ok.
67                 return true
68         }
69         r, _ := utf8.DecodeRuneInString(name)
70         return !unicode.IsLower(r)
71 }
72
73 func isTestParam(typ ast.Expr, wantType string) bool {
74         ptr, ok := typ.(*ast.StarExpr)
75         if !ok {
76                 // Not a pointer.
77                 return false
78         }
79         // No easy way of making sure it's a *testing.T or *testing.B:
80         // ensure the name of the type matches.
81         if name, ok := ptr.X.(*ast.Ident); ok {
82                 return name.Name == wantType
83         }
84         if sel, ok := ptr.X.(*ast.SelectorExpr); ok {
85                 return sel.Sel.Name == wantType
86         }
87         return false
88 }
89
90 func lookup(pkg *types.Package, name string) []types.Object {
91         if o := pkg.Scope().Lookup(name); o != nil {
92                 return []types.Object{o}
93         }
94
95         var ret []types.Object
96         // Search through the imports to see if any of them define name.
97         // It's hard to tell in general which package is being tested, so
98         // for the purposes of the analysis, allow the object to appear
99         // in any of the imports. This guarantees there are no false positives
100         // because the example needs to use the object so it must be defined
101         // in the package or one if its imports. On the other hand, false
102         // negatives are possible, but should be rare.
103         for _, imp := range pkg.Imports() {
104                 if obj := imp.Scope().Lookup(name); obj != nil {
105                         ret = append(ret, obj)
106                 }
107         }
108         return ret
109 }
110
111 func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
112         fnName := fn.Name.Name
113         if params := fn.Type.Params; len(params.List) != 0 {
114                 pass.Reportf(fn.Pos(), "%s should be niladic", fnName)
115         }
116         if results := fn.Type.Results; results != nil && len(results.List) != 0 {
117                 pass.Reportf(fn.Pos(), "%s should return nothing", fnName)
118         }
119
120         if fnName == "Example" {
121                 // Nothing more to do.
122                 return
123         }
124
125         var (
126                 exName = strings.TrimPrefix(fnName, "Example")
127                 elems  = strings.SplitN(exName, "_", 3)
128                 ident  = elems[0]
129                 objs   = lookup(pass.Pkg, ident)
130         )
131         if ident != "" && len(objs) == 0 {
132                 // Check ExampleFoo and ExampleBadFoo.
133                 pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident)
134                 // Abort since obj is absent and no subsequent checks can be performed.
135                 return
136         }
137         if len(elems) < 2 {
138                 // Nothing more to do.
139                 return
140         }
141
142         if ident == "" {
143                 // Check Example_suffix and Example_BadSuffix.
144                 if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) {
145                         pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, residual)
146                 }
147                 return
148         }
149
150         mmbr := elems[1]
151         if !isExampleSuffix(mmbr) {
152                 // Check ExampleFoo_Method and ExampleFoo_BadMethod.
153                 found := false
154                 // Check if Foo.Method exists in this package or its imports.
155                 for _, obj := range objs {
156                         if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj != nil {
157                                 found = true
158                                 break
159                         }
160                 }
161                 if !found {
162                         pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
163                 }
164         }
165         if len(elems) == 3 && !isExampleSuffix(elems[2]) {
166                 // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix.
167                 pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, elems[2])
168         }
169 }
170
171 func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
172         // Want functions with 0 results and 1 parameter.
173         if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
174                 fn.Type.Params == nil ||
175                 len(fn.Type.Params.List) != 1 ||
176                 len(fn.Type.Params.List[0].Names) > 1 {
177                 return
178         }
179
180         // The param must look like a *testing.T or *testing.B.
181         if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) {
182                 return
183         }
184
185         if !isTestSuffix(fn.Name.Name[len(prefix):]) {
186                 pass.Reportf(fn.Pos(), "%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix)
187         }
188 }