diff options
| author | dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | 2024-03-04 17:40:11 +0000 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2024-03-04 18:34:55 +0000 |
| commit | 5fc5366972c874b919f93165bb4ed4e2bcb7c350 (patch) | |
| tree | 287c3361a0dee0c72af80d9a1a66714a06e98a62 /vendor/github.com/Antonboom | |
| parent | 1be5ce38a9059c356eb193a8c34d60d61c9fc31f (diff) | |
mod: bump github.com/golangci/golangci-lint from 1.55.2 to 1.56.2
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.55.2 to 1.56.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.55.2...v1.56.2)
---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
Diffstat (limited to 'vendor/github.com/Antonboom')
22 files changed, 1313 insertions, 243 deletions
diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go index c0b98f83c..84d7e815d 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go @@ -3,8 +3,6 @@ package analyzer import ( "fmt" "go/ast" - "go/types" - "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" @@ -79,58 +77,11 @@ func (tl *testifyLint) run(pass *analysis.Pass) (any, error) { } func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) { - se, ok := ce.Fun.(*ast.SelectorExpr) - if !ok || se.Sel == nil { - return - } - fnName := se.Sel.Name - - initiatorPkg, isPkgCall := func() (*types.Package, bool) { - // Examples: - // s.Assert -> method of *suite.Suite -> package suite ("vendor/github.com/stretchr/testify/suite") - // s.Assert().Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") - // s.Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") - // reqObj.Falsef -> method of *require.Assertions -> package require ("vendor/github.com/stretchr/testify/require") - if sel, ok := pass.TypesInfo.Selections[se]; ok { - return sel.Obj().Pkg(), false - } - - // Examples: - // assert.False -> assert -> package assert ("vendor/github.com/stretchr/testify/assert") - // require.NotEqualf -> require -> package require ("vendor/github.com/stretchr/testify/require") - if id, ok := se.X.(*ast.Ident); ok { - if selObj := pass.TypesInfo.ObjectOf(id); selObj != nil { - if pkg, ok := selObj.(*types.PkgName); ok { - return pkg.Imported(), true - } - } - } - return nil, false - }() - if initiatorPkg == nil { + call := checkers.NewCallMeta(pass, ce) + if nil == call { return } - isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath) - isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath) - if !(isAssert || isRequire) { - return - } - - call := &checkers.CallMeta{ - Range: ce, - IsPkg: isPkgCall, - IsAssert: isAssert, - Selector: se, - SelectorXStr: analysisutil.NodeString(pass.Fset, se.X), - Fn: checkers.FnMeta{ - Range: se.Sel, - Name: fnName, - IsFmt: strings.HasSuffix(fnName, "f"), - }, - Args: trimTArg(pass, isAssert, ce.Args), - ArgsRaw: ce.Args, - } for _, ch := range tl.regularCheckers { if d := ch.Check(pass, call); d != nil { pass.Report(*d) @@ -140,36 +91,3 @@ func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) { } } } - -func trimTArg(pass *analysis.Pass, isAssert bool, args []ast.Expr) []ast.Expr { - if len(args) == 0 { - return args - } - - if isTestingTPtr(pass, isAssert, args[0]) { - return args[1:] - } - return args -} - -func isTestingTPtr(pass *analysis.Pass, isAssert bool, arg ast.Expr) bool { - pkgPath := testify.RequirePkgPath - if isAssert { - pkgPath = testify.AssertPkgPath - } - - testingInterfaceObj := analysisutil.ObjectOf(pass.Pkg, pkgPath, "TestingT") - if testingInterfaceObj == nil { - return false - } - - argType := pass.TypesInfo.TypeOf(arg) - if argType == nil { - return false - } - - return types.Implements( - argType, - testingInterfaceObj.Type().Underlying().(*types.Interface), - ) -} diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go index e87e35e50..de1d6017f 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go @@ -9,14 +9,34 @@ import ( // newCheckers accepts linter config and returns slices of enabled checkers sorted by priority. func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.AdvancedChecker, error) { - enabledCheckers := cfg.EnabledCheckers - if len(enabledCheckers) == 0 { - enabledCheckers = checkers.EnabledByDefault() + if err := cfg.Validate(); err != nil { + return nil, nil, err } + + enabledCheckersSet := make(map[string]struct{}) + if cfg.EnableAll { - enabledCheckers = checkers.All() + for _, checker := range checkers.All() { + enabledCheckersSet[checker] = struct{}{} + } + } else if !cfg.DisableAll { + for _, checker := range checkers.EnabledByDefault() { + enabledCheckersSet[checker] = struct{}{} + } + } + + for _, checker := range cfg.EnabledCheckers { + enabledCheckersSet[checker] = struct{}{} + } + + for _, checker := range cfg.DisabledCheckers { + delete(enabledCheckersSet, checker) } + enabledCheckers := make([]string, 0, len(enabledCheckersSet)) + for v := range enabledCheckersSet { + enabledCheckers = append(enabledCheckers, v) + } checkers.SortByPriority(enabledCheckers) regularCheckers := make([]checkers.RegularChecker, 0, len(enabledCheckers)) @@ -32,6 +52,9 @@ func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.Advan case *checkers.ExpectedActual: c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp) + case *checkers.RequireError: + c.SetFnPattern(cfg.RequireError.FnPattern.Regexp) + case *checkers.SuiteExtraAssertCall: c.SetMode(cfg.SuiteExtraAssertCall.Mode) } diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go index e01fba5c1..4e0346d2b 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go +++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go @@ -30,5 +30,5 @@ func IsObj(typesInfo *types.Info, expr ast.Expr, expected types.Object) bool { } obj := typesInfo.ObjectOf(id) - return obj.Id() == expected.Id() + return obj == expected } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go new file mode 100644 index 000000000..403691e27 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "fmt" + "strconv" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/testify" +) + +// BlankImport detects useless blank imports of testify's packages. +// These imports are useless since testify doesn't do any magic with init() function. +// +// The checker detects situations like +// +// import ( +// "testing" +// +// _ "github.com/stretchr/testify" +// _ "github.com/stretchr/testify/assert" +// _ "github.com/stretchr/testify/http" +// _ "github.com/stretchr/testify/mock" +// _ "github.com/stretchr/testify/require" +// _ "github.com/stretchr/testify/suite" +// ) +// +// and requires +// +// import ( +// "testing" +// ) +type BlankImport struct{} + +// NewBlankImport constructs BlankImport checker. +func NewBlankImport() BlankImport { return BlankImport{} } +func (BlankImport) Name() string { return "blank-import" } + +func (checker BlankImport) Check(pass *analysis.Pass, _ *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + for _, file := range pass.Files { + for _, imp := range file.Imports { + if imp.Name == nil || imp.Name.Name != "_" { + continue + } + + pkg, err := strconv.Unquote(imp.Path.Value) + if err != nil { + continue + } + if _, ok := packagesNotIntendedForBlankImport[pkg]; !ok { + continue + } + + msg := fmt.Sprintf("avoid blank import of %s as it does nothing", pkg) + diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), imp, msg, nil)) + } + } + return diagnostics +} + +var packagesNotIntendedForBlankImport = map[string]struct{}{ + testify.ModulePath: {}, + testify.AssertPkgPath: {}, + testify.HTTPPkgPath: {}, + testify.MockPkgPath: {}, + testify.RequirePkgPath: {}, + testify.SuitePkgPath: {}, +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go index 8245ab58e..c8db9420e 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go @@ -13,7 +13,10 @@ import ( // BoolCompare detects situations like // // assert.Equal(t, false, result) -// assert.NotEqual(t, result, true) +// assert.EqualValues(t, false, result) +// assert.Exactly(t, false, result) +// assert.NotEqual(t, true, result) +// assert.NotEqualValues(t, true, result) // assert.False(t, !result) // assert.True(t, result == true) // ... @@ -60,13 +63,17 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. ) } - switch call.Fn.Name { - case "Equal", "Equalf": + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": if len(call.Args) < 2 { return nil } arg1, arg2 := call.Args[0], call.Args[1] + if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + return nil + } + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) @@ -80,12 +87,16 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } - case "NotEqual", "NotEqualf": + case "NotEqual", "NotEqualValues": if len(call.Args) < 2 { return nil } arg1, arg2 := call.Args[0], call.Args[1] + if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + return nil + } + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) @@ -99,7 +110,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } @@ -109,7 +120,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) - if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2) + if ok && !isEmptyInterface(pass, survivingArg) { return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -119,12 +131,13 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) arg3, ok3 := isNegation(expr) - if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3) + if ok && !isEmptyInterface(pass, survivingArg) { return newUseFalseDiagnostic(survivingArg, expr.Pos(), expr.End()) } } - case "False", "Falsef": + case "False": if len(call.Args) < 1 { return nil } @@ -134,7 +147,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) - if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2) + if ok && !isEmptyInterface(pass, survivingArg) { return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -144,7 +158,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) arg3, ok3 := isNegation(expr) - if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3) + if ok && !isEmptyInterface(pass, survivingArg) { return newUseTrueDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -221,3 +236,13 @@ func anyVal[T any](bools []bool, vals ...T) (T, bool) { var _default T return _default, false } + +func isEmptyInterface(pass *analysis.Pass, expr ast.Expr) bool { + t, ok := pass.TypesInfo.Types[expr] + if !ok { + return false + } + + iface, ok := t.Type.Underlying().(*types.Interface) + return ok && iface.NumMethods() == 0 +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go new file mode 100644 index 000000000..44eed49a6 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go @@ -0,0 +1,136 @@ +package checkers + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" + "github.com/Antonboom/testifylint/internal/testify" +) + +// CallMeta stores meta info about assertion function/method call, for example +// +// assert.Equal(t, 42, result, "helpful comment") +type CallMeta struct { + // Range contains start and end position of assertion call. + analysis.Range + // IsPkg true if this is package (not object) call. + IsPkg bool + // IsAssert true if this is "testify/assert" package (or object) call. + IsAssert bool + // Selector is the AST expression of "assert.Equal". + Selector *ast.SelectorExpr + // SelectorXStr is a string representation of Selector's left part – value before point, e.g. "assert". + SelectorXStr string + // Fn stores meta info about assertion function itself. + Fn FnMeta + // Args stores assertion call arguments but without `t *testing.T` argument. + // E.g [42, result, "helpful comment"]. + Args []ast.Expr + // ArgsRaw stores assertion call initial arguments. + // E.g [t, 42, result, "helpful comment"]. + ArgsRaw []ast.Expr +} + +func (c CallMeta) String() string { + return c.SelectorXStr + "." + c.Fn.Name +} + +// FnMeta stores meta info about assertion function itself, for example "Equal". +type FnMeta struct { + // Range contains start and end position of function Name. + analysis.Range + // Name is a function name. + Name string + // NameFTrimmed is a function name without "f" suffix. + NameFTrimmed string + // IsFmt is true if function is formatted, e.g. "Equalf". + IsFmt bool +} + +// NewCallMeta returns meta information about testify assertion call. +// Returns nil if ast.CallExpr is not testify call. +func NewCallMeta(pass *analysis.Pass, ce *ast.CallExpr) *CallMeta { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return nil + } + fnName := se.Sel.Name + + initiatorPkg, isPkgCall := func() (*types.Package, bool) { + // Examples: + // s.Assert -> method of *suite.Suite -> package suite ("vendor/github.com/stretchr/testify/suite") + // s.Assert().Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") + // s.Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") + // reqObj.Falsef -> method of *require.Assertions -> package require ("vendor/github.com/stretchr/testify/require") + if sel, ok := pass.TypesInfo.Selections[se]; ok { + return sel.Obj().Pkg(), false + } + + // Examples: + // assert.False -> assert -> package assert ("vendor/github.com/stretchr/testify/assert") + // require.NotEqualf -> require -> package require ("vendor/github.com/stretchr/testify/require") + if id, ok := se.X.(*ast.Ident); ok { + if selObj := pass.TypesInfo.ObjectOf(id); selObj != nil { + if pkg, ok := selObj.(*types.PkgName); ok { + return pkg.Imported(), true + } + } + } + return nil, false + }() + if initiatorPkg == nil { + return nil + } + + isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath) + isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath) + if !(isAssert || isRequire) { + return nil + } + + return &CallMeta{ + Range: ce, + IsPkg: isPkgCall, + IsAssert: isAssert, + Selector: se, + SelectorXStr: analysisutil.NodeString(pass.Fset, se.X), + Fn: FnMeta{ + Range: se.Sel, + Name: fnName, + NameFTrimmed: strings.TrimSuffix(fnName, "f"), + IsFmt: strings.HasSuffix(fnName, "f"), + }, + Args: trimTArg(pass, ce.Args), + ArgsRaw: ce.Args, + } +} + +func trimTArg(pass *analysis.Pass, args []ast.Expr) []ast.Expr { + if len(args) == 0 { + return args + } + + if isTestingTPtr(pass, args[0]) { + return args[1:] + } + return args +} + +func isTestingTPtr(pass *analysis.Pass, arg ast.Expr) bool { + assertTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, "TestingT") + requireTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, "TestingT") + + argType := pass.TypesInfo.TypeOf(arg) + if argType == nil { + return false + } + + return ((assertTestingTObj != nil) && + types.Implements(argType, assertTestingTObj.Type().Underlying().(*types.Interface))) || + ((requireTestingTObj != nil) && + types.Implements(argType, requireTestingTObj.Type().Underlying().(*types.Interface))) +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go index f3249dc3c..ac23af6f6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go @@ -1,46 +1,10 @@ package checkers import ( - "go/ast" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" ) -// CallMeta stores meta info about assertion function/method call, for example -// -// assert.Equal(t, 42, result, "helpful comment") -type CallMeta struct { - // Range contains start and end position of assertion call. - analysis.Range - // IsPkg true if this is package (not object) call. - IsPkg bool - // IsAssert true if this is "testify/assert" package (or object) call. - IsAssert bool - // Selector is the AST expression of "assert.Equal". - Selector *ast.SelectorExpr - // SelectorXStr is a string representation of Selector's left part – value before point, e.g. "assert". - SelectorXStr string - // Fn stores meta info about assertion function itself. - Fn FnMeta - // Args stores assertion call arguments but without `t *testing.T` argument. - // E.g [42, result, "helpful comment"]. - Args []ast.Expr - // ArgsRaw stores assertion call initial arguments. - // E.g [t, 42, result, "helpful comment"]. - ArgsRaw []ast.Expr -} - -// FnMeta stores meta info about assertion function itself, for example "Equal". -type FnMeta struct { - // Range contains start and end position of function Name. - analysis.Range - // Name is a function name. - Name string - // IsFmt is true if function is formatted, e.g. "Equalf". - IsFmt bool -} - // Checker describes named checker. type Checker interface { Name() string diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go index 47eaafb76..e34a21bf9 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go @@ -13,12 +13,16 @@ var registry = checkersRegistry{ {factory: asCheckerFactory(NewLen), enabledByDefault: true}, {factory: asCheckerFactory(NewCompares), enabledByDefault: true}, {factory: asCheckerFactory(NewErrorNil), enabledByDefault: true}, + {factory: asCheckerFactory(NewNilCompare), enabledByDefault: true}, {factory: asCheckerFactory(NewErrorIsAs), enabledByDefault: true}, - {factory: asCheckerFactory(NewRequireError), enabledByDefault: true}, {factory: asCheckerFactory(NewExpectedActual), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteExtraAssertCall), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteDontUsePkg), enabledByDefault: true}, + {factory: asCheckerFactory(NewUselessAssert), enabledByDefault: true}, // Advanced checkers. + {factory: asCheckerFactory(NewBlankImport), enabledByDefault: true}, + {factory: asCheckerFactory(NewGoRequire), enabledByDefault: true}, + {factory: asCheckerFactory(NewRequireError), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false}, } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go index afc829f97..336a34512 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go @@ -18,6 +18,7 @@ import ( // assert.True(t, a >= b) // assert.True(t, a < b) // assert.True(t, a <= b) +// assert.False(t, a == b) // ... // // and requires @@ -46,10 +47,10 @@ func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia var tokenToProposedFn map[token.Token]string - switch call.Fn.Name { - case "True", "Truef": + switch call.Fn.NameFTrimmed { + case "True": tokenToProposedFn = tokenToProposedFnInsteadOfTrue - case "False", "Falsef": + case "False": tokenToProposedFn = tokenToProposedFnInsteadOfFalse default: return nil diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go index 79c64205f..5ad371bb4 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go @@ -15,9 +15,19 @@ import ( // // assert.Len(t, arr, 0) // assert.Equal(t, 0, len(arr)) +// assert.EqualValues(t, 0, len(arr)) +// assert.Exactly(t, 0, len(arr)) +// assert.LessOrEqual(t, len(arr), 0) +// assert.GreaterOrEqual(t, 0, len(arr)) +// assert.Less(t, len(arr), 0) +// assert.Greater(t, 0, len(arr)) +// assert.Less(t, len(arr), 1) +// assert.Greater(t, 1, len(arr)) +// // assert.NotEqual(t, 0, len(arr)) -// assert.GreaterOrEqual(t, len(arr), 1) -// ... +// assert.NotEqualValues(t, 0, len(arr)) +// assert.Less(t, 0, len(arr)) +// assert.Greater(t, len(arr), 0) // // and requires // @@ -53,13 +63,13 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D } a, b := call.Args[0], call.Args[1] - switch call.Fn.Name { - case "Len", "Lenf": + switch call.Fn.NameFTrimmed { + case "Len": if isZero(b) { return newUseEmptyDiagnostic(a.Pos(), b.End(), a) } - case "Equal", "Equalf": + case "Equal", "EqualValues", "Exactly": arg1, ok1 := isLenCallAndZero(pass, a, b) arg2, ok2 := isLenCallAndZero(pass, b, a) @@ -67,23 +77,23 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "LessOrEqual", "LessOrEqualf": + case "LessOrEqual": if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "GreaterOrEqual", "GreaterOrEqualf": + case "GreaterOrEqual": if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Less", "Lessf": - if lenArg, ok := isBuiltinLenCall(pass, a); ok && isOne(b) { + case "Less": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && (isOne(b) || isZero(b)) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Greater", "Greaterf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + case "Greater": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && (isOne(a) || isZero(a)) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } } @@ -107,8 +117,8 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi } a, b := call.Args[0], call.Args[1] - switch call.Fn.Name { - case "NotEqual", "NotEqualf": + switch call.Fn.NameFTrimmed { + case "NotEqual", "NotEqualValues": arg1, ok1 := isLenCallAndZero(pass, a, b) arg2, ok2 := isLenCallAndZero(pass, b, a) @@ -116,23 +126,13 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Greater", "Greaterf": - if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) || isOne(b) { - return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) - } - - case "Less", "Lessf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) || isOne(a) { - return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) - } - - case "GreaterOrEqual", "GreaterOrEqualf": - if lenArg, ok := isBuiltinLenCall(pass, a); ok && isOne(b) { + case "Less": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) { return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "LessOrEqual", "LessOrEqualf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + case "Greater": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go index e6abd0ba4..0363873a6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go @@ -3,6 +3,7 @@ package checkers import ( "fmt" "go/ast" + "go/types" "golang.org/x/tools/go/analysis" @@ -22,6 +23,8 @@ import ( // assert.ErrorIs(t, err, errSentinel) // assert.NotErrorIs(t, err, errSentinel) // assert.ErrorAs(t, err, &target) +// +// Also ErrorIsAs repeats go vet's "errorsas" check logic. type ErrorIsAs struct{} // NewErrorIsAs constructs ErrorIsAs checker. @@ -29,22 +32,22 @@ func NewErrorIsAs() ErrorIsAs { return ErrorIsAs{} } func (ErrorIsAs) Name() string { return "error-is-as" } func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - switch call.Fn.Name { - case "Error", "Errorf": + switch call.Fn.NameFTrimmed { + case "Error": if len(call.Args) >= 2 && isError(pass, call.Args[1]) { const proposed = "ErrorIs" msg := fmt.Sprintf("invalid usage of %[1]s.Error, use %[1]s.%[2]s instead", call.SelectorXStr, proposed) return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed)) } - case "NoError", "NoErrorf": + case "NoError": if len(call.Args) >= 2 && isError(pass, call.Args[1]) { const proposed = "NotErrorIs" msg := fmt.Sprintf("invalid usage of %[1]s.NoError, use %[1]s.%[2]s instead", call.SelectorXStr, proposed) return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed)) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } @@ -74,7 +77,7 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di ) } - case "False", "Falsef": + case "False": if len(call.Args) < 1 { return nil } @@ -97,6 +100,45 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di }), ) } + + case "ErrorAs": + if len(call.Args) < 2 { + return nil + } + + // NOTE(a.telyshev): Logic below must be consistent with + // https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/errorsas/errorsas.go + + var ( + defaultReport = fmt.Sprintf("second argument to %s must be a non-nil pointer to either a type that implements error, or to any interface type", call) //nolint:lll + errorPtrReport = fmt.Sprintf("second argument to %s should not be *error", call) + ) + + target := call.Args[1] + + if isEmptyInterface(pass, target) { + // `any` interface case. It is always allowed, since it often indicates + // a value forwarded from another source. + return nil + } + + tv, ok := pass.TypesInfo.Types[target] + if !ok { + return nil + } + + pt, ok := tv.Type.Underlying().(*types.Pointer) + if !ok { + return newDiagnostic(checker.Name(), call, defaultReport, nil) + } + if pt.Elem() == errorType { + return newDiagnostic(checker.Name(), call, errorPtrReport, nil) + } + + _, isInterface := pt.Elem().Underlying().(*types.Interface) + if !isInterface && !types.Implements(pt.Elem(), errorIface) { + return newDiagnostic(checker.Name(), call, defaultReport, nil) + } } return nil } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go index b45629a48..5b0af7458 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go @@ -14,8 +14,14 @@ import ( // // assert.Nil(t, err) // assert.NotNil(t, err) -// assert.Equal(t, err, nil) -// assert.NotEqual(t, err, nil) +// assert.Equal(t, nil, err) +// assert.EqualValues(t, nil, err) +// assert.Exactly(t, nil, err) +// assert.ErrorIs(t, err, nil) +// +// assert.NotEqual(t, nil, err) +// assert.NotEqualValues(t, nil, err) +// assert.NotErrorIs(t, err, nil) // // and requires // @@ -34,40 +40,40 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia ) proposedFn, survivingArg, replacementEndPos := func() (string, ast.Expr, token.Pos) { - switch call.Fn.Name { - case "NotNil", "NotNilf": + switch call.Fn.NameFTrimmed { + case "Nil": if len(call.Args) >= 1 && isError(pass, call.Args[0]) { - return errorFn, call.Args[0], call.Args[0].End() + return noErrorFn, call.Args[0], call.Args[0].End() } - case "Nil", "Nilf": + case "NotNil": if len(call.Args) >= 1 && isError(pass, call.Args[0]) { - return noErrorFn, call.Args[0], call.Args[0].End() + return errorFn, call.Args[0], call.Args[0].End() } - case "Equal", "Equalf": + case "Equal", "EqualValues", "Exactly", "ErrorIs": if len(call.Args) < 2 { return "", nil, token.NoPos } a, b := call.Args[0], call.Args[1] switch { - case isError(pass, a) && isNil(pass, b): + case isError(pass, a) && isNil(b): return noErrorFn, a, b.End() - case isNil(pass, a) && isError(pass, b): + case isNil(a) && isError(pass, b): return noErrorFn, b, b.End() } - case "NotEqual", "NotEqualf": + case "NotEqual", "NotEqualValues", "NotErrorIs": if len(call.Args) < 2 { return "", nil, token.NoPos } a, b := call.Args[0], call.Args[1] switch { - case isError(pass, a) && isNil(pass, b): + case isError(pass, a) && isNil(b): return errorFn, a, b.End() - case isNil(pass, a) && isError(pass, b): + case isNil(a) && isError(pass, b): return errorFn, b, b.End() } } @@ -86,7 +92,10 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia return nil } -var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) +var ( + errorType = types.Universe.Lookup("error").Type() + errorIface = errorType.Underlying().(*types.Interface) +) func isError(pass *analysis.Pass, expr ast.Expr) bool { t := pass.TypesInfo.TypeOf(expr) @@ -95,15 +104,10 @@ func isError(pass *analysis.Pass, expr ast.Expr) bool { } _, ok := t.Underlying().(*types.Interface) - return ok && types.Implements(t, errIface) + return ok && types.Implements(t, errorIface) } -func isNil(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - - b, ok := t.(*types.Basic) - return ok && b.Kind()&types.UntypedNil > 0 +func isNil(expr ast.Expr) bool { + ident, ok := expr.(*ast.Ident) + return ok && ident.Name == "nil" } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go index ff8243980..e6825eaa6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go @@ -2,10 +2,13 @@ package checkers import ( "go/ast" + "go/token" "go/types" "regexp" "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" ) // DefaultExpectedVarPattern matches variables with "expected" or "wanted" prefix or suffix in the name. @@ -14,11 +17,30 @@ var DefaultExpectedVarPattern = regexp.MustCompile( // ExpectedActual detects situation like // -// assert.NotEqual(t, result, "expected value") +// assert.Equal(t, result, expected) +// assert.EqualExportedValues(t, resultObj, User{Name: "Anton"}) +// assert.EqualValues(t, result, 42) +// assert.Exactly(t, result, int64(42)) +// assert.JSONEq(t, result, `{"version": 3}`) +// assert.InDelta(t, result, 42.42, 1.0) +// assert.InDeltaMapValues(t, result, map[string]float64{"score": 0.99}, 1.0) +// assert.InDeltaSlice(t, result, []float64{0.98, 0.99}, 1.0) +// assert.InEpsilon(t, result, 42.42, 0.0001) +// assert.InEpsilonSlice(t, result, []float64{0.9801, 0.9902}, 0.0001) +// assert.IsType(t, result, (*User)(nil)) +// assert.NotEqual(t, result, "expected") +// assert.NotEqualValues(t, result, "expected") +// assert.NotSame(t, resultPtr, &value) +// assert.Same(t, resultPtr, &value) +// assert.WithinDuration(t, resultTime, time.Date(2023, 01, 12, 11, 46, 33, 0, nil), time.Second) +// assert.YAMLEq(t, result, "version: '3'") // // and requires // -// assert.NotEqual(t, "expected value", result) +// assert.Equal(t, expected, result) +// assert.EqualExportedValues(t, User{Name: "Anton"}, resultObj) +// assert.EqualValues(t, 42, result) +// ... type ExpectedActual struct { expVarPattern *regexp.Regexp } @@ -38,9 +60,24 @@ func (checker *ExpectedActual) SetExpVarPattern(p *regexp.Regexp) *ExpectedActua } func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - switch call.Fn.Name { - case "Equal", "Equalf", "NotEqual", "NotEqualf", - "JSONEq", "JSONEqf", "YAMLEq", "YAMLEqf": + switch call.Fn.NameFTrimmed { + case "Equal", + "EqualExportedValues", + "EqualValues", + "Exactly", + "InDelta", + "InDeltaMapValues", + "InDeltaSlice", + "InEpsilon", + "InEpsilonSlice", + "IsType", + "JSONEq", + "NotEqual", + "NotEqualValues", + "NotSame", + "Same", + "WithinDuration", + "YAMLEq": default: return nil } @@ -73,21 +110,37 @@ func (checker ExpectedActual) isWrongExpectedActualOrder(pass *analysis.Pass, fi func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr ast.Expr) bool { switch v := expr.(type) { + case *ast.ParenExpr: + return checker.isExpectedValueCandidate(pass, v.X) + + case *ast.StarExpr: // *value + return checker.isExpectedValueCandidate(pass, v.X) + + case *ast.UnaryExpr: + return (v.Op == token.AND) && checker.isExpectedValueCandidate(pass, v.X) // &value + case *ast.CompositeLit: return true case *ast.CallExpr: - return isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || - isExpectedValueFactory(v, checker.expVarPattern) + return isParenExpr(v) || + isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || + isExpectedValueFactory(pass, v, checker.expVarPattern) } return isBasicLit(expr) || isUntypedConst(pass, expr) || isTypedConst(pass, expr) || isIdentNamedAsExpected(checker.expVarPattern, expr) || + isStructVarNamedAsExpected(checker.expVarPattern, expr) || isStructFieldNamedAsExpected(checker.expVarPattern, expr) } +func isParenExpr(ce *ast.CallExpr) bool { + _, ok := ce.Fun.(*ast.ParenExpr) + return ok +} + func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) bool { if len(ce.Args) != 1 { return false @@ -111,15 +164,16 @@ func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) b return false } -func isExpectedValueFactory(ce *ast.CallExpr, pattern *regexp.Regexp) bool { - if len(ce.Args) != 0 { - return false - } - +func isExpectedValueFactory(pass *analysis.Pass, ce *ast.CallExpr, pattern *regexp.Regexp) bool { switch fn := ce.Fun.(type) { case *ast.Ident: return pattern.MatchString(fn.Name) + case *ast.SelectorExpr: + timeDateFn := analysisutil.ObjectOf(pass.Pkg, "time", "Date") + if timeDateFn != nil && analysisutil.IsObj(pass.TypesInfo, fn.Sel, timeDateFn) { + return true + } return pattern.MatchString(fn.Sel.Name) } return false @@ -150,6 +204,11 @@ func isIdentNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { return ok && pattern.MatchString(id.Name) } +func isStructVarNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { + s, ok := e.(*ast.SelectorExpr) + return ok && isIdentNamedAsExpected(pattern, s.X) +} + func isStructFieldNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { s, ok := e.(*ast.SelectorExpr) return ok && isIdentNamedAsExpected(pattern, s.Sel) diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go index 7d5b358b3..10b1330de 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go @@ -1,6 +1,7 @@ package checkers import ( + "fmt" "go/ast" "go/token" "go/types" @@ -10,13 +11,15 @@ import ( // FloatCompare detects situation like // -// assert.Equal(t, 42.42, a) -// assert.True(t, a == 42.42) -// assert.False(t, a != 42.42) +// assert.Equal(t, 42.42, result) +// assert.EqualValues(t, 42.42, result) +// assert.Exactly(t, 42.42, result) +// assert.True(t, result == 42.42) +// assert.False(t, result != 42.42) // // and requires // -// assert.InEpsilon(t, 42.42, a, 0.0001) // Or assert.InDelta +// assert.InEpsilon(t, 42.42, result, 0.0001) // Or assert.InDelta type FloatCompare struct{} // NewFloatCompare constructs FloatCompare checker. @@ -25,21 +28,25 @@ func (FloatCompare) Name() string { return "float-compare" } func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { invalid := func() bool { - switch call.Fn.Name { - case "Equal", "Equalf": - return len(call.Args) > 1 && isFloat(pass, call.Args[0]) && isFloat(pass, call.Args[1]) + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + return len(call.Args) > 1 && (isFloat(pass, call.Args[0]) || isFloat(pass, call.Args[1])) - case "True", "Truef": + case "True": return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.EQL) - case "False", "Falsef": + case "False": return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.NEQ) } return false }() if invalid { - return newUseFunctionDiagnostic(checker.Name(), call, "InEpsilon (or InDelta)", nil) + format := "use %s.InEpsilon (or InDelta)" + if call.Fn.IsFmt { + format = "use %s.InEpsilonf (or InDeltaf)" + } + return newDiagnostic(checker.Name(), call, fmt.Sprintf(format, call.SelectorXStr), nil) } return nil } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go new file mode 100644 index 000000000..4c0c9d1fd --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go @@ -0,0 +1,301 @@ +package checkers + +import ( + "fmt" + "go/ast" + "go/types" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +const ( + goRequireFnReportFormat = "%s contains assertions that must only be used in the goroutine running the test function" + goRequireCallReportFormat = "%s must only be used in the goroutine running the test function" +) + +// GoRequire takes idea from go vet's "testinggoroutine" check +// and detects usage of require package's functions or assert.FailNow in the non-test goroutines +// +// go func() { +// conn, err = lis.Accept() +// require.NoError(t, err) +// +// if assert.Error(err) { +// assert.FailNow(t, msg) +// } +// }() +type GoRequire struct{} + +// NewGoRequire constructs GoRequire checker. +func NewGoRequire() GoRequire { return GoRequire{} } +func (GoRequire) Name() string { return "go-require" } + +// Check should be consistent with +// https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/testinggoroutine/testinggoroutine.go +// +// But due to the fact that the Check covers cases missed by go vet, +// the implementation turned out to be terribly complicated. +// +// In simple words, the algorithm is as follows: +// - we walk along the call tree and store the status, whether we are in the test goroutine or not; +// - if we are in a test goroutine, then require is allowed, otherwise not; +// - when we encounter the launch of a subtest or `go` statement, the status changes; +// - in order to correctly handle the return to the correct status when exiting the current function, +// we have to store a stack of statuses (inGoroutineRunningTestFunc). +// +// Other test functions called in the test function are also analyzed to make a verdict about the current function. +// This leads to recursion, which the cache of processed functions (processedFuncs) helps reduce the impact of. +// Also, because of this, we have to pre-collect a list of test function declarations (testsDecls). +func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + testsDecls := make(funcDeclarations) + inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) { + fd := node.(*ast.FuncDecl) + + if isTestingFuncOrMethod(pass, fd) { + if tf, ok := pass.TypesInfo.ObjectOf(fd.Name).(*types.Func); ok { + testsDecls[tf] = fd + } + } + }) + + var inGoroutineRunningTestFunc boolStack + processedFuncs := make(map[*ast.FuncDecl]goRequireVerdict) + + nodesFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + (*ast.GoStmt)(nil), + (*ast.CallExpr)(nil), + } + inspector.Nodes(nodesFilter, func(node ast.Node, push bool) bool { + if fd, ok := node.(*ast.FuncDecl); ok { + if !isTestingFuncOrMethod(pass, fd) { + return false + } + + if push { + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + if _, ok := node.(*ast.GoStmt); ok { + if push { + inGoroutineRunningTestFunc.Push(false) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + ce := node.(*ast.CallExpr) + if isSubTestRun(pass, ce) { + if push { + // t.Run spawns the new testing goroutine and declines + // possible warnings from previous "simple" goroutine. + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + if !push { + return false + } + if inGoroutineRunningTestFunc.Last() { + // We are in testing goroutine and can skip any assertion checks. + return true + } + + testifyCall := NewCallMeta(pass, ce) + if testifyCall != nil { + switch checker.checkCall(testifyCall) { + case goRequireVerdictRequire: + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, "require"), nil) + diagnostics = append(diagnostics, *d) + + case goRequireVerdictAssertFailNow: + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, testifyCall), nil) + diagnostics = append(diagnostics, *d) + + case goRequireVerdictNoExit: + } + return false + } + + // Case of nested function call. + { + calledFd := testsDecls.Get(pass, ce) + if calledFd == nil { + return true + } + + 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) + diagnostics = append(diagnostics, *d) + } + } + return true + }) + + return diagnostics +} + +func (checker GoRequire) checkFunc( + pass *analysis.Pass, + fd *ast.FuncDecl, + testsDecls funcDeclarations, + processedFuncs map[*ast.FuncDecl]goRequireVerdict, +) (result goRequireVerdict) { + if v, ok := processedFuncs[fd]; ok { + return v + } + + ast.Inspect(fd, func(node ast.Node) bool { + if result != goRequireVerdictNoExit { + return false + } + + if _, ok := node.(*ast.GoStmt); ok { + return false + } + + ce, ok := node.(*ast.CallExpr) + if !ok { + return true + } + + testifyCall := NewCallMeta(pass, ce) + if testifyCall != nil { + if v := checker.checkCall(testifyCall); v != goRequireVerdictNoExit { + result, processedFuncs[fd] = v, v + } + return false + } + + // Case of nested function call. + { + calledFd := testsDecls.Get(pass, ce) + if calledFd == nil { + return true + } + if calledFd == fd { + // Recursion. + return true + } + + if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit { + result = v + return false + } + return true + } + }) + + return result +} + +type goRequireVerdict int + +const ( + goRequireVerdictNoExit goRequireVerdict = iota + goRequireVerdictRequire + goRequireVerdictAssertFailNow +) + +func (checker GoRequire) checkCall(call *CallMeta) goRequireVerdict { + if !call.IsAssert { + return goRequireVerdictRequire + } + if call.Fn.NameFTrimmed == "FailNow" { + return goRequireVerdictAssertFailNow + } + return goRequireVerdictNoExit +} + +type funcDeclarations map[*types.Func]*ast.FuncDecl + +// Get returns the declaration of a called function or method. +// Currently, only static calls within the same package are supported, otherwise returns nil. +func (fd funcDeclarations) Get(pass *analysis.Pass, ce *ast.CallExpr) *ast.FuncDecl { + var obj types.Object + + switch fun := ce.Fun.(type) { + case *ast.SelectorExpr: + obj = pass.TypesInfo.ObjectOf(fun.Sel) + + case *ast.Ident: + obj = pass.TypesInfo.ObjectOf(fun) + + case *ast.IndexExpr: + if id, ok := fun.X.(*ast.Ident); ok { + obj = pass.TypesInfo.ObjectOf(id) + } + + case *ast.IndexListExpr: + if id, ok := fun.X.(*ast.Ident); ok { + obj = pass.TypesInfo.ObjectOf(id) + } + } + + if tf, ok := obj.(*types.Func); ok { + return fd[tf] + } + return nil +} + +type boolStack []bool + +func (s *boolStack) Push(v bool) { + *s = append(*s, v) +} + +func (s *boolStack) Pop() bool { + n := len(*s) + if n == 0 { + return false + } + + last := (*s)[n-1] + *s = (*s)[:n-1] + return last +} + +func (s boolStack) Last() bool { + n := len(s) + if n == 0 { + return false + } + return s[n-1] +} + +func isSubTestRun(pass *analysis.Pass, ce *ast.CallExpr) bool { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return false + } + return (isTestingTPtr(pass, se.X) || implementsTestifySuiteIface(pass, se.X)) && se.Sel.Name == "Run" +} + +func isTestingFuncOrMethod(pass *analysis.Pass, fd *ast.FuncDecl) bool { + return hasTestingTParam(pass, fd) || isTestifySuiteMethod(pass, fd) +} + +func hasTestingTParam(pass *analysis.Pass, fd *ast.FuncDecl) bool { + if fd.Type == nil || fd.Type.Params == nil { + return false + } + + for _, param := range fd.Type.Params.List { + if isTestingTPtr(pass, param.Type) { + return true + } + } + return false +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go index f10412f63..d4e6a48b5 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go @@ -10,6 +10,8 @@ import ( // Len detects situations like // // assert.Equal(t, 3, len(arr)) +// assert.EqualValues(t, 3, len(arr)) +// assert.Exactly(t, 3, len(arr)) // assert.True(t, len(arr) == 3) // // and requires @@ -24,14 +26,18 @@ func (Len) Name() string { return "len" } func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { const proposedFn = "Len" - switch call.Fn.Name { - case "Equal", "Equalf": + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": if len(call.Args) < 2 { return nil } a, b := call.Args[0], call.Args[1] if lenArg, expectedLen, ok := xorLenCall(pass, a, b); ok { + if expectedLen == b && !isIntBasicLit(expectedLen) { + // https://github.com/Antonboom/testifylint/issues/9 + return nil + } return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ Pos: a.Pos(), @@ -41,13 +47,13 @@ func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnost ) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } expr := call.Args[0] - if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok { + if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok && isIntBasicLit(expectedLen) { return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ Pos: expr.Pos(), @@ -84,3 +90,8 @@ func isLenEquality(pass *analysis.Pass, e ast.Expr) (ast.Expr, ast.Expr, bool) { } return xorLenCall(pass, be.X, be.Y) } + +func isIntBasicLit(e ast.Expr) bool { + bl, ok := e.(*ast.BasicLit) + return ok && bl.Kind == token.INT +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go new file mode 100644 index 000000000..89680a069 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// NilCompare detects situations like +// +// assert.Equal(t, nil, value) +// assert.EqualValues(t, nil, value) +// assert.Exactly(t, nil, value) +// +// assert.NotEqual(t, nil, value) +// assert.NotEqualValues(t, nil, value) +// +// and requires +// +// assert.Nil(t, value) +// assert.NotNil(t, value) +type NilCompare struct{} + +// NewNilCompare constructs NilCompare checker. +func NewNilCompare() NilCompare { return NilCompare{} } +func (NilCompare) Name() string { return "nil-compare" } + +func (checker NilCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if len(call.Args) < 2 { + return nil + } + + survivingArg, ok := xorNil(call.Args[0], call.Args[1]) + if !ok { + return nil + } + + var proposedFn string + + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + proposedFn = "Nil" + case "NotEqual", "NotEqualValues": + proposedFn = "NotNil" + default: + return nil + } + + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: call.Args[0].Pos(), + End: call.Args[1].End(), + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }), + ) +} + +func xorNil(first, second ast.Expr) (ast.Expr, bool) { + a, b := isNil(first), isNil(second) + if xor(a, b) { + if a { + return second, true + } + return first, true + } + return nil, false +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go index 2da71ed81..ab09dd447 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go @@ -1,35 +1,338 @@ package checkers -import "golang.org/x/tools/go/analysis" +import ( + "fmt" + "go/ast" + "go/token" + "regexp" -// RequireError detects situations like + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" +) + +const requireErrorReport = "for error assertions use require" + +// RequireError detects error assertions like // +// assert.Error(t, err) // s.Error(err), s.Assert().Error(err) +// assert.ErrorIs(t, err, io.EOF) +// assert.ErrorAs(t, err, &target) +// assert.EqualError(t, err, "end of file") +// assert.ErrorContains(t, err, "end of file") // assert.NoError(t, err) -// s.ErrorIs(err, io.EOF) -// s.Assert().Error(err) +// assert.NotErrorIs(t, err, io.EOF) // // and requires // -// require.NoError(t, err) -// s.Require().ErrorIs(err, io.EOF) -// s.Require().Error(err) -type RequireError struct{} +// require.Error(t, err) // s.Require().Error(err), s.Require().Error(err) +// require.ErrorIs(t, err, io.EOF) +// require.ErrorAs(t, err, &target) +// ... +// +// RequireError ignores: +// - assertion in the `if` condition; +// - the entire `if-else[-if]` block, if there is an assertion in any `if` condition; +// - the last assertion in the block, if there are no methods/functions calls after it; +// - assertions in an explicit goroutine; +// - assertions in an explicit testing cleanup function or suite teardown methods; +// - sequence of NoError assertions. +type RequireError struct { + fnPattern *regexp.Regexp +} // NewRequireError constructs RequireError checker. -func NewRequireError() RequireError { return RequireError{} } -func (RequireError) Name() string { return "require-error" } +func NewRequireError() *RequireError { return new(RequireError) } +func (RequireError) Name() string { return "require-error" } + +func (checker *RequireError) SetFnPattern(p *regexp.Regexp) *RequireError { + if p != nil { + checker.fnPattern = p + } + return checker +} + +func (checker RequireError) Check(pass *analysis.Pass, inspector *inspector.Inspector) []analysis.Diagnostic { + callsByFunc := make(map[funcID][]*callMeta) + + // Stage 1. Collect meta information about any calls inside functions. + + inspector.WithStack([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool, stack []ast.Node) bool { + if !push { + return false + } + if len(stack) < 3 { + return true + } + + fID := findSurroundingFunc(pass, stack) + if fID == nil { + return true + } + + _, prevIsIfStmt := stack[len(stack)-2].(*ast.IfStmt) + _, prevIsAssignStmt := stack[len(stack)-2].(*ast.AssignStmt) + _, prevPrevIsIfStmt := stack[len(stack)-3].(*ast.IfStmt) + inIfCond := prevIsIfStmt || (prevPrevIsIfStmt && prevIsAssignStmt) + + callExpr := node.(*ast.CallExpr) + testifyCall := NewCallMeta(pass, callExpr) + + call := &callMeta{ + call: callExpr, + testifyCall: testifyCall, + rootIf: findRootIf(stack), + parentIf: findNearestNode[*ast.IfStmt](stack), + parentBlock: findNearestNode[*ast.BlockStmt](stack), + inIfCond: inIfCond, + inNoErrorSeq: false, // Will be filled in below. + } + + callsByFunc[*fID] = append(callsByFunc[*fID], call) + return testifyCall == nil // Do not support asserts in asserts. + }) + + // Stage 2. Analyze calls and block context. + + var diagnostics []analysis.Diagnostic + + callsByBlock := map[*ast.BlockStmt][]*callMeta{} + for _, calls := range callsByFunc { + for _, c := range calls { + if b := c.parentBlock; b != nil { + callsByBlock[b] = append(callsByBlock[b], c) + } + } + } + + markCallsInNoErrorSequence(callsByBlock) + + for funcInfo, calls := range callsByFunc { + for i, c := range calls { + if funcInfo.isTestCleanup { + continue + } + if funcInfo.isGoroutine { + continue + } + + if c.testifyCall == nil { + continue + } + if !c.testifyCall.IsAssert { + continue + } + switch c.testifyCall.Fn.NameFTrimmed { + default: + continue + case "Error", "ErrorIs", "ErrorAs", "EqualError", "ErrorContains", "NoError", "NotErrorIs": + } + + if needToSkipBasedOnContext(c, i, calls, callsByBlock) { + continue + } + if p := checker.fnPattern; p != nil && !p.MatchString(c.testifyCall.Fn.Name) { + continue + } + + diagnostics = append(diagnostics, + *newDiagnostic(checker.Name(), c.testifyCall, requireErrorReport, nil)) + } + } + + return diagnostics +} + +func needToSkipBasedOnContext( + currCall *callMeta, + currCallIndex int, + otherCalls []*callMeta, + callsByBlock map[*ast.BlockStmt][]*callMeta, +) bool { + if currCall.inNoErrorSeq { + // Skip `assert.NoError` sequence. + return true + } + + if currCall.inIfCond { + // Skip assertions in the "if condition". + return true + } + + if currCall.rootIf != nil { + for _, rootCall := range otherCalls { + if (rootCall.rootIf == currCall.rootIf) && rootCall.inIfCond { + // Skip assertions in the entire if-else[-if] block, if some of "if condition" contains assertion. + return true + } + } + } + + block := currCall.parentBlock + blockCalls := callsByBlock[block] + isLastCallInBlock := blockCalls[len(blockCalls)-1] == currCall + + noCallsAfter := true -func (checker RequireError) Check(_ *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - if !call.IsAssert { - return nil + _, blockEndWithReturn := block.List[len(block.List)-1].(*ast.ReturnStmt) + if !blockEndWithReturn { + for i := currCallIndex + 1; i < len(otherCalls); i++ { + nextCall := otherCalls[i] + nextCallInElseBlock := false + + if pIf := currCall.parentIf; pIf != nil && pIf.Else != nil { + ast.Inspect(pIf.Else, func(n ast.Node) bool { + if n == nextCall.call { + nextCallInElseBlock = true + return false + } + return true + }) + } + + if !nextCallInElseBlock { + noCallsAfter = false + break + } + } } - const msg = "for error assertions use require" + // Skip assertion if this is the last operation in the test. + return isLastCallInBlock && noCallsAfter +} + +func findSurroundingFunc(pass *analysis.Pass, stack []ast.Node) *funcID { + for i := len(stack) - 2; i >= 0; i-- { + var fType *ast.FuncType + var fName string + var isTestCleanup bool + var isGoroutine bool + + switch fd := stack[i].(type) { + case *ast.FuncDecl: + fType, fName = fd.Type, fd.Name.Name + + if isTestifySuiteMethod(pass, fd) { + if ident := fd.Name; ident != nil && isAfterTestMethod(ident.Name) { + isTestCleanup = true + } + } + + case *ast.FuncLit: + fType, fName = fd.Type, "anonymous" + + if i >= 2 { //nolint:nestif + if ce, ok := stack[i-1].(*ast.CallExpr); ok { + if se, ok := ce.Fun.(*ast.SelectorExpr); ok { + isTestCleanup = isTestingTPtr(pass, se.X) && se.Sel != nil && (se.Sel.Name == "Cleanup") + } + + if _, ok := stack[i-2].(*ast.GoStmt); ok { + isGoroutine = true + } + } + } - switch call.Fn.Name { - case "Error", "ErrorIs", "ErrorAs", "EqualError", "ErrorContains", "NoError", "NotErrorIs", - "Errorf", "ErrorIsf", "ErrorAsf", "EqualErrorf", "ErrorContainsf", "NoErrorf", "NotErrorIsf": - return newDiagnostic(checker.Name(), call, msg, nil) + default: + continue + } + + return &funcID{ + pos: fType.Pos(), + posStr: pass.Fset.Position(fType.Pos()).String(), + name: fName, + isTestCleanup: isTestCleanup, + isGoroutine: isGoroutine, + } } return nil } + +func findRootIf(stack []ast.Node) *ast.IfStmt { + nearestIf, i := findNearestNodeWithIdx[*ast.IfStmt](stack) + for ; i > 0; i-- { + parent, ok := stack[i-1].(*ast.IfStmt) + if ok { + nearestIf = parent + } else { + break + } + } + return nearestIf +} + +func findNearestNode[T ast.Node](stack []ast.Node) (v T) { + v, _ = findNearestNodeWithIdx[T](stack) + return +} + +func findNearestNodeWithIdx[T ast.Node](stack []ast.Node) (v T, index int) { + for i := len(stack) - 2; i >= 0; i-- { + if n, ok := stack[i].(T); ok { + return n, i + } + } + return +} + +func markCallsInNoErrorSequence(callsByBlock map[*ast.BlockStmt][]*callMeta) { + for _, calls := range callsByBlock { + for i, c := range calls { + if c.testifyCall == nil { + continue + } + + var prevIsNoError bool + if i > 0 { + if prev := calls[i-1].testifyCall; prev != nil { + prevIsNoError = isNoErrorAssertion(prev.Fn.Name) + } + } + + var nextIsNoError bool + if i < len(calls)-1 { + if next := calls[i+1].testifyCall; next != nil { + nextIsNoError = isNoErrorAssertion(next.Fn.Name) + } + } + + if isNoErrorAssertion(c.testifyCall.Fn.Name) && (prevIsNoError || nextIsNoError) { + calls[i].inNoErrorSeq = true + } + } + } +} + +type callMeta struct { + call *ast.CallExpr + testifyCall *CallMeta + rootIf *ast.IfStmt // The root `if` in if-else[-if] chain. + parentIf *ast.IfStmt // The nearest `if`, can be equal with rootIf. + parentBlock *ast.BlockStmt + inIfCond bool // True for code like `if assert.ErrorAs(t, err, &target) {`. + inNoErrorSeq bool // True for sequence of `assert.NoError` assertions. +} + +type funcID struct { + pos token.Pos + posStr string + name string + isTestCleanup bool + isGoroutine bool +} + +func (id funcID) String() string { + return fmt.Sprintf("%s at %s", id.name, id.posStr) +} + +func isAfterTestMethod(name string) bool { + // https://github.com/stretchr/testify/blob/master/suite/interfaces.go + switch name { + case "TearDownSuite", "TearDownTest", "AfterTest", "HandleStats", "TearDownSubTest": + return true + } + return false +} + +func isNoErrorAssertion(fnName string) bool { + return (fnName == "NoError") || (fnName == "NoErrorf") +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go new file mode 100644 index 000000000..669f9d187 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go @@ -0,0 +1,71 @@ +package checkers + +import ( + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// UselessAssert detects useless asserts like +// +// 1) Asserting of the same variable +// +// assert.Equal(t, tt.value, tt.value) +// assert.ElementsMatch(t, users, users) +// ... +// +// 2) Open for contribution... +type UselessAssert struct{} + +// NewUselessAssert constructs UselessAssert checker. +func NewUselessAssert() UselessAssert { return UselessAssert{} } +func (UselessAssert) Name() string { return "useless-assert" } + +func (checker UselessAssert) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + switch call.Fn.NameFTrimmed { + case + "Contains", + "ElementsMatch", + "Equal", + "EqualExportedValues", + "EqualValues", + "ErrorAs", + "ErrorIs", + "Exactly", + "Greater", + "GreaterOrEqual", + "Implements", + "InDelta", + "InDeltaMapValues", + "InDeltaSlice", + "InEpsilon", + "InEpsilonSlice", + "IsType", + "JSONEq", + "Less", + "LessOrEqual", + "NotEqual", + "NotEqualValues", + "NotErrorIs", + "NotRegexp", + "NotSame", + "NotSubset", + "Regexp", + "Same", + "Subset", + "WithinDuration", + "YAMLEq": + default: + return nil + } + + if len(call.Args) < 2 { + return nil + } + first, second := call.Args[0], call.Args[1] + + if analysisutil.NodeString(pass.Fset, first) == analysisutil.NodeString(pass.Fset, second) { + return newDiagnostic(checker.Name(), call, "asserting of the same variable", nil) + } + return nil +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/config.go b/vendor/github.com/Antonboom/testifylint/internal/config/config.go index 51f627008..6dcfbdb52 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/config.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/config.go @@ -1,7 +1,9 @@ package config import ( + "errors" "flag" + "fmt" "github.com/Antonboom/testifylint/internal/checkers" ) @@ -9,11 +11,16 @@ import ( // NewDefault builds default testifylint config. func NewDefault() Config { return Config{ - EnableAll: false, - EnabledCheckers: checkers.EnabledByDefault(), + EnableAll: false, + DisabledCheckers: nil, + DisableAll: false, + EnabledCheckers: nil, ExpectedActual: ExpectedActualConfig{ ExpVarPattern: RegexpValue{checkers.DefaultExpectedVarPattern}, }, + RequireError: RequireErrorConfig{ + FnPattern: RegexpValue{nil}, + }, SuiteExtraAssertCall: SuiteExtraAssertCallConfig{ Mode: checkers.DefaultSuiteExtraAssertCallMode, }, @@ -22,9 +29,13 @@ func NewDefault() Config { // Config implements testifylint configuration. type Config struct { - EnableAll bool - EnabledCheckers KnownCheckersValue + EnableAll bool + DisabledCheckers KnownCheckersValue + DisableAll bool + EnabledCheckers KnownCheckersValue + ExpectedActual ExpectedActualConfig + RequireError RequireErrorConfig SuiteExtraAssertCall SuiteExtraAssertCallConfig } @@ -33,16 +44,55 @@ type ExpectedActualConfig struct { ExpVarPattern RegexpValue } +// RequireErrorConfig implements configuration of checkers.RequireError. +type RequireErrorConfig struct { + FnPattern RegexpValue +} + // SuiteExtraAssertCallConfig implements configuration of checkers.SuiteExtraAssertCall. type SuiteExtraAssertCallConfig struct { Mode checkers.SuiteExtraAssertCallMode } +func (cfg Config) Validate() error { + if cfg.EnableAll { + if cfg.DisableAll { + return errors.New("enable-all and disable-all options must not be combined") + } + + if len(cfg.EnabledCheckers) != 0 { + return errors.New("enable-all and enable options must not be combined") + } + } + + if cfg.DisableAll { + if len(cfg.DisabledCheckers) != 0 { + return errors.New("disable-all and disable options must not be combined") + } + + if len(cfg.EnabledCheckers) == 0 { + return errors.New("all checkers were disabled, but no one checker was enabled: at least one must be enabled") + } + } + + for _, checker := range cfg.DisabledCheckers { + if cfg.EnabledCheckers.Contains(checker) { + return fmt.Errorf("checker %q disabled and enabled at one moment", checker) + } + } + + return nil +} + // BindToFlags binds Config fields to according flags. func BindToFlags(cfg *Config, fs *flag.FlagSet) { fs.BoolVar(&cfg.EnableAll, "enable-all", false, "enable all checkers") - fs.Var(&cfg.EnabledCheckers, "enable", "comma separated list of enabled checkers") + fs.Var(&cfg.DisabledCheckers, "disable", "comma separated list of disabled checkers (to exclude from enabled by default)") + fs.BoolVar(&cfg.DisableAll, "disable-all", false, "disable all checkers") + fs.Var(&cfg.EnabledCheckers, "enable", "comma separated list of enabled checkers (in addition to enabled by default)") + fs.Var(&cfg.ExpectedActual.ExpVarPattern, "expected-actual.pattern", "regexp for expected variable name") + fs.Var(&cfg.RequireError.FnPattern, "require-error.fn-pattern", "regexp for error assertions that should only be analyzed") fs.Var(NewEnumValue(suiteExtraAssertCallModeAsString, &cfg.SuiteExtraAssertCall.Mode), "suite-extra-assert-call.mode", "to require or remove extra Assert() call") } diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go index 2f0ee978f..5b08ec47b 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go @@ -35,6 +35,15 @@ func (kcv *KnownCheckersValue) Set(v string) error { return nil } +func (kcv KnownCheckersValue) Contains(v string) bool { + for _, checker := range kcv { + if checker == v { + return true + } + } + return false +} + // RegexpValue is a special wrapper for support of flag.FlagSet over regexp.Regexp. // Original regexp is available through RegexpValue.Regexp. type RegexpValue struct { diff --git a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go index 45731aa97..3476e4040 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go +++ b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go @@ -4,10 +4,14 @@ const ( ModulePath = "github.com/stretchr/testify" AssertPkgName = "assert" + HTTPPkgName = "http" + MockPkgName = "mock" RequirePkgName = "require" SuitePkgName = "suite" AssertPkgPath = ModulePath + "/" + AssertPkgName + HTTPPkgPath = ModulePath + "/" + HTTPPkgName + MockPkgPath = ModulePath + "/" + MockPkgName RequirePkgPath = ModulePath + "/" + RequirePkgName SuitePkgPath = ModulePath + "/" + SuitePkgName ) |
