1
0
mirror of https://github.com/mgechev/revive.git synced 2024-11-24 08:32:22 +02:00
revive/rule/defer.go

150 lines
3.5 KiB
Go
Raw Normal View History

2020-05-24 20:49:49 +02:00
package rule
import (
"fmt"
"go/ast"
2022-04-10 09:06:59 +02:00
"sync"
2020-05-24 20:49:49 +02:00
"github.com/mgechev/revive/lint"
)
// DeferRule lints unused params in functions.
2021-10-17 20:34:48 +02:00
type DeferRule struct {
allow map[string]bool
2022-04-10 09:06:59 +02:00
sync.Mutex
2021-10-17 20:34:48 +02:00
}
2020-05-24 20:49:49 +02:00
2022-04-10 09:06:59 +02:00
func (r *DeferRule) configure(arguments lint.Arguments) {
r.Lock()
2021-10-17 20:34:48 +02:00
if r.allow == nil {
r.allow = r.allowFromArgs(arguments)
}
2022-04-10 09:06:59 +02:00
r.Unlock()
}
// Apply applies the rule to given file.
func (r *DeferRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
r.configure(arguments)
2020-05-24 20:49:49 +02:00
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
2021-10-17 20:34:48 +02:00
w := lintDeferRule{onFailure: onFailure, allow: r.allow}
2020-05-24 20:49:49 +02:00
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
2022-04-10 11:55:13 +02:00
func (*DeferRule) Name() string {
2020-05-24 20:49:49 +02:00
return "defer"
}
2022-04-10 11:55:13 +02:00
func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool {
2020-05-24 20:49:49 +02:00
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:
2021-10-23 13:25:41 +02:00
if !w.inADefer && isIdent(n.Fun, "recover") {
2020-05-24 20:49:49 +02:00
// 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,
}
2020-05-24 20:49:49 +02:00
ast.Walk(nw, n)
}
func (w lintDeferRule) newFailure(msg string, node ast.Node, confidence float64, cat, subcase string) {
2020-05-24 20:49:49 +02:00
if !w.allow[subcase] {
return
}
w.onFailure(lint.Failure{
Confidence: confidence,
Node: node,
Category: cat,
Failure: msg,
})
}