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 }