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  }