miagilepner 7cd5685e2e
VAULT-13169 Require go docs for all new test functions (#18971)
* example for checking go doc tests

* add analyzer test and action

* get metadata step

* install revgrep

* fix for ci

* add revgrep to go.mod

* clarify how analysistest works
2023-02-07 10:41:04 +01:00

79 lines
1.6 KiB
Go

package analyzer
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
}