5
5
"go/ast"
6
6
"go/token"
7
7
"go/types"
8
- "log"
9
- "os"
10
8
"regexp"
11
9
"strings"
12
10
@@ -51,11 +49,11 @@ type WrapcheckConfig struct {
51
49
// unwrapped.
52
50
//
53
51
// For example, an ignoreSigRegexp of `[]string{"\.New.*Err\("}`` will ignore errors
54
- // returned from any signture whose method name starts with "New" and ends with "Err"
52
+ // returned from any signature whose method name starts with "New" and ends with "Err"
55
53
// due to the signature matching the regular expression `\.New.*Err\(`.
56
54
//
57
55
// Note that this is similar to the ignoreSigs configuration, but provides
58
- // slightly more flexibility in defining rules by which signtures will be
56
+ // slightly more flexibility in defining rules by which signatures will be
59
57
// ignored.
60
58
IgnoreSigRegexps []string `mapstructure:"ignoreSigRegexps" yaml:"ignoreSigRegexps"`
61
59
@@ -71,13 +69,23 @@ type WrapcheckConfig struct {
71
69
// ignorePackageGlobs:
72
70
// - encoding/*
73
71
IgnorePackageGlobs []string `mapstructure:"ignorePackageGlobs" yaml:"ignorePackageGlobs"`
72
+
73
+ // IgnoreInterfaceRegexps defines a list of regular expressions which, if matched
74
+ // to a underlying interface name, will ignore unwrapped errors returned from a
75
+ // function whose call is defined on the given interface.
76
+ //
77
+ // For example, an ignoreInterfaceRegexps of `[]string{"Transac(tor|tion)"}`` will ignore errors
78
+ // returned from any function whose call is defined on a interface named 'Transactor'
79
+ // or 'Transaction' due to the name matching the regular expression `Transac(tor|tion)`.
80
+ IgnoreInterfaceRegexps []string `mapstructure:"ignoreInterfaceRegexps" yaml:"ignoreInterfaceRegexps"`
74
81
}
75
82
76
83
func NewDefaultConfig () WrapcheckConfig {
77
84
return WrapcheckConfig {
78
- IgnoreSigs : DefaultIgnoreSigs ,
79
- IgnoreSigRegexps : []string {},
80
- IgnorePackageGlobs : []string {},
85
+ IgnoreSigs : DefaultIgnoreSigs ,
86
+ IgnoreSigRegexps : []string {},
87
+ IgnorePackageGlobs : []string {},
88
+ IgnoreInterfaceRegexps : []string {},
81
89
}
82
90
}
83
91
@@ -91,7 +99,21 @@ func NewAnalyzer(cfg WrapcheckConfig) *analysis.Analyzer {
91
99
92
100
func run (cfg WrapcheckConfig ) func (* analysis.Pass ) (interface {}, error ) {
93
101
// Precompile the regexps, report the error
94
- ignoreSigRegexp , err := compileRegexps (cfg .IgnoreSigRegexps )
102
+ var (
103
+ ignoreSigRegexp []* regexp.Regexp
104
+ ignoreInterfaceRegexps []* regexp.Regexp
105
+ ignorePackageGlobs []glob.Glob
106
+ err error
107
+ )
108
+
109
+ ignoreSigRegexp , err = compileRegexps (cfg .IgnoreSigRegexps )
110
+ if err == nil {
111
+ ignoreInterfaceRegexps , err = compileRegexps (cfg .IgnoreInterfaceRegexps )
112
+ }
113
+ if err == nil {
114
+ ignorePackageGlobs , err = compileGlobs (cfg .IgnorePackageGlobs )
115
+
116
+ }
95
117
96
118
return func (pass * analysis.Pass ) (interface {}, error ) {
97
119
if err != nil {
@@ -120,7 +142,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
120
142
// tuple check is required.
121
143
122
144
if isError (pass .TypesInfo .TypeOf (expr )) {
123
- reportUnwrapped (pass , retFn , retFn .Pos (), cfg , ignoreSigRegexp )
145
+ reportUnwrapped (pass , retFn , retFn .Pos (), cfg , ignoreSigRegexp , ignoreInterfaceRegexps , ignorePackageGlobs )
124
146
return true
125
147
}
126
148
@@ -138,7 +160,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
138
160
return true
139
161
}
140
162
if isError (v .Type ()) {
141
- reportUnwrapped (pass , retFn , expr .Pos (), cfg , ignoreSigRegexp )
163
+ reportUnwrapped (pass , retFn , expr .Pos (), cfg , ignoreSigRegexp , ignoreInterfaceRegexps , ignorePackageGlobs )
142
164
return true
143
165
}
144
166
}
@@ -200,7 +222,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
200
222
return true
201
223
}
202
224
203
- reportUnwrapped (pass , call , ident .NamePos , cfg , ignoreSigRegexp )
225
+ reportUnwrapped (pass , call , ident .NamePos , cfg , ignoreSigRegexp , ignoreInterfaceRegexps , ignorePackageGlobs )
204
226
}
205
227
206
228
return true
@@ -213,7 +235,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
213
235
214
236
// Report unwrapped takes a call expression and an identifier and reports
215
237
// if the call is unwrapped.
216
- func reportUnwrapped (pass * analysis.Pass , call * ast.CallExpr , tokenPos token.Pos , cfg WrapcheckConfig , regexps []* regexp.Regexp ) {
238
+ func reportUnwrapped (pass * analysis.Pass , call * ast.CallExpr , tokenPos token.Pos , cfg WrapcheckConfig , regexpsSig []* regexp.Regexp , regexpsInter [] * regexp. Regexp , pkgGlobs []glob. Glob ) {
217
239
sel , ok := call .Fun .(* ast.SelectorExpr )
218
240
if ! ok {
219
241
return
@@ -224,21 +246,26 @@ func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos
224
246
225
247
if contains (cfg .IgnoreSigs , fnSig ) {
226
248
return
227
- } else if containsMatch (regexps , fnSig ) {
249
+ } else if containsMatch (regexpsSig , fnSig ) {
228
250
return
229
251
}
230
252
231
253
// Check if the underlying type of the "x" in x.y.z is an interface, as
232
- // errors returned from interface types should be wrapped.
254
+ // errors returned from interface types should be wrapped, unless ignored
255
+ // as per `ignoreInterfaceRegexps`
233
256
if isInterface (pass , sel ) {
234
- pass .Reportf (tokenPos , "error returned from interface method should be wrapped: sig: %s" , fnSig )
235
- return
257
+ name := types .TypeString (pass .TypesInfo .TypeOf (sel .X ), func (p * types.Package ) string { return p .Name () })
258
+ if containsMatch (regexpsInter , name ) {
259
+ } else {
260
+ pass .Reportf (tokenPos , "error returned from interface method should be wrapped: sig: %s" , fnSig )
261
+ return
262
+ }
236
263
}
237
264
238
265
// Check whether the function being called comes from another package,
239
266
// as functions called across package boundaries which returns errors
240
267
// should be wrapped
241
- if isFromOtherPkg (pass , sel , cfg ) {
268
+ if isFromOtherPkg (pass , sel , pkgGlobs ) {
242
269
pass .Reportf (tokenPos , "error returned from external package is unwrapped: sig: %s" , fnSig )
243
270
return
244
271
}
@@ -251,23 +278,14 @@ func isInterface(pass *analysis.Pass, sel *ast.SelectorExpr) bool {
251
278
return ok
252
279
}
253
280
254
- // isFromotherPkg returns whether the function is defined in the pacakge
281
+ // isFromotherPkg returns whether the function is defined in the package
255
282
// currently under analysis or is considered external. It will ignore packages
256
283
// defined in config.IgnorePackageGlobs.
257
- func isFromOtherPkg (pass * analysis.Pass , sel * ast.SelectorExpr , config WrapcheckConfig ) bool {
284
+ func isFromOtherPkg (pass * analysis.Pass , sel * ast.SelectorExpr , pkgGlobs []glob. Glob ) bool {
258
285
// The package of the function that we are calling which returns the error
259
286
fn := pass .TypesInfo .ObjectOf (sel .Sel )
260
-
261
- for _ , globString := range config .IgnorePackageGlobs {
262
- g , err := glob .Compile (globString )
263
- if err != nil {
264
- log .Printf ("unable to parse glob: %s\n " , globString )
265
- os .Exit (1 )
266
- }
267
-
268
- if g .Match (fn .Pkg ().Path ()) {
269
- return false
270
- }
287
+ if containsMatchGlob (pkgGlobs , fn .Pkg ().Path ()) {
288
+ return false
271
289
}
272
290
273
291
// If it's not a package name, then we should check the selector to make sure
@@ -350,6 +368,15 @@ func containsMatch(regexps []*regexp.Regexp, el string) bool {
350
368
return false
351
369
}
352
370
371
+ func containsMatchGlob (globs []glob.Glob , el string ) bool {
372
+ for _ , g := range globs {
373
+ if g .Match (el ) {
374
+ return true
375
+ }
376
+ }
377
+ return false
378
+ }
379
+
353
380
// isError returns whether or not the provided type interface is an error
354
381
func isError (typ types.Type ) bool {
355
382
if typ == nil {
@@ -384,3 +411,18 @@ func compileRegexps(regexps []string) ([]*regexp.Regexp, error) {
384
411
385
412
return compiledRegexps , nil
386
413
}
414
+
415
+ // compileGlobs compiles a set of globs, returning them for use,
416
+ // or the first encountered error due to an invalid expression.
417
+ func compileGlobs (globs []string ) ([]glob.Glob , error ) {
418
+ var compiledGlobs []glob.Glob
419
+ for _ , globString := range globs {
420
+ glob , err := glob .Compile (globString )
421
+ if err != nil {
422
+ return nil , fmt .Errorf ("unable to compile globs %s: %v\n " , glob , err )
423
+ }
424
+
425
+ compiledGlobs = append (compiledGlobs , glob )
426
+ }
427
+ return compiledGlobs , nil
428
+ }
0 commit comments