/
unnecessaryDefer_checker.go
112 lines (97 loc) · 2.79 KB
/
unnecessaryDefer_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
103
104
105
106
107
108
109
110
111
112
package checkers
import (
"go/ast"
"github.com/go-critic/go-critic/checkers/internal/astwalk"
"github.com/go-critic/go-critic/linter"
"github.com/go-toolsmith/astfmt"
)
func init() {
var info linter.CheckerInfo
info.Name = "unnecessaryDefer"
info.Tags = []string{linter.DiagnosticTag, linter.ExperimentalTag}
info.Summary = "Detects redundantly deferred calls"
info.Before = `
func() {
defer os.Remove(filename)
}`
info.After = `
func() {
os.Remove(filename)
}`
collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
return astwalk.WalkerForFuncDecl(&unnecessaryDeferChecker{ctx: ctx}), nil
})
}
type unnecessaryDeferChecker struct {
astwalk.WalkHandler
ctx *linter.CheckerContext
isFunc bool
}
// Visit implements the ast.Visitor. This visitor keeps track of the block
// statement belongs to a function or any other block. If the block is not a
// function and ends with a defer statement that should be OK since it's
// deferring the outer function.
func (c *unnecessaryDeferChecker) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl, *ast.FuncLit:
c.isFunc = true
case *ast.BlockStmt:
c.checkDeferBeforeReturn(n)
default:
c.isFunc = false
}
return c
}
func (c *unnecessaryDeferChecker) VisitFuncDecl(funcDecl *ast.FuncDecl) {
// We always start as a function (*ast.FuncDecl.Body passed)
c.isFunc = true
ast.Walk(c, funcDecl.Body)
}
func (c *unnecessaryDeferChecker) checkDeferBeforeReturn(funcDecl *ast.BlockStmt) {
// Check if we have an explicit return or if it's just the end of the scope.
explicitReturn := false
retIndex := len(funcDecl.List)
for i, stmt := range funcDecl.List {
retStmt, ok := stmt.(*ast.ReturnStmt)
if !ok {
continue
}
explicitReturn = true
if !c.isTrivialReturn(retStmt) {
continue
}
retIndex = i
break
}
if retIndex == 0 {
return
}
if deferStmt, ok := funcDecl.List[retIndex-1].(*ast.DeferStmt); ok {
// If the block is a function and ending with return or if we have an
// explicit return in any other block we should warn about
// unnecessary defer.
if c.isFunc || explicitReturn {
c.warn(deferStmt)
}
}
}
func (c *unnecessaryDeferChecker) isTrivialReturn(ret *ast.ReturnStmt) bool {
for _, e := range ret.Results {
if !c.isConstExpr(e) {
return false
}
}
return true
}
func (c *unnecessaryDeferChecker) isConstExpr(e ast.Expr) bool {
return c.ctx.TypesInfo.Types[e].Value != nil
}
func (c *unnecessaryDeferChecker) warn(deferStmt *ast.DeferStmt) {
s := astfmt.Sprint(deferStmt)
if fnlit, ok := deferStmt.Call.Fun.(*ast.FuncLit); ok {
// To avoid long and multi-line warning messages,
// collapse the function literals.
s = "defer " + astfmt.Sprint(fnlit.Type) + "{...}(...)"
}
c.ctx.Warn(deferStmt, "%s is placed just before return", s)
}