Skip to content

Commit 673b9be

Browse files
committed
require-error: ignore assertion in bool expr
1 parent 8bf6d9d commit 673b9be

File tree

5 files changed

+82
-15
lines changed

5 files changed

+82
-15
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ assertion call. For more complex checkers, use the [checkers.AdvancedChecker](./
9595
If the checker turns out to be too “fat”, then you can omit some obviously rare combinations,
9696
especially if they are covered by other checkers. Usually these are expressions in `assert.True/False`.
9797

98+
Remember that [assert.TestingT](https://pkg.go.dev/github.com/stretchr/testify/assert#TestingT) and
99+
[require.TestingT](https://pkg.go.dev/github.com/stretchr/testify/require#TestingT) are different interfaces,
100+
which may be important in some contexts.
101+
98102
### 8) Improve tests from p.4 if necessary
99103

100104
Pay attention to `Assertion` and `NewAssertionExpander`, but keep your tests as small as possible.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,9 @@ By default `require-error` only checks the `*Error*` assertions, presented above
480480
You can set `--require-error.fn-pattern` flag to limit the checking to certain calls (but still from the list above).
481481
For example, `--require-error.fn-pattern="^(Errorf?|NoErrorf?)$"` will only check `Error`, `Errorf`, `NoError`, and `NoErrorf`.
482482

483-
Also, to minimize the number of false positives, `require-error` ignores:
483+
Also, to minimize false positives, `require-error` ignores:
484484
- assertion in the `if` condition;
485+
- assertion in the bool expression;
485486
- the entire `if-else[-if]` block, if there is an assertion in any `if` condition;
486487
- the last assertion in the block, if there are no methods/functions calls after it;
487488
- assertions in an explicit goroutine;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package requireerrorskiplogic
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"github.com/stretchr/testify/require"
6+
)
7+
8+
func assertRegexpError1(regexp any) assert.ErrorAssertionFunc {
9+
return func(t assert.TestingT, got error, msg ...any) bool {
10+
if h, ok := t.(interface{ Helper() }); ok {
11+
h.Helper()
12+
}
13+
return assert.Error(t, got, msg...) && assert.Regexp(t, regexp, got.Error(), msg...)
14+
}
15+
}
16+
17+
func assertRegexpError2(regexp any) assert.ErrorAssertionFunc {
18+
return func(t assert.TestingT, got error, msg ...any) bool {
19+
if h, ok := t.(interface{ Helper() }); ok {
20+
h.Helper()
21+
}
22+
assert.Error(t, got, msg...) // want "require-error: for error assertions use require"
23+
return assert.Regexp(t, regexp, got.Error(), msg...)
24+
}
25+
}
26+
27+
func assertRegexpError3(regexp any) assert.ErrorAssertionFunc {
28+
return func(t assert.TestingT, got error, msg ...any) bool {
29+
if h, ok := t.(interface{ Helper() }); ok {
30+
h.Helper()
31+
}
32+
return assert.Error(t, got, msg...) &&
33+
(assert.Regexp(t, regexp, got.Error(), msg...) ||
34+
assert.ErrorContains(t, got, "failed to"))
35+
}
36+
}
37+
38+
func requireRegexpError1(regexp any) require.ErrorAssertionFunc {
39+
return func(t require.TestingT, got error, msg ...any) {
40+
if h, ok := t.(interface{ Helper() }); ok {
41+
h.Helper()
42+
}
43+
assert.Error(t, got, msg...) // want "require-error: for error assertions use require"
44+
assert.Regexp(t, regexp, got.Error(), msg...)
45+
}
46+
}
47+
48+
func requireRegexpError2(regexp any) require.ErrorAssertionFunc {
49+
return func(t require.TestingT, got error, msg ...any) {
50+
if h, ok := t.(interface{ Helper() }); ok {
51+
h.Helper()
52+
}
53+
require.Error(t, got, msg...)
54+
assert.Regexp(t, regexp, got.Error(), msg...)
55+
}
56+
}

internal/checkers/call_meta.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,23 @@ func trimTArg(pass *analysis.Pass, args []ast.Expr) []ast.Expr {
121121
}
122122

123123
func isTestingTPtr(pass *analysis.Pass, arg ast.Expr) bool {
124+
return implementsAssertTestingT(pass, arg) || implementsRequireTestingT(pass, arg)
125+
}
126+
127+
func implementsAssertTestingT(pass *analysis.Pass, e ast.Expr) bool {
124128
assertTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, "TestingT")
129+
return (assertTestingTObj != nil) && implements(pass, e, assertTestingTObj)
130+
}
131+
132+
func implementsRequireTestingT(pass *analysis.Pass, e ast.Expr) bool {
125133
requireTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, "TestingT")
134+
return (requireTestingTObj != nil) && implements(pass, e, requireTestingTObj)
135+
}
126136

