Skip to content

Commit a31d5af

Browse files
authored
refactor (rule/modifies-value-receiver): replace AST walker by iteration over declarations (#1165)
1 parent 361c744 commit a31d5af

File tree

1 file changed

+83
-85
lines changed

1 file changed

+83
-85
lines changed

rule/modifies_value_receiver.go

Lines changed: 83 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -12,111 +12,52 @@ import (
1212
type ModifiesValRecRule struct{}
1313

1414
// Apply applies the rule to given file.
15-
func (*ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
15+
func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
1616
var failures []lint.Failure
1717

18-
onFailure := func(failure lint.Failure) {
19-
failures = append(failures, failure)
20-
}
21-
22-
w := lintModifiesValRecRule{file: file, onFailure: onFailure}
2318
file.Pkg.TypeCheck()
24-
ast.Walk(w, file.AST)
25-
26-
return failures
27-
}
28-
29-
// Name returns the rule name.
30-
func (*ModifiesValRecRule) Name() string {
31-
return "modifies-value-receiver"
32-
}
33-
34-
type lintModifiesValRecRule struct {
35-
file *lint.File
36-
onFailure func(lint.Failure)
37-
}
38-
39-
func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
40-
switch n := node.(type) {
41-
case *ast.FuncDecl:
42-
if n.Recv == nil {
43-
return nil // skip, not a method
44-
}
45-
46-
receiver := n.Recv.List[0]
47-
if _, ok := receiver.Type.(*ast.StarExpr); ok {
48-
return nil // skip, method with pointer receiver
19+
for _, decl := range file.AST.Decls {
20+
funcDecl, ok := decl.(*ast.FuncDecl)
21+
isAMethod := ok && funcDecl.Recv != nil
22+
if !isAMethod {
23+
continue // skip, not a method
4924
}
5025

51-
if w.skipType(receiver.Type) {
52-
return nil // skip, receiver is a map or array
53-
}
54-
55-
if len(receiver.Names) < 1 {
56-
return nil // skip, anonymous receiver
26+
receiver := funcDecl.Recv.List[0]
27+
if r.mustSkip(receiver, file.Pkg) {
28+
continue
5729
}
5830

5931
receiverName := receiver.Names[0].Name
60-
if receiverName == "_" {
61-
return nil // skip, anonymous receiver
62-
}
63-
64-
receiverAssignmentFinder := func(n ast.Node) bool {
65-
// look for assignments with the receiver in the right hand
66-
assignment, ok := n.(*ast.AssignStmt)
67-
if !ok {
68-
return false
69-
}
70-
71-
for _, exp := range assignment.Lhs {
72-
switch e := exp.(type) {
73-
case *ast.IndexExpr: // receiver...[] = ...
74-
continue
75-
case *ast.StarExpr: // *receiver = ...
76-
continue
77-
case *ast.SelectorExpr: // receiver.field = ...
78-
name := w.getNameFromExpr(e.X)
79-
if name == "" || name != receiverName {
80-
continue
81-
}
82-
case *ast.Ident: // receiver := ...
83-
if e.Name != receiverName {
84-
continue
85-
}
86-
default:
87-
continue
88-
}
89-
90-
return true
91-
}
92-
93-
return false
94-
}
95-
96-
assignmentsToReceiver := pick(n.Body, receiverAssignmentFinder)
32+
assignmentsToReceiver := r.getAssignmentsToReceiver(receiverName, funcDecl.Body)
9733
if len(assignmentsToReceiver) == 0 {
98-
return nil // receiver is not modified
34+
continue // receiver is not modified
9935
}
10036

101-
methodReturnsReceiver := len(w.findReturnReceiverStatements(receiverName, n.Body)) > 0
37+
methodReturnsReceiver := len(r.findReturnReceiverStatements(receiverName, funcDecl.Body)) > 0
10238
if methodReturnsReceiver {
103-
return nil // modification seems legit (see issue #1066)
39+
continue // modification seems legit (see issue #1066)
10440
}
10541

10642
for _, assignment := range assignmentsToReceiver {
107-
w.onFailure(lint.Failure{
43+
failures = append(failures, lint.Failure{
10844
Node: assignment,
10945
Confidence: 1,
11046
Failure: "suspicious assignment to a by-value method receiver",
11147
})
11248
}
11349
}
11450

115-
return w
51+
return failures
52+
}
53+
54+
// Name returns the rule name.
55+
func (*ModifiesValRecRule) Name() string {
56+
return "modifies-value-receiver"
11657
}
11758

118-
func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
119-
rt := w.file.Pkg.TypeOf(t)
59+
func (r *ModifiesValRecRule) skipType(t ast.Expr, pkg *lint.Package) bool {
60+
rt := pkg.TypeOf(t)
12061
if rt == nil {
12162
return false
12263
}
@@ -128,7 +69,7 @@ func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
12869
return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[")
12970
}
13071

131-
func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
72+
func (*ModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
13273
ident, ok := ie.(*ast.Ident)
13374
if !ok {
13475
return ""
@@ -137,7 +78,7 @@ func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
13778
return ident.Name
13879
}
13980

140-
func (w lintModifiesValRecRule) findReturnReceiverStatements(receiverName string, target ast.Node) []ast.Node {
81+
func (r *ModifiesValRecRule) findReturnReceiverStatements(receiverName string, target ast.Node) []ast.Node {
14182
finder := func(n ast.Node) bool {
14283
// look for returns with the receiver as value
14384
returnStatement, ok := n.(*ast.ReturnStmt)
@@ -148,7 +89,7 @@ func (w lintModifiesValRecRule) findReturnReceiverStatements(receiverName string
14889
for _, exp := range returnStatement.Results {
14990
switch e := exp.(type) {
15091
case *ast.SelectorExpr: // receiver.field = ...
151-
name := w.getNameFromExpr(e.X)
92+
name := r.getNameFromExpr(e.X)
15293
if name == "" || name != receiverName {
15394
continue
15495
}
@@ -160,7 +101,7 @@ func (w lintModifiesValRecRule) findReturnReceiverStatements(receiverName string
160101
if e.Op != token.AND {
161102
continue
162103
}
163-
name := w.getNameFromExpr(e.X)
104+
name := r.getNameFromExpr(e.X)
164105
if name == "" || name != receiverName {
165106
continue
166107
}
@@ -177,3 +118,60 @@ func (w lintModifiesValRecRule) findReturnReceiverStatements(receiverName string
177118

178119
return pick(target, finder)
179120
}
121+
122+
func (r *ModifiesValRecRule) mustSkip(receiver *ast.Field, pkg *lint.Package) bool {
123+
if _, ok := receiver.Type.(*ast.StarExpr); ok {
124+
return true // skip, method with pointer receiver
125+
}
126+
127+
if len(receiver.Names) < 1 {
128+
return true // skip, anonymous receiver
129+
}
130+
131+
receiverName := receiver.Names[0].Name
132+
if receiverName == "_" {
133+
return true // skip, anonymous receiver
134+
}
135+
136+
if r.skipType(receiver.Type, pkg) {
137+
return true // skip, receiver is a map or array
138+
}
139+
140+
return false
141+
}
142+
143+
func (r *ModifiesValRecRule) getAssignmentsToReceiver(receiverName string, funcBody *ast.BlockStmt) []ast.Node {
144+
receiverAssignmentFinder := func(n ast.Node) bool {
145+
// look for assignments with the receiver in the right hand
146+
assignment, ok := n.(*ast.AssignStmt)
147+
if !ok {
148+
return false
149+
}
150+
151+
for _, exp := range assignment.Lhs {
152+
switch e := exp.(type) {
153+
case *ast.IndexExpr: // receiver...[] = ...
154+
continue
155+
case *ast.StarExpr: // *receiver = ...
156+
continue
157+
case *ast.SelectorExpr: // receiver.field = ...
158+
name := r.getNameFromExpr(e.X)
159+
if name == "" || name != receiverName {
160+
continue
161+
}
162+
case *ast.Ident: // receiver := ...
163+
if e.Name != receiverName {
164+
continue
165+
}
166+
default:
167+
continue
168+
}
169+
170+
return true
171+
}
172+
173+
return false
174+
}
175+
176+
return pick(funcBody, receiverAssignmentFinder)
177+
}

0 commit comments

Comments
 (0)