github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/defer.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 7 "github.com/songshiyun/revive/lint" 8 ) 9 10 // DeferRule lints unused params in functions. 11 type DeferRule struct { 12 allow map[string]bool 13 } 14 15 // Apply applies the rule to given file. 16 func (r *DeferRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { 17 if r.allow == nil { 18 r.allow = r.allowFromArgs(arguments) 19 } 20 var failures []lint.Failure 21 onFailure := func(failure lint.Failure) { 22 failures = append(failures, failure) 23 } 24 25 w := lintDeferRule{onFailure: onFailure, allow: r.allow} 26 27 ast.Walk(w, file.AST) 28 29 return failures 30 } 31 32 // Name returns the rule name. 33 func (r *DeferRule) Name() string { 34 return "defer" 35 } 36 37 func (r *DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { 38 if len(args) < 1 { 39 allow := map[string]bool{ 40 "loop": true, 41 "call-chain": true, 42 "method-call": true, 43 "return": true, 44 "recover": true, 45 } 46 47 return allow 48 } 49 50 aa, ok := args[0].([]interface{}) 51 if !ok { 52 panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0])) 53 } 54 55 allow := make(map[string]bool, len(aa)) 56 for _, subcase := range aa { 57 sc, ok := subcase.(string) 58 if !ok { 59 panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase)) 60 } 61 allow[sc] = true 62 } 63 64 return allow 65 } 66 67 type lintDeferRule struct { 68 onFailure func(lint.Failure) 69 inALoop bool 70 inADefer bool 71 inAFuncLit bool 72 allow map[string]bool 73 } 74 75 func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { 76 switch n := node.(type) { 77 case *ast.ForStmt: 78 w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit) 79 return nil 80 case *ast.RangeStmt: 81 w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit) 82 return nil 83 case *ast.FuncLit: 84 w.visitSubtree(n.Body, w.inADefer, false, true) 85 return nil 86 case *ast.ReturnStmt: 87 if len(n.Results) != 0 && w.inADefer && w.inAFuncLit { 88 w.newFailure("return in a defer function has no effect", n, 1.0, "logic", "return") 89 } 90 case *ast.CallExpr: 91 if !w.inADefer && isIdent(n.Fun, "recover") { 92 // confidence is not 1 because recover can be in a function that is deferred elsewhere 93 w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover") 94 } 95 case *ast.DeferStmt: 96 w.visitSubtree(n.Call.Fun, true, false, false) 97 98 if w.inALoop { 99 w.newFailure("prefer not to defer inside loops", n, 1.0, "bad practice", "loop") 100 } 101 102 switch fn := n.Call.Fun.(type) { 103 case *ast.CallExpr: 104 w.newFailure("prefer not to defer chains of function calls", fn, 1.0, "bad practice", "call-chain") 105 case *ast.SelectorExpr: 106 if id, ok := fn.X.(*ast.Ident); ok { 107 isMethodCall := id != nil && id.Obj != nil && id.Obj.Kind == ast.Typ 108 if isMethodCall { 109 w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, "bad practice", "method-call") 110 } 111 } 112 } 113 return nil 114 } 115 116 return w 117 } 118 119 func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) { 120 nw := &lintDeferRule{ 121 onFailure: w.onFailure, 122 inADefer: inADefer, 123 inALoop: inALoop, 124 inAFuncLit: inAFuncLit, 125 allow: w.allow, 126 } 127 ast.Walk(nw, n) 128 } 129 130 func (w lintDeferRule) newFailure(msg string, node ast.Node, confidence float64, cat, subcase string) { 131 if !w.allow[subcase] { 132 return 133 } 134 135 w.onFailure(lint.Failure{ 136 Confidence: confidence, 137 Node: node, 138 Category: cat, 139 Failure: msg, 140 }) 141 }