blob: 797c3ceec83593edf7447dfbe7a4c7b9f7511f90 [file] [log] [blame]
// 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
}