Skip to content

Commit c3bdbc7

Browse files
ccoVeilleAntonboom
andauthored
empty: support empty strings, Zero for strings and len + bubbled useless-assert cases (#129)
* empty: detect more cases Related to #119 * empty: finalize new cases * empty: finalize implementation * useless-assert: support cases moved from empty * review fixes * useless-assert: more cases --------- Co-authored-by: Anton Telyshev <[email protected]>
1 parent eb776aa commit c3bdbc7

File tree

11 files changed

+1579
-248
lines changed

11 files changed

+1579
-248
lines changed

README.md

+64-24
Original file line numberDiff line numberDiff line change
@@ -246,35 +246,60 @@ assert.NotContains(t, a, "abc123")
246246
```go
247247
248248
assert.Len(t, arr, 0)
249+
assert.Zero(t, str)
250+
assert.Zero(t, len(arr))
249251
assert.Equal(t, 0, len(arr))
250252
assert.EqualValues(t, 0, len(arr))
251253
assert.Exactly(t, 0, len(arr))
252254
assert.LessOrEqual(t, len(arr), 0)
253255
assert.GreaterOrEqual(t, 0, len(arr))
254-
assert.Less(t, len(arr), 0)
255-
assert.Greater(t, 0, len(arr))
256256
assert.Less(t, len(arr), 1)
257257
assert.Greater(t, 1, len(arr))
258-
assert.Zero(t, len(arr))
259-
assert.Empty(t, len(arr))
258+
assert.Equal(t, "", str)
259+
assert.EqualValues(t, "", str)
260+
assert.Exactly(t, "", str)
261+
assert.Equal(t, ``, str)
262+
assert.EqualValues(t, ``, str)
263+
assert.Exactly(t, ``, str)
260264

265+
assert.Positive(t, len(arr))
266+
assert.NotZero(t, str)
267+
assert.NotZero(t, len(arr))
261268
assert.NotEqual(t, 0, len(arr))
262269
assert.NotEqualValues(t, 0, len(arr))
263-
assert.Less(t, 0, len(arr))
264270
assert.Greater(t, len(arr), 0)
265-
assert.Positive(t, len(arr))
266-
assert.NotZero(t, len(arr))
267-
assert.NotEmpty(t, len(arr))
271+
assert.Less(t, 0, len(arr))
272+
assert.NotEqual(t, "", str)
273+
assert.NotEqualValues(t, "", str)
274+
assert.NotEqual(t, ``, str)
275+
assert.NotEqualValues(t, ``, str)
268276

269277
270278
assert.Empty(t, arr)
271-
assert.NotEmpty(t, err)
279+
assert.NotEmpty(t, arr)
272280
```
273281

274282
**Autofix**: true. <br>
275283
**Enabled by default**: true. <br>
276284
**Reason**: More appropriate `testify` API with clearer failure message.
277285

286+
Also `empty` removes extra `len` call in `*Emtpy` assertions:
287+
288+
```go
289+
290+
assert.Empty(t, len(arr))
291+
assert.NotEmpty(t, len(arr))
292+
293+
294+
assert.Empty(t, arr)
295+
assert.NotEmpty(t, arr)
296+
```
297+
298+
P.S. `empty` does not remove the string conversion and keeps as is:
299+
300+
- `string(bytes)` – to keep assert failure message readability;
301+
- `string(str)` – in favor of [unconvert](https://golangci-lint.run/usage/linters/#unconvert).
302+
278303
---
279304

280305
### encoded-compare
@@ -1074,25 +1099,40 @@ assert.False(t, num != num)
10741099
And against these meaningless assertions:
10751100

10761101
```go
1077-
assert.Empty(t, "")
1078-
assert.False(t, false)
1102+
assert.Empty(t, "value") // Any string literal.
1103+
assert.Error(t, nil)
1104+
assert.False(t, false) // Any bool literal.
10791105
assert.Implements(t, (*any)(nil), new(Conn))
1080-
assert.Negative(t, -42)
1106+
assert.Negative(t, 42) // Any int literal.
10811107
assert.Nil(t, nil)
10821108
assert.NoError(t, nil)
1083-
assert.NotEmpty(t, "value")
1084-
assert.NotZero(t, 42)
1085-
assert.NotZero(t, "value")
1086-
assert.Positive(t, 42)
1087-
assert.True(t, true)
1088-
assert.Zero(t, 0)
1089-
assert.Zero(t, "")
1109+
assert.NotEmpty(t, "value") // Any string literal.
1110+
assert.NotImplements(t, (*any)(nil), new(Conn))
1111+
assert.NotNil(t, nil)
1112+
assert.NotZero(t, 42) // Any int literal.
1113+
assert.NotZero(t, "value") // Any string literal.
1114+
assert.NotZero(t, nil)
1115+
assert.NotZero(t, false) // Any bool literal.
1116+
assert.Positive(t, 42) // Any int literal.
1117+
assert.True(t, true) // Any bool literal.
1118+
assert.Zero(t, 42) // Any int literal.
1119+
assert.Zero(t, "value") // Any string literal.
10901120
assert.Zero(t, nil)
1091-
1092-
assert.GreaterOrEqual(t, uintVal, 0)
1093-
assert.LessOrEqual(t, 0, uintVal)
1094-
assert.GreaterOrEqual(t, len(x), 0)
1095-
assert.LessOrEqual(t, 0, len(x))
1121+
assert.Zero(t, false) // Any bool literal.
1122+
1123+
assert.Negative(len(x))
1124+
assert.Less(len(x), 0)
1125+
assert.Greater(0, len(x))
1126+
assert.Positive(len(x))
1127+
assert.GreaterOrEqual(len(x), 0)
1128+
assert.LessOrEqual(0, len(x))
1129+
1130+
assert.Negative(uintVal)
1131+
assert.Less(uintVal, 0)
1132+
assert.Greater(0, uintVal)
1133+
assert.Positive(uintVal)
1134+
assert.GreaterOrEqual(uintVal, 0)
1135+
assert.LessOrEqual(0, uintVal)
10961136
```
10971137

10981138
**Autofix**: false. <br>

analyzer/testdata/src/checkers-default/empty/empty_test.go

+492-70
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

analyzer/testdata/src/checkers-default/empty/empty_test.go.golden

+436-14
Large diffs are not rendered by default.

analyzer/testdata/src/checkers-default/useless-assert/useless_assert_test.go

+308-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/checkers/bool_compare.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.
4343
}
4444

4545
newUseFnDiagnostic := func(proposed string, survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
46-
if !isBuiltinBool(pass, survivingArg) {
46+
if !hasBoolType(pass, survivingArg) {
4747
if checker.ignoreCustomTypes {
4848
return nil
4949
}
@@ -65,7 +65,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.
6565
}
6666

6767
newNeedSimplifyDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
68-
if !isBuiltinBool(pass, survivingArg) {
68+
if !hasBoolType(pass, survivingArg) {
6969
if checker.ignoreCustomTypes {
7070
return nil
7171
}
@@ -103,15 +103,15 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.
103103
switch {
104104
case xor(t1, t2):
105105
survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1)
106-
if call.Fn.NameFTrimmed == "Exactly" && !isBuiltinBool(pass, survivingArg) {
106+
if call.Fn.NameFTrimmed == "Exactly" && !hasBoolType(pass, survivingArg) {
107107
// NOTE(a.telyshev): `Exactly` assumes no type conversion.
108108
return nil
109109
}
110110
return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End())
111111

112112
case xor(f1, f2):
113113
survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1)
114-
if call.Fn.NameFTrimmed == "Exactly" && !isBuiltinBool(pass, survivingArg) {
114+
if call.Fn.NameFTrimmed == "Exactly" && !hasBoolType(pass, survivingArg) {
115115
// NOTE(a.telyshev): `Exactly` assumes no type conversion.
116116
return nil
117117
}

internal/checkers/empty.go

+49-20
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,41 @@ import (
1111

1212
// Empty detects situations like
1313
//
14-
// assert.Len(t, arr, 0)
15-
// assert.Equal(t, 0, len(arr))
16-
// assert.EqualValues(t, 0, len(arr))
17-
// assert.Exactly(t, 0, len(arr))
18-
// assert.LessOrEqual(t, len(arr), 0)
19-
// assert.GreaterOrEqual(t, 0, len(arr))
20-
// assert.Less(t, len(arr), 0)
21-
// assert.Greater(t, 0, len(arr))
22-
// assert.Less(t, len(arr), 1)
23-
// assert.Greater(t, 1, len(arr))
24-
// assert.Zero(t, len(arr))
25-
// assert.Empty(t, len(arr))
14+
// assert.Len(t, arr, 0)
15+
// assert.Zero(t, str)
16+
// assert.Zero(t, len(arr))
17+
// assert.Equal(t, 0, len(arr))
18+
// assert.EqualValues(t, 0, len(arr))
19+
// assert.Exactly(t, 0, len(arr))
20+
// assert.LessOrEqual(t, len(arr), 0)
21+
// assert.GreaterOrEqual(t, 0, len(arr))
22+
// assert.Less(t, len(arr), 1)
23+
// assert.Greater(t, 1, len(arr))
24+
// assert.Equal(t, "", str)
25+
// assert.EqualValues(t, "", str)
26+
// assert.Exactly(t, "", str)
27+
// assert.Equal(t, “, str)
28+
// assert.EqualValues(t, “, str)
29+
// assert.Exactly(t, “, str)
2630
//
27-
// assert.NotEqual(t, 0, len(arr))
28-
// assert.NotEqualValues(t, 0, len(arr))
29-
// assert.Less(t, 0, len(arr))
30-
// assert.Greater(t, len(arr), 0)
31-
// assert.Positive(t, len(arr))
32-
// assert.NotZero(t, len(arr))
33-
// assert.NotEmpty(t, len(arr))
31+
// assert.Positive(t, len(arr))
32+
// assert.NotZero(t, str)
33+
// assert.NotZero(t, len(arr))
34+
// assert.NotEqual(t, 0, len(arr))
35+
// assert.NotEqualValues(t, 0, len(arr))
36+
// assert.Greater(t, len(arr), 0)
37+
// assert.Less(t, 0, len(arr))
38+
// assert.NotEqual(t, "", str)
39+
// assert.NotEqualValues(t, "", str)
40+
// assert.NotEqual(t, “, str)
41+
// assert.NotEqualValues(t, “, str)
3442
//
3543
// and requires
3644
//
3745
// assert.Empty(t, arr)
3846
// assert.NotEmpty(t, arr)
47+
//
48+
// Also Empty removes extra `len` call.
3949
type Empty struct{}
4050

4151
// NewEmpty constructs Empty checker.
@@ -67,6 +77,9 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D
6777

6878
switch call.Fn.NameFTrimmed {
6979
case "Zero":
80+
if hasStringType(pass, a) {
81+
return newUseEmptyDiagnostic(a.Pos(), a.End(), a)
82+
}
7083
if lenArg, ok := isBuiltinLenCall(pass, a); ok {
7184
return newUseEmptyDiagnostic(a.Pos(), a.End(), lenArg)
7285
}
@@ -89,6 +102,10 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D
89102
}
90103

91104
case "Equal", "EqualValues", "Exactly":
105+
if isEmptyStringLit(a) {
106+
return newUseEmptyDiagnostic(a.Pos(), b.End(), b)
107+
}
108+
92109
arg1, ok1 := isLenCallAndZero(pass, a, b)
93110
arg2, ok2 := isLenCallAndZero(pass, b, a)
94111

@@ -136,7 +153,15 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi
136153
a := call.Args[0]
137154

138155
switch call.Fn.NameFTrimmed {
139-
case "NotZero", "Positive":
156+
case "Positive":
157+
if lenArg, ok := isBuiltinLenCall(pass, a); ok {
158+
return newUseNotEmptyDiagnostic(a.Pos(), a.End(), lenArg)
159+
}
160+
161+
case "NotZero":
162+
if hasStringType(pass, a) {
163+
return newUseNotEmptyDiagnostic(a.Pos(), a.End(), a)
164+
}
140165
if lenArg, ok := isBuiltinLenCall(pass, a); ok {
141166
return newUseNotEmptyDiagnostic(a.Pos(), a.End(), lenArg)
142167
}
@@ -154,6 +179,10 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi
154179

155180
switch call.Fn.NameFTrimmed {
156181
case "NotEqual", "NotEqualValues":
182+
if isEmptyStringLit(a) {
183+
return newUseNotEmptyDiagnostic(a.Pos(), b.End(), b)
184+
}
185+
157186
arg1, ok1 := isLenCallAndZero(pass, a, b)
158187
arg2, ok2 := isLenCallAndZero(pass, b, a)
159188

internal/checkers/helpers_basic_type.go

+4-14
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,14 @@ func isIntNumber(e ast.Expr, rhs int) bool {
6161
return ok && (lhs == rhs)
6262
}
6363

64-
func isNegativeIntNumber(e ast.Expr) bool {
65-
v, ok := isIntBasicLit(e)
66-
return ok && v < 0
67-
}
68-
69-
func isPositiveIntNumber(e ast.Expr) bool {
70-
v, ok := isIntBasicLit(e)
71-
return ok && v > 0
72-
}
73-
74-
func isEmptyStringLit(e ast.Expr) bool {
64+
func isStringLit(e ast.Expr) bool {
7565
bl, ok := e.(*ast.BasicLit)
76-
return ok && bl.Kind == token.STRING && bl.Value == `""`
66+
return ok && bl.Kind == token.STRING
7767
}
7868

79-
func isNotEmptyStringLit(e ast.Expr) bool {
69+
func isEmptyStringLit(e ast.Expr) bool {
8070
bl, ok := e.(*ast.BasicLit)
81-
return ok && bl.Kind == token.STRING && bl.Value != `""`
71+
return ok && bl.Kind == token.STRING && (bl.Value == `""` || bl.Value == "``")
8272
}
8373

8474
func isBasicLit(e ast.Expr) bool {

internal/checkers/helpers_bool.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ var (
1414
trueObj = types.Universe.Lookup("true")
1515
)
1616

17+
func isUntypedBool(pass *analysis.Pass, e ast.Expr) bool {
18+
return isUntypedTrue(pass, e) || isUntypedFalse(pass, e)
19+
}
20+
1721
func isUntypedTrue(pass *analysis.Pass, e ast.Expr) bool {
1822
return analysisutil.IsObj(pass.TypesInfo, e, trueObj)
1923
}
@@ -22,7 +26,7 @@ func isUntypedFalse(pass *analysis.Pass, e ast.Expr) bool {
2226
return analysisutil.IsObj(pass.TypesInfo, e, falseObj)
2327
}
2428

25-
func isBuiltinBool(pass *analysis.Pass, e ast.Expr) bool {
29+
func hasBoolType(pass *analysis.Pass, e ast.Expr) bool {
2630
basicType, ok := pass.TypesInfo.TypeOf(e).(*types.Basic)
2731
return ok && basicType.Kind() == types.Bool
2832
}

0 commit comments

Comments
 (0)