103 lines
2.5 KiB
Go
103 lines
2.5 KiB
Go
package checkers
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"github.com/go-lintpack/lintpack"
|
|
"github.com/go-lintpack/lintpack/astwalk"
|
|
"github.com/go-toolsmith/astequal"
|
|
"github.com/go-toolsmith/typep"
|
|
)
|
|
|
|
func init() {
|
|
var info lintpack.CheckerInfo
|
|
info.Name = "dupSubExpr"
|
|
info.Tags = []string{"diagnostic"}
|
|
info.Summary = "Detects suspicious duplicated sub-expressions"
|
|
info.Before = `
|
|
sort.Slice(xs, func(i, j int) bool {
|
|
return xs[i].v < xs[i].v // Duplicated index
|
|
})`
|
|
info.After = `
|
|
sort.Slice(xs, func(i, j int) bool {
|
|
return xs[i].v < xs[j].v
|
|
})`
|
|
|
|
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
|
c := &dupSubExprChecker{ctx: ctx}
|
|
|
|
ops := []struct {
|
|
op token.Token
|
|
float bool // Whether float args require special care
|
|
}{
|
|
{op: token.LOR}, // x || x
|
|
{op: token.LAND}, // x && x
|
|
{op: token.OR}, // x | x
|
|
{op: token.AND}, // x & x
|
|
{op: token.XOR}, // x ^ x
|
|
{op: token.LSS}, // x < x
|
|
{op: token.GTR}, // x > x
|
|
{op: token.AND_NOT}, // x &^ x
|
|
{op: token.REM}, // x % x
|
|
|
|
{op: token.EQL, float: true}, // x == x
|
|
{op: token.NEQ, float: true}, // x != x
|
|
{op: token.LEQ, float: true}, // x <= x
|
|
{op: token.GEQ, float: true}, // x >= x
|
|
{op: token.QUO, float: true}, // x / x
|
|
{op: token.SUB, float: true}, // x - x
|
|
}
|
|
|
|
c.opSet = make(map[token.Token]bool)
|
|
c.floatOpsSet = make(map[token.Token]bool)
|
|
for _, opInfo := range ops {
|
|
c.opSet[opInfo.op] = true
|
|
if opInfo.float {
|
|
c.floatOpsSet[opInfo.op] = true
|
|
}
|
|
}
|
|
|
|
return astwalk.WalkerForExpr(c)
|
|
})
|
|
}
|
|
|
|
type dupSubExprChecker struct {
|
|
astwalk.WalkHandler
|
|
ctx *lintpack.CheckerContext
|
|
|
|
// opSet is a set of binary operations that do not make
|
|
// sense with duplicated (same) RHS and LHS.
|
|
opSet map[token.Token]bool
|
|
|
|
floatOpsSet map[token.Token]bool
|
|
}
|
|
|
|
func (c *dupSubExprChecker) VisitExpr(expr ast.Expr) {
|
|
if expr, ok := expr.(*ast.BinaryExpr); ok {
|
|
c.checkBinaryExpr(expr)
|
|
}
|
|
}
|
|
|
|
func (c *dupSubExprChecker) checkBinaryExpr(expr *ast.BinaryExpr) {
|
|
if !c.opSet[expr.Op] {
|
|
return
|
|
}
|
|
if c.resultIsFloat(expr.X) && c.floatOpsSet[expr.Op] {
|
|
return
|
|
}
|
|
if typep.SideEffectFree(c.ctx.TypesInfo, expr) && c.opSet[expr.Op] && astequal.Expr(expr.X, expr.Y) {
|
|
c.warn(expr)
|
|
}
|
|
}
|
|
|
|
func (c *dupSubExprChecker) resultIsFloat(expr ast.Expr) bool {
|
|
typ, ok := c.ctx.TypesInfo.TypeOf(expr).(*types.Basic)
|
|
return ok && typ.Info()&types.IsFloat != 0
|
|
}
|
|
|
|
func (c *dupSubExprChecker) warn(cause *ast.BinaryExpr) {
|
|
c.ctx.Warn(cause, "suspicious identical LHS and RHS for `%s` operator", cause.Op)
|
|
}
|