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.
|
|
|
|
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:
|
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,
|
2022-03-02 09:24:55 +02:00
|
|
|
allow: w.allow,
|
|
|
|
}
|
2020-05-24 20:49:49 +02:00
|
|
|
ast.Walk(nw, n)
|
|
|
|
}
|
|
|
|
|
2022-03-02 09:24:55 +02:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|