127-
argType := pass.TypesInfo.TypeOf(arg)
128-
if argType == nil {
137+
func implements(pass *analysis.Pass, e ast.Expr, ifaceObj types.Object) bool {
138+
t := pass.TypesInfo.TypeOf(e)
139+
if t == nil {
129140
return false
130141
}
131-
132-
return ((assertTestingTObj != nil) &&
133-
types.Implements(argType, assertTestingTObj.Type().Underlying().(*types.Interface))) ||
134-
((requireTestingTObj != nil) &&
135-
types.Implements(argType, requireTestingTObj.Type().Underlying().(*types.Interface)))
142+
return types.Implements(t, ifaceObj.Type().Underlying().(*types.Interface))
136143
}

internal/checkers/require_error.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ func (checker RequireError) Check(pass *analysis.Pass, inspector *inspector.Insp
7474
_, prevPrevIsIfStmt := stack[len(stack)-3].(*ast.IfStmt)
7575
inIfCond := prevIsIfStmt || (prevPrevIsIfStmt && prevIsAssignStmt)
7676

77+
_, inBoolExpr := stack[len(stack)-2].(*ast.BinaryExpr)
78+
7779
callExpr := node.(*ast.CallExpr)
7880
testifyCall := NewCallMeta(pass, callExpr)
7981

@@ -84,6 +86,7 @@ func (checker RequireError) Check(pass *analysis.Pass, inspector *inspector.Insp
8486
parentIf: findNearestNode[*ast.IfStmt](stack),
8587
parentBlock: findNearestNode[*ast.BlockStmt](stack),
8688
inIfCond: inIfCond,
89+
inBoolExpr: inBoolExpr,
8790
inNoErrorSeq: false, // Will be filled in below.
8891
}
8992

@@ -148,13 +151,8 @@ func needToSkipBasedOnContext(
148151
otherCalls []*callMeta,
149152
callsByBlock map[*ast.BlockStmt][]*callMeta,
150153
) bool {
151-
if currCall.inNoErrorSeq {
152-
// Skip `assert.NoError` sequence.
153-
return true
154-
}
155-
156-
if currCall.inIfCond {
157-
// Skip assertions in the "if condition".
154+
switch {
155+
case currCall.inIfCond, currCall.inBoolExpr, currCall.inNoErrorSeq:
158156
return true
159157
}
160158

@@ -309,6 +307,7 @@ type callMeta struct {
309307
parentIf *ast.IfStmt // The nearest `if`, can be equal with rootIf.
310308
parentBlock *ast.BlockStmt
311309
inIfCond bool // True for code like `if assert.ErrorAs(t, err, &target) {`.
310+
inBoolExpr bool // True for code like `assert.Error(t, err) && assert.ErrorContains(t, err, "value")`
312311
inNoErrorSeq bool // True for sequence of `assert.NoError` assertions.
313312
}
314313

0 commit comments

Comments
 (0)