blob: e771f9c8f94cb08106d906f32629b365934f7df1 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package godoctests
import (
"go/ast"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var Analyzer = &analysis.Analyzer{
Name: "godoctests",
Doc: "Verifies that every go test has a go doc",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
func run(pass *analysis.Pass) (interface{}, error) {
inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.FuncDecl)(nil),
}
inspector.Preorder(nodeFilter, func(node ast.Node) {
funcDecl, ok := node.(*ast.FuncDecl)
if !ok {
return
}
// starts with 'Test'
if !strings.HasPrefix(funcDecl.Name.Name, "Test") {
return
}
// has one parameter
params := funcDecl.Type.Params.List
if len(params) != 1 {
return
}
// parameter is a pointer
firstParamType, ok := params[0].Type.(*ast.StarExpr)
if !ok {
return
}
selector, ok := firstParamType.X.(*ast.SelectorExpr)
if !ok {
return
}
// the pointer comes from package 'testing'
selectorIdent, ok := selector.X.(*ast.Ident)
if !ok {
return
}
if selectorIdent.Name != "testing" {
return
}
// the pointer has type 'T'
if selector.Sel == nil || selector.Sel.Name != "T" {
return
}
// then there must be a godoc
if funcDecl.Doc == nil {
pass.Reportf(node.Pos(), "Test %s is missing a go doc",
funcDecl.Name.Name)
} else if !strings.HasPrefix(funcDecl.Doc.Text(), funcDecl.Name.Name) {
pass.Reportf(node.Pos(), "Test %s must have a go doc beginning with the function name",
funcDecl.Name.Name)
}
})
return nil, nil
}