github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/range-val-in-closure.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/mgechev/revive/lint"
     8  )
     9  
    10  // RangeValInClosureRule lints given else constructs.
    11  type RangeValInClosureRule struct{}
    12  
    13  // Apply applies the rule to given file.
    14  func (r *RangeValInClosureRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    15  	var failures []lint.Failure
    16  
    17  	walker := rangeValInClosure{
    18  		onFailure: func(failure lint.Failure) {
    19  			failures = append(failures, failure)
    20  		},
    21  	}
    22  
    23  	ast.Walk(walker, file.AST)
    24  
    25  	return failures
    26  }
    27  
    28  // Name returns the rule name.
    29  func (r *RangeValInClosureRule) Name() string {
    30  	return "range-val-in-closure"
    31  }
    32  
    33  type rangeValInClosure struct {
    34  	onFailure func(lint.Failure)
    35  }
    36  
    37  func (w rangeValInClosure) Visit(node ast.Node) ast.Visitor {
    38  
    39  	// Find the variables updated by the loop statement.
    40  	var vars []*ast.Ident
    41  	addVar := func(expr ast.Expr) {
    42  		if id, ok := expr.(*ast.Ident); ok {
    43  			vars = append(vars, id)
    44  		}
    45  	}
    46  	var body *ast.BlockStmt
    47  	switch n := node.(type) {
    48  	case *ast.RangeStmt:
    49  		body = n.Body
    50  		addVar(n.Key)
    51  		addVar(n.Value)
    52  	case *ast.ForStmt:
    53  		body = n.Body
    54  		switch post := n.Post.(type) {
    55  		case *ast.AssignStmt:
    56  			// e.g. for p = head; p != nil; p = p.next
    57  			for _, lhs := range post.Lhs {
    58  				addVar(lhs)
    59  			}
    60  		case *ast.IncDecStmt:
    61  			// e.g. for i := 0; i < n; i++
    62  			addVar(post.X)
    63  		}
    64  	}
    65  	if vars == nil {
    66  		return w
    67  	}
    68  
    69  	// Inspect a go or defer statement
    70  	// if it's the last one in the loop body.
    71  	// (We give up if there are following statements,
    72  	// because it's hard to prove go isn't followed by wait,
    73  	// or defer by return.)
    74  	if len(body.List) == 0 {
    75  		return w
    76  	}
    77  	var last *ast.CallExpr
    78  	switch s := body.List[len(body.List)-1].(type) {
    79  	case *ast.GoStmt:
    80  		last = s.Call
    81  	case *ast.DeferStmt:
    82  		last = s.Call
    83  	default:
    84  		return w
    85  	}
    86  	lit, ok := last.Fun.(*ast.FuncLit)
    87  	if !ok {
    88  		return w
    89  	}
    90  	if lit.Type == nil {
    91  		// Not referring to a variable (e.g. struct field name)
    92  		return w
    93  	}
    94  	ast.Inspect(lit.Body, func(n ast.Node) bool {
    95  		id, ok := n.(*ast.Ident)
    96  		if !ok || id.Obj == nil {
    97  			return true
    98  		}
    99  		for _, v := range vars {
   100  			if v.Obj == id.Obj {
   101  				w.onFailure(lint.Failure{
   102  					Confidence: 1,
   103  					Failure:    fmt.Sprintf("loop variable %v captured by func literal", id.Name),
   104  					Node:       n,
   105  				})
   106  			}
   107  		}
   108  		return true
   109  	})
   110  	return w
   111  }