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