diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2914942..1e993d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -142,7 +142,6 @@ Describe a new checker in [checkers section](./README.md#checkers). - [http-sugar](#http-sugar) - [require-len](#require-len) - [suite-test-name](#suite-test-name) -- [useless-assert](#useless-assert) --- @@ -367,26 +366,5 @@ func (s *HandlersSuite) Test_UsecaseSuccess() --- -### useless-assert - -Support more complex cases, e.g. - -```go -body, err := io.ReadAll(rr.Body) -require.NoError(t, err) -require.NoError(t, err) ❌ - -expectedJSON, err := json.Marshal(expected) -require.NoError(t, err) -require.JSONEq(t, string(expectedJSON), string(body)) -``` - -```go -require.NoError(t, err) -assert.ErrorContains(t, err, "user") ❌ -``` - ---- - Any other figments of your imagination are welcome 🙏
List of `testify` functions [here](https://pkg.go.dev/github.com/stretchr/testify@master/assert#pkg-functions). diff --git a/README.md b/README.md index 1496d40..b1c6380 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ https://golangci-lint.run/usage/linters/#testifylint | [compares](#compares) | ✅ | ✅ | | [contains](#contains) | ✅ | ✅ | | [empty](#empty) | ✅ | ✅ | +| [encoded-compare](#encoded-compare) | ✅ | ✅ | | [error-is-as](#error-is-as) | ✅ | 🤏 | | [error-nil](#error-nil) | ✅ | ✅ | | [expected-actual](#expected-actual) | ✅ | ✅ | @@ -274,6 +275,39 @@ assert.NotEmpty(t, err) --- +### encoded-compare + +```go +❌ +assert.Equal(t, `{"foo": "bar"}`, body) +assert.EqualValues(t, `{"foo": "bar"}`, body) +assert.Exactly(t, `{"foo": "bar"}`, body) +assert.Equal(t, expectedJSON, resultJSON) +assert.Equal(t, expBodyConst, w.Body.String()) +assert.Equal(t, fmt.Sprintf(`{"value":"%s"}`, hexString), result) +assert.Equal(t, "{}", json.RawMessage(resp)) +assert.Equal(t, expJSON, strings.Trim(string(resultJSONBytes), "\n")) // + Replace, ReplaceAll, TrimSpace + +assert.Equal(t, expectedYML, conf) + +✅ +assert.JSONEq(t, `{"foo": "bar"}`, body) +assert.YAMLEq(t, expectedYML, conf) +``` + +**Autofix**: true.
+**Enabled by default**: true.
+**Reason**: Protection from bugs and more appropriate `testify` API with clearer failure message. + +`encoded-compare` detects JSON-style string constants (usable in `fmt.Sprintf` also) and JSON-style/YAML-style named +variables. If variable is converted to `json.RawMessage`, then it is considered JSON unconditionally. + +When fixing, `encoded-compare` removes unnecessary conversions to `[]byte`, `string`, `json.RawMessage` and calls of +`strings.Replace`, `strings.ReplaceAll`, `strings.Trim`, `strings.TrimSpace`, and adds a conversion to `string` when +needed. + +--- + ### error-is-as ```go @@ -654,7 +688,7 @@ assert.Equal(t, (chan Event)(nil), eventsChan) assert.NotEqual(t, (chan Event)(nil), eventsChan) ``` -But in the case of `Equal`, `NotEqual` and `Exactly` type casting approach still doesn't work for the function type. +But in the case of `Equal`, `NotEqual` and `Exactly` type conversion approach still doesn't work for the function type. The best option here is to just use `Nil` / `NotNil` (see [details](https://github.com/stretchr/testify/issues/1524)). @@ -884,22 +918,51 @@ a [checkers.AdvancedChecker](https://github.com/Antonboom/testifylint/blob/67632 ### useless-assert -Currently the checker guards against assertion of the same variable: +The checker guards against assertion of the same variable: ```go -❌ +assert.Contains(t, tt.value, tt.value) +assert.ElementsMatch(t, tt.value, tt.value) assert.Equal(t, tt.value, tt.value) -assert.ElementsMatch(t, users, users) -// And so on... +assert.EqualExportedValues(t, tt.value, tt.value) +// And other assert functions... + assert.True(t, num > num) +assert.True(t, num < num) +assert.True(t, num >= num) +assert.True(t, num <= num) +assert.True(t, num == num) +assert.True(t, num != num) + +assert.False(t, num > num) +assert.False(t, num < num) +assert.False(t, num >= num) +assert.False(t, num <= num) assert.False(t, num == num) +assert.False(t, num != num) ``` -More complex cases are [open for contribution](CONTRIBUTING.md#useless-assert). +And against these meaningless assertions: +```go +assert.Empty(t, "") +assert.False(t, false) +assert.Implements(t, (*any)(nil), new(Conn)) +assert.Negative(t, -42) +assert.Nil(t, nil) +assert.NoError(t, nil) +assert.NotEmpty(t, "value") +assert.NotZero(t, 42) +assert.NotZero(t, "value") +assert.Positive(t, 42) +assert.True(t, true) +assert.Zero(t, 0) +assert.Zero(t, "") +assert.Zero(t, nil) +``` **Autofix**: false.
**Enabled by default**: true.
-**Reason**: Protection from bugs and possible dead code. +**Reason**: Protection from bugs and dead code. --- diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go index 281f352..695c4e3 100644 --- a/analyzer/analyzer_test.go +++ b/analyzer/analyzer_test.go @@ -13,6 +13,29 @@ import ( func TestTestifyLint(t *testing.T) { t.Parallel() + for _, checker := range checkers.All() { + checker := checker + + t.Run(checker, func(t *testing.T) { + t.Parallel() + + anlzr := analyzer.New() + if err := anlzr.Flags.Set("disable-all", "true"); err != nil { + t.Fatal(err) + } + if err := anlzr.Flags.Set("enable", checker); err != nil { + t.Fatal(err) + } + + pkg := filepath.Join("checkers-default", checker) + analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), anlzr, pkg) + }) + } +} + +func TestTestifyLint_NotDefaultCases(t *testing.T) { + t.Parallel() + cases := []struct { dir string flags map[string]string @@ -53,6 +76,20 @@ func TestTestifyLint(t *testing.T) { "expected-actual.pattern": "goldenValue", }, }, + { + dir: "formatter-issue170", + flags: map[string]string{ + "disable-all": "true", + "enable": checkers.NewFormatter().Name(), + }, + }, + { + dir: "formatter-issue170-suite", + flags: map[string]string{ + "disable-all": "true", + "enable": checkers.NewFormatter().Name(), + }, + }, { dir: "formatter-not-defaults", flags: map[string]string{ @@ -129,26 +166,3 @@ func TestTestifyLint(t *testing.T) { }) } } - -func TestTestifyLint_CheckersDefault(t *testing.T) { - t.Parallel() - - for _, checker := range checkers.All() { - checker := checker - - t.Run(checker, func(t *testing.T) { - t.Parallel() - - anlzr := analyzer.New() - if err := anlzr.Flags.Set("disable-all", "true"); err != nil { - t.Fatal(err) - } - if err := anlzr.Flags.Set("enable", checker); err != nil { - t.Fatal(err) - } - - pkg := filepath.Join("checkers-default", checker) - analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), anlzr, pkg) - }) - } -} diff --git a/analyzer/checkers_factory_test.go b/analyzer/checkers_factory_test.go index 6533494..28bfb1d 100644 --- a/analyzer/checkers_factory_test.go +++ b/analyzer/checkers_factory_test.go @@ -25,6 +25,7 @@ func Test_newCheckers(t *testing.T) { checkers.NewErrorNil(), checkers.NewNilCompare(), checkers.NewErrorIsAs(), + checkers.NewEncodedCompare(), checkers.NewExpectedActual(), checkers.NewRegexp(), checkers.NewSuiteExtraAssertCall(), @@ -43,6 +44,7 @@ func Test_newCheckers(t *testing.T) { checkers.NewErrorNil(), checkers.NewNilCompare(), checkers.NewErrorIsAs(), + checkers.NewEncodedCompare(), checkers.NewExpectedActual(), checkers.NewRegexp(), checkers.NewSuiteExtraAssertCall(), diff --git a/analyzer/testdata/src/checkers-default/empty/empty_test.go b/analyzer/testdata/src/checkers-default/empty/empty_test.go index d20e16a..88c624e 100644 --- a/analyzer/testdata/src/checkers-default/empty/empty_test.go +++ b/analyzer/testdata/src/checkers-default/empty/empty_test.go @@ -38,8 +38,12 @@ func TestEmptyChecker(t *testing.T) { assert.Greaterf(t, 1, len(elems), "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" assert.Zero(t, len(elems)) // want "empty: use assert\\.Empty" assert.Zerof(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, len(elems)) // want "empty: use assert\\.Empty" - assert.Emptyf(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Zero(t, len([]string{"e"})) // want "empty: use assert\\.Empty" + assert.Zerof(t, len([]string{"e"}), "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, len(elems)) // want "empty: remove unnecessary len" + assert.Emptyf(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" + assert.Empty(t, len([]string{"e"})) // want "empty: remove unnecessary len" + assert.Emptyf(t, len([]string{"e"}), "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" assert.Less(t, len(elems), 0) // want "empty: use assert\\.Empty" assert.Lessf(t, len(elems), 0, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" assert.Greater(t, 0, len(elems)) // want "empty: use assert\\.Empty" @@ -73,8 +77,12 @@ func TestEmptyChecker(t *testing.T) { assert.Positivef(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" assert.NotZero(t, len(elems)) // want "empty: use assert\\.NotEmpty" assert.NotZerof(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, len(elems)) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotZero(t, len([]string{"e"})) // want "empty: use assert\\.NotEmpty" + assert.NotZerof(t, len([]string{"e"}), "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, len(elems)) // want "empty: remove unnecessary len" + assert.NotEmptyf(t, len(elems), "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" + assert.NotEmpty(t, len([]string{"e"})) // want "empty: remove unnecessary len" + assert.NotEmptyf(t, len([]string{"e"}), "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" // Valid. assert.NotEmpty(t, elems) diff --git a/analyzer/testdata/src/checkers-default/empty/empty_test.go.golden b/analyzer/testdata/src/checkers-default/empty/empty_test.go.golden index e6eb42c..958a3a1 100644 --- a/analyzer/testdata/src/checkers-default/empty/empty_test.go.golden +++ b/analyzer/testdata/src/checkers-default/empty/empty_test.go.golden @@ -14,40 +14,44 @@ func TestEmptyChecker(t *testing.T) { // assert.Empty cases. { // Invalid. - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" - assert.Empty(t, elems) // want "empty: use assert\\.Empty" - assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, []string{"e"}) // want "empty: use assert\\.Empty" + assert.Emptyf(t, []string{"e"}, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: remove unnecessary len" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" + assert.Empty(t, []string{"e"}) // want "empty: remove unnecessary len" + assert.Emptyf(t, []string{"e"}, "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" + assert.Empty(t, elems) // want "empty: use assert\\.Empty" + assert.Emptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.Emptyf" // Valid. assert.Empty(t, elems) @@ -57,24 +61,28 @@ func TestEmptyChecker(t *testing.T) { // assert.NotEmpty cases. { // Invalid. - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" - assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" - assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, []string{"e"}) // want "empty: use assert\\.NotEmpty" + assert.NotEmptyf(t, []string{"e"}, "msg with args %d %s", 42, "42") // want "empty: use assert\\.NotEmptyf" + assert.NotEmpty(t, elems) // want "empty: remove unnecessary len" + assert.NotEmptyf(t, elems, "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" + assert.NotEmpty(t, []string{"e"}) // want "empty: remove unnecessary len" + assert.NotEmptyf(t, []string{"e"}, "msg with args %d %s", 42, "42") // want "empty: remove unnecessary len" // Valid. assert.NotEmpty(t, elems) diff --git a/analyzer/testdata/src/checkers-default/encoded-compare/encoded_compare_test.go b/analyzer/testdata/src/checkers-default/encoded-compare/encoded_compare_test.go new file mode 100644 index 0000000..d4c9c6c --- /dev/null +++ b/analyzer/testdata/src/checkers-default/encoded-compare/encoded_compare_test.go @@ -0,0 +1,198 @@ +// Code generated by testifylint/internal/testgen. DO NOT EDIT. + +package encodedcompare + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncodedCompareChecker(t *testing.T) { + var respBody, raw, hexString, toJSON, expJSON, resultJSON, jsonb, resJson string + var conf, expectedYAML, expYaml, ymlResult, yamlResult, expYML, outputYaml string + var respBytes, resultJSONBytes []byte + w := httptest.NewRecorder() + var batch interface{ ParentSummary() []byte } + var res [1]struct{ Data []byte } + var output bytes.Buffer + + const expBody = `{"status":"healthy","message":"","peer_count":1}` + + // Invalid. + { + assert.Equal(t, `{"name":"name","value":1000}`, respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `{"name":"name","value":1000}`, respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, expBody, respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, expBody, respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, `{"status":404,"message":"abc"}`, string(respBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `{"status":404,"message":"abc"}`, string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, `{"message":"success"}`, w.Body.String()) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `{"message":"success"}`, w.Body.String(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", string(w.Body.Bytes())) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", string(w.Body.Bytes()), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, "{\n\t\"msg\": \"hello world\"\n}", respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, "{\n\t\"msg\": \"hello world\"\n}", respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, fmt.Sprintf(`{"value":"%s","valuePtr":"%s"}`, hexString, hexString), string(respBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, fmt.Sprintf(`{"value":"%s","valuePtr":"%s"}`, hexString, hexString), string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, `[{"@id":"a","b":[{"@id":"c"}]}]`, toJSON) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `[{"@id":"a","b":[{"@id":"c"}]}]`, toJSON, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, "{\"FirstName\":\"john\",\"LastName\":\"doe\",\"Age\":26,\"Height\":182.88}", string(resJson)) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, "{\"FirstName\":\"john\",\"LastName\":\"doe\",\"Age\":26,\"Height\":182.88}", string(resJson), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, expJSON, resultJSON) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, expJSON, resultJSON, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, jsonb, respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, jsonb, respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, respBody, jsonb) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, respBody, jsonb, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, expJSON, resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, expJSON, resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, expectedYAML, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, expectedYAML, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, expYaml, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, expYaml, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, ymlResult, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, ymlResult, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, yamlResult, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, yamlResult, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, expYML, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, expYML, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, conf, expectedYAML) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, conf, expectedYAML, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, outputYaml, string(output.Bytes())) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, outputYaml, string(output.Bytes()), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, json.RawMessage(`{"uuid": "b65b1a22-db6d-4f5a-9b3d-7302368a82e6"}`), batch.ParentSummary()) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, json.RawMessage(`{"uuid": "b65b1a22-db6d-4f5a-9b3d-7302368a82e6"}`), batch.ParentSummary(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, res[0].Data, json.RawMessage([]byte(`{"name":"new"}`))) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, res[0].Data, json.RawMessage([]byte(`{"name":"new"}`)), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, json.RawMessage(raw), json.RawMessage(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, json.RawMessage(raw), json.RawMessage(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, json.RawMessage(raw), raw) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, json.RawMessage(raw), raw, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, json.RawMessage("{}"), respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, json.RawMessage("{}"), respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, respBody, json.RawMessage("null")) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, respBody, json.RawMessage("null"), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, json.RawMessage(`["more","raw","things"]`), resultJSONBytes) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, json.RawMessage(`["more","raw","things"]`), resultJSONBytes, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, "{}", string(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, "{}", string(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, []byte(expJSON), resultJSONBytes) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, []byte(expJSON), resultJSONBytes, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, []byte(expYaml), respBytes) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, []byte(expYaml), respBytes, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, expJSON, strings.Trim(string(resultJSONBytes), "\n")) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, expJSON, strings.Trim(string(resultJSONBytes), "\n"), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, raw, strings.Replace(jsonb, "\n", "", -1)) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, raw, strings.Replace(jsonb, "\n", "", -1), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, `{"status":"healthy","message":"","peer_count":1}`, strings.ReplaceAll(string(respBytes), " ", "")) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `{"status":"healthy","message":"","peer_count":1}`, strings.ReplaceAll(string(respBytes), " ", ""), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, `{"foo":"bar"}`, strings.TrimSpace(w.Body.String())) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `{"foo":"bar"}`, strings.TrimSpace(w.Body.String()), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, `{"bar":"foo"}`, strings.TrimSpace(string(w.Body.Bytes()))) // want "encoded-compare: use assert\\.JSONEq" + assert.Equalf(t, `{"bar":"foo"}`, strings.TrimSpace(string(w.Body.Bytes())), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Equal(t, strings.TrimSpace(strings.ReplaceAll(expYaml, "\t", " ")), strings.TrimSpace(string(respBytes))) // want "encoded-compare: use assert\\.YAMLEq" + assert.Equalf(t, strings.TrimSpace(strings.ReplaceAll(expYaml, "\t", " ")), strings.TrimSpace(string(respBytes)), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.EqualValues(t, expJSON, resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.EqualValuesf(t, expJSON, resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.Exactly(t, expJSON, resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.Exactlyf(t, expJSON, resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.EqualValues(t, expYaml, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.EqualValuesf(t, expYaml, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Exactly(t, expYaml, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.Exactlyf(t, expYaml, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, ` // want "encoded-compare: use assert\.JSONEq" +{ + "id": 123, + "method": "get_prop", + "params": ["power","sat"] +} +`, raw) + assert.Equal(t, ` // want "encoded-compare: use assert\.JSONEq" +{ + "id": 123, + "method": "get_prop", + "params": ["power","sat"] +} +`, raw, "msg with args %d %s", 42, "42") + } + + // Valid. + { + assert.JSONEq(t, `{"name":"name","value":1000}`, respBody) + assert.JSONEqf(t, `{"name":"name","value":1000}`, respBody, "msg with args %d %s", 42, "42") + assert.JSONEq(t, expJSON, resultJSON) + assert.JSONEqf(t, expJSON, resultJSON, "msg with args %d %s", 42, "42") + assert.JSONEq(t, `{"foo":"bar"}`, `{"foo":"bar"}`) + assert.JSONEqf(t, `{"foo":"bar"}`, `{"foo":"bar"}`, "msg with args %d %s", 42, "42") + assert.JSONEq(t, `{"message":"success"}`, w.Body.String()) + assert.JSONEqf(t, `{"message":"success"}`, w.Body.String(), "msg with args %d %s", 42, "42") + assert.JSONEq(t, fmt.Sprintf(`{"value":"%s"}`, hexString), resJson) + assert.JSONEqf(t, fmt.Sprintf(`{"value":"%s"}`, hexString), resJson, "msg with args %d %s", 42, "42") + assert.JSONEq(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String()) + assert.JSONEqf(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String(), "msg with args %d %s", 42, "42") + assert.YAMLEq(t, expYaml, conf) + assert.YAMLEqf(t, expYaml, conf, "msg with args %d %s", 42, "42") + } + + // Ignored. + { + assert.Equal(t, "{{ .StepName }}", "use", "command name incorrect") + assert.Equalf(t, "{{ .StepName }}", "use", "command name incorrect", "msg with args %d %s", 42, "42") + assert.Equal(t, json.RawMessage{}, respBody) + assert.Equalf(t, json.RawMessage{}, respBody, "msg with args %d %s", 42, "42") + assert.Equal(t, json.RawMessage(nil), respBody) + assert.Equalf(t, json.RawMessage(nil), respBody, "msg with args %d %s", 42, "42") + assert.Equal(t, raw, raw) + assert.Equalf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.EqualValues(t, raw, raw) + assert.EqualValuesf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.Exactly(t, raw, raw) + assert.Exactlyf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.JSONEq(t, raw, raw) + assert.JSONEqf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.Equal(t, string(respBytes), raw) + assert.Equalf(t, string(respBytes), raw, "msg with args %d %s", 42, "42") + assert.EqualValues(t, raw, string(respBytes)) + assert.EqualValuesf(t, raw, string(respBytes), "msg with args %d %s", 42, "42") + assert.Exactly(t, string(respBytes), raw) + assert.Exactlyf(t, string(respBytes), raw, "msg with args %d %s", 42, "42") + assert.JSONEq(t, raw, string(respBytes)) + assert.JSONEqf(t, raw, string(respBytes), "msg with args %d %s", 42, "42") + assert.NotEqual(t, raw, resultJSON) + assert.NotEqualf(t, raw, resultJSON, "msg with args %d %s", 42, "42") + assert.NotEqualValues(t, resultJSON, resultJSON) + assert.NotEqualValuesf(t, resultJSON, resultJSON, "msg with args %d %s", 42, "42") + assert.YAMLEq(t, ` +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 +images: + - name: foo + newName: bar + - name: bar + newName: baz + newTag: "123" +`, conf) + assert.YAMLEqf(t, ` +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 +images: + - name: foo + newName: bar + - name: bar + newName: baz + newTag: "123" +`, conf, "msg with args %d %s", 42, "42") + assert.YAMLEq(t, "kind: Kustomization", "kind: Kustomization") + assert.YAMLEqf(t, "kind: Kustomization", "kind: Kustomization", "msg with args %d %s", 42, "42") + assert.YAMLEq(t, raw, conf) + assert.YAMLEqf(t, raw, conf, "msg with args %d %s", 42, "42") + assert.YAMLEq(t, raw, string(respBytes)) + assert.YAMLEqf(t, raw, string(respBytes), "msg with args %d %s", 42, "42") + } +} diff --git a/analyzer/testdata/src/checkers-default/encoded-compare/encoded_compare_test.go.golden b/analyzer/testdata/src/checkers-default/encoded-compare/encoded_compare_test.go.golden new file mode 100644 index 0000000..f031d71 --- /dev/null +++ b/analyzer/testdata/src/checkers-default/encoded-compare/encoded_compare_test.go.golden @@ -0,0 +1,198 @@ +// Code generated by testifylint/internal/testgen. DO NOT EDIT. + +package encodedcompare + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncodedCompareChecker(t *testing.T) { + var respBody, raw, hexString, toJSON, expJSON, resultJSON, jsonb, resJson string + var conf, expectedYAML, expYaml, ymlResult, yamlResult, expYML, outputYaml string + var respBytes, resultJSONBytes []byte + w := httptest.NewRecorder() + var batch interface{ ParentSummary() []byte } + var res [1]struct{ Data []byte } + var output bytes.Buffer + + const expBody = `{"status":"healthy","message":"","peer_count":1}` + + // Invalid. + { + assert.JSONEq(t, `{"name":"name","value":1000}`, respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"name":"name","value":1000}`, respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, expBody, respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expBody, respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `{"status":404,"message":"abc"}`, string(respBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"status":404,"message":"abc"}`, string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `{"message":"success"}`, w.Body.String()) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"message":"success"}`, w.Body.String(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String()) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, "{\n\t\"msg\": \"hello world\"\n}", respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, "{\n\t\"msg\": \"hello world\"\n}", respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, fmt.Sprintf(`{"value":"%s","valuePtr":"%s"}`, hexString, hexString), string(respBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, fmt.Sprintf(`{"value":"%s","valuePtr":"%s"}`, hexString, hexString), string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `[{"@id":"a","b":[{"@id":"c"}]}]`, toJSON) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `[{"@id":"a","b":[{"@id":"c"}]}]`, toJSON, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, "{\"FirstName\":\"john\",\"LastName\":\"doe\",\"Age\":26,\"Height\":182.88}", resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, "{\"FirstName\":\"john\",\"LastName\":\"doe\",\"Age\":26,\"Height\":182.88}", resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, expJSON, resultJSON) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expJSON, resultJSON, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, jsonb, respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, jsonb, respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, respBody, jsonb) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, respBody, jsonb, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, expJSON, resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expJSON, resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.YAMLEq(t, expectedYAML, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expectedYAML, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, expYaml, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expYaml, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, ymlResult, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, ymlResult, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, yamlResult, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, yamlResult, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, expYML, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expYML, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, conf, expectedYAML) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, conf, expectedYAML, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, outputYaml, output.String()) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, outputYaml, output.String(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.JSONEq(t, `{"uuid": "b65b1a22-db6d-4f5a-9b3d-7302368a82e6"}`, string(batch.ParentSummary())) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"uuid": "b65b1a22-db6d-4f5a-9b3d-7302368a82e6"}`, string(batch.ParentSummary()), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, string(res[0].Data), `{"name":"new"}`) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, string(res[0].Data), `{"name":"new"}`, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, raw, string(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, raw, string(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, raw, raw) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, raw, raw, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, "{}", respBody) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, "{}", respBody, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, respBody, "null") // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, respBody, "null", "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `["more","raw","things"]`, string(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `["more","raw","things"]`, string(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, "{}", string(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, "{}", string(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, expJSON, string(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expJSON, string(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.YAMLEq(t, expYaml, string(respBytes)) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expYaml, string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.JSONEq(t, expJSON, string(resultJSONBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expJSON, string(resultJSONBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, raw, jsonb) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, raw, jsonb, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `{"status":"healthy","message":"","peer_count":1}`, string(respBytes)) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"status":"healthy","message":"","peer_count":1}`, string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"foo":"bar"}`, w.Body.String(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, `{"bar":"foo"}`, w.Body.String()) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, `{"bar":"foo"}`, w.Body.String(), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.YAMLEq(t, expYaml, string(respBytes)) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expYaml, string(respBytes), "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.JSONEq(t, expJSON, resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expJSON, resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.JSONEq(t, expJSON, resJson) // want "encoded-compare: use assert\\.JSONEq" + assert.JSONEqf(t, expJSON, resJson, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.JSONEqf" + assert.YAMLEq(t, expYaml, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expYaml, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.YAMLEq(t, expYaml, conf) // want "encoded-compare: use assert\\.YAMLEq" + assert.YAMLEqf(t, expYaml, conf, "msg with args %d %s", 42, "42") // want "encoded-compare: use assert\\.YAMLEqf" + assert.Equal(t, ` // want "encoded-compare: use assert\.JSONEq" +{ + "id": 123, + "method": "get_prop", + "params": ["power","sat"] +} +`, raw) + assert.Equal(t, ` // want "encoded-compare: use assert\.JSONEq" +{ + "id": 123, + "method": "get_prop", + "params": ["power","sat"] +} +`, raw, "msg with args %d %s", 42, "42") + } + + // Valid. + { + assert.JSONEq(t, `{"name":"name","value":1000}`, respBody) + assert.JSONEqf(t, `{"name":"name","value":1000}`, respBody, "msg with args %d %s", 42, "42") + assert.JSONEq(t, expJSON, resultJSON) + assert.JSONEqf(t, expJSON, resultJSON, "msg with args %d %s", 42, "42") + assert.JSONEq(t, `{"foo":"bar"}`, `{"foo":"bar"}`) + assert.JSONEqf(t, `{"foo":"bar"}`, `{"foo":"bar"}`, "msg with args %d %s", 42, "42") + assert.JSONEq(t, `{"message":"success"}`, w.Body.String()) + assert.JSONEqf(t, `{"message":"success"}`, w.Body.String(), "msg with args %d %s", 42, "42") + assert.JSONEq(t, fmt.Sprintf(`{"value":"%s"}`, hexString), resJson) + assert.JSONEqf(t, fmt.Sprintf(`{"value":"%s"}`, hexString), resJson, "msg with args %d %s", 42, "42") + assert.JSONEq(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String()) + assert.JSONEqf(t, "{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String(), "msg with args %d %s", 42, "42") + assert.YAMLEq(t, expYaml, conf) + assert.YAMLEqf(t, expYaml, conf, "msg with args %d %s", 42, "42") + } + + // Ignored. + { + assert.Equal(t, "{{ .StepName }}", "use", "command name incorrect") + assert.Equalf(t, "{{ .StepName }}", "use", "command name incorrect", "msg with args %d %s", 42, "42") + assert.Equal(t, json.RawMessage{}, respBody) + assert.Equalf(t, json.RawMessage{}, respBody, "msg with args %d %s", 42, "42") + assert.Equal(t, json.RawMessage(nil), respBody) + assert.Equalf(t, json.RawMessage(nil), respBody, "msg with args %d %s", 42, "42") + assert.Equal(t, raw, raw) + assert.Equalf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.EqualValues(t, raw, raw) + assert.EqualValuesf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.Exactly(t, raw, raw) + assert.Exactlyf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.JSONEq(t, raw, raw) + assert.JSONEqf(t, raw, raw, "msg with args %d %s", 42, "42") + assert.Equal(t, string(respBytes), raw) + assert.Equalf(t, string(respBytes), raw, "msg with args %d %s", 42, "42") + assert.EqualValues(t, raw, string(respBytes)) + assert.EqualValuesf(t, raw, string(respBytes), "msg with args %d %s", 42, "42") + assert.Exactly(t, string(respBytes), raw) + assert.Exactlyf(t, string(respBytes), raw, "msg with args %d %s", 42, "42") + assert.JSONEq(t, raw, string(respBytes)) + assert.JSONEqf(t, raw, string(respBytes), "msg with args %d %s", 42, "42") + assert.NotEqual(t, raw, resultJSON) + assert.NotEqualf(t, raw, resultJSON, "msg with args %d %s", 42, "42") + assert.NotEqualValues(t, resultJSON, resultJSON) + assert.NotEqualValuesf(t, resultJSON, resultJSON, "msg with args %d %s", 42, "42") + assert.YAMLEq(t, ` +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 +images: + - name: foo + newName: bar + - name: bar + newName: baz + newTag: "123" +`, conf) + assert.YAMLEqf(t, ` +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 +images: + - name: foo + newName: bar + - name: bar + newName: baz + newTag: "123" +`, conf, "msg with args %d %s", 42, "42") + assert.YAMLEq(t, "kind: Kustomization", "kind: Kustomization") + assert.YAMLEqf(t, "kind: Kustomization", "kind: Kustomization", "msg with args %d %s", 42, "42") + assert.YAMLEq(t, raw, conf) + assert.YAMLEqf(t, raw, conf, "msg with args %d %s", 42, "42") + assert.YAMLEq(t, raw, string(respBytes)) + assert.YAMLEqf(t, raw, string(respBytes), "msg with args %d %s", 42, "42") + } +} diff --git a/analyzer/testdata/src/checkers-default/useless-assert/useless_assert_test.go b/analyzer/testdata/src/checkers-default/useless-assert/useless_assert_test.go index 8781eeb..14e4af1 100644 --- a/analyzer/testdata/src/checkers-default/useless-assert/useless_assert_test.go +++ b/analyzer/testdata/src/checkers-default/useless-assert/useless_assert_test.go @@ -15,6 +15,7 @@ func TestUselessAssertChecker(t *testing.T) { var elapsed time.Time var str string var num int + var b bool var tc testCase // Invalid. @@ -61,6 +62,104 @@ func TestUselessAssertChecker(t *testing.T) { assert.IsTypef(t, (*testCase)(nil), (*testCase)(nil), "msg") // want "useless-assert: asserting of the same variable" assert.IsTypef(t, (*testCase)(nil), (*testCase)(nil), "msg with arg %d", 42) // want "useless-assert: asserting of the same variable" assert.IsTypef(t, (*testCase)(nil), (*testCase)(nil), "msg with args %d %s", 42, "42") // want "useless-assert: asserting of the same variable" + assert.Empty(t, "") // want "useless-assert: meaningless assertion" + assert.Empty(t, "", "msg") // want "useless-assert: meaningless assertion" + assert.Empty(t, "", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Empty(t, "", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Emptyf(t, "", "msg") // want "useless-assert: meaningless assertion" + assert.Emptyf(t, "", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Emptyf(t, "", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.False(t, false) // want "useless-assert: meaningless assertion" + assert.False(t, false, "msg") // want "useless-assert: meaningless assertion" + assert.False(t, false, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.False(t, false, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Falsef(t, false, "msg") // want "useless-assert: meaningless assertion" + assert.Falsef(t, false, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Falsef(t, false, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Implements(t, (*any)(nil), new(testing.T)) // want "useless-assert: meaningless assertion" + assert.Implements(t, (*any)(nil), new(testing.T), "msg") // want "useless-assert: meaningless assertion" + assert.Implements(t, (*any)(nil), new(testing.T), "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Implements(t, (*any)(nil), new(testing.T), "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Implementsf(t, (*any)(nil), new(testing.T), "msg") // want "useless-assert: meaningless assertion" + assert.Implementsf(t, (*any)(nil), new(testing.T), "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Implementsf(t, (*any)(nil), new(testing.T), "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Negative(t, -42) // want "useless-assert: meaningless assertion" + assert.Negative(t, -42, "msg") // want "useless-assert: meaningless assertion" + assert.Negative(t, -42, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Negative(t, -42, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Negativef(t, -42, "msg") // want "useless-assert: meaningless assertion" + assert.Negativef(t, -42, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Negativef(t, -42, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Nil(t, nil) // want "useless-assert: meaningless assertion" + assert.Nil(t, nil, "msg") // want "useless-assert: meaningless assertion" + assert.Nil(t, nil, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Nil(t, nil, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Nilf(t, nil, "msg") // want "useless-assert: meaningless assertion" + assert.Nilf(t, nil, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Nilf(t, nil, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NoError(t, nil) // want "useless-assert: meaningless assertion" + assert.NoError(t, nil, "msg") // want "useless-assert: meaningless assertion" + assert.NoError(t, nil, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NoError(t, nil, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NoErrorf(t, nil, "msg") // want "useless-assert: meaningless assertion" + assert.NoErrorf(t, nil, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NoErrorf(t, nil, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NotEmpty(t, "value") // want "useless-assert: meaningless assertion" + assert.NotEmpty(t, "value", "msg") // want "useless-assert: meaningless assertion" + assert.NotEmpty(t, "value", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NotEmpty(t, "value", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NotEmptyf(t, "value", "msg") // want "useless-assert: meaningless assertion" + assert.NotEmptyf(t, "value", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NotEmptyf(t, "value", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NotZero(t, 42) // want "useless-assert: meaningless assertion" + assert.NotZero(t, 42, "msg") // want "useless-assert: meaningless assertion" + assert.NotZero(t, 42, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NotZero(t, 42, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NotZerof(t, 42, "msg") // want "useless-assert: meaningless assertion" + assert.NotZerof(t, 42, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NotZerof(t, 42, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NotZero(t, "value") // want "useless-assert: meaningless assertion" + assert.NotZero(t, "value", "msg") // want "useless-assert: meaningless assertion" + assert.NotZero(t, "value", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NotZero(t, "value", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.NotZerof(t, "value", "msg") // want "useless-assert: meaningless assertion" + assert.NotZerof(t, "value", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.NotZerof(t, "value", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Positive(t, 42) // want "useless-assert: meaningless assertion" + assert.Positive(t, 42, "msg") // want "useless-assert: meaningless assertion" + assert.Positive(t, 42, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Positive(t, 42, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Positivef(t, 42, "msg") // want "useless-assert: meaningless assertion" + assert.Positivef(t, 42, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Positivef(t, 42, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.True(t, true) // want "useless-assert: meaningless assertion" + assert.True(t, true, "msg") // want "useless-assert: meaningless assertion" + assert.True(t, true, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.True(t, true, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Truef(t, true, "msg") // want "useless-assert: meaningless assertion" + assert.Truef(t, true, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Truef(t, true, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Zero(t, 0) // want "useless-assert: meaningless assertion" + assert.Zero(t, 0, "msg") // want "useless-assert: meaningless assertion" + assert.Zero(t, 0, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Zero(t, 0, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Zerof(t, 0, "msg") // want "useless-assert: meaningless assertion" + assert.Zerof(t, 0, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Zerof(t, 0, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Zero(t, "") // want "useless-assert: meaningless assertion" + assert.Zero(t, "", "msg") // want "useless-assert: meaningless assertion" + assert.Zero(t, "", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Zero(t, "", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Zerof(t, "", "msg") // want "useless-assert: meaningless assertion" + assert.Zerof(t, "", "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Zerof(t, "", "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Zero(t, nil) // want "useless-assert: meaningless assertion" + assert.Zero(t, nil, "msg") // want "useless-assert: meaningless assertion" + assert.Zero(t, nil, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Zero(t, nil, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" + assert.Zerof(t, nil, "msg") // want "useless-assert: meaningless assertion" + assert.Zerof(t, nil, "msg with arg %d", 42) // want "useless-assert: meaningless assertion" + assert.Zerof(t, nil, "msg with args %d %s", 42, "42") // want "useless-assert: meaningless assertion" assert.Contains(t, value, value) // want "useless-assert: asserting of the same variable" assert.Containsf(t, value, value, "msg with args %d %s", 42, "42") // want "useless-assert: asserting of the same variable" assert.ElementsMatch(t, value, value) // want "useless-assert: asserting of the same variable" @@ -186,6 +285,90 @@ func TestUselessAssertChecker(t *testing.T) { assert.IsTypef(t, tc, testCase{}, "msg") assert.IsTypef(t, tc, testCase{}, "msg with arg %d", 42) assert.IsTypef(t, tc, testCase{}, "msg with args %d %s", 42, "42") + assert.Empty(t, str) + assert.Empty(t, str, "msg") + assert.Empty(t, str, "msg with arg %d", 42) + assert.Empty(t, str, "msg with args %d %s", 42, "42") + assert.Emptyf(t, str, "msg") + assert.Emptyf(t, str, "msg with arg %d", 42) + assert.Emptyf(t, str, "msg with args %d %s", 42, "42") + assert.False(t, b) + assert.False(t, b, "msg") + assert.False(t, b, "msg with arg %d", 42) + assert.False(t, b, "msg with args %d %s", 42, "42") + assert.Falsef(t, b, "msg") + assert.Falsef(t, b, "msg with arg %d", 42) + assert.Falsef(t, b, "msg with args %d %s", 42, "42") + assert.Implements(t, (*testing.TB)(nil), new(testing.T)) + assert.Implements(t, (*testing.TB)(nil), new(testing.T), "msg") + assert.Implements(t, (*testing.TB)(nil), new(testing.T), "msg with arg %d", 42) + assert.Implements(t, (*testing.TB)(nil), new(testing.T), "msg with args %d %s", 42, "42") + assert.Implementsf(t, (*testing.TB)(nil), new(testing.T), "msg") + assert.Implementsf(t, (*testing.TB)(nil), new(testing.T), "msg with arg %d", 42) + assert.Implementsf(t, (*testing.TB)(nil), new(testing.T), "msg with args %d %s", 42, "42") + assert.Negative(t, num) + assert.Negative(t, num, "msg") + assert.Negative(t, num, "msg with arg %d", 42) + assert.Negative(t, num, "msg with args %d %s", 42, "42") + assert.Negativef(t, num, "msg") + assert.Negativef(t, num, "msg with arg %d", 42) + assert.Negativef(t, num, "msg with args %d %s", 42, "42") + assert.Nil(t, new(testCase)) + assert.Nil(t, new(testCase), "msg") + assert.Nil(t, new(testCase), "msg with arg %d", 42) + assert.Nil(t, new(testCase), "msg with args %d %s", 42, "42") + assert.Nilf(t, new(testCase), "msg") + assert.Nilf(t, new(testCase), "msg with arg %d", 42) + assert.Nilf(t, new(testCase), "msg with args %d %s", 42, "42") + assert.NoError(t, err) + assert.NoError(t, err, "msg") + assert.NoError(t, err, "msg with arg %d", 42) + assert.NoError(t, err, "msg with args %d %s", 42, "42") + assert.NoErrorf(t, err, "msg") + assert.NoErrorf(t, err, "msg with arg %d", 42) + assert.NoErrorf(t, err, "msg with args %d %s", 42, "42") + assert.NotEmpty(t, str) + assert.NotEmpty(t, str, "msg") + assert.NotEmpty(t, str, "msg with arg %d", 42) + assert.NotEmpty(t, str, "msg with args %d %s", 42, "42") + assert.NotEmptyf(t, str, "msg") + assert.NotEmptyf(t, str, "msg with arg %d", 42) + assert.NotEmptyf(t, str, "msg with args %d %s", 42, "42") + assert.Positive(t, num) + assert.Positive(t, num, "msg") + assert.Positive(t, num, "msg with arg %d", 42) + assert.Positive(t, num, "msg with args %d %s", 42, "42") + assert.Positivef(t, num, "msg") + assert.Positivef(t, num, "msg with arg %d", 42) + assert.Positivef(t, num, "msg with args %d %s", 42, "42") + assert.True(t, b) + assert.True(t, b, "msg") + assert.True(t, b, "msg with arg %d", 42) + assert.True(t, b, "msg with args %d %s", 42, "42") + assert.Truef(t, b, "msg") + assert.Truef(t, b, "msg with arg %d", 42) + assert.Truef(t, b, "msg with args %d %s", 42, "42") + assert.Zero(t, num) + assert.Zero(t, num, "msg") + assert.Zero(t, num, "msg with arg %d", 42) + assert.Zero(t, num, "msg with args %d %s", 42, "42") + assert.Zerof(t, num, "msg") + assert.Zerof(t, num, "msg with arg %d", 42) + assert.Zerof(t, num, "msg with args %d %s", 42, "42") + assert.Zero(t, str) + assert.Zero(t, str, "msg") + assert.Zero(t, str, "msg with arg %d", 42) + assert.Zero(t, str, "msg with args %d %s", 42, "42") + assert.Zerof(t, str, "msg") + assert.Zerof(t, str, "msg with arg %d", 42) + assert.Zerof(t, str, "msg with args %d %s", 42, "42") + assert.Zero(t, new(testCase)) + assert.Zero(t, new(testCase), "msg") + assert.Zero(t, new(testCase), "msg with arg %d", 42) + assert.Zero(t, new(testCase), "msg with args %d %s", 42, "42") + assert.Zerof(t, new(testCase), "msg") + assert.Zerof(t, new(testCase), "msg with arg %d", 42) + assert.Zerof(t, new(testCase), "msg with args %d %s", 42, "42") } } diff --git a/analyzer/testdata/src/debug/useless_assert_test.go b/analyzer/testdata/src/debug/useless_assert_test.go new file mode 100644 index 0000000..f54e1b6 --- /dev/null +++ b/analyzer/testdata/src/debug/useless_assert_test.go @@ -0,0 +1,25 @@ +package debug + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUselessAsserts(t *testing.T) { + assert.Empty(t, "") + assert.False(t, false) + assert.Implements(t, (*any)(nil), new(testing.T)) + assert.Negative(t, -42) + assert.Nil(t, nil) + assert.NoError(t, nil) + assert.NotEmpty(t, "value") + assert.NotZero(t, 42) + assert.NotZero(t, "value") + assert.Zero(t, nil) + assert.Positive(t, 42) + assert.True(t, true) + assert.Zero(t, 0) + assert.Zero(t, "") + assert.Zero(t, nil) +} diff --git a/analyzer/testdata/src/formatter-issue170-suite/suie_only_test.go b/analyzer/testdata/src/formatter-issue170-suite/suie_only_test.go new file mode 100644 index 0000000..e73ca94 --- /dev/null +++ b/analyzer/testdata/src/formatter-issue170-suite/suie_only_test.go @@ -0,0 +1,24 @@ +package formatterissue170 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestFormatter(t *testing.T) { + suite.Run(t, new(FormatterSuite)) +} + +type FormatterSuite struct { + suite.Suite +} + +func (s *FormatterSuite) TestFormatter() { + s.True(false, fmt.Sprintf("expected %v, got %v", true, false)) // want "formatter: remove unnecessary fmt\\.Sprintf" +} + +func (s FormatterSuite) TestFormatterValueRecv() { + s.False(true, fmt.Sprintf("expected %v, got %v", true, false)) // want "formatter: remove unnecessary fmt\\.Sprintf" +} diff --git a/analyzer/testdata/src/formatter-issue170-suite/suie_only_test.go.golden b/analyzer/testdata/src/formatter-issue170-suite/suie_only_test.go.golden new file mode 100644 index 0000000..32c6600 --- /dev/null +++ b/analyzer/testdata/src/formatter-issue170-suite/suie_only_test.go.golden @@ -0,0 +1,24 @@ +package formatterissue170 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestFormatter(t *testing.T) { + suite.Run(t, new(FormatterSuite)) +} + +type FormatterSuite struct { + suite.Suite +} + +func (s *FormatterSuite) TestFormatter() { + s.True(false, "expected %v, got %v", true, false) // want "formatter: remove unnecessary fmt\\.Sprintf" +} + +func (s FormatterSuite) TestFormatterValueRecv() { + s.False(true, "expected %v, got %v", true, false) // want "formatter: remove unnecessary fmt\\.Sprintf" +} diff --git a/analyzer/testdata/src/formatter-issue170/require_only_test.go b/analyzer/testdata/src/formatter-issue170/require_only_test.go new file mode 100644 index 0000000..5957a21 --- /dev/null +++ b/analyzer/testdata/src/formatter-issue170/require_only_test.go @@ -0,0 +1,12 @@ +package formatterissue170 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormatter(t *testing.T) { + require.True(t, false, fmt.Sprintf("expected %v, got %v", true, false)) // want "formatter: remove unnecessary fmt\\.Sprintf" +} diff --git a/analyzer/testdata/src/formatter-issue170/require_only_test.go.golden b/analyzer/testdata/src/formatter-issue170/require_only_test.go.golden new file mode 100644 index 0000000..26a1e49 --- /dev/null +++ b/analyzer/testdata/src/formatter-issue170/require_only_test.go.golden @@ -0,0 +1,12 @@ +package formatterissue170 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormatter(t *testing.T) { + require.True(t, false, "expected %v, got %v", true, false) // want "formatter: remove unnecessary fmt\\.Sprintf" +} diff --git a/internal/analysisutil/encoded.go b/internal/analysisutil/encoded.go new file mode 100644 index 0000000..cafc283 --- /dev/null +++ b/internal/analysisutil/encoded.go @@ -0,0 +1,46 @@ +package analysisutil + +import "strings" + +var whitespaceRemover = strings.NewReplacer("\n", "", "\\n", "", "\t", "", "\\t", "", " ", "") + +// IsJSONLike returns true if the string has JSON format features. +// A positive result can be returned for invalid JSON as well. +func IsJSONLike(s string) bool { + s = whitespaceRemover.Replace(unescape(s)) + + var startMatch bool + for _, prefix := range []string{ + `{{`, `{[`, `{"`, + `[{{`, `[{[`, `[{"`, + } { + if strings.HasPrefix(s, prefix) { + startMatch = true + break + } + } + if !startMatch { + return false + } + + for _, keyValue := range []string{`":{`, `":[`, `":"`} { + if strings.Contains(s, keyValue) { + return true + } + } + return false + + // NOTE(a.telyshev): We do not check the end of the string, because this is usually a field for typos. + // And one of the reasons for using JSON-specific assertions is to catch typos like this. +} + +func unescape(s string) string { + s = strings.ReplaceAll(s, `\"`, `"`) + s = unquote(s, `"`) + s = unquote(s, "`") + return s +} + +func unquote(s string, q string) string { + return strings.TrimLeft(strings.TrimRight(s, q), q) +} diff --git a/internal/analysisutil/encoded_test.go b/internal/analysisutil/encoded_test.go new file mode 100644 index 0000000..afd2706 --- /dev/null +++ b/internal/analysisutil/encoded_test.go @@ -0,0 +1,64 @@ +package analysisutil_test + +import ( + "testing" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +func TestIsJSONLike(t *testing.T) { + cases := []struct { + in string + expected bool + }{ + { + in: `[{"name": "values-files", "array": ["values-dev.yaml"]}, {"name": "helm-parameters", "map": {"image.tag": "v1.2.3"}}]`, + expected: true, + }, + { + in: `{"labels":{"aaa":"111"},"annotations":{"ccc":"333"}}`, + expected: true, + }, + { + in: "{\"message\":\"No user was found in the LDAP server(s) with that username\"}", + expected: true, + }, + { + in: `"{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}"`, + expected: true, + }, + { + in: `"{\"message\":\"No user was found in the LDAP server(s) with that username\"}"`, + expected: true, + }, + { + in: `{"uuid": "b65b1a22-db6d-4f5a-9b3d-7302368a82e6"}`, + expected: true, + }, + { + in: `apiVersion: 3`, + expected: false, + }, + { + in: `[{}]`, + expected: false, + }, + { + in: `{{ .TemplateVar }}`, + expected: false, + }, + { + in: `{{-.TemplateVar}}`, + expected: false, + }, + } + + for _, tt := range cases { + t.Run("", func(t *testing.T) { + isJSON := analysisutil.IsJSONLike(tt.in) + if isJSON != tt.expected { + t.FailNow() + } + }) + } +} diff --git a/internal/checkers/blank_import.go b/internal/checkers/blank_import.go index 403691e..56cd64e 100644 --- a/internal/checkers/blank_import.go +++ b/internal/checkers/blank_import.go @@ -53,7 +53,7 @@ func (checker BlankImport) Check(pass *analysis.Pass, _ *inspector.Inspector) (d } msg := fmt.Sprintf("avoid blank import of %s as it does nothing", pkg) - diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), imp, msg, nil)) + diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), imp, msg)) } } return diagnostics diff --git a/internal/checkers/bool_compare.go b/internal/checkers/bool_compare.go index d125c43..67959b6 100644 --- a/internal/checkers/bool_compare.go +++ b/internal/checkers/bool_compare.go @@ -49,13 +49,11 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. } survivingArg = newBoolCast(survivingArg) } - return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ - Pos: replaceStart, - End: replaceEnd, - NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), - }), - ) + return newUseFunctionDiagnostic(checker.Name(), call, proposed, analysis.TextEdit{ + Pos: replaceStart, + End: replaceEnd, + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }) } newUseTrueDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { @@ -74,7 +72,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. survivingArg = newBoolCast(survivingArg) } return newDiagnostic(checker.Name(), call, "need to simplify the assertion", - &analysis.SuggestedFix{ + analysis.SuggestedFix{ Message: "Simplify the assertion", TextEdits: []analysis.TextEdit{{ Pos: replaceStart, @@ -106,7 +104,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. case xor(t1, t2): survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1) if call.Fn.NameFTrimmed == "Exactly" && !isBuiltinBool(pass, survivingArg) { - // NOTE(a.telyshev): `Exactly` assumes no type casting. + // NOTE(a.telyshev): `Exactly` assumes no type conversion. return nil } return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) @@ -114,7 +112,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. case xor(f1, f2): survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1) if call.Fn.NameFTrimmed == "Exactly" && !isBuiltinBool(pass, survivingArg) { - // NOTE(a.telyshev): `Exactly` assumes no type casting. + // NOTE(a.telyshev): `Exactly` assumes no type conversion. return nil } return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) diff --git a/internal/checkers/checkers_registry.go b/internal/checkers/checkers_registry.go index 993c42f..f881be4 100644 --- a/internal/checkers/checkers_registry.go +++ b/internal/checkers/checkers_registry.go @@ -17,6 +17,7 @@ var registry = checkersRegistry{ {factory: asCheckerFactory(NewErrorNil), enabledByDefault: true}, {factory: asCheckerFactory(NewNilCompare), enabledByDefault: true}, {factory: asCheckerFactory(NewErrorIsAs), enabledByDefault: true}, + {factory: asCheckerFactory(NewEncodedCompare), enabledByDefault: true}, {factory: asCheckerFactory(NewExpectedActual), enabledByDefault: true}, {factory: asCheckerFactory(NewRegexp), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteExtraAssertCall), enabledByDefault: true}, diff --git a/internal/checkers/checkers_registry_test.go b/internal/checkers/checkers_registry_test.go index ecdbefd..08c3a39 100644 --- a/internal/checkers/checkers_registry_test.go +++ b/internal/checkers/checkers_registry_test.go @@ -45,6 +45,7 @@ func TestAll(t *testing.T) { "error-nil", "nil-compare", "error-is-as", + "encoded-compare", "expected-actual", "regexp", "suite-extra-assert-call", @@ -81,6 +82,7 @@ func TestEnabledByDefault(t *testing.T) { "error-nil", "nil-compare", "error-is-as", + "encoded-compare", "expected-actual", "regexp", "suite-extra-assert-call", diff --git a/internal/checkers/compares.go b/internal/checkers/compares.go index bdde03d..f0c4013 100644 --- a/internal/checkers/compares.go +++ b/internal/checkers/compares.go @@ -61,7 +61,9 @@ func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia return nil } - if isPointer(pass, be.X) && isPointer(pass, be.Y) { + _, xp := isPointer(pass, be.X) + _, yp := isPointer(pass, be.Y) + if xp && yp { switch proposedFn { case "Equal": proposedFn = "Same" @@ -72,12 +74,11 @@ func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia a, b := be.X, be.Y return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, - newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + analysis.TextEdit{ Pos: be.X.Pos(), End: be.Y.End(), NewText: formatAsCallArgs(pass, a, b), - }), - ) + }) } var tokenToProposedFnInsteadOfTrue = map[token.Token]string{ diff --git a/internal/checkers/contains.go b/internal/checkers/contains.go index afbb0d2..07f76c6 100644 --- a/internal/checkers/contains.go +++ b/internal/checkers/contains.go @@ -63,10 +63,9 @@ func (checker Contains) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia } return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: call.Args[0].Pos(), End: call.Args[0].End(), NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]), - }), - ) + }) } diff --git a/internal/checkers/empty.go b/internal/checkers/empty.go index eafecb6..71657fe 100644 --- a/internal/checkers/empty.go +++ b/internal/checkers/empty.go @@ -53,25 +53,28 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D newUseEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic { const proposed = "Empty" return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: replaceStart, End: replaceEnd, NewText: analysisutil.NodeBytes(pass.Fset, replaceWith), - }), - ) + }) } if len(call.Args) == 0 { return nil } - a := call.Args[0] + switch call.Fn.NameFTrimmed { - case "Zero", "Empty": - lenArg, ok := isBuiltinLenCall(pass, a) - if ok { + case "Zero": + if lenArg, ok := isBuiltinLenCall(pass, a); ok { return newUseEmptyDiagnostic(a.Pos(), a.End(), lenArg) } + + case "Empty": + if lenArg, ok := isBuiltinLenCall(pass, a); ok { + return newRemoveLenDiagnostic(pass, checker.Name(), call, a, lenArg) + } } if len(call.Args) < 2 { @@ -120,25 +123,28 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi newUseNotEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic { const proposed = "NotEmpty" return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: replaceStart, End: replaceEnd, NewText: analysisutil.NodeBytes(pass.Fset, replaceWith), - }), - ) + }) } if len(call.Args) == 0 { return nil } - a := call.Args[0] + switch call.Fn.NameFTrimmed { - case "NotZero", "NotEmpty", "Positive": - lenArg, ok := isBuiltinLenCall(pass, a) - if ok { + case "NotZero", "Positive": + if lenArg, ok := isBuiltinLenCall(pass, a); ok { return newUseNotEmptyDiagnostic(a.Pos(), a.End(), lenArg) } + + case "NotEmpty": + if lenArg, ok := isBuiltinLenCall(pass, a); ok { + return newRemoveLenDiagnostic(pass, checker.Name(), call, a, lenArg) + } } if len(call.Args) < 2 { diff --git a/internal/checkers/encoded_compare.go b/internal/checkers/encoded_compare.go new file mode 100644 index 0000000..53c74ac --- /dev/null +++ b/internal/checkers/encoded_compare.go @@ -0,0 +1,101 @@ +package checkers + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" +) + +// EncodedCompare detects situations like +// +// assert.Equal(t, `{"foo": "bar"}`, body) +// assert.EqualValues(t, `{"foo": "bar"}`, body) +// assert.Exactly(t, `{"foo": "bar"}`, body) +// assert.Equal(t, expectedJSON, resultJSON) +// assert.Equal(t, expBodyConst, w.Body.String()) +// assert.Equal(t, fmt.Sprintf(`{"value":"%s"}`, hexString), result) +// assert.Equal(t, "{}", json.RawMessage(resp)) +// assert.Equal(t, expJSON, strings.Trim(string(resultJSONBytes), "\n")) // + Replace, ReplaceAll, TrimSpace +// +// assert.Equal(t, expectedYML, conf) +// +// and requires +// +// assert.JSONEq(t, `{"foo": "bar"}`, body) +// assert.YAMLEq(t, expectedYML, conf) +type EncodedCompare struct{} + +// NewEncodedCompare constructs EncodedCompare checker. +func NewEncodedCompare() EncodedCompare { return EncodedCompare{} } +func (EncodedCompare) Name() string { return "encoded-compare" } + +func (checker EncodedCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + default: + return nil + } + + if len(call.Args) < 2 { + return nil + } + lhs, rhs := call.Args[0], call.Args[1] + + a, aIsExplicitJSON := checker.unwrap(pass, call.Args[0]) + b, bIsExplicitJSON := checker.unwrap(pass, call.Args[1]) + + var proposed string + switch { + case aIsExplicitJSON, bIsExplicitJSON, isJSONStyleExpr(pass, a), isJSONStyleExpr(pass, b): + proposed = "JSONEq" + case isYAMLStyleExpr(a), isYAMLStyleExpr(b): + proposed = "YAMLEq" + } + + if proposed != "" { + return newUseFunctionDiagnostic(checker.Name(), call, proposed, + analysis.TextEdit{ + Pos: lhs.Pos(), + End: lhs.End(), + NewText: formatWithStringCastForBytes(pass, a), + }, + analysis.TextEdit{ + Pos: rhs.Pos(), + End: rhs.End(), + NewText: formatWithStringCastForBytes(pass, b), + }, + ) + } + return nil +} + +// unwrap unwraps expression from string, []byte, strings.Replace(All), strings.Trim(Space) and json.RawMessage conversions. +// Returns true in the second argument, if json.RawMessage was in the chain. +func (checker EncodedCompare) unwrap(pass *analysis.Pass, e ast.Expr) (ast.Expr, bool) { + ce, ok := e.(*ast.CallExpr) + if !ok { + return e, false + } + if len(ce.Args) == 0 { + return e, false + } + + if isJSONRawMessageCast(pass, ce) { + if isNil(ce.Args[0]) { // NOTE(a.telyshev): Ignore json.RawMessage(nil) case. + return checker.unwrap(pass, ce.Args[0]) + } + + v, _ := checker.unwrap(pass, ce.Args[0]) + return v, true + } + + if isIdentWithName("string", ce.Fun) || + isByteArray(ce.Fun) || + isStringsReplaceCall(pass, ce) || + isStringsReplaceAllCall(pass, ce) || + isStringsTrimCall(pass, ce) || + isStringsTrimSpaceCall(pass, ce) { + return checker.unwrap(pass, ce.Args[0]) + } + return e, false +} diff --git a/internal/checkers/error_is_as.go b/internal/checkers/error_is_as.go index ab92c2e..f2812c9 100644 --- a/internal/checkers/error_is_as.go +++ b/internal/checkers/error_is_as.go @@ -67,12 +67,11 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di } if proposed != "" { return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: ce.Pos(), End: ce.End(), NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]), - }), - ) + }) } case "False": @@ -91,12 +90,11 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di if isErrorsIsCall(pass, ce) { const proposed = "NotErrorIs" return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: ce.Pos(), End: ce.End(), NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]), - }), - ) + }) } case "ErrorAs": @@ -127,15 +125,15 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di pt, ok := tv.Type.Underlying().(*types.Pointer) if !ok { - return newDiagnostic(checker.Name(), call, defaultReport, nil) + return newDiagnostic(checker.Name(), call, defaultReport) } if pt.Elem() == errorType { - return newDiagnostic(checker.Name(), call, errorPtrReport, nil) + return newDiagnostic(checker.Name(), call, errorPtrReport) } _, isInterface := pt.Elem().Underlying().(*types.Interface) if !isInterface && !types.Implements(pt.Elem(), errorIface) { - return newDiagnostic(checker.Name(), call, defaultReport, nil) + return newDiagnostic(checker.Name(), call, defaultReport) } } return nil diff --git a/internal/checkers/error_nil.go b/internal/checkers/error_nil.go index ea61c49..b9f28df 100644 --- a/internal/checkers/error_nil.go +++ b/internal/checkers/error_nil.go @@ -85,12 +85,11 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia if proposedFn != "" { return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, - newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + analysis.TextEdit{ Pos: call.Args[0].Pos(), End: replacementEndPos, NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), - }), - ) + }) } return nil } diff --git a/internal/checkers/expected_actual.go b/internal/checkers/expected_actual.go index 77784dc..351d675 100644 --- a/internal/checkers/expected_actual.go +++ b/internal/checkers/expected_actual.go @@ -87,7 +87,7 @@ func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analys first, second := call.Args[0], call.Args[1] if checker.isWrongExpectedActualOrder(pass, first, second) { - return newDiagnostic(checker.Name(), call, "need to reverse actual and expected values", &analysis.SuggestedFix{ + return newDiagnostic(checker.Name(), call, "need to reverse actual and expected values", analysis.SuggestedFix{ Message: "Reverse actual and expected values", TextEdits: []analysis.TextEdit{ { diff --git a/internal/checkers/float_compare.go b/internal/checkers/float_compare.go index 7436f9c..6bc22cd 100644 --- a/internal/checkers/float_compare.go +++ b/internal/checkers/float_compare.go @@ -44,7 +44,7 @@ func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis if call.Fn.IsFmt { format = "use %s.InEpsilonf (or InDeltaf)" } - return newDiagnostic(checker.Name(), call, fmt.Sprintf(format, call.SelectorXStr), nil) + return newDiagnostic(checker.Name(), call, fmt.Sprintf(format, call.SelectorXStr)) } return nil } diff --git a/internal/checkers/formatter.go b/internal/checkers/formatter.go index 3401bb0..896b6bf 100644 --- a/internal/checkers/formatter.go +++ b/internal/checkers/formatter.go @@ -1,8 +1,6 @@ package checkers import ( - "fmt" - "go/ast" "go/types" "strconv" @@ -60,7 +58,7 @@ func (checker Formatter) Check(pass *analysis.Pass, call *CallMeta) (result *ana } func (checker Formatter) checkNotFmtAssertion(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - msgAndArgsPos, ok := isPrintfLikeCall(pass, call, call.Fn.Signature) + msgAndArgsPos, ok := isPrintfLikeCall(pass, call) if !ok { return nil } @@ -71,21 +69,15 @@ func (checker Formatter) checkNotFmtAssertion(pass *analysis.Pass, call *CallMet msgAndArgs := call.ArgsRaw[msgAndArgsPos] if args, ok := isFmtSprintfCall(pass, msgAndArgs); ok { if checker.requireFFuncs { - msg := fmt.Sprintf("remove unnecessary fmt.Sprintf and use %s.%s", call.SelectorXStr, fFunc) - return newDiagnostic(checker.Name(), call, msg, - newSuggestedFuncReplacement(call, fFunc, analysis.TextEdit{ - Pos: msgAndArgs.Pos(), - End: msgAndArgs.End(), - NewText: formatAsCallArgs(pass, args...), - }), - ) + return newRemoveFnAndUseDiagnostic(pass, checker.Name(), call, fFunc, + "fmt.Sprintf", msgAndArgs, args...) } return newRemoveSprintfDiagnostic(pass, checker.Name(), call, msgAndArgs, args) } } if checker.requireFFuncs { - return newUseFunctionDiagnostic(checker.Name(), call, fFunc, newSuggestedFuncReplacement(call, fFunc)) + return newUseFunctionDiagnostic(checker.Name(), call, fFunc) } return nil } @@ -109,7 +101,7 @@ func (checker Formatter) checkFmtAssertion(pass *analysis.Pass, call *CallMeta) defer func() { pass.Report = report }() pass.Report = func(d analysis.Diagnostic) { - result = newDiagnostic(checker.Name(), call, d.Message, nil) + result = newDiagnostic(checker.Name(), call, d.Message) } format, err := strconv.Unquote(analysisutil.NodeString(pass.Fset, msg)) @@ -121,21 +113,51 @@ func (checker Formatter) checkFmtAssertion(pass *analysis.Pass, call *CallMeta) return result } -func isPrintfLikeCall(pass *analysis.Pass, call *CallMeta, sig *types.Signature) (int, bool) { - msgAndArgsPos := getMsgAndArgsPosition(sig) +func isPrintfLikeCall(pass *analysis.Pass, call *CallMeta) (int, bool) { + msgAndArgsPos := getMsgAndArgsPosition(call.Fn.Signature) if msgAndArgsPos < 0 { return -1, false } - fmtFn := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, call.Fn.Name+"f") - if fmtFn == nil { - // NOTE(a.telyshev): No formatted analogue of assertion. + if !assertHasFormattedAnalogue(pass, call) { return -1, false } return msgAndArgsPos, len(call.ArgsRaw) > msgAndArgsPos } +func assertHasFormattedAnalogue(pass *analysis.Pass, call *CallMeta) bool { + if fn := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, call.Fn.Name+"f"); fn != nil { + return true + } + + if fn := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, call.Fn.Name+"f"); fn != nil { + return true + } + + recv := call.Fn.Signature.Recv() + if recv == nil { + return false + } + + recvT := recv.Type() + if ptr, ok := recv.Type().(*types.Pointer); ok { + recvT = ptr.Elem() + } + + suite, ok := recvT.(*types.Named) + if !ok { + return false + } + for i := 0; i < suite.NumMethods(); i++ { + if suite.Method(i).Name() == call.Fn.Name+"f" { + return true + } + } + + return false +} + func getMsgAndArgsPosition(sig *types.Signature) int { params := sig.Params() if params.Len() < 1 { @@ -162,26 +184,3 @@ func getMsgPosition(sig *types.Signature) int { } return -1 } - -func isFmtSprintfCall(pass *analysis.Pass, expr ast.Expr) ([]ast.Expr, bool) { - ce, ok := expr.(*ast.CallExpr) - if !ok { - return nil, false - } - - se, ok := ce.Fun.(*ast.SelectorExpr) - if !ok { - return nil, false - } - - sprintfObj := analysisutil.ObjectOf(pass.Pkg, "fmt", "Sprintf") - if sprintfObj == nil { - return nil, false - } - - if !analysisutil.IsObj(pass.TypesInfo, se.Sel, sprintfObj) { - return nil, false - } - - return ce.Args, true -} diff --git a/internal/checkers/go_require.go b/internal/checkers/go_require.go index 060c960..8b0d399 100644 --- a/internal/checkers/go_require.go +++ b/internal/checkers/go_require.go @@ -142,11 +142,11 @@ func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspect if testifyCall != nil { switch checker.checkCall(testifyCall) { case goRequireVerdictRequire: - d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, "require"), nil) + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, "require")) diagnostics = append(diagnostics, *d) case goRequireVerdictAssertFailNow: - d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, testifyCall), nil) + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, testifyCall)) diagnostics = append(diagnostics, *d) case goRequireVerdictNoExit: @@ -163,7 +163,7 @@ func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspect if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit { caller := analysisutil.NodeString(pass.Fset, ce.Fun) - d := newDiagnostic(checker.Name(), ce, fmt.Sprintf(goRequireFnReportFormat, caller), nil) + d := newDiagnostic(checker.Name(), ce, fmt.Sprintf(goRequireFnReportFormat, caller)) diagnostics = append(diagnostics, *d) } } @@ -198,11 +198,11 @@ func (checker GoRequire) checkHTTPHandlers(pass *analysis.Pass, insp *inspector. switch checker.checkCall(testifyCall) { case goRequireVerdictRequire: - d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireHTTPHandlerReportFormat, "require"), nil) + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireHTTPHandlerReportFormat, "require")) diagnostics = append(diagnostics, *d) case goRequireVerdictAssertFailNow: - d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireHTTPHandlerReportFormat, testifyCall), nil) + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireHTTPHandlerReportFormat, testifyCall)) diagnostics = append(diagnostics, *d) case goRequireVerdictNoExit: diff --git a/internal/checkers/helpers_basic_type.go b/internal/checkers/helpers_basic_type.go index 432a303..9b43e91 100644 --- a/internal/checkers/helpers_basic_type.go +++ b/internal/checkers/helpers_basic_type.go @@ -1,10 +1,10 @@ package checkers import ( - "fmt" "go/ast" "go/token" "go/types" + "strconv" "golang.org/x/tools/go/analysis" ) @@ -56,9 +56,29 @@ func isTypedIntNumber(e ast.Expr, v int, types ...string) bool { return false } -func isIntNumber(e ast.Expr, v int) bool { +func isIntNumber(e ast.Expr, rhs int) bool { + lhs, ok := isIntBasicLit(e) + return ok && (lhs == rhs) +} + +func isNegativeIntNumber(e ast.Expr) bool { + v, ok := isIntBasicLit(e) + return ok && v < 0 +} + +func isPositiveIntNumber(e ast.Expr) bool { + v, ok := isIntBasicLit(e) + return ok && v > 0 +} + +func isEmptyStringLit(e ast.Expr) bool { + bl, ok := e.(*ast.BasicLit) + return ok && bl.Kind == token.STRING && bl.Value == `""` +} + +func isNotEmptyStringLit(e ast.Expr) bool { bl, ok := e.(*ast.BasicLit) - return ok && bl.Kind == token.INT && bl.Value == fmt.Sprintf("%d", v) + return ok && bl.Kind == token.STRING && bl.Value != `""` } func isBasicLit(e ast.Expr) bool { @@ -66,9 +86,27 @@ func isBasicLit(e ast.Expr) bool { return ok } -func isIntBasicLit(e ast.Expr) bool { +func isIntBasicLit(e ast.Expr) (int, bool) { + if un, ok := e.(*ast.UnaryExpr); ok { + if un.Op == token.SUB { + v, ok := isIntBasicLit(un.X) + return -1 * v, ok + } + } + bl, ok := e.(*ast.BasicLit) - return ok && bl.Kind == token.INT + if !ok { + return 0, false + } + if bl.Kind != token.INT { + return 0, false + } + + v, err := strconv.Atoi(bl.Value) + if err != nil { + return 0, false + } + return v, true } func isUntypedConst(pass *analysis.Pass, e ast.Expr) bool { @@ -98,12 +136,37 @@ func isUnderlying(pass *analysis.Pass, e ast.Expr, flag types.BasicInfo) bool { return ok && (bt.Info()&flag > 0) } -func isPointer(pass *analysis.Pass, e ast.Expr) bool { - _, ok := pass.TypesInfo.TypeOf(e).(*types.Pointer) - return ok +func isPointer(pass *analysis.Pass, e ast.Expr) (types.Type, bool) { + ptr, ok := pass.TypesInfo.TypeOf(e).(*types.Pointer) + if !ok { + return nil, false + } + return ptr.Elem(), true +} + +// isByteArray returns true if expression is `[]byte` itself. +func isByteArray(e ast.Expr) bool { + at, ok := e.(*ast.ArrayType) + return ok && isIdentWithName("byte", at.Elt) +} + +// hasBytesType returns true if the expression is of `[]byte` type. +func hasBytesType(pass *analysis.Pass, e ast.Expr) bool { + t := pass.TypesInfo.TypeOf(e) + if t == nil { + return false + } + + sl, ok := t.(*types.Slice) + if !ok { + return false + } + + el, ok := sl.Elem().(*types.Basic) + return ok && el.Kind() == types.Uint8 } -// untype returns v from type(v) expression or v itself if there is no type cast. +// untype returns v from type(v) expression or v itself if there is no type conversion. func untype(e ast.Expr) ast.Expr { ce, ok := e.(*ast.CallExpr) if !ok || len(ce.Args) != 1 { diff --git a/internal/checkers/helpers_diagnostic.go b/internal/checkers/helpers_diagnostic.go index 1f5c019..f12d87a 100644 --- a/internal/checkers/helpers_diagnostic.go +++ b/internal/checkers/helpers_diagnostic.go @@ -7,11 +7,32 @@ import ( "golang.org/x/tools/go/analysis" ) +func newRemoveFnAndUseDiagnostic( + pass *analysis.Pass, + checker string, + call *CallMeta, + proposedFn string, + removedFn string, + removedFnPos analysis.Range, + removedFnArgs ...ast.Expr, +) *analysis.Diagnostic { + f := proposedFn + if call.Fn.IsFmt { + f += "f" + } + msg := fmt.Sprintf("remove unnecessary %s and use %s.%s", removedFn, call.SelectorXStr, f) + + return newDiagnostic(checker, call, msg, + newSuggestedFuncRemoving(pass, removedFn, removedFnPos, removedFnArgs...), + newSuggestedFuncReplacement(call, proposedFn), + ) +} + func newUseFunctionDiagnostic( checker string, call *CallMeta, proposedFn string, - fix *analysis.SuggestedFix, + additionalEdits ...analysis.TextEdit, ) *analysis.Diagnostic { f := proposedFn if call.Fn.IsFmt { @@ -19,17 +40,18 @@ func newUseFunctionDiagnostic( } msg := fmt.Sprintf("use %s.%s", call.SelectorXStr, f) - return newDiagnostic(checker, call, msg, fix) + return newDiagnostic(checker, call, msg, + newSuggestedFuncReplacement(call, proposedFn, additionalEdits...)) } -func newRemoveSprintfDiagnostic( +func newRemoveLenDiagnostic( pass *analysis.Pass, checker string, call *CallMeta, fnPos analysis.Range, - fnArgs []ast.Expr, + fnArg ast.Expr, ) *analysis.Diagnostic { - return newRemoveFnDiagnostic(pass, checker, call, "fmt.Sprintf", fnPos, fnArgs...) + return newRemoveFnDiagnostic(pass, checker, call, "len", fnPos, fnArg) } func newRemoveMustCompileDiagnostic( @@ -42,6 +64,16 @@ func newRemoveMustCompileDiagnostic( return newRemoveFnDiagnostic(pass, checker, call, "regexp.MustCompile", fnPos, fnArg) } +func newRemoveSprintfDiagnostic( + pass *analysis.Pass, + checker string, + call *CallMeta, + fnPos analysis.Range, + fnArgs []ast.Expr, +) *analysis.Diagnostic { + return newRemoveFnDiagnostic(pass, checker, call, "fmt.Sprintf", fnPos, fnArgs...) +} + func newRemoveFnDiagnostic( pass *analysis.Pass, checker string, @@ -50,23 +82,15 @@ func newRemoveFnDiagnostic( fnPos analysis.Range, fnArgs ...ast.Expr, ) *analysis.Diagnostic { - return newDiagnostic(checker, call, "remove unnecessary "+fnName, &analysis.SuggestedFix{ - Message: fmt.Sprintf("Remove `%s`", fnName), - TextEdits: []analysis.TextEdit{ - { - Pos: fnPos.Pos(), - End: fnPos.End(), - NewText: formatAsCallArgs(pass, fnArgs...), - }, - }, - }) + return newDiagnostic(checker, call, "remove unnecessary "+fnName, + newSuggestedFuncRemoving(pass, fnName, fnPos, fnArgs...)) } func newDiagnostic( checker string, rng analysis.Range, msg string, - fix *analysis.SuggestedFix, + fixes ...analysis.SuggestedFix, ) *analysis.Diagnostic { d := analysis.Diagnostic{ Pos: rng.Pos(), @@ -74,21 +98,39 @@ func newDiagnostic( Category: checker, Message: checker + ": " + msg, } - if fix != nil { - d.SuggestedFixes = []analysis.SuggestedFix{*fix} + if len(fixes) != 0 { + d.SuggestedFixes = fixes } return &d } +func newSuggestedFuncRemoving( + pass *analysis.Pass, + fnName string, + fnPos analysis.Range, + fnArgs ...ast.Expr, +) analysis.SuggestedFix { + return analysis.SuggestedFix{ + Message: fmt.Sprintf("Remove `%s`", fnName), + TextEdits: []analysis.TextEdit{ + { + Pos: fnPos.Pos(), + End: fnPos.End(), + NewText: formatAsCallArgs(pass, fnArgs...), + }, + }, + } +} + func newSuggestedFuncReplacement( call *CallMeta, proposedFn string, additionalEdits ...analysis.TextEdit, -) *analysis.SuggestedFix { +) analysis.SuggestedFix { if call.Fn.IsFmt { proposedFn += "f" } - return &analysis.SuggestedFix{ + return analysis.SuggestedFix{ Message: fmt.Sprintf("Replace `%s` with `%s`", call.Fn.Name, proposedFn), TextEdits: append([]analysis.TextEdit{ { diff --git a/internal/checkers/helpers_encoded.go b/internal/checkers/helpers_encoded.go new file mode 100644 index 0000000..35a497a --- /dev/null +++ b/internal/checkers/helpers_encoded.go @@ -0,0 +1,40 @@ +package checkers + +import ( + "go/ast" + "go/token" + "regexp" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +var ( + jsonIdentRe = regexp.MustCompile(`json|JSON|Json`) + yamlIdentRe = regexp.MustCompile(`yaml|YAML|Yaml|yml|YML|Yml`) +) + +func isJSONStyleExpr(pass *analysis.Pass, e ast.Expr) bool { + if isIdentNamedAfterPattern(jsonIdentRe, e) { + return true + } + + if t, ok := pass.TypesInfo.Types[e]; ok && t.Value != nil { + return analysisutil.IsJSONLike(t.Value.String()) + } + + if bl, ok := e.(*ast.BasicLit); ok { + return bl.Kind == token.STRING && analysisutil.IsJSONLike(bl.Value) + } + + if args, ok := isFmtSprintfCall(pass, e); ok { + return isJSONStyleExpr(pass, args[0]) + } + + return false +} + +func isYAMLStyleExpr(e ast.Expr) bool { + return isIdentNamedAfterPattern(yamlIdentRe, e) +} diff --git a/internal/checkers/helpers_format.go b/internal/checkers/helpers_format.go index 765fce5..d69c428 100644 --- a/internal/checkers/helpers_format.go +++ b/internal/checkers/helpers_format.go @@ -3,6 +3,7 @@ package checkers import ( "bytes" "go/ast" + "strings" "golang.org/x/tools/go/analysis" @@ -24,3 +25,37 @@ func formatAsCallArgs(pass *analysis.Pass, args ...ast.Expr) []byte { } return buf.Bytes() } + +func formatWithStringCastForBytes(pass *analysis.Pass, e ast.Expr) []byte { + if !hasBytesType(pass, e) { + return analysisutil.NodeBytes(pass.Fset, e) + } + + if se, ok := isBufferBytesCall(pass, e); ok { + return []byte(analysisutil.NodeString(pass.Fset, se) + ".String()") + } + return []byte("string(" + analysisutil.NodeString(pass.Fset, e) + ")") +} + +func isBufferBytesCall(pass *analysis.Pass, e ast.Expr) (ast.Node, bool) { + ce, ok := e.(*ast.CallExpr) + if !ok { + return nil, false + } + + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return nil, false + } + + if !isIdentWithName("Bytes", se.Sel) { + return nil, false + } + if t := pass.TypesInfo.TypeOf(se.X); t != nil { + // NOTE(a.telyshev): This is hack, because `bytes` package can be not imported, + // and we cannot do "true" comparison with `Buffer` object. + return se.X, strings.TrimPrefix(t.String(), "*") == "bytes.Buffer" + } + + return nil, false +} diff --git a/internal/checkers/helpers_interface.go b/internal/checkers/helpers_interface.go index b0c0d13..ad39c72 100644 --- a/internal/checkers/helpers_interface.go +++ b/internal/checkers/helpers_interface.go @@ -15,8 +15,11 @@ func isEmptyInterface(pass *analysis.Pass, expr ast.Expr) bool { if !ok { return false } + return isEmptyInterfaceType(t.Type) +} - iface, ok := t.Type.Underlying().(*types.Interface) +func isEmptyInterfaceType(t types.Type) bool { + iface, ok := t.Underlying().(*types.Interface) return ok && iface.NumMethods() == 0 } diff --git a/internal/checkers/helpers_pkg_func.go b/internal/checkers/helpers_pkg_func.go index a81cbc6..daf3093 100644 --- a/internal/checkers/helpers_pkg_func.go +++ b/internal/checkers/helpers_pkg_func.go @@ -8,6 +8,18 @@ import ( "github.com/Antonboom/testifylint/internal/analysisutil" ) +func isFmtSprintfCall(pass *analysis.Pass, e ast.Expr) ([]ast.Expr, bool) { + ce, ok := e.(*ast.CallExpr) + if !ok { + return nil, false + } + return ce.Args, isPkgFnCall(pass, ce, "fmt", "Sprintf") +} + +func isJSONRawMessageCast(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isPkgFnCall(pass, ce, "encoding/json", "RawMessage") +} + func isRegexpMustCompileCall(pass *analysis.Pass, ce *ast.CallExpr) bool { return isPkgFnCall(pass, ce, "regexp", "MustCompile") } @@ -16,16 +28,32 @@ func isStringsContainsCall(pass *analysis.Pass, ce *ast.CallExpr) bool { return isPkgFnCall(pass, ce, "strings", "Contains") } +func isStringsReplaceCall(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isPkgFnCall(pass, ce, "strings", "Replace") +} + +func isStringsReplaceAllCall(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isPkgFnCall(pass, ce, "strings", "ReplaceAll") +} + +func isStringsTrimCall(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isPkgFnCall(pass, ce, "strings", "Trim") +} + +func isStringsTrimSpaceCall(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isPkgFnCall(pass, ce, "strings", "TrimSpace") +} + func isPkgFnCall(pass *analysis.Pass, ce *ast.CallExpr, pkg, fn string) bool { se, ok := ce.Fun.(*ast.SelectorExpr) if !ok { return false } - errorsIsObj := analysisutil.ObjectOf(pass.Pkg, pkg, fn) - if errorsIsObj == nil { + fnObj := analysisutil.ObjectOf(pass.Pkg, pkg, fn) + if fnObj == nil { return false } - return analysisutil.IsObj(pass.TypesInfo, se.Sel, errorsIsObj) + return analysisutil.IsObj(pass.TypesInfo, se.Sel, fnObj) } diff --git a/internal/checkers/len.go b/internal/checkers/len.go index 4733056..c240a61 100644 --- a/internal/checkers/len.go +++ b/internal/checkers/len.go @@ -31,17 +31,16 @@ func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnost a, b := call.Args[0], call.Args[1] if lenArg, expectedLen, ok := xorLenCall(pass, a, b); ok { - if expectedLen == b && !isIntBasicLit(expectedLen) { + if _, ok := isIntBasicLit(expectedLen); (expectedLen == b) && !ok { // https://github.com/Antonboom/testifylint/issues/9 return nil } return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, - newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + analysis.TextEdit{ Pos: a.Pos(), End: b.End(), NewText: formatAsCallArgs(pass, lenArg, expectedLen), - }), - ) + }) } case "True": @@ -50,14 +49,16 @@ func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnost } expr := call.Args[0] - if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok && isIntBasicLit(expectedLen) { + if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok { + if _, ok := isIntBasicLit(expectedLen); !ok { + return nil + } return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, - newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + analysis.TextEdit{ Pos: expr.Pos(), End: expr.End(), NewText: formatAsCallArgs(pass, lenArg, expectedLen), - }), - ) + }) } } return nil diff --git a/internal/checkers/negative_postive.go b/internal/checkers/negative_positive.go similarity index 96% rename from internal/checkers/negative_postive.go rename to internal/checkers/negative_positive.go index 274021f..a61bbdf 100644 --- a/internal/checkers/negative_postive.go +++ b/internal/checkers/negative_positive.go @@ -48,12 +48,11 @@ func (checker NegativePositive) checkNegative(pass *analysis.Pass, call *CallMet newUseNegativeDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic { const proposed = "Negative" return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: replaceStart, End: replaceEnd, NewText: analysisutil.NodeBytes(pass.Fset, replaceWith), - }), - ) + }) } // NOTE(a.telyshev): We ignore uint-asserts as being no sense for assert.Negative. @@ -114,12 +113,11 @@ func (checker NegativePositive) checkPositive(pass *analysis.Pass, call *CallMet newUsePositiveDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic { const proposed = "Positive" return newUseFunctionDiagnostic(checker.Name(), call, proposed, - newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + analysis.TextEdit{ Pos: replaceStart, End: replaceEnd, NewText: analysisutil.NodeBytes(pass.Fset, replaceWith), - }), - ) + }) } switch call.Fn.NameFTrimmed { diff --git a/internal/checkers/nil_compare.go b/internal/checkers/nil_compare.go index 47c4a73..fc1adb7 100644 --- a/internal/checkers/nil_compare.go +++ b/internal/checkers/nil_compare.go @@ -47,10 +47,9 @@ func (checker NilCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.D } return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, - newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + analysis.TextEdit{ Pos: call.Args[0].Pos(), End: call.Args[1].End(), NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), - }), - ) + }) } diff --git a/internal/checkers/require_error.go b/internal/checkers/require_error.go index 2fa8452..e4e30aa 100644 --- a/internal/checkers/require_error.go +++ b/internal/checkers/require_error.go @@ -134,7 +134,7 @@ func (checker RequireError) Check(pass *analysis.Pass, inspector *inspector.Insp } diagnostics = append(diagnostics, - *newDiagnostic(checker.Name(), c.testifyCall, requireErrorReport, nil)) + *newDiagnostic(checker.Name(), c.testifyCall, requireErrorReport)) } } diff --git a/internal/checkers/suite_broken_parallel.go b/internal/checkers/suite_broken_parallel.go index f830fd2..4374c93 100644 --- a/internal/checkers/suite_broken_parallel.go +++ b/internal/checkers/suite_broken_parallel.go @@ -68,7 +68,7 @@ func (checker SuiteBrokenParallel) Check(pass *analysis.Pass, insp *inspector.In } nextLine := pass.Fset.Position(ce.Pos()).Line + 1 - d := newDiagnostic(checker.Name(), ce, report, &analysis.SuggestedFix{ + d := newDiagnostic(checker.Name(), ce, report, analysis.SuggestedFix{ Message: fmt.Sprintf("Remove `%s` call", analysisutil.NodeString(pass.Fset, ce)), TextEdits: []analysis.TextEdit{ { diff --git a/internal/checkers/suite_dont_use_pkg.go b/internal/checkers/suite_dont_use_pkg.go index 6150ae7..4fbfbe7 100644 --- a/internal/checkers/suite_dont_use_pkg.go +++ b/internal/checkers/suite_dont_use_pkg.go @@ -60,7 +60,7 @@ func (checker SuiteDontUsePkg) Check(pass *analysis.Pass, call *CallMeta) *analy } msg := fmt.Sprintf("use %s.%s", newSelector, call.Fn.Name) - return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{ + return newDiagnostic(checker.Name(), call, msg, analysis.SuggestedFix{ Message: fmt.Sprintf("Replace `%s` with `%s`", call.SelectorXStr, newSelector), TextEdits: []analysis.TextEdit{ // Replace package function with suite method. diff --git a/internal/checkers/suite_extra_assert_call.go b/internal/checkers/suite_extra_assert_call.go index 9adfe51..fdea324 100644 --- a/internal/checkers/suite_extra_assert_call.go +++ b/internal/checkers/suite_extra_assert_call.go @@ -61,7 +61,7 @@ func (checker SuiteExtraAssertCall) Check(pass *analysis.Pass, call *CallMeta) * } msg := fmt.Sprintf("use an explicit %s.Assert().%s", analysisutil.NodeString(pass.Fset, x), call.Fn.Name) - return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{ + return newDiagnostic(checker.Name(), call, msg, analysis.SuggestedFix{ Message: "Add `Assert()` call", TextEdits: []analysis.TextEdit{{ Pos: x.End(), @@ -85,7 +85,7 @@ func (checker SuiteExtraAssertCall) Check(pass *analysis.Pass, call *CallMeta) * } msg := fmt.Sprintf("need to simplify the assertion to %s.%s", analysisutil.NodeString(pass.Fset, se.X), call.Fn.Name) - return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{ + return newDiagnostic(checker.Name(), call, msg, analysis.SuggestedFix{ Message: "Remove `Assert()` call", TextEdits: []analysis.TextEdit{{ Pos: se.Sel.Pos(), diff --git a/internal/checkers/suite_subtest_run.go b/internal/checkers/suite_subtest_run.go index 67d9c25..525d5ff 100644 --- a/internal/checkers/suite_subtest_run.go +++ b/internal/checkers/suite_subtest_run.go @@ -53,7 +53,7 @@ func (checker SuiteSubtestRun) Check(pass *analysis.Pass, insp *inspector.Inspec if implementsTestifySuite(pass, tCallSel.X) && implementsTestingT(pass, tCall) { msg := fmt.Sprintf("use %s.Run to run subtest", analysisutil.NodeString(pass.Fset, tCallSel.X)) - diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), ce, msg, nil)) + diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), ce, msg)) } }) return diagnostics diff --git a/internal/checkers/suite_thelper.go b/internal/checkers/suite_thelper.go index 5945529..a229e43 100644 --- a/internal/checkers/suite_thelper.go +++ b/internal/checkers/suite_thelper.go @@ -51,7 +51,7 @@ func (checker SuiteTHelper) Check(pass *analysis.Pass, inspector *inspector.Insp } msg := fmt.Sprintf("suite helper method must start with " + helperCallStr) - d := newDiagnostic(checker.Name(), fd, msg, &analysis.SuggestedFix{ + d := newDiagnostic(checker.Name(), fd, msg, analysis.SuggestedFix{ Message: fmt.Sprintf("Insert `%s`", helperCallStr), TextEdits: []analysis.TextEdit{ { diff --git a/internal/checkers/useless_assert.go b/internal/checkers/useless_assert.go index 6f206d0..045706e 100644 --- a/internal/checkers/useless_assert.go +++ b/internal/checkers/useless_assert.go @@ -10,15 +10,40 @@ import ( // UselessAssert detects useless asserts like // -// 1) Asserting of the same variable -// +// assert.Contains(t, tt.value, tt.value) +// assert.ElementsMatch(t, tt.value, tt.value) // assert.Equal(t, tt.value, tt.value) -// assert.ElementsMatch(t, users, users) +// assert.EqualExportedValues(t, tt.value, tt.value) // ... +// // assert.True(t, num > num) +// assert.True(t, num < num) +// assert.True(t, num >= num) +// assert.True(t, num <= num) +// assert.True(t, num == num) +// assert.True(t, num != num) +// +// assert.False(t, num > num) +// assert.False(t, num < num) +// assert.False(t, num >= num) +// assert.False(t, num <= num) // assert.False(t, num == num) +// assert.False(t, num != num) // -// 2) Open for contribution... +// assert.Empty(t, "") +// assert.False(t, false) +// assert.Implements(t, (*any)(nil), new(Conn)) +// assert.Negative(t, -42) +// assert.Nil(t, nil) +// assert.NoError(t, nil) +// assert.NotEmpty(t, "value") +// assert.NotZero(t, 42) +// assert.NotZero(t, "value") +// assert.Positive(t, 42) +// assert.True(t, true) +// assert.Zero(t, 0) +// assert.Zero(t, "") +// assert.Zero(t, nil) type UselessAssert struct{} // NewUselessAssert constructs UselessAssert checker. @@ -26,6 +51,58 @@ func NewUselessAssert() UselessAssert { return UselessAssert{} } func (UselessAssert) Name() string { return "useless-assert" } func (checker UselessAssert) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if d := checker.checkSameVars(pass, call); d != nil { + return d + } + + var isMeaningless bool + switch call.Fn.NameFTrimmed { + case "Empty": + isMeaningless = (len(call.Args) >= 1) && isEmptyStringLit(call.Args[0]) + + case "False": + isMeaningless = (len(call.Args) >= 1) && isUntypedFalse(pass, call.Args[0]) + + case "Implements": + if len(call.Args) < 2 { + return nil + } + + elem, ok := isPointer(pass, call.Args[0]) + isMeaningless = ok && isEmptyInterfaceType(elem) + + case "Negative": + isMeaningless = (len(call.Args) >= 1) && isNegativeIntNumber(call.Args[0]) + + case "Nil", "NoError": + isMeaningless = (len(call.Args) >= 1) && isNil(call.Args[0]) + + case "NotEmpty": + isMeaningless = (len(call.Args) >= 1) && isNotEmptyStringLit(call.Args[0]) + + case "NotZero": + isMeaningless = (len(call.Args) >= 1) && + (isNotEmptyStringLit(call.Args[0]) || + isNegativeIntNumber(call.Args[0]) || isPositiveIntNumber(call.Args[0])) + + case "Positive": + isMeaningless = (len(call.Args) >= 1) && isPositiveIntNumber(call.Args[0]) + + case "True": + isMeaningless = (len(call.Args) >= 1) && isUntypedTrue(pass, call.Args[0]) + + case "Zero": + isMeaningless = (len(call.Args) >= 1) && + (isZero(call.Args[0]) || isEmptyStringLit(call.Args[0]) || isNil(call.Args[0])) + } + + if isMeaningless { + return newDiagnostic(checker.Name(), call, "meaningless assertion") + } + return nil +} + +func (checker UselessAssert) checkSameVars(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { var first, second ast.Node switch call.Fn.NameFTrimmed { @@ -82,7 +159,7 @@ func (checker UselessAssert) Check(pass *analysis.Pass, call *CallMeta) *analysi } if analysisutil.NodeString(pass.Fset, first) == analysisutil.NodeString(pass.Fset, second) { - return newDiagnostic(checker.Name(), call, "asserting of the same variable", nil) + return newDiagnostic(checker.Name(), call, "asserting of the same variable") } return nil } diff --git a/internal/testgen/gen_empty.go b/internal/testgen/gen_empty.go index 0d1b89e..21e115f 100644 --- a/internal/testgen/gen_empty.go +++ b/internal/testgen/gen_empty.go @@ -15,8 +15,9 @@ func (EmptyTestsGenerator) Checker() checkers.Checker { func (g EmptyTestsGenerator) TemplateData() any { var ( - checker = g.Checker().Name() - report = checker + ": use %s.%s" + checker = g.Checker().Name() + report = checker + ": use %s.%s" + reportRemoveLen = checker + ": remove unnecessary len" ) type test struct { @@ -60,7 +61,9 @@ func (g EmptyTestsGenerator) TemplateData() any { {Fn: "Less", Argsf: "len(elems), 1", ReportMsgf: report, ProposedFn: "Empty", ProposedArgsf: "elems"}, {Fn: "Greater", Argsf: "1, len(elems)", ReportMsgf: report, ProposedFn: "Empty", ProposedArgsf: "elems"}, {Fn: "Zero", Argsf: "len(elems)", ReportMsgf: report, ProposedFn: "Empty", ProposedArgsf: "elems"}, - {Fn: "Empty", Argsf: "len(elems)", ReportMsgf: report, ProposedFn: "Empty", ProposedArgsf: "elems"}, + {Fn: "Zero", Argsf: `len([]string{"e"})`, ReportMsgf: report, ProposedFn: "Empty", ProposedArgsf: `[]string{"e"}`}, + {Fn: "Empty", Argsf: "len(elems)", ReportMsgf: reportRemoveLen, ProposedArgsf: "elems"}, + {Fn: "Empty", Argsf: `len([]string{"e"})`, ReportMsgf: reportRemoveLen, ProposedArgsf: `[]string{"e"}`}, // Bullshit, but supported by the checker: // n < 0, n <= 0 @@ -88,7 +91,9 @@ func (g EmptyTestsGenerator) TemplateData() any { {Fn: "Greater", Argsf: "len(elems), 0", ReportMsgf: report, ProposedFn: "NotEmpty", ProposedArgsf: "elems"}, {Fn: "Positive", Argsf: "len(elems)", ReportMsgf: report, ProposedFn: "NotEmpty", ProposedArgsf: "elems"}, {Fn: "NotZero", Argsf: "len(elems)", ReportMsgf: report, ProposedFn: "NotEmpty", ProposedArgsf: "elems"}, - {Fn: "NotEmpty", Argsf: "len(elems)", ReportMsgf: report, ProposedFn: "NotEmpty", ProposedArgsf: "elems"}, + {Fn: "NotZero", Argsf: `len([]string{"e"})`, ReportMsgf: report, ProposedFn: "NotEmpty", ProposedArgsf: `[]string{"e"}`}, + {Fn: "NotEmpty", Argsf: "len(elems)", ReportMsgf: reportRemoveLen, ProposedArgsf: "elems"}, + {Fn: "NotEmpty", Argsf: `len([]string{"e"})`, ReportMsgf: reportRemoveLen, ProposedArgsf: `[]string{"e"}`}, }, ValidAssertions: []Assertion{ {Fn: "NotEmpty", Argsf: "elems"}, diff --git a/internal/testgen/gen_encoded_compare.go b/internal/testgen/gen_encoded_compare.go new file mode 100644 index 0000000..f0e2276 --- /dev/null +++ b/internal/testgen/gen_encoded_compare.go @@ -0,0 +1,340 @@ +package main + +import ( + "strings" + "text/template" + + "github.com/Antonboom/testifylint/internal/checkers" +) + +type EncodedCompareTestsGenerator struct{} + +func (EncodedCompareTestsGenerator) Checker() checkers.Checker { + return checkers.NewEncodedCompare() +} + +func (g EncodedCompareTestsGenerator) TemplateData() any { + var ( + checker = g.Checker().Name() + report = checker + ": use %s.%s" + ) + + const multiLineJSON = `{ + "id": 123, + "method": "get_prop", + "params": ["power","sat"] +} +` + multiLineCase := "assert.Equal(t, ` // want \"encoded-compare: use assert\\.JSONEq\"\n" + multiLineJSON + "`, raw)" + multiLineCase += "\nassert.Equal(t, ` // want \"encoded-compare: use assert\\.JSONEq\"\n" + multiLineJSON + + "`" + `, raw, "msg with args %d %s", 42, "42")` + + const multiLineYAML = ` +kind: Kustomization +apiVersion: kustomize.config.k8s.io/v1beta1 +images: + - name: foo + newName: bar + - name: bar + newName: baz + newTag: "123" +` + + return struct { + CheckerName CheckerName + InvalidAssertions []Assertion + MultiLineJSONCase string + ValidAssertions []Assertion + IgnoredAssertions []Assertion + }{ + CheckerName: CheckerName(checker), + InvalidAssertions: []Assertion{ + // Raw strings cases. + { + Fn: "Equal", Argsf: "`{\"name\":\"name\",\"value\":1000}`, respBody", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "expBody, respBody", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "`{\"status\":404,\"message\":\"abc\"}`, string(respBytes)", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "`{\"message\":\"success\"}`, w.Body.String()", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: `"{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", string(w.Body.Bytes())`, + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: `"{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String()`, + }, + { + Fn: "Equal", Argsf: `"{\n\t\"msg\": \"hello world\"\n}", respBody`, + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "fmt.Sprintf(`{\"value\":\"%s\",\"valuePtr\":\"%s\"}`, hexString, hexString), string(respBytes)", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + + // Variable name cases. + { + Fn: "Equal", Argsf: "`[{\"@id\":\"a\",\"b\":[{\"@id\":\"c\"}]}]`, toJSON", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: `"{\"FirstName\":\"john\",\"LastName\":\"doe\",\"Age\":26,\"Height\":182.88}", string(resJson)`, + ProposedArgsf: `"{\"FirstName\":\"john\",\"LastName\":\"doe\",\"Age\":26,\"Height\":182.88}", resJson`, + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "expJSON, resultJSON", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "jsonb, respBody", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "respBody, jsonb", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "expJSON, resJson", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + + { + Fn: "Equal", Argsf: "expectedYAML, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Equal", Argsf: "expYaml, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Equal", Argsf: "ymlResult, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Equal", Argsf: "yamlResult, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Equal", Argsf: "expYML, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Equal", Argsf: "conf, expectedYAML", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Equal", Argsf: "outputYaml, string(output.Bytes())", + ReportMsgf: report, ProposedFn: "YAMLEq", + ProposedArgsf: "outputYaml, output.String()", + }, + + // Type conversion cases. + { + Fn: "Equal", Argsf: "json.RawMessage(`{\"uuid\": \"b65b1a22-db6d-4f5a-9b3d-7302368a82e6\"}`), batch.ParentSummary()", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "`{\"uuid\": \"b65b1a22-db6d-4f5a-9b3d-7302368a82e6\"}`, string(batch.ParentSummary())", + }, + { + Fn: "Equal", Argsf: "res[0].Data, json.RawMessage([]byte(`{\"name\":\"new\"}`))", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "string(res[0].Data), `{\"name\":\"new\"}`", + }, + { + Fn: "Equal", Argsf: "json.RawMessage(raw), json.RawMessage(resultJSONBytes)", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "raw, string(resultJSONBytes)", + }, + { + Fn: "Equal", Argsf: "json.RawMessage(raw), raw", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "raw, raw", + }, + { + Fn: "Equal", Argsf: `json.RawMessage("{}"), respBody`, + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: `"{}", respBody`, + }, + { + Fn: "Equal", Argsf: `respBody, json.RawMessage("null")`, + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: `respBody, "null"`, + }, + { + Fn: "Equal", Argsf: "json.RawMessage(`[\"more\",\"raw\",\"things\"]`), resultJSONBytes", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "`[\"more\",\"raw\",\"things\"]`, string(resultJSONBytes)", + }, + + { + Fn: "Equal", Argsf: `"{}", string(resultJSONBytes)`, + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Equal", Argsf: "[]byte(expJSON), resultJSONBytes", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "expJSON, string(resultJSONBytes)", + }, + { + Fn: "Equal", Argsf: "[]byte(expYaml), respBytes", + ReportMsgf: report, ProposedFn: "YAMLEq", + ProposedArgsf: "expYaml, string(respBytes)", + }, + + // Replace/Trim cases. + { + Fn: "Equal", Argsf: `expJSON, strings.Trim(string(resultJSONBytes), "\n")`, + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "expJSON, string(resultJSONBytes)", + }, + { + Fn: "Equal", Argsf: `raw, strings.Replace(jsonb, "\n", "", -1)`, + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "raw, jsonb", + }, + { + Fn: "Equal", Argsf: "`{\"status\":\"healthy\",\"message\":\"\",\"peer_count\":1}`," + + " strings.ReplaceAll(string(respBytes), \" \", \"\")", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "`{\"status\":\"healthy\",\"message\":\"\",\"peer_count\":1}`, string(respBytes)", + }, + { + Fn: "Equal", Argsf: "`{\"foo\":\"bar\"}`, strings.TrimSpace(w.Body.String())", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "`{\"foo\":\"bar\"}`, w.Body.String()", + }, + { + Fn: "Equal", Argsf: "`{\"bar\":\"foo\"}`, strings.TrimSpace(string(w.Body.Bytes()))", + ReportMsgf: report, ProposedFn: "JSONEq", + ProposedArgsf: "`{\"bar\":\"foo\"}`, w.Body.String()", + }, + { + Fn: "Equal", Argsf: `strings.TrimSpace(strings.ReplaceAll(expYaml, "\t", " ")), strings.TrimSpace(string(respBytes))`, + ReportMsgf: report, ProposedFn: "YAMLEq", + ProposedArgsf: "expYaml, string(respBytes)", + }, + + // Other Equal* cases. + { + Fn: "EqualValues", Argsf: "expJSON, resJson", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "Exactly", Argsf: "expJSON, resJson", + ReportMsgf: report, ProposedFn: "JSONEq", + }, + { + Fn: "EqualValues", Argsf: "expYaml, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + { + Fn: "Exactly", Argsf: "expYaml, conf", + ReportMsgf: report, ProposedFn: "YAMLEq", + }, + }, + MultiLineJSONCase: multiLineCase, + ValidAssertions: []Assertion{ + {Fn: "JSONEq", Argsf: "`{\"name\":\"name\",\"value\":1000}`, respBody"}, + {Fn: "JSONEq", Argsf: "expJSON, resultJSON"}, + {Fn: "JSONEq", Argsf: "`{\"foo\":\"bar\"}`, `{\"foo\":\"bar\"}`"}, + {Fn: "JSONEq", Argsf: "`{\"message\":\"success\"}`, w.Body.String()"}, + {Fn: "JSONEq", Argsf: "fmt.Sprintf(`{\"value\":\"%s\"}`, hexString), resJson"}, + {Fn: "JSONEq", Argsf: `"{\n \"first\": \"Tobi\",\n \"last\": \"Ferret\"\n}", w.Body.String()`}, + + {Fn: "YAMLEq", Argsf: "expYaml, conf"}, + }, + IgnoredAssertions: []Assertion{ + {Fn: "Equal", Argsf: `"{{ .StepName }}", "use", "command name incorrect"`}, + {Fn: "Equal", Argsf: "json.RawMessage{}, respBody"}, + {Fn: "Equal", Argsf: "json.RawMessage(nil), respBody"}, + + {Fn: "Equal", Argsf: "raw, raw"}, + {Fn: "EqualValues", Argsf: "raw, raw"}, + {Fn: "Exactly", Argsf: "raw, raw"}, + {Fn: "JSONEq", Argsf: "raw, raw"}, + + {Fn: "Equal", Argsf: "string(respBytes), raw"}, + {Fn: "EqualValues", Argsf: "raw, string(respBytes)"}, + {Fn: "Exactly", Argsf: "string(respBytes), raw"}, + {Fn: "JSONEq", Argsf: "raw, string(respBytes)"}, + + {Fn: "NotEqual", Argsf: "raw, resultJSON"}, + {Fn: "NotEqualValues", Argsf: "resultJSON, resultJSON"}, + + {Fn: "YAMLEq", Argsf: "`" + multiLineYAML + "`, conf"}, // Not supported. + {Fn: "YAMLEq", Argsf: `"kind: Kustomization", "kind: Kustomization"`}, // Not supported. + {Fn: "YAMLEq", Argsf: "raw, conf"}, + {Fn: "YAMLEq", Argsf: "raw, string(respBytes)"}, + }, + } +} + +func (EncodedCompareTestsGenerator) ErroredTemplate() Executor { + return template.Must(template.New("EncodedCompareTestsGenerator.ErroredTemplate"). + Funcs(fm). + Parse(encodedCompareTestTmpl)) +} + +func (EncodedCompareTestsGenerator) GoldenTemplate() Executor { + return template.Must(template.New("EncodedCompareTestsGenerator.GoldenTemplate"). + Funcs(fm). + Parse(strings.ReplaceAll(encodedCompareTestTmpl, "NewAssertionExpander", "NewAssertionExpander.AsGolden"))) +} + +const encodedCompareTestTmpl = header + ` + +package {{ .CheckerName.AsPkgName }} + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func {{ .CheckerName.AsTestName }}(t *testing.T) { + var respBody, raw, hexString, toJSON, expJSON, resultJSON, jsonb, resJson string + var conf, expectedYAML, expYaml, ymlResult, yamlResult, expYML, outputYaml string + var respBytes, resultJSONBytes []byte + w := httptest.NewRecorder() + var batch interface { ParentSummary() []byte } + var res [1]struct{ Data []byte } + var output bytes.Buffer + + const expBody = ` + "`{\"status\":\"healthy\",\"message\":\"\",\"peer_count\":1}`" + ` + + // Invalid. + { + {{- range $ai, $assrn := $.InvalidAssertions }} + {{ NewAssertionExpander.Expand $assrn "assert" "t" nil }} + {{- end }} + {{ .MultiLineJSONCase }} + } + + // Valid. + { + {{- range $ai, $assrn := $.ValidAssertions }} + {{ NewAssertionExpander.Expand $assrn "assert" "t" nil }} + {{- end }} + } + + // Ignored. + { + {{- range $ai, $assrn := $.IgnoredAssertions }} + {{ NewAssertionExpander.Expand $assrn "assert" "t" nil }} + {{- end }} + } +} +` diff --git a/internal/testgen/gen_useless_assert.go b/internal/testgen/gen_useless_assert.go index 5394c63..fc27f93 100644 --- a/internal/testgen/gen_useless_assert.go +++ b/internal/testgen/gen_useless_assert.go @@ -16,6 +16,7 @@ func (g UselessAssertTestsGenerator) TemplateData() any { var ( checker = g.Checker().Name() sameVarReport = checker + ": asserting of the same variable" + defaultReport = checker + ": meaningless assertion" ) var twoSideAssertions []Assertion @@ -86,6 +87,21 @@ func (g UselessAssertTestsGenerator) TemplateData() any { {Fn: "Equal", Argsf: "tc.A(), tc.A()", ReportMsgf: sameVarReport}, {Fn: "Equal", Argsf: "testCase{}.A().B().C(), testCase{}.A().B().C()", ReportMsgf: sameVarReport}, {Fn: "IsType", Argsf: "(*testCase)(nil), (*testCase)(nil)", ReportMsgf: sameVarReport}, + + {Fn: "Empty", Argsf: `""`, ReportMsgf: defaultReport}, + {Fn: "False", Argsf: "false", ReportMsgf: defaultReport}, + {Fn: "Implements", Argsf: "(*any)(nil), new(testing.T)", ReportMsgf: defaultReport}, + {Fn: "Negative", Argsf: "-42", ReportMsgf: defaultReport}, + {Fn: "Nil", Argsf: "nil", ReportMsgf: defaultReport}, + {Fn: "NoError", Argsf: "nil", ReportMsgf: defaultReport}, + {Fn: "NotEmpty", Argsf: `"value"`, ReportMsgf: defaultReport}, + {Fn: "NotZero", Argsf: `42`, ReportMsgf: defaultReport}, + {Fn: "NotZero", Argsf: `"value"`, ReportMsgf: defaultReport}, + {Fn: "Positive", Argsf: "42", ReportMsgf: defaultReport}, + {Fn: "True", Argsf: "true", ReportMsgf: defaultReport}, + {Fn: "Zero", Argsf: "0", ReportMsgf: defaultReport}, + {Fn: "Zero", Argsf: `""`, ReportMsgf: defaultReport}, + {Fn: "Zero", Argsf: "nil", ReportMsgf: defaultReport}, }, InvalidAssertions: twoSideAssertions, ValidAssertions: []Assertion{ @@ -94,6 +110,19 @@ func (g UselessAssertTestsGenerator) TemplateData() any { {Fn: "Equal", Argsf: `tc.A(), "tc.A()"`}, {Fn: "Equal", Argsf: "testCase{}.A().B().C(), tc.A().B().C()"}, {Fn: "IsType", Argsf: "tc, testCase{}"}, + + {Fn: "Empty", Argsf: "str"}, + {Fn: "False", Argsf: "b"}, + {Fn: "Implements", Argsf: "(*testing.TB)(nil), new(testing.T)"}, + {Fn: "Negative", Argsf: "num"}, + {Fn: "Nil", Argsf: "new(testCase)"}, + {Fn: "NoError", Argsf: "err"}, + {Fn: "NotEmpty", Argsf: "str"}, + {Fn: "Positive", Argsf: "num"}, + {Fn: "True", Argsf: "b"}, + {Fn: "Zero", Argsf: "num"}, + {Fn: "Zero", Argsf: "str"}, + {Fn: "Zero", Argsf: "new(testCase)"}, }, } } @@ -126,6 +155,7 @@ func {{ .CheckerName.AsTestName }}(t *testing.T) { var elapsed time.Time var str string var num int + var b bool var tc testCase // Invalid. diff --git a/internal/testgen/main.go b/internal/testgen/main.go index c3984b6..5836b6a 100644 --- a/internal/testgen/main.go +++ b/internal/testgen/main.go @@ -29,6 +29,7 @@ var checkerTestsGenerators = []CheckerTestsGenerator{ ComparesTestsGenerator{}, ContainsTestsGenerator{}, EmptyTestsGenerator{}, + EncodedCompareTestsGenerator{}, ErrorNilTestsGenerator{}, ErrorIsAsTestsGenerator{}, ExpectedActualTestsGenerator{},