diff options
Diffstat (limited to 'vendor/github.com/mgechev/revive/rule/defer.go')
-rw-r--r-- | vendor/github.com/mgechev/revive/rule/defer.go | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/vendor/github.com/mgechev/revive/rule/defer.go b/vendor/github.com/mgechev/revive/rule/defer.go new file mode 100644 index 0000000000..2ec7ef47c2 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/defer.go @@ -0,0 +1,137 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// DeferRule lints unused params in functions. +type DeferRule struct{} + +// Apply applies the rule to given file. +func (r *DeferRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + allow := r.allowFromArgs(arguments) + + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintDeferRule{onFailure: onFailure, allow: allow} + + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *DeferRule) Name() string { + return "defer" +} + +func (r *DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { + if len(args) < 1 { + allow := map[string]bool{ + "loop": true, + "call-chain": true, + "method-call": true, + "return": true, + "recover": true, + } + + return allow + } + + aa, ok := args[0].([]interface{}) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0])) + } + + allow := make(map[string]bool, len(aa)) + for _, subcase := range aa { + sc, ok := subcase.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase)) + } + allow[sc] = true + } + + return allow +} + +type lintDeferRule struct { + onFailure func(lint.Failure) + inALoop bool + inADefer bool + inAFuncLit bool + allow map[string]bool +} + +func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.ForStmt: + w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit) + return nil + case *ast.RangeStmt: + w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit) + return nil + case *ast.FuncLit: + w.visitSubtree(n.Body, w.inADefer, false, true) + return nil + case *ast.ReturnStmt: + if len(n.Results) != 0 && w.inADefer && w.inAFuncLit { + w.newFailure("return in a defer function has no effect", n, 1.0, "logic", "return") + } + case *ast.CallExpr: + if isIdent(n.Fun, "recover") && !w.inADefer { + // confidence is not 1 because recover can be in a function that is deferred elsewhere + w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover") + } + case *ast.DeferStmt: + w.visitSubtree(n.Call.Fun, true, false, false) + + if w.inALoop { + w.newFailure("prefer not to defer inside loops", n, 1.0, "bad practice", "loop") + } + + switch fn := n.Call.Fun.(type) { + case *ast.CallExpr: + w.newFailure("prefer not to defer chains of function calls", fn, 1.0, "bad practice", "call-chain") + case *ast.SelectorExpr: + if id, ok := fn.X.(*ast.Ident); ok { + isMethodCall := id != nil && id.Obj != nil && id.Obj.Kind == ast.Typ + if isMethodCall { + w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, "bad practice", "method-call") + } + } + } + return nil + } + + return w +} + +func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) { + nw := &lintDeferRule{ + onFailure: w.onFailure, + inADefer: inADefer, + inALoop: inALoop, + inAFuncLit: inAFuncLit, + allow: w.allow} + ast.Walk(nw, n) +} + +func (w lintDeferRule) newFailure(msg string, node ast.Node, confidence float64, cat string, subcase string) { + if !w.allow[subcase] { + return + } + + w.onFailure(lint.Failure{ + Confidence: confidence, + Node: node, + Category: cat, + Failure: msg, + }) +} |