| // 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 | |
| } |