+++ /dev/null
-// Copyright 2015 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 tests defines an Analyzer that checks for common mistaken
-// usages of tests and examples.
-package tests
-
-import (
- "go/ast"
- "go/types"
- "strings"
- "unicode"
- "unicode/utf8"
-
- "golang.org/x/tools/go/analysis"
-)
-
-const Doc = `check for common mistaken usages of tests and examples
-
-The tests checker walks Test, Benchmark and Example functions checking
-malformed names, wrong signatures and examples documenting non-existent
-identifiers.
-
-Please see the documentation for package testing in golang.org/pkg/testing
-for the conventions that are enforced for Tests, Benchmarks, and Examples.`
-
-var Analyzer = &analysis.Analyzer{
- Name: "tests",
- Doc: Doc,
- Run: run,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- for _, f := range pass.Files {
- if !strings.HasSuffix(pass.Fset.File(f.Pos()).Name(), "_test.go") {
- continue
- }
- for _, decl := range f.Decls {
- fn, ok := decl.(*ast.FuncDecl)
- if !ok || fn.Recv != nil {
- // Ignore non-functions or functions with receivers.
- continue
- }
-
- switch {
- case strings.HasPrefix(fn.Name.Name, "Example"):
- checkExample(pass, fn)
- case strings.HasPrefix(fn.Name.Name, "Test"):
- checkTest(pass, fn, "Test")
- case strings.HasPrefix(fn.Name.Name, "Benchmark"):
- checkTest(pass, fn, "Benchmark")
- }
- }
- }
- return nil, nil
-}
-
-func isExampleSuffix(s string) bool {
- r, size := utf8.DecodeRuneInString(s)
- return size > 0 && unicode.IsLower(r)
-}
-
-func isTestSuffix(name string) bool {
- if len(name) == 0 {
- // "Test" is ok.
- return true
- }
- r, _ := utf8.DecodeRuneInString(name)
- return !unicode.IsLower(r)
-}
-
-func isTestParam(typ ast.Expr, wantType string) bool {
- ptr, ok := typ.(*ast.StarExpr)
- if !ok {
- // Not a pointer.
- return false
- }
- // No easy way of making sure it's a *testing.T or *testing.B:
- // ensure the name of the type matches.
- if name, ok := ptr.X.(*ast.Ident); ok {
- return name.Name == wantType
- }
- if sel, ok := ptr.X.(*ast.SelectorExpr); ok {
- return sel.Sel.Name == wantType
- }
- return false
-}
-
-func lookup(pkg *types.Package, name string) []types.Object {
- if o := pkg.Scope().Lookup(name); o != nil {
- return []types.Object{o}
- }
-
- var ret []types.Object
- // Search through the imports to see if any of them define name.
- // It's hard to tell in general which package is being tested, so
- // for the purposes of the analysis, allow the object to appear
- // in any of the imports. This guarantees there are no false positives
- // because the example needs to use the object so it must be defined
- // in the package or one if its imports. On the other hand, false
- // negatives are possible, but should be rare.
- for _, imp := range pkg.Imports() {
- if obj := imp.Scope().Lookup(name); obj != nil {
- ret = append(ret, obj)
- }
- }
- return ret
-}
-
-func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
- fnName := fn.Name.Name
- if params := fn.Type.Params; len(params.List) != 0 {
- pass.Reportf(fn.Pos(), "%s should be niladic", fnName)
- }
- if results := fn.Type.Results; results != nil && len(results.List) != 0 {
- pass.Reportf(fn.Pos(), "%s should return nothing", fnName)
- }
-
- if fnName == "Example" {
- // Nothing more to do.
- return
- }
-
- var (
- exName = strings.TrimPrefix(fnName, "Example")
- elems = strings.SplitN(exName, "_", 3)
- ident = elems[0]
- objs = lookup(pass.Pkg, ident)
- )
- if ident != "" && len(objs) == 0 {
- // Check ExampleFoo and ExampleBadFoo.
- pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident)
- // Abort since obj is absent and no subsequent checks can be performed.
- return
- }
- if len(elems) < 2 {
- // Nothing more to do.
- return
- }
-
- if ident == "" {
- // Check Example_suffix and Example_BadSuffix.
- if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) {
- pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, residual)
- }
- return
- }
-
- mmbr := elems[1]
- if !isExampleSuffix(mmbr) {
- // Check ExampleFoo_Method and ExampleFoo_BadMethod.
- found := false
- // Check if Foo.Method exists in this package or its imports.
- for _, obj := range objs {
- if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj != nil {
- found = true
- break
- }
- }
- if !found {
- pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
- }
- }
- if len(elems) == 3 && !isExampleSuffix(elems[2]) {
- // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix.
- pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, elems[2])
- }
-}
-
-func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
- // Want functions with 0 results and 1 parameter.
- if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
- fn.Type.Params == nil ||
- len(fn.Type.Params.List) != 1 ||
- len(fn.Type.Params.List[0].Names) > 1 {
- return
- }
-
- // The param must look like a *testing.T or *testing.B.
- if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) {
- return
- }
-
- if !isTestSuffix(fn.Name.Name[len(prefix):]) {
- pass.Reportf(fn.Pos(), "%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix)
- }
-}