Skip to content

Commit 1d12a32

Browse files
authored
feat: ignore signatures via regex (#21)
* Ignore error signatures via regexp * Update docs with new ignoreSigRegexps configuration * Fix docs typo
1 parent 849598b commit 1d12a32

File tree

5 files changed

+128
-1
lines changed

5 files changed

+128
-1
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ ignoreSigs:
3939
- .WithMessagef(
4040
- .WithStack(
4141

42+
# An array of strings which specify regular expressions of signatures to ignore.
43+
# This is similar to the ignoreSigs configuration above, but gives slightly more
44+
# flexibility.
45+
ignoreSigRegexps:
46+
- \.New.*Error\(
47+
4248
# An array of glob patterns which, if any match the package of the function
4349
# returning the error, will skip wrapcheck analysis for this error. This is
4450
# useful for broadly ignoring packages and/or subpackages from wrapcheck
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ignoreSigRegexps:
2+
- json\.[a-zA-Z0-9_-]+\(
3+
- ^func[\s]+\(.+wrapcheck.+\.error.*\)
4+
- \.NewMod[\d]+Error\(
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
type errorer interface {
12+
Decode(v interface{}) error
13+
}
14+
15+
func main() {
16+
do1()
17+
18+
d := json.NewDecoder(strings.NewReader("hello world"))
19+
do2(d)
20+
21+
do3(5)
22+
do4(5)
23+
}
24+
25+
func do1() error {
26+
_, err := json.Marshal(struct{}{}) // external package ignored by package+method-name regexp
27+
if err != nil {
28+
return err
29+
}
30+
31+
return nil
32+
}
33+
34+
func do2(fn errorer) error {
35+
var str string
36+
err := fn.Decode(&str) // interface ignored by regexp
37+
if err != nil {
38+
return err
39+
}
40+
41+
return nil
42+
}
43+
44+
func do3(i int) error {
45+
if i%2 == 0 {
46+
return errors.NewMod2Error(fmt.Sprintf("%d is an even number", i)) // external package ignored by method-name regexp
47+
}
48+
49+
return nil
50+
}
51+
52+
func do4(i int) error {
53+
if i%3 == 0 {
54+
return errors.NewMod3Error(fmt.Sprintf("%d is divisible by 3", i)) // external package ignored by method-name regexp
55+
}
56+
57+
return nil
58+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package errors
2+
3+
type Mod2Error struct {
4+
msg string
5+
}
6+
7+
func NewMod2Error(msg string) error {
8+
return &Mod2Error{msg}
9+
}
10+
11+
func (e Mod2Error) Error() string {
12+
return e.msg
13+
}
14+
15+
type Mod3Error struct {
16+
msg string
17+
}
18+
19+
func NewMod3Error(msg string) error {
20+
return &Mod3Error{msg}
21+
}
22+
23+
func (e Mod3Error) Error() string {
24+
return e.msg
25+
}

wrapcheck/wrapcheck.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"go/types"
77
"log"
88
"os"
9+
"regexp"
910
"strings"
1011

1112
"github.com/gobwas/glob"
@@ -33,7 +34,7 @@ type WrapcheckConfig struct {
3334
// allows you to specify functions that wrapcheck will not report as
3435
// unwrapped.
3536
//
36-
// For example, an ingoredSig of `[]string{"errors.New("}` will ignore errors
37+
// For example, an ignoreSig of `[]string{"errors.New("}` will ignore errors
3738
// returned from the stdlib package error's function:
3839
//
3940
// `func errors.New(message string) error`
@@ -45,6 +46,20 @@ type WrapcheckConfig struct {
4546
// list to your config.
4647
IgnoreSigs []string `mapstructure:"ignoreSigs" yaml:"ignoreSigs"`
4748

49+
// IgnoreSigRegexps defines a list of regular expressions which if matched
50+
// to the signature of the function call returning the error, will be ignored. This
51+
// allows you to specify functions that wrapcheck will not report as
52+
// unwrapped.
53+
//
54+
// For example, an ignoreSigRegexp of `[]string{"\.New.*Err\("}`` will ignore errors
55+
// returned from any signture whose method name starts with "New" and ends with "Err"
56+
// due to the signature matching the regular expression `\.New.*Err\(`.
57+
//
58+
// Note that this is similar to the ignoreSigs configuration, but provides
59+
// slightly more flexibility in defining rules by which signtures will be
60+
// ignored.
61+
IgnoreSigRegexps []string `mapstructure:"ignoreSigRegexps" yaml:"ignoreSigRegexps"`
62+
4863
// IgnorePackageGlobs defines a list of globs which, if matching the package
4964
// of the function returning the error, will ignore the error when doing
5065
// wrapcheck analysis.
@@ -62,6 +77,7 @@ type WrapcheckConfig struct {
6277
func NewDefaultConfig() WrapcheckConfig {
6378
return WrapcheckConfig{
6479
IgnoreSigs: DefaultIgnoreSigs,
80+
IgnoreSigRegexps: []string{},
6581
IgnorePackageGlobs: []string{},
6682
}
6783
}
@@ -206,6 +222,8 @@ func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos
206222
fnSig := pass.TypesInfo.ObjectOf(sel.Sel).String()
207223
if contains(cfg.IgnoreSigs, fnSig) {
208224
return
225+
} else if containsMatch(cfg.IgnoreSigRegexps, fnSig) {
226+
return
209227
}
210228

211229
// Check if the underlying type of the "x" in x.y.z is an interface, as
@@ -317,6 +335,22 @@ func contains(slice []string, el string) bool {
317335
return false
318336
}
319337

338+
func containsMatch(slice []string, el string) bool {
339+
for _, s := range slice {
340+
re, err := regexp.Compile(s)
341+
if err != nil {
342+
log.Printf("unable to parse regexp: %s\n", s)
343+
os.Exit(1)
344+
}
345+
346+
if re.MatchString(el) {
347+
return true
348+
}
349+
}
350+
351+
return false
352+
}
353+
320354
// isError returns whether or not the provided type interface is an error
321355
func isError(typ types.Type) bool {
322356
if typ == nil {

0 commit comments

Comments
 (0)