aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Antonboom
diff options
context:
space:
mode:
authordependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>2024-03-04 17:40:11 +0000
committerTaras Madan <tarasmadan@google.com>2024-03-04 18:34:55 +0000
commit5fc5366972c874b919f93165bb4ed4e2bcb7c350 (patch)
tree287c3361a0dee0c72af80d9a1a66714a06e98a62 /vendor/github.com/Antonboom
parent1be5ce38a9059c356eb193a8c34d60d61c9fc31f (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')
-rw-r--r--vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go86
-rw-r--r--vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go31
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go2
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go69
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go45
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go136
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go36
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go6
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go7
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go54
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go52
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go50
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go83
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go27
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go301
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/len.go19
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go69
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go339
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go71
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/config/config.go60
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go9
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/testify/const.go4
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
)