| // 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 main |
| |
| import ( |
| "go/ast" |
| "go/types" |
| "strings" |
| "unicode" |
| "unicode/utf8" |
| ) |
| |
| func init() { |
| register("example", |
| "check for common mistaken usages of documentation examples", |
| checkExample, |
| funcDecl) |
| } |
| |
| func isExampleSuffix(s string) bool { |
| r, size := utf8.DecodeRuneInString(s) |
| return size > 0 && unicode.IsLower(r) |
| } |
| |
| // checkExample walks the documentation example functions checking for common |
| // mistakes of misnamed functions, failure to map functions to existing |
| // identifiers, etc. |
| func checkExample(f *File, node ast.Node) { |
| if !strings.HasSuffix(f.name, "_test.go") { |
| return |
| } |
| var ( |
| pkg = f.pkg |
| pkgName = pkg.typesPkg.Name() |
| scopes = []*types.Scope{pkg.typesPkg.Scope()} |
| lookup = func(name string) types.Object { |
| for _, scope := range scopes { |
| if o := scope.Lookup(name); o != nil { |
| return o |
| } |
| } |
| return nil |
| } |
| ) |
| if strings.HasSuffix(pkgName, "_test") { |
| // Treat 'package foo_test' as an alias for 'package foo'. |
| var ( |
| basePkg = strings.TrimSuffix(pkgName, "_test") |
| pkg = f.pkg |
| ) |
| for _, p := range pkg.typesPkg.Imports() { |
| if p.Name() == basePkg { |
| scopes = append(scopes, p.Scope()) |
| break |
| } |
| } |
| } |
| fn, ok := node.(*ast.FuncDecl) |
| if !ok { |
| // Ignore non-functions. |
| return |
| } |
| var ( |
| fnName = fn.Name.Name |
| report = func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } |
| ) |
| if fn.Recv != nil || !strings.HasPrefix(fnName, "Example") { |
| // Ignore methods and types not named "Example". |
| return |
| } |
| if params := fn.Type.Params; len(params.List) != 0 { |
| report("%s should be niladic", fnName) |
| } |
| if results := fn.Type.Results; results != nil && len(results.List) != 0 { |
| report("%s should return nothing", fnName) |
| } |
| if fnName == "Example" { |
| // Nothing more to do. |
| return |
| } |
| if filesRun && !includesNonTest { |
| // The coherence checks between a test and the package it tests |
| // will report false positives if no non-test files have |
| // been provided. |
| return |
| } |
| var ( |
| exName = strings.TrimPrefix(fnName, "Example") |
| elems = strings.SplitN(exName, "_", 3) |
| ident = elems[0] |
| obj = lookup(ident) |
| ) |
| if ident != "" && obj == nil { |
| // Check ExampleFoo and ExampleBadFoo. |
| report("%s refers to unknown identifier: %s", fnName, ident) |
| // Abort since obj is absent and no subsequent checks can be performed. |
| return |
| } |
| if elemCnt := strings.Count(exName, "_"); elemCnt == 0 { |
| // Nothing more to do. |
| return |
| } |
| mmbr := elems[1] |
| if ident == "" { |
| // Check Example_suffix and Example_BadSuffix. |
| if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { |
| report("%s has malformed example suffix: %s", fnName, residual) |
| } |
| return |
| } |
| if !isExampleSuffix(mmbr) { |
| // Check ExampleFoo_Method and ExampleFoo_BadMethod. |
| if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { |
| report("%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. |
| report("%s has malformed example suffix: %s", fnName, elems[2]) |
| } |
| return |
| } |