103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
package checkers
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
|
|
"github.com/go-critic/checkers/internal/lintutil"
|
|
"github.com/go-lintpack/lintpack"
|
|
"github.com/go-lintpack/lintpack/astwalk"
|
|
"github.com/go-toolsmith/astequal"
|
|
)
|
|
|
|
func init() {
|
|
var info lintpack.CheckerInfo
|
|
info.Name = "appendCombine"
|
|
info.Tags = []string{"performance"}
|
|
info.Summary = "Detects `append` chains to the same slice that can be done in a single `append` call"
|
|
info.Before = `
|
|
xs = append(xs, 1)
|
|
xs = append(xs, 2)`
|
|
info.After = `xs = append(xs, 1, 2)`
|
|
|
|
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
|
return astwalk.WalkerForStmtList(&appendCombineChecker{ctx: ctx})
|
|
})
|
|
}
|
|
|
|
type appendCombineChecker struct {
|
|
astwalk.WalkHandler
|
|
ctx *lintpack.CheckerContext
|
|
}
|
|
|
|
func (c *appendCombineChecker) VisitStmtList(list []ast.Stmt) {
|
|
var cause ast.Node // First append
|
|
var slice ast.Expr // Slice being appended to
|
|
chain := 0 // How much appends in a row we've seen
|
|
|
|
// Break the chain.
|
|
// If enough appends are in chain, print warning.
|
|
flush := func() {
|
|
if chain > 1 {
|
|
c.warn(cause, chain)
|
|
}
|
|
chain = 0
|
|
slice = nil
|
|
}
|
|
|
|
for _, stmt := range list {
|
|
call := c.matchAppend(stmt, slice)
|
|
if call == nil {
|
|
flush()
|
|
continue
|
|
}
|
|
|
|
if chain == 0 {
|
|
// First append in a chain.
|
|
chain = 1
|
|
slice = call.Args[0]
|
|
cause = stmt
|
|
} else {
|
|
chain++
|
|
}
|
|
}
|
|
|
|
// Required for printing chains that consist of trailing
|
|
// statements from the list.
|
|
flush()
|
|
}
|
|
|
|
func (c *appendCombineChecker) matchAppend(stmt ast.Stmt, slice ast.Expr) *ast.CallExpr {
|
|
// Seeking for:
|
|
// slice = append(slice, xs...)
|
|
// xs are 0-N append arguments, but not variadic argument,
|
|
// because it makes append combining impossible.
|
|
|
|
assign := lintutil.AsAssignStmt(stmt)
|
|
if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
|
|
return nil
|
|
}
|
|
|
|
call, ok := assign.Rhs[0].(*ast.CallExpr)
|
|
{
|
|
cond := ok &&
|
|
qualifiedName(call.Fun) == "append" &&
|
|
call.Ellipsis == token.NoPos &&
|
|
astequal.Expr(assign.Lhs[0], call.Args[0])
|
|
if !cond {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Check that current append slice match previous append slice.
|
|
// Otherwise we should break the chain.
|
|
if slice == nil || astequal.Expr(slice, call.Args[0]) {
|
|
return call
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *appendCombineChecker) warn(cause ast.Node, chain int) {
|
|
c.ctx.Warn(cause, "can combine chain of %d appends into one", chain)
|
|
}
|