github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/modifies-value-receiver.go (about)

     1  package rule
     2  
     3  import (
     4  	"go/ast"
     5  	"strings"
     6  
     7  	"github.com/mgechev/revive/lint"
     8  )
     9  
    10  // ModifiesValRecRule lints assignments to value method-receivers.
    11  type ModifiesValRecRule struct{}
    12  
    13  // Apply applies the rule to given file.
    14  func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    15  	var failures []lint.Failure
    16  
    17  	onFailure := func(failure lint.Failure) {
    18  		failures = append(failures, failure)
    19  	}
    20  
    21  	w := lintModifiesValRecRule{file: file, onFailure: onFailure}
    22  	file.Pkg.TypeCheck()
    23  	ast.Walk(w, file.AST)
    24  
    25  	return failures
    26  }
    27  
    28  // Name returns the rule name.
    29  func (r *ModifiesValRecRule) Name() string {
    30  	return "modifies-value-receiver"
    31  }
    32  
    33  type lintModifiesValRecRule struct {
    34  	file      *lint.File
    35  	onFailure func(lint.Failure)
    36  }
    37  
    38  func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
    39  	switch n := node.(type) {
    40  	case *ast.FuncDecl:
    41  		if n.Recv == nil {
    42  			return nil // skip, not a method
    43  		}
    44  
    45  		receiver := n.Recv.List[0]
    46  		if _, ok := receiver.Type.(*ast.StarExpr); ok {
    47  			return nil // skip, method with pointer receiver
    48  		}
    49  
    50  		if w.skipType(receiver.Type) {
    51  			return nil // skip, receiver is a map or array
    52  		}
    53  
    54  		if len(receiver.Names) < 1 {
    55  			return nil // skip, anonymous receiver
    56  		}
    57  
    58  		receiverName := receiver.Names[0].Name
    59  		if receiverName == "_" {
    60  			return nil // skip, anonymous receiver
    61  		}
    62  
    63  		fselect := func(n ast.Node) bool {
    64  			// look for assignments with the receiver in the right hand
    65  			asgmt, ok := n.(*ast.AssignStmt)
    66  			if !ok {
    67  				return false
    68  			}
    69  
    70  			for _, exp := range asgmt.Lhs {
    71  				switch e := exp.(type) {
    72  				case *ast.IndexExpr: // receiver...[] = ...
    73  					continue
    74  				case *ast.StarExpr: // *receiver = ...
    75  					continue
    76  				case *ast.SelectorExpr: // receiver.field = ...
    77  					name := w.getNameFromExpr(e.X)
    78  					if name == "" || name != receiverName {
    79  						continue
    80  					}
    81  
    82  					if w.skipType(ast.Expr(e.Sel)) {
    83  						continue
    84  					}
    85  
    86  				case *ast.Ident: // receiver := ...
    87  					if e.Name != receiverName {
    88  						continue
    89  					}
    90  				default:
    91  					continue
    92  				}
    93  
    94  				return true
    95  			}
    96  
    97  			return false
    98  		}
    99  
   100  		assignmentsToReceiver := pick(n.Body, fselect, nil)
   101  
   102  		for _, assignment := range assignmentsToReceiver {
   103  			w.onFailure(lint.Failure{
   104  				Node:       assignment,
   105  				Confidence: 1,
   106  				Failure:    "suspicious assignment to a by-value method receiver",
   107  			})
   108  		}
   109  	}
   110  
   111  	return w
   112  }
   113  
   114  func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
   115  	rt := w.file.Pkg.TypeOf(t)
   116  	if rt == nil {
   117  		return false
   118  	}
   119  
   120  	rt = rt.Underlying()
   121  	rtName := rt.String()
   122  
   123  	// skip when receiver is a map or array
   124  	return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[")
   125  }
   126  
   127  func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
   128  	ident, ok := ie.(*ast.Ident)
   129  	if !ok {
   130  		return ""
   131  	}
   132  
   133  	return ident.Name
   134  }