Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
2.7 KiB
134 lines
2.7 KiB
package rule |
|
|
|
import ( |
|
"go/ast" |
|
"strings" |
|
|
|
"github.com/mgechev/revive/lint" |
|
) |
|
|
|
// ModifiesValRecRule lints assignments to value method-receivers. |
|
type ModifiesValRecRule struct{} |
|
|
|
// Apply applies the rule to given file. |
|
func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { |
|
var failures []lint.Failure |
|
|
|
onFailure := func(failure lint.Failure) { |
|
failures = append(failures, failure) |
|
} |
|
|
|
w := lintModifiesValRecRule{file: file, onFailure: onFailure} |
|
file.Pkg.TypeCheck() |
|
ast.Walk(w, file.AST) |
|
|
|
return failures |
|
} |
|
|
|
// Name returns the rule name. |
|
func (r *ModifiesValRecRule) Name() string { |
|
return "modifies-value-receiver" |
|
} |
|
|
|
type lintModifiesValRecRule struct { |
|
file *lint.File |
|
onFailure func(lint.Failure) |
|
} |
|
|
|
func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor { |
|
switch n := node.(type) { |
|
case *ast.FuncDecl: |
|
if n.Recv == nil { |
|
return nil // skip, not a method |
|
} |
|
|
|
receiver := n.Recv.List[0] |
|
if _, ok := receiver.Type.(*ast.StarExpr); ok { |
|
return nil // skip, method with pointer receiver |
|
} |
|
|
|
if w.skipType(receiver.Type) { |
|
return nil // skip, receiver is a map or array |
|
} |
|
|
|
if len(receiver.Names) < 1 { |
|
return nil // skip, anonymous receiver |
|
} |
|
|
|
receiverName := receiver.Names[0].Name |
|
if receiverName == "_" { |
|
return nil // skip, anonymous receiver |
|
} |
|
|
|
fselect := func(n ast.Node) bool { |
|
// look for assignments with the receiver in the right hand |
|
asgmt, ok := n.(*ast.AssignStmt) |
|
if !ok { |
|
return false |
|
} |
|
|
|
for _, exp := range asgmt.Lhs { |
|
switch e := exp.(type) { |
|
case *ast.IndexExpr: // receiver...[] = ... |
|
continue |
|
case *ast.StarExpr: // *receiver = ... |
|
continue |
|
case *ast.SelectorExpr: // receiver.field = ... |
|
name := w.getNameFromExpr(e.X) |
|
if name == "" || name != receiverName { |
|
continue |
|
} |
|
|
|
if w.skipType(ast.Expr(e.Sel)) { |
|
continue |
|
} |
|
|
|
case *ast.Ident: // receiver := ... |
|
if e.Name != receiverName { |
|
continue |
|
} |
|
default: |
|
continue |
|
} |
|
|
|
return true |
|
} |
|
|
|
return false |
|
} |
|
|
|
assignmentsToReceiver := pick(n.Body, fselect, nil) |
|
|
|
for _, assignment := range assignmentsToReceiver { |
|
w.onFailure(lint.Failure{ |
|
Node: assignment, |
|
Confidence: 1, |
|
Failure: "suspicious assignment to a by-value method receiver", |
|
}) |
|
} |
|
} |
|
|
|
return w |
|
} |
|
|
|
func (w lintModifiesValRecRule) skipType(t ast.Expr) bool { |
|
rt := w.file.Pkg.TypeOf(t) |
|
if rt == nil { |
|
return false |
|
} |
|
|
|
rt = rt.Underlying() |
|
rtName := rt.String() |
|
|
|
// skip when receiver is a map or array |
|
return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[") |
|
} |
|
|
|
func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string { |
|
ident, ok := ie.(*ast.Ident) |
|
if !ok { |
|
return "" |
|
} |
|
|
|
return ident.Name |
|
}
|
|
|