woodpecker/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go
2021-11-16 21:07:53 +01:00

148 lines
3 KiB
Go

package analyzer
import (
"go/ast"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
const (
name = "nilnil"
doc = "Checks that there is no simultaneous return of `nil` error and an invalid value."
reportMsg = "return both the `nil` error and invalid value: use a sentinel error instead"
)
// New returns new nilnil analyzer.
func New() *analysis.Analyzer {
n := newNilNil()
a := &analysis.Analyzer{
Name: name,
Doc: doc,
Run: n.run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
a.Flags.Var(&n.checkedTypes, "checked-types", "coma separated list")
return a
}
type nilNil struct {
checkedTypes checkedTypes
}
func newNilNil() *nilNil {
return &nilNil{
checkedTypes: newDefaultCheckedTypes(),
}
}
var (
types = []ast.Node{(*ast.TypeSpec)(nil)}
funcAndReturns = []ast.Node{
(*ast.FuncDecl)(nil),
(*ast.FuncLit)(nil),
(*ast.ReturnStmt)(nil),
}
)
type typeSpecByName map[string]*ast.TypeSpec
func (n *nilNil) run(pass *analysis.Pass) (interface{}, error) {
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
typeSpecs := typeSpecByName{}
insp.Preorder(types, func(node ast.Node) {
t := node.(*ast.TypeSpec)
typeSpecs[t.Name.Name] = t
})
var fs funcTypeStack
insp.Nodes(funcAndReturns, func(node ast.Node, push bool) (proceed bool) {
switch v := node.(type) {
case *ast.FuncLit:
if push {
fs.Push(v.Type)
} else {
fs.Pop()
}
case *ast.FuncDecl:
if push {
fs.Push(v.Type)
} else {
fs.Pop()
}
case *ast.ReturnStmt:
ft := fs.Top() // Current function.
if !push || len(v.Results) != 2 || ft == nil || ft.Results == nil || len(ft.Results.List) != 2 {
return false
}
fRes1, fRes2 := ft.Results.List[0], ft.Results.List[1]
if !(n.isDangerNilField(fRes1, typeSpecs) && n.isErrorField(fRes2)) {
return
}
rRes1, rRes2 := v.Results[0], v.Results[1]
if isNil(rRes1) && isNil(rRes2) {
pass.Reportf(v.Pos(), reportMsg)
}
}
return true
})
return nil, nil
}
func (n *nilNil) isDangerNilField(f *ast.Field, typeSpecs typeSpecByName) bool {
return n.isDangerNilType(f.Type, typeSpecs)
}
func (n *nilNil) isDangerNilType(t ast.Expr, typeSpecs typeSpecByName) bool {
switch v := t.(type) {
case *ast.StarExpr:
return n.checkedTypes.Contains(ptrType)
case *ast.FuncType:
return n.checkedTypes.Contains(funcType)
case *ast.InterfaceType:
return n.checkedTypes.Contains(ifaceType)
case *ast.MapType:
return n.checkedTypes.Contains(mapType)
case *ast.ChanType:
return n.checkedTypes.Contains(chanType)
case *ast.Ident:
if t, ok := typeSpecs[v.Name]; ok {
return n.isDangerNilType(t.Type, nil)
}
}
return false
}
func (n *nilNil) isErrorField(f *ast.Field) bool {
return isIdent(f.Type, "error")
}
func isNil(e ast.Expr) bool {
return isIdent(e, "nil")
}
func isIdent(n ast.Node, name string) bool {
i, ok := n.(*ast.Ident)
if !ok {
return false
}
return i.Name == name
}