diff --git a/README.md b/README.md index 461cbf9..c9a6840 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ ignoreSigs: - .WithMessagef( - .WithStack( +# An array of strings which specify regular expressions of signatures to ignore. +# This is similar to the ignoreSigs configuration above, but gives slightly more +# flexibility. +ignoreSigRegexps: +- \.New.*Error\( + # An array of glob patterns which, if any match the package of the function # returning the error, will skip wrapcheck analysis for this error. This is # useful for broadly ignoring packages and/or subpackages from wrapcheck diff --git a/wrapcheck/testdata/config_ignoreSigRegexps/.wrapcheck.yaml b/wrapcheck/testdata/config_ignoreSigRegexps/.wrapcheck.yaml new file mode 100644 index 0000000..e5a75ee --- /dev/null +++ b/wrapcheck/testdata/config_ignoreSigRegexps/.wrapcheck.yaml @@ -0,0 +1,4 @@ +ignoreSigRegexps: +- json\.[a-zA-Z0-9_-]+\( +- ^func[\s]+\(.+wrapcheck.+\.error.*\) +- \.NewMod[\d]+Error\( diff --git a/wrapcheck/testdata/config_ignoreSigRegexps/main.go b/wrapcheck/testdata/config_ignoreSigRegexps/main.go new file mode 100644 index 0000000..9aef6f6 --- /dev/null +++ b/wrapcheck/testdata/config_ignoreSigRegexps/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/pkg/errors" +) + +type errorer interface { + Decode(v interface{}) error +} + +func main() { + do1() + + d := json.NewDecoder(strings.NewReader("hello world")) + do2(d) + + do3(5) + do4(5) +} + +func do1() error { + _, err := json.Marshal(struct{}{}) // external package ignored by package+method-name regexp + if err != nil { + return err + } + + return nil +} + +func do2(fn errorer) error { + var str string + err := fn.Decode(&str) // interface ignored by regexp + if err != nil { + return err + } + + return nil +} + +func do3(i int) error { + if i%2 == 0 { + return errors.NewMod2Error(fmt.Sprintf("%d is an even number", i)) // external package ignored by method-name regexp + } + + return nil +} + +func do4(i int) error { + if i%3 == 0 { + return errors.NewMod3Error(fmt.Sprintf("%d is divisible by 3", i)) // external package ignored by method-name regexp + } + + return nil +} diff --git a/wrapcheck/testdata/config_ignoreSigRegexps/src/github.com/pkg/errors/errors.go b/wrapcheck/testdata/config_ignoreSigRegexps/src/github.com/pkg/errors/errors.go new file mode 100644 index 0000000..d5a4522 --- /dev/null +++ b/wrapcheck/testdata/config_ignoreSigRegexps/src/github.com/pkg/errors/errors.go @@ -0,0 +1,25 @@ +package errors + +type Mod2Error struct { + msg string +} + +func NewMod2Error(msg string) error { + return &Mod2Error{msg} +} + +func (e Mod2Error) Error() string { + return e.msg +} + +type Mod3Error struct { + msg string +} + +func NewMod3Error(msg string) error { + return &Mod3Error{msg} +} + +func (e Mod3Error) Error() string { + return e.msg +} diff --git a/wrapcheck/wrapcheck.go b/wrapcheck/wrapcheck.go index 3b445e2..8168da3 100644 --- a/wrapcheck/wrapcheck.go +++ b/wrapcheck/wrapcheck.go @@ -6,6 +6,7 @@ import ( "go/types" "log" "os" + "regexp" "strings" "github.com/gobwas/glob" @@ -33,7 +34,7 @@ type WrapcheckConfig struct { // allows you to specify functions that wrapcheck will not report as // unwrapped. // - // For example, an ingoredSig of `[]string{"errors.New("}` will ignore errors + // For example, an ignoreSig of `[]string{"errors.New("}` will ignore errors // returned from the stdlib package error's function: // // `func errors.New(message string) error` @@ -45,6 +46,20 @@ type WrapcheckConfig struct { // list to your config. IgnoreSigs []string `mapstructure:"ignoreSigs" yaml:"ignoreSigs"` + // IgnoreSigRegexps defines a list of regular expressions which if matched + // to the signature of the function call returning the error, will be ignored. This + // allows you to specify functions that wrapcheck will not report as + // unwrapped. + // + // For example, an ignoreSigRegexp of `[]string{"\.New.*Err\("}`` will ignore errors + // returned from any signture whose method name starts with "New" and ends with "Err" + // due to the signature matching the regular expression `\.New.*Err\(`. + // + // Note that this is similar to the ignoreSigs configuration, but provides + // slightly more flexibility in defining rules by which signtures will be + // ignored. + IgnoreSigRegexps []string `mapstructure:"ignoreSigRegexps" yaml:"ignoreSigRegexps"` + // IgnorePackageGlobs defines a list of globs which, if matching the package // of the function returning the error, will ignore the error when doing // wrapcheck analysis. @@ -62,6 +77,7 @@ type WrapcheckConfig struct { func NewDefaultConfig() WrapcheckConfig { return WrapcheckConfig{ IgnoreSigs: DefaultIgnoreSigs, + IgnoreSigRegexps: []string{}, IgnorePackageGlobs: []string{}, } } @@ -206,6 +222,8 @@ func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos fnSig := pass.TypesInfo.ObjectOf(sel.Sel).String() if contains(cfg.IgnoreSigs, fnSig) { return + } else if containsMatch(cfg.IgnoreSigRegexps, fnSig) { + return } // 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 { return false } +func containsMatch(slice []string, el string) bool { + for _, s := range slice { + re, err := regexp.Compile(s) + if err != nil { + log.Printf("unable to parse regexp: %s\n", s) + os.Exit(1) + } + + if re.MatchString(el) { + return true + } + } + + return false +} + // isError returns whether or not the provided type interface is an error func isError(typ types.Type) bool { if typ == nil {