Skip to content

Commit de820d7

Browse files
authored
formatter: support non-string-message checks (#221)
* formatter: warn on non-string single arg * formatter: warn on args after non-string single arg * formatter: support --formatter.allow-non-string-msg * formatter: checker refactoring * formatter: ignore calls with ellipsis * formatter: replace allow-non-string-msg with require-string-msg * formatter: introduce Fail/FailNow failure message check * formatter: describe Fail/FailNow failure message check in README * self-review fixes
1 parent a4092df commit de820d7

18 files changed

+543
-45
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ Describe a new checker in [checkers section](./README.md#checkers).
174174
assert.Equal(t, err.Error(), "user not found")
175175
assert.Equal(t, err, errSentinel) // Through `reflect.DeepEqual` causes error strings to be compared.
176176
assert.NotEqual(t, err, errSentinel)
177+
require.Error(t, fmt.Errorf("you need to specify either logGroupName or logGroupArn"), err) // grafana case
177178
// etc.
178179

179180
✅ assert.ErrorIs(t, err, ErrUserNotFound)

README.md

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ $ testifylint --enable-all --disable=empty,error-is-as ./...
7373
# Checkers configuration.
7474
$ testifylint --bool-compare.ignore-custom-types ./...
7575
$ testifylint --expected-actual.pattern=^wanted$ ./...
76-
$ testifylint --formatter.check-format-string --formatter.require-f-funcs ./...
76+
$ testifylint --formatter.check-format-string --formatter.require-f-funcs --formatter.require-string-msg ./...
7777
$ testifylint --go-require.ignore-http-handlers ./...
7878
$ testifylint --require-error.fn-pattern="^(Errorf?|NoErrorf?)$" ./...
7979
$ testifylint --suite-extra-assert-call.mode=require ./...
@@ -471,11 +471,12 @@ disable this feature, use `--formatter.check-format-string=false` flag.
471471

472472
#### 3)
473473

474-
Requirement of the f-assertions (e.g. assert.Equal**f**) if format string is used. Disabled by default, use
474+
Requirement of the f-assertions (e.g. assert.Equal**f**) if format string is used. Disabled by default, use
475475
`--formatter.require-f-funcs` flag to enable. <br>
476476

477477
This helps follow Go's implicit convention _"Printf-like functions must end with `f`"_ and sets the stage for moving to
478-
`v2` of `testify`. In this way the checker resembles the [goprintffuncname](https://github.com/jirfag/go-printf-func-name)
478+
`v2` of `testify`. In this way the checker resembles
479+
the [goprintffuncname](https://github.com/jirfag/go-printf-func-name)
479480
linter (included in [golangci-lint](https://golangci-lint.run/usage/linters/)). <br>
480481

481482
Also, verbs in the format string of f-assertions are highlighted by an IDE, e.g. GoLand:
@@ -485,7 +486,7 @@ Also, verbs in the format string of f-assertions are highlighted by an IDE, e.g.
485486
<br>
486487

487488
> [!CAUTION]
488-
> `--formatter.require-f-funcs` requires f-assertions, **even if there are no variable-length variables**, i.e. it
489+
> `--formatter.require-f-funcs` requires f-assertions, **even if there are no variable-length variables**, i.e. it
489490
> requires `require.NoErrorf` for both these cases:
490491
> ```
491492
> require.NoErrorf(t, err, "unexpected error")
@@ -566,6 +567,44 @@ in `v2` of `testify`.
566567

567568
</details>
568569

570+
#### 4)
571+
572+
Validating the first argument of `msgAndArgs` has `string` type (based on `testify`'s
573+
maintainer's [feedback](https://github.com/stretchr/testify/issues/1679#issuecomment-2480629257)):
574+
575+
```go
576+
577+
assert.Equal(t, 1, strings.Count(b.String(), "hello"), tc)
578+
579+
580+
assert.Equal(t, 1, strings.Count(b.String(), "hello"), "%+v", tc)
581+
```
582+
583+
You can disable this behaviour with the `--formatter.require-string-msg=false` flag.
584+
585+
#### 5)
586+
587+
Validating there are no arguments in `msgAndArgs` if message is not a string:
588+
589+
```go
590+
591+
assert.True(t, cco.IsCardNumber(valid), i, valid) // Causes panic.
592+
```
593+
594+
See [testify's issue](https://github.com/stretchr/testify/issues/1679) for details.
595+
596+
#### 6)
597+
598+
Finally, it checks that failure message in `Fail` and `FailNow` is not used as a format string (which won't work):
599+
600+
```go
601+
602+
assert.Fail(t, "test case [%d] failed. %+v != %+v", idx, tc.expected, actual) // Causes panic.
603+
604+
605+
assert.Fail(t, "good luck!", "test case [%d] failed. %+v != %+v", idx, tc.expected, actual)
606+
```
607+
569608
---
570609

571610
### go-require
@@ -981,6 +1020,7 @@ assert.False(t, num != num)
9811020
```
9821021

9831022
And against these meaningless assertions:
1023+
9841024
```go
9851025
assert.Empty(t, "")
9861026
assert.False(t, false)

Taskfile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ tasks:
5050
test:checker:
5151
deps: [ test:gen ]
5252
cmds:
53-
- go test -count 1 -run TestTestifyLint_CheckersDefault/{{.CLI_ARGS}} ./analyzer
53+
- go test -count 1 -run TestTestifyLint/{{.CLI_ARGS}} ./analyzer
5454

5555
test:coverage:
5656
env:

analyzer/analyzer_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ func TestTestifyLint_NotDefaultCases(t *testing.T) {
105105
"enable": checkers.NewFormatter().Name(),
106106
"formatter.check-format-string": "false",
107107
"formatter.require-f-funcs": "true",
108+
"formatter.require-string-msg": "false",
109+
},
110+
},
111+
{
112+
dir: "formatter-require-f-for-non-string-arg",
113+
flags: map[string]string{
114+
"disable-all": "true",
115+
"enable": checkers.NewFormatter().Name(),
116+
"formatter.require-f-funcs": "true",
108117
},
109118
},
110119
{

analyzer/checkers_factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.Advan
5858
case *checkers.Formatter:
5959
c.SetCheckFormatString(cfg.Formatter.CheckFormatString)
6060
c.SetRequireFFuncs(cfg.Formatter.RequireFFuncs)
61+
c.SetRequireStringMsg(cfg.Formatter.RequireStringMsg)
6162

6263
case *checkers.GoRequire:
6364
c.SetIgnoreHTTPHandlers(cfg.GoRequire.IgnoreHTTPHandlers)

analyzer/checkers_factory_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ func Test_newCheckers(t *testing.T) {
6969
checkers.NewSuiteTHelper(),
7070
}
7171

72-
zeroedFormatter := checkers.RegularChecker(checkers.NewFormatter().
72+
formatterWithoutEnabledOptions := checkers.RegularChecker(checkers.NewFormatter().
7373
SetCheckFormatString(false).
74-
SetRequireFFuncs(false))
74+
SetRequireFFuncs(false).
75+
SetRequireStringMsg(false))
7576

7677
cases := []struct {
7778
name string
@@ -82,7 +83,7 @@ func Test_newCheckers(t *testing.T) {
8283
{
8384
name: "no config",
8485
cfg: config.Config{},
85-
expRegular: replace(enabledByDefaultRegularCheckers, zeroedFormatter),
86+
expRegular: replace(enabledByDefaultRegularCheckers, formatterWithoutEnabledOptions),
8687
expAdvanced: enabledByDefaultAdvancedCheckers,
8788
},
8889
{
@@ -116,7 +117,7 @@ func Test_newCheckers(t *testing.T) {
116117
checkers.NewSuiteTHelper().Name(),
117118
},
118119
},
119-
expRegular: filter(replace(allRegularCheckers, zeroedFormatter), config.KnownCheckersValue{
120+
expRegular: filter(replace(allRegularCheckers, formatterWithoutEnabledOptions), config.KnownCheckersValue{
120121
checkers.NewRequireError().Name(),
121122
checkers.NewSuiteTHelper().Name(),
122123
}),
@@ -132,7 +133,7 @@ func Test_newCheckers(t *testing.T) {
132133
checkers.NewSuiteTHelper().Name(),
133134
},
134135
},
135-
expRegular: replace(enabledByDefaultRegularCheckers, zeroedFormatter),
136+
expRegular: replace(enabledByDefaultRegularCheckers, formatterWithoutEnabledOptions),
136137
expAdvanced: allAdvancedCheckers,
137138
},
138139
{
@@ -144,7 +145,7 @@ func Test_newCheckers(t *testing.T) {
144145
checkers.NewRequireError().Name(),
145146
},
146147
},
147-
expRegular: filter(replace(enabledByDefaultRegularCheckers, zeroedFormatter), config.KnownCheckersValue{
148+
expRegular: filter(replace(enabledByDefaultRegularCheckers, formatterWithoutEnabledOptions), config.KnownCheckersValue{
148149
checkers.NewNilCompare().Name(),
149150
checkers.NewErrorNil().Name(),
150151
checkers.NewRequireError().Name(),

analyzer/testdata/src/checkers-default/formatter/formatter_test.go

Lines changed: 86 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)