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  }