55 "go/ast"
66 "go/token"
77 "go/types"
8- "log"
9- "os"
108 "regexp"
119 "strings"
1210
@@ -51,11 +49,11 @@ type WrapcheckConfig struct {
5149 // unwrapped.
5250 //
5351 // 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"
5553 // due to the signature matching the regular expression `\.New.*Err\(`.
5654 //
5755 // 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
5957 // ignored.
6058 IgnoreSigRegexps []string `mapstructure:"ignoreSigRegexps" yaml:"ignoreSigRegexps"`
6159
@@ -71,13 +69,23 @@ type WrapcheckConfig struct {
7169 // ignorePackageGlobs:
7270 // - encoding/*
7371 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"`
7481}
7582
7683func NewDefaultConfig () WrapcheckConfig {
7784 return WrapcheckConfig {
78- IgnoreSigs : DefaultIgnoreSigs ,
79- IgnoreSigRegexps : []string {},
80- IgnorePackageGlobs : []string {},
85+ IgnoreSigs : DefaultIgnoreSigs ,
86+ IgnoreSigRegexps : []string {},
87+ IgnorePackageGlobs : []string {},
88+ IgnoreInterfaceRegexps : []string {},
8189 }
8290}
8391
@@ -91,7 +99,21 @@ func NewAnalyzer(cfg WrapcheckConfig) *analysis.Analyzer {
9199
92100func run (cfg WrapcheckConfig ) func (* analysis.Pass ) (interface {}, error ) {
93101 // 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+ }
95117
96118 return func (pass * analysis.Pass ) (interface {}, error ) {
97119 if err != nil {
@@ -120,7 +142,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
120142 // tuple check is required.
121143
122144 if isError (pass .TypesInfo .TypeOf (expr )) {
123- reportUnwrapped (pass , retFn , retFn .Pos (), cfg , ignoreSigRegexp )
145+ reportUnwrapped (pass , retFn , retFn .Pos (), cfg , ignoreSigRegexp , ignoreInterfaceRegexps , ignorePackageGlobs )
124146 return true
125147 }
126148
@@ -138,7 +160,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
138160 return true
139161 }
140162 if isError (v .Type ()) {
141- reportUnwrapped (pass , retFn , expr .Pos (), cfg , ignoreSigRegexp )
163+ reportUnwrapped (pass , retFn , expr .Pos (), cfg , ignoreSigRegexp , ignoreInterfaceRegexps , ignorePackageGlobs )
142164 return true
143165 }
144166 }
@@ -200,7 +222,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
200222 return true
201223 }
202224
203- reportUnwrapped (pass , call , ident .NamePos , cfg , ignoreSigRegexp )
225+ reportUnwrapped (pass , call , ident .NamePos , cfg , ignoreSigRegexp , ignoreInterfaceRegexps , ignorePackageGlobs )
204226 }
205227
206228 return true
@@ -213,7 +235,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
213235
214236// Report unwrapped takes a call expression and an identifier and reports
215237// 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 ) {
217239 sel , ok := call .Fun .(* ast.SelectorExpr )
218240 if ! ok {
219241 return
@@ -224,21 +246,26 @@ func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos
224246
225247 if contains (cfg .IgnoreSigs , fnSig ) {
226248 return
227- } else if containsMatch (regexps , fnSig ) {
249+ } else if containsMatch (regexpsSig , fnSig ) {
228250 return
229251 }
230252
231253 // 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`
233256 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+ }
236263 }
237264
238265 // Check whether the function being called comes from another package,
239266 // as functions called across package boundaries which returns errors
240267 // should be wrapped
241- if isFromOtherPkg (pass , sel , cfg ) {
268+ if isFromOtherPkg (pass , sel , pkgGlobs ) {
242269 pass .Reportf (tokenPos , "error returned from external package is unwrapped: sig: %s" , fnSig )
243270 return
244271 }
@@ -251,23 +278,14 @@ func isInterface(pass *analysis.Pass, sel *ast.SelectorExpr) bool {
251278 return ok
252279}
253280
254- // isFromotherPkg returns whether the function is defined in the pacakge
281+ // isFromotherPkg returns whether the function is defined in the package
255282// currently under analysis or is considered external. It will ignore packages
256283// 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 {
258285 // The package of the function that we are calling which returns the error
259286 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
271289 }
272290
273291 // 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 {
350368 return false
351369}
352370
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+
353380// isError returns whether or not the provided type interface is an error
354381func isError (typ types.Type ) bool {
355382 if typ == nil {
@@ -384,3 +411,18 @@ func compileRegexps(regexps []string) ([]*regexp.Regexp, error) {
384411
385412 return compiledRegexps , nil
386413}
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