2018-11-07 09:11:08 +03:00

147 lines
3.9 KiB
Go

package checkers
import (
"go/ast"
"go/token"
"go/types"
"strings"
"github.com/go-critic/checkers/internal/lintutil"
"github.com/go-lintpack/lintpack"
"golang.org/x/tools/go/ast/astutil"
)
// isUnitTestFunc reports whether FuncDecl declares testing function.
func isUnitTestFunc(ctx *lintpack.CheckerContext, fn *ast.FuncDecl) bool {
if !strings.HasPrefix(fn.Name.Name, "Test") {
return false
}
typ := ctx.TypesInfo.TypeOf(fn.Name)
if sig, ok := typ.(*types.Signature); ok {
return sig.Results().Len() == 0 &&
sig.Params().Len() == 1 &&
sig.Params().At(0).Type().String() == "*testing.T"
}
return false
}
// qualifiedName returns called expr fully-quallified name.
//
// It works for simple identifiers like f => "f" and identifiers
// from other package like pkg.f => "pkg.f".
//
// For all unexpected expressions returns empty string.
func qualifiedName(x ast.Expr) string {
switch x := x.(type) {
case *ast.SelectorExpr:
pkg, ok := x.X.(*ast.Ident)
if !ok {
return ""
}
return pkg.Name + "." + x.Sel.Name
case *ast.Ident:
return x.Name
default:
return ""
}
}
// identOf returns identifier for x that can be used to obtain associated types.Object.
// Returns nil for expressions that yield temporary results, like `f().field`.
func identOf(x ast.Node) *ast.Ident {
switch x := x.(type) {
case *ast.Ident:
return x
case *ast.SelectorExpr:
return identOf(x.Sel)
case *ast.TypeAssertExpr:
// x.(type) - x may contain ident.
return identOf(x.X)
case *ast.IndexExpr:
// x[i] - x may contain ident.
return identOf(x.X)
case *ast.StarExpr:
// *x - x may contain ident.
return identOf(x.X)
case *ast.SliceExpr:
// x[:] - x may contain ident.
return identOf(x.X)
default:
// Note that this function is not comprehensive.
return nil
}
}
// findNode applies pred for root and all it's childs until it returns true.
// Matched node is returned.
// If none of the nodes matched predicate, nil is returned.
func findNode(root ast.Node, pred func(ast.Node) bool) ast.Node {
var found ast.Node
astutil.Apply(root, nil, func(cur *astutil.Cursor) bool {
if pred(cur.Node()) {
found = cur.Node()
return false
}
return true
})
return found
}
// containsNode reports whether `findNode(root, pred)!=nil`.
func containsNode(root ast.Node, pred func(ast.Node) bool) bool {
return findNode(root, pred) != nil
}
// isSafeExpr reports whether expr is softly safe expression and contains
// no significant side-effects. As opposed to strictly safe expressions,
// soft safe expressions permit some forms of side-effects, like
// panic possibility during indexing or nil pointer dereference.
//
// Uses types info to determine type conversion expressions that
// are the only permitted kinds of call expressions.
func isSafeExpr(info *types.Info, expr ast.Expr) bool {
// This list switch is not comprehensive and uses
// whitelist to be on the conservative side.
// Can be extended as needed.
//
// Note that it is not very strict "safe" as
// index expressions are permitted even though they
// may cause panics.
switch expr := expr.(type) {
case *ast.StarExpr:
return isSafeExpr(info, expr.X)
case *ast.BinaryExpr:
return isSafeExpr(info, expr.X) && isSafeExpr(info, expr.Y)
case *ast.UnaryExpr:
return expr.Op != token.ARROW && isSafeExpr(info, expr.X)
case *ast.BasicLit, *ast.Ident:
return true
case *ast.IndexExpr:
return isSafeExpr(info, expr.X) && isSafeExpr(info, expr.Index)
case *ast.SelectorExpr:
return isSafeExpr(info, expr.X)
case *ast.ParenExpr:
return isSafeExpr(info, expr.X)
case *ast.CompositeLit:
return isSafeExprList(info, expr.Elts)
case *ast.CallExpr:
return lintutil.IsTypeExpr(info, expr.Fun) &&
isSafeExprList(info, expr.Args)
default:
return false
}
}
// isSafeExprList reports whether every expr in list is safe.
// See isSafeExpr.
func isSafeExprList(info *types.Info, list []ast.Expr) bool {
for _, expr := range list {
if !isSafeExpr(info, expr) {
return false
}
}
return true
}