@@ -17,10 +17,25 @@ type ErrorStringsRule struct{}
17
17
func (r * ErrorStringsRule ) Apply (file * lint.File , _ lint.Arguments ) []lint.Failure {
18
18
var failures []lint.Failure
19
19
20
+ var errorFunctions = map [string ]map [string ]struct {}{
21
+ "fmt" : {
22
+ "Errorf" : {},
23
+ },
24
+ "errors" : {
25
+ "Errorf" : {},
26
+ "WithMessage" : {},
27
+ "Wrap" : {},
28
+ "New" : {},
29
+ "WithMessagef" : {},
30
+ "Wrapf" : {},
31
+ },
32
+ }
33
+
20
34
fileAst := file .AST
21
35
walker := lintErrorStrings {
22
- file : file ,
23
- fileAst : fileAst ,
36
+ file : file ,
37
+ fileAst : fileAst ,
38
+ errorFunctions : errorFunctions ,
24
39
onFailure : func (failure lint.Failure ) {
25
40
failures = append (failures , failure )
26
41
},
@@ -37,24 +52,31 @@ func (r *ErrorStringsRule) Name() string {
37
52
}
38
53
39
54
type lintErrorStrings struct {
40
- file * lint.File
41
- fileAst * ast.File
42
- onFailure func (lint.Failure )
55
+ file * lint.File
56
+ fileAst * ast.File
57
+ errorFunctions map [string ]map [string ]struct {}
58
+ onFailure func (lint.Failure )
43
59
}
44
60
61
+ // Visit browses the AST
45
62
func (w lintErrorStrings ) Visit (n ast.Node ) ast.Visitor {
46
63
ce , ok := n .(* ast.CallExpr )
47
64
if ! ok {
48
65
return w
49
66
}
50
- if ! isPkgDot (ce .Fun , "errors" , "New" ) && ! isPkgDot (ce .Fun , "fmt" , "Errorf" ) {
67
+
68
+ if len (ce .Args ) < 1 {
51
69
return w
52
70
}
53
- if len (ce .Args ) < 1 {
71
+
72
+ // expression matches the known pkg.function
73
+ ok = w .match (ce )
74
+ if ! ok {
54
75
return w
55
76
}
56
- str , ok := ce .Args [0 ].(* ast.BasicLit )
57
- if ! ok || str .Kind != token .STRING {
77
+
78
+ str , ok := w .getMessage (ce )
79
+ if ! ok {
58
80
return w
59
81
}
60
82
s , _ := strconv .Unquote (str .Value ) // can assume well-formed Go
@@ -65,7 +87,6 @@ func (w lintErrorStrings) Visit(n ast.Node) ast.Visitor {
65
87
if clean {
66
88
return w
67
89
}
68
-
69
90
w .onFailure (lint.Failure {
70
91
Node : str ,
71
92
Confidence : conf ,
@@ -75,6 +96,52 @@ func (w lintErrorStrings) Visit(n ast.Node) ast.Visitor {
75
96
return w
76
97
}
77
98
99
+ // match returns true if the expression corresponds to the known pkg.function
100
+ // i.e.: errors.Wrap
101
+ func (w lintErrorStrings ) match (expr * ast.CallExpr ) bool {
102
+ sel , ok := expr .Fun .(* ast.SelectorExpr )
103
+ if ! ok {
104
+ return false
105
+ }
106
+ // retrieve the package
107
+ id , ok := sel .X .(* ast.Ident )
108
+ functions , ok := w .errorFunctions [id .Name ]
109
+ if ! ok {
110
+ return false
111
+ }
112
+ // retrieve the function
113
+ _ , ok = functions [sel .Sel .Name ]
114
+ return ok
115
+ }
116
+
117
+ // getMessage returns the message depending on its position
118
+ // returns false if the cast is unsuccessful
119
+ func (w lintErrorStrings ) getMessage (expr * ast.CallExpr ) (s * ast.BasicLit , success bool ) {
120
+ str , ok := w .checkArg (expr , 0 )
121
+ if ok {
122
+ return str , true
123
+ }
124
+ if len (expr .Args ) < 2 {
125
+ return s , false
126
+ }
127
+ str , ok = w .checkArg (expr , 1 )
128
+ if ! ok {
129
+ return s , false
130
+ }
131
+ return str , true
132
+ }
133
+
134
+ func (lintErrorStrings ) checkArg (expr * ast.CallExpr , arg int ) (s * ast.BasicLit , success bool ) {
135
+ str , ok := expr .Args [arg ].(* ast.BasicLit )
136
+ if ! ok {
137
+ return s , false
138
+ }
139
+ if str .Kind != token .STRING {
140
+ return s , false
141
+ }
142
+ return str , true
143
+ }
144
+
78
145
func lintErrorString (s string ) (isClean bool , conf float64 ) {
79
146
const basicConfidence = 0.8
80
147
const capConfidence = basicConfidence - 0.2
0 commit comments