This repository has been archived by the owner on Dec 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
appendCombine_checker.go
102 lines (87 loc) · 2.35 KB
/
appendCombine_checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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)
}