github.com/peggyl/go@v0.0.0-20151008231540-ae315999c2d5/src/cmd/vet/example.go (about) 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 main 6 7 import ( 8 "go/ast" 9 "go/types" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 ) 14 15 func init() { 16 register("example", 17 "check for common mistaken usages of documentation examples", 18 checkExample, 19 funcDecl) 20 } 21 22 func isExampleSuffix(s string) bool { 23 r, size := utf8.DecodeRuneInString(s) 24 return size > 0 && unicode.IsLower(r) 25 } 26 27 // checkExample walks the documentation example functions checking for common 28 // mistakes of misnamed functions, failure to map functions to existing 29 // identifiers, etc. 30 func checkExample(f *File, node ast.Node) { 31 if !strings.HasSuffix(f.name, "_test.go") { 32 return 33 } 34 var ( 35 pkg = f.pkg 36 pkgName = pkg.typesPkg.Name() 37 scopes = []*types.Scope{pkg.typesPkg.Scope()} 38 lookup = func(name string) types.Object { 39 for _, scope := range scopes { 40 if o := scope.Lookup(name); o != nil { 41 return o 42 } 43 } 44 return nil 45 } 46 ) 47 if strings.HasSuffix(pkgName, "_test") { 48 // Treat 'package foo_test' as an alias for 'package foo'. 49 var ( 50 basePkg = strings.TrimSuffix(pkgName, "_test") 51 pkg = f.pkg 52 ) 53 for _, p := range pkg.typesPkg.Imports() { 54 if p.Name() == basePkg { 55 scopes = append(scopes, p.Scope()) 56 break 57 } 58 } 59 } 60 fn, ok := node.(*ast.FuncDecl) 61 if !ok { 62 // Ignore non-functions. 63 return 64 } 65 var ( 66 fnName = fn.Name.Name 67 report = func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } 68 ) 69 if fn.Recv != nil || !strings.HasPrefix(fnName, "Example") { 70 // Ignore methods and types not named "Example". 71 return 72 } 73 if params := fn.Type.Params; len(params.List) != 0 { 74 report("%s should be niladic", fnName) 75 } 76 if results := fn.Type.Results; results != nil && len(results.List) != 0 { 77 report("%s should return nothing", fnName) 78 } 79 if fnName == "Example" { 80 // Nothing more to do. 81 return 82 } 83 if filesRun && !includesNonTest { 84 // The coherence checks between a test and the package it tests 85 // will report false positives if no non-test files have 86 // been provided. 87 return 88 } 89 var ( 90 exName = strings.TrimPrefix(fnName, "Example") 91 elems = strings.SplitN(exName, "_", 3) 92 ident = elems[0] 93 obj = lookup(ident) 94 ) 95 if ident != "" && obj == nil { 96 // Check ExampleFoo and ExampleBadFoo. 97 report("%s refers to unknown identifier: %s", fnName, ident) 98 // Abort since obj is absent and no subsequent checks can be performed. 99 return 100 } 101 if elemCnt := strings.Count(exName, "_"); elemCnt == 0 { 102 // Nothing more to do. 103 return 104 } 105 mmbr := elems[1] 106 if ident == "" { 107 // Check Example_suffix and Example_BadSuffix. 108 if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { 109 report("%s has malformed example suffix: %s", fnName, residual) 110 } 111 return 112 } 113 if !isExampleSuffix(mmbr) { 114 // Check ExampleFoo_Method and ExampleFoo_BadMethod. 115 if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { 116 report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) 117 } 118 } 119 if len(elems) == 3 && !isExampleSuffix(elems[2]) { 120 // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix. 121 report("%s has malformed example suffix: %s", fnName, elems[2]) 122 } 123 return 124 }