Skip to content

Commit fab0c41

Browse files
butuzovtomarrell
andauthored
refactor: earlier compilation of ignoreSigRegexp (#24)
* dev: refactor `regexp` usage * fix: separating `ignoreSigRegexp` usage * test: add missing testcase * fix: added missing `.wrapcheck.yaml` * Slight refactor, file for test analysistest skipping * Comment refactor Co-authored-by: Tom Arrell <tom.arrell@gmail.com>
1 parent 13a5495 commit fab0c41

File tree

4 files changed

+73
-29
lines changed

4 files changed

+73
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignoreSigRegexps:
2+
- json\.[a-zA-Z0-9_-

wrapcheck/testdata/config_ignoreSigRegexps_fail/analysistest_skip

Whitespace-only changes.

wrapcheck/wrapcheck.go

+47-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package wrapcheck
22

33
import (
4+
"fmt"
45
"go/ast"
56
"go/token"
67
"go/types"
@@ -13,18 +14,16 @@ import (
1314
"golang.org/x/tools/go/analysis"
1415
)
1516

16-
var (
17-
DefaultIgnoreSigs = []string{
18-
".Errorf(",
19-
"errors.New(",
20-
"errors.Unwrap(",
21-
".Wrap(",
22-
".Wrapf(",
23-
".WithMessage(",
24-
".WithMessagef(",
25-
".WithStack(",
26-
}
27-
)
17+
var DefaultIgnoreSigs = []string{
18+
".Errorf(",
19+
"errors.New(",
20+
"errors.Unwrap(",
21+
".Wrap(",
22+
".Wrapf(",
23+
".WithMessage(",
24+
".WithMessagef(",
25+
".WithStack(",
26+
}
2827

2928
// WrapcheckConfig is the set of configuration values which configure the
3029
// behaviour of the linter.
@@ -91,7 +90,14 @@ func NewAnalyzer(cfg WrapcheckConfig) *analysis.Analyzer {
9190
}
9291

9392
func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
93+
// Precompile the regexps, report the error
94+
ignoreSigRegexp, err := compileRegexps(cfg.IgnoreSigRegexps)
95+
9496
return func(pass *analysis.Pass) (interface{}, error) {
97+
if err != nil {
98+
return nil, err
99+
}
100+
95101
for _, file := range pass.Files {
96102
ast.Inspect(file, func(n ast.Node) bool {
97103
ret, ok := n.(*ast.ReturnStmt)
@@ -112,8 +118,9 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
112118
// If the return type of the function is a single error. This will not
113119
// match an error within multiple return values, for that, the below
114120
// tuple check is required.
121+
115122
if isError(pass.TypesInfo.TypeOf(expr)) {
116-
reportUnwrapped(pass, retFn, retFn.Pos(), cfg)
123+
reportUnwrapped(pass, retFn, retFn.Pos(), cfg, ignoreSigRegexp)
117124
return true
118125
}
119126

@@ -131,7 +138,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
131138
return true
132139
}
133140
if isError(v.Type()) {
134-
reportUnwrapped(pass, retFn, expr.Pos(), cfg)
141+
reportUnwrapped(pass, retFn, expr.Pos(), cfg, ignoreSigRegexp)
135142
return true
136143
}
137144
}
@@ -146,9 +153,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
146153
return true
147154
}
148155

149-
var (
150-
call *ast.CallExpr
151-
)
156+
var call *ast.CallExpr
152157

153158
// Attempt to find the most recent short assign
154159
if shortAss := prevErrAssign(pass, file, ident); shortAss != nil {
@@ -195,7 +200,7 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
195200
return true
196201
}
197202

198-
reportUnwrapped(pass, call, ident.NamePos, cfg)
203+
reportUnwrapped(pass, call, ident.NamePos, cfg, ignoreSigRegexp)
199204
}
200205

201206
return true
@@ -208,17 +213,18 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) {
208213

209214
// Report unwrapped takes a call expression and an identifier and reports
210215
// if the call is unwrapped.
211-
func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos, cfg WrapcheckConfig) {
216+
func reportUnwrapped(pass *analysis.Pass, call *ast.CallExpr, tokenPos token.Pos, cfg WrapcheckConfig, regexps []*regexp.Regexp) {
212217
sel, ok := call.Fun.(*ast.SelectorExpr)
213218
if !ok {
214219
return
215220
}
216221

217222
// Check for ignored signatures
218223
fnSig := pass.TypesInfo.ObjectOf(sel.Sel).String()
224+
219225
if contains(cfg.IgnoreSigs, fnSig) {
220226
return
221-
} else if containsMatch(cfg.IgnoreSigRegexps, fnSig) {
227+
} else if containsMatch(regexps, fnSig) {
222228
return
223229
}
224230

@@ -245,6 +251,9 @@ func isInterface(pass *analysis.Pass, sel *ast.SelectorExpr) bool {
245251
return ok
246252
}
247253

254+
// isFromotherPkg returns whether the function is defined in the pacakge
255+
// currently under analysis or is considered external. It will ignore packages
256+
// defined in config.IgnorePackageGlobs.
248257
func isFromOtherPkg(pass *analysis.Pass, sel *ast.SelectorExpr, config WrapcheckConfig) bool {
249258
// The package of the function that we are calling which returns the error
250259
fn := pass.TypesInfo.ObjectOf(sel.Sel)
@@ -331,14 +340,8 @@ func contains(slice []string, el string) bool {
331340
return false
332341
}
333342

334-
func containsMatch(slice []string, el string) bool {
335-
for _, s := range slice {
336-
re, err := regexp.Compile(s)
337-
if err != nil {
338-
log.Printf("unable to parse regexp: %s\n", s)
339-
os.Exit(1)
340-
}
341-
343+
func containsMatch(regexps []*regexp.Regexp, el string) bool {
344+
for _, re := range regexps {
342345
if re.MatchString(el) {
343346
return true
344347
}
@@ -365,3 +368,19 @@ func isUnresolved(file *ast.File, ident *ast.Ident) bool {
365368

366369
return false
367370
}
371+
372+
// compileRegexps compiles a set of regular expressions returning them for use,
373+
// or the first encountered error due to an invalid expression.
374+
func compileRegexps(regexps []string) ([]*regexp.Regexp, error) {
375+
var compiledRegexps []*regexp.Regexp
376+
for _, reg := range regexps {
377+
re, err := regexp.Compile(reg)
378+
if err != nil {
379+
return nil, fmt.Errorf("unable to compile regexp %s: %v\n", reg, err)
380+
}
381+
382+
compiledRegexps = append(compiledRegexps, re)
383+
}
384+
385+
return compiledRegexps, nil
386+
}

wrapcheck/wrapcheck_test.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
"gopkg.in/yaml.v3"
1313
)
1414

15+
// A file present in the directory named "analysis_skip" will cause the primary
16+
// analysis tests to skip this directory due to needing explicit tests.
17+
const skipfile = "analysistest_skip"
18+
1519
func TestAnalyzer(t *testing.T) {
1620
// Load the dirs under ./testdata
1721
p, err := filepath.Abs("./testdata")
@@ -29,6 +33,12 @@ func TestAnalyzer(t *testing.T) {
2933
dirPath, err := filepath.Abs(path.Join("./testdata", f.Name()))
3034
assert.NoError(t, err)
3135

36+
// Check if the test is marked for skipping analysistest
37+
if _, err := os.Stat(path.Join(dirPath, skipfile)); err == nil {
38+
t.Logf("skipping test: %s\n", t.Name())
39+
t.Skip()
40+
}
41+
3242
configPath := path.Join(dirPath, ".wrapcheck.yaml")
3343
if _, err := os.Stat(configPath); os.IsNotExist(err) {
3444
// There is no config
@@ -40,11 +50,24 @@ func TestAnalyzer(t *testing.T) {
4050

4151
var config WrapcheckConfig
4252
assert.NoError(t, yaml.Unmarshal(configFile, &config))
43-
4453
analysistest.Run(t, dirPath, NewAnalyzer(config))
4554
} else {
4655
assert.FailNow(t, err.Error())
4756
}
4857
})
4958
}
5059
}
60+
61+
func TestRegexpCompileFail(t *testing.T) {
62+
configFile, err := os.ReadFile("./testdata/config_ignoreSigRegexps_fail/.wrapcheck.yaml")
63+
assert.NoError(t, err)
64+
65+
var config WrapcheckConfig
66+
assert.NoError(t, yaml.Unmarshal(configFile, &config))
67+
68+
a := NewAnalyzer(config)
69+
70+
results, err := a.Run(nil) // Doesn't matter what we pass
71+
assert.Nil(t, results)
72+
assert.Contains(t, err.Error(), "unable to compile regexp json\\.[a-zA-Z0-9_-")
73+
}

0 commit comments

Comments
 (0)