aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Antonboom
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2023-12-05 15:10:03 +0100
committerTaras Madan <tarasmadan@google.com>2023-12-06 11:31:44 +0000
commit2ab72b4feef2c97f22f90cfbf9e45a6cfcd08bda (patch)
treea6d19b94b6399fcc00a6cfa430885cd349dd1533 /vendor/github.com/Antonboom
parente08e8f492d31d672cc245944c185f8aadf2ee695 (diff)
vendor: updates
Diffstat (limited to 'vendor/github.com/Antonboom')
-rw-r--r--vendor/github.com/Antonboom/testifylint/LICENSE21
-rw-r--r--vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go175
-rw-r--r--vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go48
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go9
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go28
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go34
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go34
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go19
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go223
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go59
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go101
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go95
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go60
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go172
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go124
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go109
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go156
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go63
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/len.go86
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go35
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go96
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go99
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go130
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/config/config.go53
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go105
-rw-r--r--vendor/github.com/Antonboom/testifylint/internal/testify/const.go13
26 files changed, 2147 insertions, 0 deletions
diff --git a/vendor/github.com/Antonboom/testifylint/LICENSE b/vendor/github.com/Antonboom/testifylint/LICENSE
new file mode 100644
index 000000000..9b1cf3a39
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Anton Telyshev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go
new file mode 100644
index 000000000..c0b98f83c
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go
@@ -0,0 +1,175 @@
+package analyzer
+
+import (
+ "fmt"
+ "go/ast"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/inspector"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+ "github.com/Antonboom/testifylint/internal/checkers"
+ "github.com/Antonboom/testifylint/internal/config"
+ "github.com/Antonboom/testifylint/internal/testify"
+)
+
+const (
+ name = "testifylint"
+ doc = "Checks usage of " + testify.ModulePath + "."
+ url = "https://github.com/antonboom/" + name
+)
+
+// New returns new instance of testifylint analyzer.
+func New() *analysis.Analyzer {
+ cfg := config.NewDefault()
+
+ analyzer := &analysis.Analyzer{
+ Name: name,
+ Doc: doc,
+ URL: url,
+ Run: func(pass *analysis.Pass) (any, error) {
+ regularCheckers, advancedCheckers, err := newCheckers(cfg)
+ if err != nil {
+ return nil, fmt.Errorf("build checkers: %v", err)
+ }
+
+ tl := &testifyLint{
+ regularCheckers: regularCheckers,
+ advancedCheckers: advancedCheckers,
+ }
+ return tl.run(pass)
+ },
+ }
+ config.BindToFlags(&cfg, &analyzer.Flags)
+
+ return analyzer
+}
+
+type testifyLint struct {
+ regularCheckers []checkers.RegularChecker
+ advancedCheckers []checkers.AdvancedChecker
+}
+
+func (tl *testifyLint) run(pass *analysis.Pass) (any, error) {
+ filesToAnalysis := make([]*ast.File, 0, len(pass.Files))
+ for _, f := range pass.Files {
+ if !analysisutil.Imports(f, testify.AssertPkgPath, testify.RequirePkgPath, testify.SuitePkgPath) {
+ continue
+ }
+ filesToAnalysis = append(filesToAnalysis, f)
+ }
+
+ insp := inspector.New(filesToAnalysis)
+
+ // Regular checkers.
+ insp.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node) {
+ tl.regularCheck(pass, node.(*ast.CallExpr))
+ })
+
+ // Advanced checkers.
+ for _, ch := range tl.advancedCheckers {
+ for _, d := range ch.Check(pass, insp) {
+ pass.Report(d)
+ }
+ }
+
+ return nil, nil
+}
+
+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 {
+ 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)
+ // NOTE(a.telyshev): I'm not interested in multiple diagnostics per assertion.
+ // This simplifies the code and also makes the linter more efficient.
+ return
+ }
+ }
+}
+
+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
new file mode 100644
index 000000000..e87e35e50
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go
@@ -0,0 +1,48 @@
+package analyzer
+
+import (
+ "fmt"
+
+ "github.com/Antonboom/testifylint/internal/checkers"
+ "github.com/Antonboom/testifylint/internal/config"
+)
+
+// 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 cfg.EnableAll {
+ enabledCheckers = checkers.All()
+ }
+
+ checkers.SortByPriority(enabledCheckers)
+
+ regularCheckers := make([]checkers.RegularChecker, 0, len(enabledCheckers))
+ advancedCheckers := make([]checkers.AdvancedChecker, 0, len(enabledCheckers)/2)
+
+ for _, name := range enabledCheckers {
+ ch, ok := checkers.Get(name)
+ if !ok {
+ return nil, nil, fmt.Errorf("unknown checker %q", name)
+ }
+
+ switch c := ch.(type) {
+ case *checkers.ExpectedActual:
+ c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp)
+
+ case *checkers.SuiteExtraAssertCall:
+ c.SetMode(cfg.SuiteExtraAssertCall.Mode)
+ }
+
+ switch casted := ch.(type) {
+ case checkers.RegularChecker:
+ regularCheckers = append(regularCheckers, casted)
+ case checkers.AdvancedChecker:
+ advancedCheckers = append(advancedCheckers, casted)
+ }
+ }
+
+ return regularCheckers, advancedCheckers, nil
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go
new file mode 100644
index 000000000..b57cbd938
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go
@@ -0,0 +1,9 @@
+// Package analysisutil contains functions common for `analyzer` and `internal/checkers` packages.
+// In addition, it is intended to "lighten" these packages.
+//
+// If the function is common to several packages, or it makes sense to test it separately without
+// "polluting" the target package with tests of private functionality, then you can put function in this package.
+//
+// It's important to avoid dependency on `golang.org/x/tools/go/analysis` in the helpers API.
+// This makes the API "narrower" and also allows you to test functions without some "abstraction leaks".
+package analysisutil
diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go
new file mode 100644
index 000000000..3fc1f42b8
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go
@@ -0,0 +1,28 @@
+package analysisutil
+
+import (
+ "go/ast"
+ "strconv"
+)
+
+// Imports tells if the file imports at least one of the packages.
+// If no packages provided then function returns false.
+func Imports(file *ast.File, pkgs ...string) bool {
+ for _, i := range file.Imports {
+ if i.Path == nil {
+ continue
+ }
+
+ path, err := strconv.Unquote(i.Path.Value)
+ if err != nil {
+ continue
+ }
+ // NOTE(a.telyshev): Don't use `slices.Contains` to keep the minimum module version 1.20.
+ for _, pkg := range pkgs { // Small O(n).
+ if pkg == path {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go
new file mode 100644
index 000000000..fcb4b847f
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go
@@ -0,0 +1,34 @@
+package analysisutil
+
+import (
+ "bytes"
+ "go/ast"
+ "go/format"
+ "go/token"
+)
+
+// NodeString is a more powerful analogue of types.ExprString.
+// Return empty string if node AST is invalid.
+func NodeString(fset *token.FileSet, node ast.Node) string {
+ if v := formatNode(fset, node); v != nil {
+ return v.String()
+ }
+ return ""
+}
+
+// NodeBytes works as NodeString but returns a byte slice.
+// Return nil if node AST is invalid.
+func NodeBytes(fset *token.FileSet, node ast.Node) []byte {
+ if v := formatNode(fset, node); v != nil {
+ return v.Bytes()
+ }
+ return nil
+}
+
+func formatNode(fset *token.FileSet, node ast.Node) *bytes.Buffer {
+ buf := new(bytes.Buffer)
+ if err := format.Node(buf, fset, node); err != nil {
+ return nil
+ }
+ return buf
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go
new file mode 100644
index 000000000..e01fba5c1
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go
@@ -0,0 +1,34 @@
+package analysisutil
+
+import (
+ "go/ast"
+ "go/types"
+)
+
+// ObjectOf works in context of Golang package and returns types.Object for the given object's package and name.
+// The search is based on the provided package and its dependencies (imports).
+// Returns nil if the object is not found.
+func ObjectOf(pkg *types.Package, objPkg, objName string) types.Object {
+ if pkg.Path() == objPkg {
+ return pkg.Scope().Lookup(objName)
+ }
+
+ for _, i := range pkg.Imports() {
+ if trimVendor(i.Path()) == objPkg {
+ return i.Scope().Lookup(objName)
+ }
+ }
+ return nil
+}
+
+// IsObj returns true if expression is identifier which notes to given types.Object.
+// Useful in combination with types.Universe objects.
+func IsObj(typesInfo *types.Info, expr ast.Expr, expected types.Object) bool {
+ id, ok := expr.(*ast.Ident)
+ if !ok {
+ return false
+ }
+
+ obj := typesInfo.ObjectOf(id)
+ return obj.Id() == expected.Id()
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go
new file mode 100644
index 000000000..d34be5d34
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go
@@ -0,0 +1,19 @@
+package analysisutil
+
+import (
+ "go/types"
+ "strings"
+)
+
+// IsPkg checks that package has corresponding objName and path.
+// Supports vendored packages.
+func IsPkg(pkg *types.Package, name, path string) bool {
+ return pkg.Name() == name && trimVendor(pkg.Path()) == path
+}
+
+func trimVendor(path string) string {
+ if strings.HasPrefix(path, "vendor/") {
+ return path[len("vendor/"):]
+ }
+ return path
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go
new file mode 100644
index 000000000..8245ab58e
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go
@@ -0,0 +1,223 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+)
+
+// BoolCompare detects situations like
+//
+// assert.Equal(t, false, result)
+// assert.NotEqual(t, result, true)
+// assert.False(t, !result)
+// assert.True(t, result == true)
+// ...
+//
+// and requires
+//
+// assert.False(t, result)
+// assert.True(t, result)
+type BoolCompare struct{} //
+
+// NewBoolCompare constructs BoolCompare checker.
+func NewBoolCompare() BoolCompare { return BoolCompare{} }
+func (BoolCompare) Name() string { return "bool-compare" }
+
+func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ newUseFnDiagnostic := func(proposed string, survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
+ return newUseFunctionDiagnostic(checker.Name(), call, proposed,
+ newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
+ Pos: replaceStart,
+ End: replaceEnd,
+ NewText: analysisutil.NodeBytes(pass.Fset, survivingArg),
+ }),
+ )
+ }
+
+ newUseTrueDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
+ return newUseFnDiagnostic("True", survivingArg, replaceStart, replaceEnd)
+ }
+
+ newUseFalseDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
+ return newUseFnDiagnostic("False", survivingArg, replaceStart, replaceEnd)
+ }
+
+ newNeedSimplifyDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
+ return newDiagnostic(checker.Name(), call, "need to simplify the assertion",
+ &analysis.SuggestedFix{
+ Message: "Simplify the assertion",
+ TextEdits: []analysis.TextEdit{{
+ Pos: replaceStart,
+ End: replaceEnd,
+ NewText: analysisutil.NodeBytes(pass.Fset, survivingArg),
+ }},
+ },
+ )
+ }
+
+ switch call.Fn.Name {
+ case "Equal", "Equalf":
+ if len(call.Args) < 2 {
+ return nil
+ }
+
+ arg1, arg2 := call.Args[0], call.Args[1]
+ t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2)
+ f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2)
+
+ switch {
+ case xor(t1, t2):
+ survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1)
+ return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End())
+
+ case xor(f1, f2):
+ survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1)
+ return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End())
+ }
+
+ case "NotEqual", "NotEqualf":
+ if len(call.Args) < 2 {
+ return nil
+ }
+
+ arg1, arg2 := call.Args[0], call.Args[1]
+ t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2)
+ f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2)
+
+ switch {
+ case xor(t1, t2):
+ survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1)
+ return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End())
+
+ case xor(f1, f2):
+ survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1)
+ return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End())
+ }
+
+ case "True", "Truef":
+ if len(call.Args) < 1 {
+ return nil
+ }
+ expr := call.Args[0]
+
+ {
+ arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL)
+ arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ)
+
+ if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok {
+ return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End())
+ }
+ }
+
+ {
+ arg1, ok1 := isComparisonWithTrue(pass, expr, token.NEQ)
+ arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL)
+ arg3, ok3 := isNegation(expr)
+
+ if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok {
+ return newUseFalseDiagnostic(survivingArg, expr.Pos(), expr.End())
+ }
+ }
+
+ case "False", "Falsef":
+ if len(call.Args) < 1 {
+ return nil
+ }
+ expr := call.Args[0]
+
+ {
+ arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL)
+ arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ)
+
+ if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok {
+ return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End())
+ }
+ }
+
+ {
+ arg1, ok1 := isComparisonWithTrue(pass, expr, token.NEQ)
+ arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL)
+ arg3, ok3 := isNegation(expr)
+
+ if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok {
+ return newUseTrueDiagnostic(survivingArg, expr.Pos(), expr.End())
+ }
+ }
+ }
+ return nil
+}
+
+var (
+ falseObj = types.Universe.Lookup("false")
+ trueObj = types.Universe.Lookup("true")
+)
+
+func isUntypedTrue(pass *analysis.Pass, e ast.Expr) bool {
+ return analysisutil.IsObj(pass.TypesInfo, e, trueObj)
+}
+
+func isUntypedFalse(pass *analysis.Pass, e ast.Expr) bool {
+ return analysisutil.IsObj(pass.TypesInfo, e, falseObj)
+}
+
+func isComparisonWithTrue(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) {
+ return isComparisonWith(pass, e, isUntypedTrue, op)
+}
+
+func isComparisonWithFalse(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) {
+ return isComparisonWith(pass, e, isUntypedFalse, op)
+}
+
+type predicate func(pass *analysis.Pass, e ast.Expr) bool
+
+func isComparisonWith(pass *analysis.Pass, e ast.Expr, predicate predicate, op token.Token) (ast.Expr, bool) {
+ be, ok := e.(*ast.BinaryExpr)
+ if !ok {
+ return nil, false
+ }
+ if be.Op != op {
+ return nil, false
+ }
+
+ t1, t2 := predicate(pass, be.X), predicate(pass, be.Y)
+ if xor(t1, t2) {
+ if t1 {
+ return be.Y, true
+ }
+ return be.X, true
+ }
+ return nil, false
+}
+
+func isNegation(e ast.Expr) (ast.Expr, bool) {
+ ue, ok := e.(*ast.UnaryExpr)
+ if !ok {
+ return nil, false
+ }
+ return ue.X, ue.Op == token.NOT
+}
+
+func xor(a, b bool) bool {
+ return a != b
+}
+
+// anyVal returns the first value[i] for which bools[i] is true.
+func anyVal[T any](bools []bool, vals ...T) (T, bool) {
+ if len(bools) != len(vals) {
+ panic("inconsistent usage of valOr") //nolint:forbidigo // Does not depend on the code being analyzed.
+ }
+
+ for i, b := range bools {
+ if b {
+ return vals[i], true
+ }
+ }
+
+ var _default T
+ return _default, false
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go
new file mode 100644
index 000000000..f3249dc3c
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go
@@ -0,0 +1,59 @@
+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
+}
+
+// RegularChecker check assertion call presented in CallMeta form.
+type RegularChecker interface {
+ Checker
+ Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic
+}
+
+// AdvancedChecker implements complex Check logic different from trivial CallMeta check.
+type AdvancedChecker interface {
+ Checker
+ Check(pass *analysis.Pass, inspector *inspector.Inspector) []analysis.Diagnostic
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go
new file mode 100644
index 000000000..47eaafb76
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go
@@ -0,0 +1,101 @@
+package checkers
+
+import (
+ "sort"
+)
+
+// registry stores checkers meta information in checkers' priority order.
+var registry = checkersRegistry{
+ // Regular checkers.
+ {factory: asCheckerFactory(NewFloatCompare), enabledByDefault: true},
+ {factory: asCheckerFactory(NewBoolCompare), enabledByDefault: true},
+ {factory: asCheckerFactory(NewEmpty), enabledByDefault: true},
+ {factory: asCheckerFactory(NewLen), enabledByDefault: true},
+ {factory: asCheckerFactory(NewCompares), enabledByDefault: true},
+ {factory: asCheckerFactory(NewErrorNil), 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},
+ // Advanced checkers.
+ {factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false},
+}
+
+type checkersRegistry []checkerMeta
+
+type checkerMeta struct {
+ factory checkerFactory
+ enabledByDefault bool
+}
+
+type checkerFactory func() Checker
+
+func asCheckerFactory[T Checker](fn func() T) checkerFactory {
+ return func() Checker {
+ return fn()
+ }
+}
+
+func (r checkersRegistry) get(name string) (m checkerMeta, priority int, found bool) {
+ for i, meta := range r {
+ if meta.factory().Name() == name {
+ return meta, i, true
+ }
+ }
+ return checkerMeta{}, 0, false
+}
+
+// All returns all checkers names sorted by checker's priority.
+func All() []string {
+ result := make([]string, 0, len(registry))
+ for _, meta := range registry {
+ result = append(result, meta.factory().Name())
+ }
+ return result
+}
+
+// EnabledByDefault returns checkers enabled by default sorted by checker's priority.
+func EnabledByDefault() []string {
+ result := make([]string, 0, len(registry))
+ for _, meta := range registry {
+ if meta.enabledByDefault {
+ result = append(result, meta.factory().Name())
+ }
+ }
+ return result
+}
+
+// Get returns new checker instance by checker's name.
+func Get(name string) (Checker, bool) {
+ meta, _, ok := registry.get(name)
+ if ok {
+ return meta.factory(), true
+ }
+ return nil, false
+}
+
+// IsKnown checks if there is a checker with that name.
+func IsKnown(name string) bool {
+ _, _, ok := registry.get(name)
+ return ok
+}
+
+// IsEnabledByDefault returns true if a checker is enabled by default.
+// Returns false if there is no such checker in the registry.
+// For pre-validation use Get or IsKnown.
+func IsEnabledByDefault(name string) bool {
+ meta, _, ok := registry.get(name)
+ return ok && meta.enabledByDefault
+}
+
+// SortByPriority mutates the input checkers names by sorting them in checker priority order.
+// Ignores unknown checkers. For pre-validation use Get or IsKnown.
+func SortByPriority(checkers []string) {
+ sort.Slice(checkers, func(i, j int) bool {
+ lhs, rhs := checkers[i], checkers[j]
+ _, lhsPriority, _ := registry.get(lhs)
+ _, rhsPriority, _ := registry.get(rhs)
+ return lhsPriority < rhsPriority
+ })
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go
new file mode 100644
index 000000000..afc829f97
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go
@@ -0,0 +1,95 @@
+package checkers
+
+import (
+ "bytes"
+ "go/ast"
+ "go/token"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+)
+
+// Compares detects situations like
+//
+// assert.True(t, a == b)
+// assert.True(t, a != b)
+// assert.True(t, a > b)
+// assert.True(t, a >= b)
+// assert.True(t, a < b)
+// assert.True(t, a <= b)
+// ...
+//
+// and requires
+//
+// assert.Equal(t, a, b)
+// assert.NotEqual(t, a, b)
+// assert.Greater(t, a, b)
+// assert.GreaterOrEqual(t, a, b)
+// assert.Less(t, a, b)
+// assert.LessOrEqual(t, a, b)
+type Compares struct{}
+
+// NewCompares constructs Compares checker.
+func NewCompares() Compares { return Compares{} }
+func (Compares) Name() string { return "compares" }
+
+func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ if len(call.Args) < 1 {
+ return nil
+ }
+
+ be, ok := call.Args[0].(*ast.BinaryExpr)
+ if !ok {
+ return nil
+ }
+
+ var tokenToProposedFn map[token.Token]string
+
+ switch call.Fn.Name {
+ case "True", "Truef":
+ tokenToProposedFn = tokenToProposedFnInsteadOfTrue
+ case "False", "Falsef":
+ tokenToProposedFn = tokenToProposedFnInsteadOfFalse
+ default:
+ return nil
+ }
+
+ if proposedFn, ok := tokenToProposedFn[be.Op]; ok {
+ a, b := be.X, be.Y
+ return newUseFunctionDiagnostic(checker.Name(), call, proposedFn,
+ newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{
+ Pos: be.X.Pos(),
+ End: be.Y.End(),
+ NewText: formatAsCallArgs(pass, a, b),
+ }),
+ )
+ }
+ return nil
+}
+
+var tokenToProposedFnInsteadOfTrue = map[token.Token]string{
+ token.EQL: "Equal",
+ token.NEQ: "NotEqual",
+ token.GTR: "Greater",
+ token.GEQ: "GreaterOrEqual",
+ token.LSS: "Less",
+ token.LEQ: "LessOrEqual",
+}
+
+var tokenToProposedFnInsteadOfFalse = map[token.Token]string{
+ token.EQL: "NotEqual",
+ token.NEQ: "Equal",
+ token.GTR: "LessOrEqual",
+ token.GEQ: "Less",
+ token.LSS: "GreaterOrEqual",
+ token.LEQ: "Greater",
+}
+
+// formatAsCallArgs joins a and b and return bytes like `a, b`.
+func formatAsCallArgs(pass *analysis.Pass, a, b ast.Node) []byte {
+ return bytes.Join([][]byte{
+ analysisutil.NodeBytes(pass.Fset, a),
+ analysisutil.NodeBytes(pass.Fset, b),
+ }, []byte(", "))
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go
new file mode 100644
index 000000000..4ab69c69b
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go
@@ -0,0 +1,60 @@
+package checkers
+
+import (
+ "fmt"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+func newUseFunctionDiagnostic(
+ checker string,
+ call *CallMeta,
+ proposedFn string,
+ fix *analysis.SuggestedFix,
+) *analysis.Diagnostic {
+ f := proposedFn
+ if call.Fn.IsFmt {
+ f += "f"
+ }
+ msg := fmt.Sprintf("use %s.%s", call.SelectorXStr, f)
+
+ return newDiagnostic(checker, call, msg, fix)
+}
+
+func newDiagnostic(
+ checker string,
+ rng analysis.Range,
+ msg string,
+ fix *analysis.SuggestedFix,
+) *analysis.Diagnostic {
+ d := analysis.Diagnostic{
+ Pos: rng.Pos(),
+ End: rng.End(),
+ Category: checker,
+ Message: checker + ": " + msg,
+ }
+ if fix != nil {
+ d.SuggestedFixes = []analysis.SuggestedFix{*fix}
+ }
+ return &d
+}
+
+func newSuggestedFuncReplacement(
+ call *CallMeta,
+ proposedFn string,
+ additionalEdits ...analysis.TextEdit,
+) *analysis.SuggestedFix {
+ if call.Fn.IsFmt {
+ proposedFn += "f"
+ }
+ return &analysis.SuggestedFix{
+ Message: fmt.Sprintf("Replace `%s` with `%s`", call.Fn.Name, proposedFn),
+ TextEdits: append([]analysis.TextEdit{
+ {
+ Pos: call.Fn.Pos(),
+ End: call.Fn.End(),
+ NewText: []byte(proposedFn),
+ },
+ }, additionalEdits...),
+ }
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go
new file mode 100644
index 000000000..79c64205f
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go
@@ -0,0 +1,172 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+)
+
+// Empty detects situations like
+//
+// assert.Len(t, arr, 0)
+// assert.Equal(t, 0, len(arr))
+// assert.NotEqual(t, 0, len(arr))
+// assert.GreaterOrEqual(t, len(arr), 1)
+// ...
+//
+// and requires
+//
+// assert.Empty(t, arr)
+// assert.NotEmpty(t, arr)
+type Empty struct{}
+
+// NewEmpty constructs Empty checker.
+func NewEmpty() Empty { return Empty{} }
+func (Empty) Name() string { return "empty" }
+
+func (checker Empty) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ if d := checker.checkEmpty(pass, call); d != nil {
+ return d
+ }
+ return checker.checkNotEmpty(pass, call)
+}
+
+func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { //nolint:gocognit
+ newUseEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic {
+ const proposed = "Empty"
+ return newUseFunctionDiagnostic(checker.Name(), call, proposed,
+ newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
+ Pos: replaceStart,
+ End: replaceEnd,
+ NewText: analysisutil.NodeBytes(pass.Fset, replaceWith),
+ }),
+ )
+ }
+
+ if len(call.Args) < 2 {
+ return nil
+ }
+ a, b := call.Args[0], call.Args[1]
+
+ switch call.Fn.Name {
+ case "Len", "Lenf":
+ if isZero(b) {
+ return newUseEmptyDiagnostic(a.Pos(), b.End(), a)
+ }
+
+ case "Equal", "Equalf":
+ arg1, ok1 := isLenCallAndZero(pass, a, b)
+ arg2, ok2 := isLenCallAndZero(pass, b, a)
+
+ if lenArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok {
+ return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
+ }
+
+ case "LessOrEqual", "LessOrEqualf":
+ if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) {
+ return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
+ }
+
+ case "GreaterOrEqual", "GreaterOrEqualf":
+ 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) {
+ return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
+ }
+
+ case "Greater", "Greaterf":
+ if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) {
+ return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
+ }
+ }
+ return nil
+}
+
+func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { //nolint:gocognit
+ newUseNotEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic {
+ const proposed = "NotEmpty"
+ return newUseFunctionDiagnostic(checker.Name(), call, proposed,
+ newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
+ Pos: replaceStart,
+ End: replaceEnd,
+ NewText: analysisutil.NodeBytes(pass.Fset, replaceWith),
+ }),
+ )
+ }
+
+ if len(call.Args) < 2 {
+ return nil
+ }
+ a, b := call.Args[0], call.Args[1]
+
+ switch call.Fn.Name {
+ case "NotEqual", "NotEqualf":
+ arg1, ok1 := isLenCallAndZero(pass, a, b)
+ arg2, ok2 := isLenCallAndZero(pass, b, a)
+
+ if lenArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok {
+ 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) {
+ return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg)
+ }
+
+ case "LessOrEqual", "LessOrEqualf":
+ if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) {
+ return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg)
+ }
+ }
+ return nil
+}
+
+var lenObj = types.Universe.Lookup("len")
+
+func isLenCallAndZero(pass *analysis.Pass, a, b ast.Expr) (ast.Expr, bool) {
+ lenArg, ok := isBuiltinLenCall(pass, a)
+ return lenArg, ok && isZero(b)
+}
+
+func isBuiltinLenCall(pass *analysis.Pass, e ast.Expr) (ast.Expr, bool) {
+ ce, ok := e.(*ast.CallExpr)
+ if !ok {
+ return nil, false
+ }
+
+ if analysisutil.IsObj(pass.TypesInfo, ce.Fun, lenObj) && len(ce.Args) == 1 {
+ return ce.Args[0], true
+ }
+ return nil, false
+}
+
+func isZero(e ast.Expr) bool {
+ return isIntNumber(e, 0)
+}
+
+func isOne(e ast.Expr) bool {
+ return isIntNumber(e, 1)
+}
+
+func isIntNumber(e ast.Expr, v int) bool {
+ bl, ok := e.(*ast.BasicLit)
+ return ok && bl.Kind == token.INT && bl.Value == fmt.Sprintf("%d", v)
+}
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
new file mode 100644
index 000000000..e6abd0ba4
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go
@@ -0,0 +1,124 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+)
+
+// ErrorIsAs detects situations like
+//
+// assert.Error(t, err, errSentinel)
+// assert.NoError(t, err, errSentinel)
+// assert.True(t, errors.Is(err, errSentinel))
+// assert.False(t, errors.Is(err, errSentinel))
+// assert.True(t, errors.As(err, &target))
+//
+// and requires
+//
+// assert.ErrorIs(t, err, errSentinel)
+// assert.NotErrorIs(t, err, errSentinel)
+// assert.ErrorAs(t, err, &target)
+type ErrorIsAs struct{}
+
+// NewErrorIsAs constructs ErrorIsAs checker.
+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":
+ 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":
+ 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":
+ if len(call.Args) < 1 {
+ return nil
+ }
+
+ ce, ok := call.Args[0].(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+ if len(ce.Args) != 2 {
+ return nil
+ }
+
+ var proposed string
+ switch {
+ case isErrorsIsCall(pass, ce):
+ proposed = "ErrorIs"
+ case isErrorsAsCall(pass, ce):
+ proposed = "ErrorAs"
+ }
+ if proposed != "" {
+ return newUseFunctionDiagnostic(checker.Name(), call, proposed,
+ newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
+ Pos: ce.Pos(),
+ End: ce.End(),
+ NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]),
+ }),
+ )
+ }
+
+ case "False", "Falsef":
+ if len(call.Args) < 1 {
+ return nil
+ }
+
+ ce, ok := call.Args[0].(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+ if len(ce.Args) != 2 {
+ return nil
+ }
+
+ if isErrorsIsCall(pass, ce) {
+ const proposed = "NotErrorIs"
+ return newUseFunctionDiagnostic(checker.Name(), call, proposed,
+ newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
+ Pos: ce.Pos(),
+ End: ce.End(),
+ NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]),
+ }),
+ )
+ }
+ }
+ return nil
+}
+
+func isErrorsIsCall(pass *analysis.Pass, ce *ast.CallExpr) bool {
+ return isErrorsPkgFnCall(pass, ce, "Is")
+}
+
+func isErrorsAsCall(pass *analysis.Pass, ce *ast.CallExpr) bool {
+ return isErrorsPkgFnCall(pass, ce, "As")
+}
+
+func isErrorsPkgFnCall(pass *analysis.Pass, ce *ast.CallExpr, fn string) bool {
+ se, ok := ce.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return false
+ }
+
+ errorsIsObj := analysisutil.ObjectOf(pass.Pkg, "errors", fn)
+ if errorsIsObj == nil {
+ return false
+ }
+
+ return analysisutil.IsObj(pass.TypesInfo, se.Sel, errorsIsObj)
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go
new file mode 100644
index 000000000..b45629a48
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go
@@ -0,0 +1,109 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+)
+
+// ErrorNil detects situations like
+//
+// assert.Nil(t, err)
+// assert.NotNil(t, err)
+// assert.Equal(t, err, nil)
+// assert.NotEqual(t, err, nil)
+//
+// and requires
+//
+// assert.NoError(t, err)
+// assert.Error(t, err)
+type ErrorNil struct{}
+
+// NewErrorNil constructs ErrorNil checker.
+func NewErrorNil() ErrorNil { return ErrorNil{} }
+func (ErrorNil) Name() string { return "error-nil" }
+
+func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ const (
+ errorFn = "Error"
+ noErrorFn = "NoError"
+ )
+
+ proposedFn, survivingArg, replacementEndPos := func() (string, ast.Expr, token.Pos) {
+ switch call.Fn.Name {
+ case "NotNil", "NotNilf":
+ if len(call.Args) >= 1 && isError(pass, call.Args[0]) {
+ return errorFn, call.Args[0], call.Args[0].End()
+ }
+
+ case "Nil", "Nilf":
+ if len(call.Args) >= 1 && isError(pass, call.Args[0]) {
+ return noErrorFn, call.Args[0], call.Args[0].End()
+ }
+
+ case "Equal", "Equalf":
+ 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):
+ return noErrorFn, a, b.End()
+ case isNil(pass, a) && isError(pass, b):
+ return noErrorFn, b, b.End()
+ }
+
+ case "NotEqual", "NotEqualf":
+ 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):
+ return errorFn, a, b.End()
+ case isNil(pass, a) && isError(pass, b):
+ return errorFn, b, b.End()
+ }
+ }
+ return "", nil, token.NoPos
+ }()
+
+ if proposedFn != "" {
+ return newUseFunctionDiagnostic(checker.Name(), call, proposedFn,
+ newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{
+ Pos: call.Args[0].Pos(),
+ End: replacementEndPos,
+ NewText: analysisutil.NodeBytes(pass.Fset, survivingArg),
+ }),
+ )
+ }
+ return nil
+}
+
+var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
+
+func isError(pass *analysis.Pass, expr ast.Expr) bool {
+ t := pass.TypesInfo.TypeOf(expr)
+ if t == nil {
+ return false
+ }
+
+ _, ok := t.Underlying().(*types.Interface)
+ return ok && types.Implements(t, errIface)
+}
+
+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
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go
new file mode 100644
index 000000000..ff8243980
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go
@@ -0,0 +1,156 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+ "regexp"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// DefaultExpectedVarPattern matches variables with "expected" or "wanted" prefix or suffix in the name.
+var DefaultExpectedVarPattern = regexp.MustCompile(
+ `(^(exp(ected)?|want(ed)?)([A-Z]\w*)?$)|(^(\w*[a-z])?(Exp(ected)?|Want(ed)?)$)`)
+
+// ExpectedActual detects situation like
+//
+// assert.NotEqual(t, result, "expected value")
+//
+// and requires
+//
+// assert.NotEqual(t, "expected value", result)
+type ExpectedActual struct {
+ expVarPattern *regexp.Regexp
+}
+
+// NewExpectedActual constructs ExpectedActual checker using DefaultExpectedVarPattern.
+func NewExpectedActual() *ExpectedActual {
+ return &ExpectedActual{expVarPattern: DefaultExpectedVarPattern}
+}
+
+func (ExpectedActual) Name() string { return "expected-actual" }
+
+func (checker *ExpectedActual) SetExpVarPattern(p *regexp.Regexp) *ExpectedActual {
+ if p != nil {
+ checker.expVarPattern = p
+ }
+ return checker
+}
+
+func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ switch call.Fn.Name {
+ case "Equal", "Equalf", "NotEqual", "NotEqualf",
+ "JSONEq", "JSONEqf", "YAMLEq", "YAMLEqf":
+ default:
+ return nil
+ }
+
+ if len(call.Args) < 2 {
+ return nil
+ }
+ first, second := call.Args[0], call.Args[1]
+
+ if checker.isWrongExpectedActualOrder(pass, first, second) {
+ return newDiagnostic(checker.Name(), call, "need to reverse actual and expected values", &analysis.SuggestedFix{
+ Message: "Reverse actual and expected values",
+ TextEdits: []analysis.TextEdit{
+ {
+ Pos: first.Pos(),
+ End: second.End(),
+ NewText: formatAsCallArgs(pass, second, first),
+ },
+ },
+ })
+ }
+ return nil
+}
+
+func (checker ExpectedActual) isWrongExpectedActualOrder(pass *analysis.Pass, first, second ast.Expr) bool {
+ leftIsCandidate := checker.isExpectedValueCandidate(pass, first)
+ rightIsCandidate := checker.isExpectedValueCandidate(pass, second)
+ return rightIsCandidate && !leftIsCandidate
+}
+
+func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr ast.Expr) bool {
+ switch v := expr.(type) {
+ case *ast.CompositeLit:
+ return true
+
+ case *ast.CallExpr:
+ return isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) ||
+ isExpectedValueFactory(v, checker.expVarPattern)
+ }
+
+ return isBasicLit(expr) ||
+ isUntypedConst(pass, expr) ||
+ isTypedConst(pass, expr) ||
+ isIdentNamedAsExpected(checker.expVarPattern, expr) ||
+ isStructFieldNamedAsExpected(checker.expVarPattern, expr)
+}
+
+func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) bool {
+ if len(ce.Args) != 1 {
+ return false
+ }
+
+ fn, ok := ce.Fun.(*ast.Ident)
+ if !ok {
+ return false
+ }
+
+ switch fn.Name {
+ case "complex64", "complex128":
+ return true
+
+ case "uint", "uint8", "uint16", "uint32", "uint64",
+ "int", "int8", "int16", "int32", "int64",
+ "float32", "float64",
+ "rune", "string":
+ return isBasicLit(ce.Args[0]) || isIdentNamedAsExpected(pattern, ce.Args[0])
+ }
+ return false
+}
+
+func isExpectedValueFactory(ce *ast.CallExpr, pattern *regexp.Regexp) bool {
+ if len(ce.Args) != 0 {
+ return false
+ }
+
+ switch fn := ce.Fun.(type) {
+ case *ast.Ident:
+ return pattern.MatchString(fn.Name)
+ case *ast.SelectorExpr:
+ return pattern.MatchString(fn.Sel.Name)
+ }
+ return false
+}
+
+func isBasicLit(e ast.Expr) bool {
+ _, ok := e.(*ast.BasicLit)
+ return ok
+}
+
+func isUntypedConst(p *analysis.Pass, e ast.Expr) bool {
+ t := p.TypesInfo.TypeOf(e)
+ if t == nil {
+ return false
+ }
+
+ b, ok := t.(*types.Basic)
+ return ok && b.Info()&types.IsUntyped > 0
+}
+
+func isTypedConst(p *analysis.Pass, e ast.Expr) bool {
+ tt, ok := p.TypesInfo.Types[e]
+ return ok && tt.IsValue() && tt.Value != nil
+}
+
+func isIdentNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool {
+ id, ok := e.(*ast.Ident)
+ return ok && pattern.MatchString(id.Name)
+}
+
+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
new file mode 100644
index 000000000..7d5b358b3
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go
@@ -0,0 +1,63 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// FloatCompare detects situation like
+//
+// assert.Equal(t, 42.42, a)
+// assert.True(t, a == 42.42)
+// assert.False(t, a != 42.42)
+//
+// and requires
+//
+// assert.InEpsilon(t, 42.42, a, 0.0001) // Or assert.InDelta
+type FloatCompare struct{}
+
+// NewFloatCompare constructs FloatCompare checker.
+func NewFloatCompare() FloatCompare { return FloatCompare{} }
+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])
+
+ case "True", "Truef":
+ return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.EQL)
+
+ case "False", "Falsef":
+ 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)
+ }
+ return nil
+}
+
+func isFloat(pass *analysis.Pass, expr ast.Expr) bool {
+ t := pass.TypesInfo.TypeOf(expr)
+ if t == nil {
+ return false
+ }
+
+ bt, ok := t.Underlying().(*types.Basic)
+ return ok && (bt.Info()&types.IsFloat > 0)
+}
+
+func isFloatCompare(p *analysis.Pass, e ast.Expr, op token.Token) bool {
+ be, ok := e.(*ast.BinaryExpr)
+ if !ok {
+ return false
+ }
+ return be.Op == op && (isFloat(p, be.X) || isFloat(p, be.Y))
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go
new file mode 100644
index 000000000..f10412f63
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go
@@ -0,0 +1,86 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// Len detects situations like
+//
+// assert.Equal(t, 3, len(arr))
+// assert.True(t, len(arr) == 3)
+//
+// and requires
+//
+// assert.Len(t, arr, 3)
+type Len struct{}
+
+// NewLen constructs Len checker.
+func NewLen() Len { return Len{} }
+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":
+ if len(call.Args) < 2 {
+ return nil
+ }
+ a, b := call.Args[0], call.Args[1]
+
+ if lenArg, expectedLen, ok := xorLenCall(pass, a, b); ok {
+ return newUseFunctionDiagnostic(checker.Name(), call, proposedFn,
+ newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{
+ Pos: a.Pos(),
+ End: b.End(),
+ NewText: formatAsCallArgs(pass, lenArg, expectedLen),
+ }),
+ )
+ }
+
+ case "True", "Truef":
+ if len(call.Args) < 1 {
+ return nil
+ }
+ expr := call.Args[0]
+
+ if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok {
+ return newUseFunctionDiagnostic(checker.Name(), call, proposedFn,
+ newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{
+ Pos: expr.Pos(),
+ End: expr.End(),
+ NewText: formatAsCallArgs(pass, lenArg, expectedLen),
+ }),
+ )
+ }
+ }
+ return nil
+}
+
+func xorLenCall(pass *analysis.Pass, a, b ast.Expr) (lenArg ast.Expr, expectedLen ast.Expr, ok bool) {
+ arg1, ok1 := isBuiltinLenCall(pass, a)
+ arg2, ok2 := isBuiltinLenCall(pass, b)
+
+ if xor(ok1, ok2) {
+ if ok1 {
+ return arg1, b, true
+ }
+ return arg2, a, true
+ }
+ return nil, nil, false
+}
+
+func isLenEquality(pass *analysis.Pass, e ast.Expr) (ast.Expr, ast.Expr, bool) {
+ be, ok := e.(*ast.BinaryExpr)
+ if !ok {
+ return nil, nil, false
+ }
+
+ if be.Op != token.EQL {
+ return nil, nil, false
+ }
+ return xorLenCall(pass, be.X, be.Y)
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go
new file mode 100644
index 000000000..2da71ed81
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go
@@ -0,0 +1,35 @@
+package checkers
+
+import "golang.org/x/tools/go/analysis"
+
+// RequireError detects situations like
+//
+// assert.NoError(t, err)
+// s.ErrorIs(err, io.EOF)
+// s.Assert().Error(err)
+//
+// and requires
+//
+// require.NoError(t, err)
+// s.Require().ErrorIs(err, io.EOF)
+// s.Require().Error(err)
+type RequireError struct{}
+
+// NewRequireError constructs RequireError checker.
+func NewRequireError() RequireError { return RequireError{} }
+func (RequireError) Name() string { return "require-error" }
+
+func (checker RequireError) Check(_ *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ if !call.IsAssert {
+ return nil
+ }
+
+ const msg = "for error assertions use require"
+
+ 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)
+ }
+ return nil
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go
new file mode 100644
index 000000000..bf84f6378
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go
@@ -0,0 +1,96 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+ "github.com/Antonboom/testifylint/internal/testify"
+)
+
+// SuiteDontUsePkg detects situation like
+//
+// func (s *MySuite) TestSomething() {
+// assert.Equal(s.T(), 42, value)
+// }
+//
+// and requires
+//
+// func (s *MySuite) TestSomething() {
+// s.Equal(42, value)
+// }
+type SuiteDontUsePkg struct{}
+
+// NewSuiteDontUsePkg constructs SuiteDontUsePkg checker.
+func NewSuiteDontUsePkg() SuiteDontUsePkg { return SuiteDontUsePkg{} }
+func (SuiteDontUsePkg) Name() string { return "suite-dont-use-pkg" }
+
+func (checker SuiteDontUsePkg) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ if !call.IsPkg {
+ return nil
+ }
+
+ args := call.ArgsRaw
+ if len(args) < 2 {
+ return nil
+ }
+ t := args[0]
+
+ ce, ok := t.(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+ se, ok := ce.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return nil
+ }
+ if se.X == nil || !implementsTestifySuiteIface(pass, se.X) {
+ return nil
+ }
+ if se.Sel == nil || se.Sel.Name != "T" {
+ return nil
+ }
+ rcv, ok := se.X.(*ast.Ident) // At this point we ensure that `s.T()` is used as the first argument of assertion.
+ if !ok {
+ return nil
+ }
+
+ newSelector := rcv.Name
+ if !call.IsAssert {
+ newSelector += "." + "Require()"
+ }
+
+ msg := fmt.Sprintf("use %s.%s", newSelector, call.Fn.Name)
+ return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{
+ Message: fmt.Sprintf("Replace `%s` with `%s`", call.SelectorXStr, newSelector),
+ TextEdits: []analysis.TextEdit{
+ // Replace package function with suite method.
+ {
+ Pos: call.Selector.X.Pos(),
+ End: call.Selector.X.End(),
+ NewText: []byte(newSelector),
+ },
+ // Remove `s.T()`.
+ {
+ Pos: t.Pos(),
+ End: args[1].Pos(),
+ NewText: []byte(""),
+ },
+ },
+ })
+}
+
+func implementsTestifySuiteIface(pass *analysis.Pass, rcv ast.Expr) bool {
+ suiteIface := analysisutil.ObjectOf(pass.Pkg, testify.SuitePkgPath, "TestingSuite")
+ if suiteIface == nil {
+ return false
+ }
+
+ return types.Implements(
+ pass.TypesInfo.TypeOf(rcv),
+ suiteIface.Type().Underlying().(*types.Interface),
+ )
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go
new file mode 100644
index 000000000..791488b65
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go
@@ -0,0 +1,99 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+)
+
+// SuiteExtraAssertCallMode reflects different modes of work of SuiteExtraAssertCall checker.
+type SuiteExtraAssertCallMode int
+
+const (
+ SuiteExtraAssertCallModeRemove SuiteExtraAssertCallMode = iota
+ SuiteExtraAssertCallModeRequire
+)
+
+const DefaultSuiteExtraAssertCallMode = SuiteExtraAssertCallModeRemove
+
+// SuiteExtraAssertCall detects situation like
+//
+// func (s *MySuite) TestSomething() {
+// s.Assert().Equal(42, value)
+// }
+//
+// and requires
+//
+// func (s *MySuite) TestSomething() {
+// s.Equal(42, value)
+// }
+//
+// or vice versa (depending on the configurable mode).
+type SuiteExtraAssertCall struct {
+ mode SuiteExtraAssertCallMode
+}
+
+// NewSuiteExtraAssertCall constructs SuiteExtraAssertCall checker.
+func NewSuiteExtraAssertCall() *SuiteExtraAssertCall {
+ return &SuiteExtraAssertCall{mode: DefaultSuiteExtraAssertCallMode}
+}
+
+func (SuiteExtraAssertCall) Name() string { return "suite-extra-assert-call" }
+
+func (checker *SuiteExtraAssertCall) SetMode(m SuiteExtraAssertCallMode) *SuiteExtraAssertCall {
+ checker.mode = m
+ return checker
+}
+
+func (checker SuiteExtraAssertCall) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
+ if call.IsPkg {
+ return nil
+ }
+
+ switch checker.mode {
+ case SuiteExtraAssertCallModeRequire:
+ x, ok := call.Selector.X.(*ast.Ident) // s.True
+ if !ok || x == nil || !implementsTestifySuiteIface(pass, x) {
+ return nil
+ }
+
+ msg := fmt.Sprintf("use an explicit %s.Assert().%s", analysisutil.NodeString(pass.Fset, x), call.Fn.Name)
+ return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{
+ Message: "Add `Assert()` call",
+ TextEdits: []analysis.TextEdit{{
+ Pos: x.End(),
+ End: x.End(), // Pure insertion.
+ NewText: []byte(".Assert()"),
+ }},
+ })
+
+ case SuiteExtraAssertCallModeRemove:
+ x, ok := call.Selector.X.(*ast.CallExpr) // s.Assert().True
+ if !ok {
+ return nil
+ }
+
+ se, ok := x.Fun.(*ast.SelectorExpr)
+ if !ok || se == nil || !implementsTestifySuiteIface(pass, se.X) {
+ return nil
+ }
+ if se.Sel == nil || se.Sel.Name != "Assert" {
+ return nil
+ }
+
+ msg := fmt.Sprintf("need to simplify the assertion to %s.%s", analysisutil.NodeString(pass.Fset, se.X), call.Fn.Name)
+ return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{
+ Message: "Remove `Assert()` call",
+ TextEdits: []analysis.TextEdit{{
+ Pos: se.Sel.Pos(),
+ End: x.End() + 1, // +1 for dot.
+ NewText: []byte(""),
+ }},
+ })
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go
new file mode 100644
index 000000000..5cadc93ad
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go
@@ -0,0 +1,130 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/inspector"
+
+ "github.com/Antonboom/testifylint/internal/analysisutil"
+ "github.com/Antonboom/testifylint/internal/testify"
+)
+
+// SuiteTHelper requires t.Helper() call in suite helpers:
+//
+// func (s *RoomSuite) assertRoomRound(roundID RoundID) {
+// s.T().Helper()
+// s.Equal(roundID, s.getRoom().CurrentRound.ID)
+// }
+type SuiteTHelper struct{}
+
+// NewSuiteTHelper constructs SuiteTHelper checker.
+func NewSuiteTHelper() SuiteTHelper { return SuiteTHelper{} }
+func (SuiteTHelper) Name() string { return "suite-thelper" }
+
+func (checker SuiteTHelper) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diagnostics []analysis.Diagnostic) {
+ inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) {
+ fd := node.(*ast.FuncDecl)
+ if !isTestifySuiteMethod(pass, fd) {
+ return
+ }
+
+ if ident := fd.Name; ident == nil || isTestMethod(ident.Name) || isServiceMethod(ident.Name) {
+ return
+ }
+
+ if !containsSuiteAssertions(pass, fd) {
+ return
+ }
+
+ rcv := fd.Recv.List[0]
+ if len(rcv.Names) != 1 || rcv.Names[0] == nil {
+ return
+ }
+ rcvName := rcv.Names[0].Name
+
+ helperCallStr := fmt.Sprintf("%s.T().Helper()", rcvName)
+
+ firstStmt := fd.Body.List[0]
+ if analysisutil.NodeString(pass.Fset, firstStmt) == helperCallStr {
+ return
+ }
+
+ msg := fmt.Sprintf("suite helper method must start with " + helperCallStr)
+ d := newDiagnostic(checker.Name(), fd, msg, &analysis.SuggestedFix{
+ Message: fmt.Sprintf("Insert `%s`", helperCallStr),
+ TextEdits: []analysis.TextEdit{
+ {
+ Pos: firstStmt.Pos(),
+ End: firstStmt.Pos(), // Pure insertion.
+ NewText: []byte(helperCallStr + "\n\n"),
+ },
+ },
+ })
+ diagnostics = append(diagnostics, *d)
+ })
+ return diagnostics
+}
+
+func isTestifySuiteMethod(pass *analysis.Pass, fDecl *ast.FuncDecl) bool {
+ if fDecl.Recv == nil || len(fDecl.Recv.List) != 1 {
+ return false
+ }
+
+ rcv := fDecl.Recv.List[0]
+ return implementsTestifySuiteIface(pass, rcv.Type)
+}
+
+func isTestMethod(name string) bool {
+ return strings.HasPrefix(name, "Test")
+}
+
+func isServiceMethod(name string) bool {
+ // https://github.com/stretchr/testify/blob/master/suite/interfaces.go
+ switch name {
+ case "T", "SetT", "SetS", "SetupSuite", "SetupTest", "TearDownSuite", "TearDownTest",
+ "BeforeTest", "AfterTest", "HandleStats", "SetupSubTest", "TearDownSubTest":
+ return true
+ }
+ return false
+}
+
+func containsSuiteAssertions(pass *analysis.Pass, fn *ast.FuncDecl) bool {
+ if fn.Body == nil {
+ return false
+ }
+
+ for _, s := range fn.Body.List {
+ if isSuiteAssertion(pass, s) {
+ return true
+ }
+ }
+ return false
+}
+
+func isSuiteAssertion(pass *analysis.Pass, stmt ast.Stmt) bool {
+ expr, ok := stmt.(*ast.ExprStmt)
+ if !ok {
+ return false
+ }
+
+ ce, ok := expr.X.(*ast.CallExpr)
+ if !ok {
+ return false
+ }
+
+ se, ok := ce.Fun.(*ast.SelectorExpr)
+ if !ok || se.Sel == nil {
+ return false
+ }
+
+ if sel, ok := pass.TypesInfo.Selections[se]; ok {
+ pkg := sel.Obj().Pkg()
+ isAssert := analysisutil.IsPkg(pkg, testify.AssertPkgName, testify.AssertPkgPath)
+ isRequire := analysisutil.IsPkg(pkg, testify.RequirePkgName, testify.RequirePkgPath)
+ return isAssert || isRequire
+ }
+ return false
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/config.go b/vendor/github.com/Antonboom/testifylint/internal/config/config.go
new file mode 100644
index 000000000..51f627008
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/config/config.go
@@ -0,0 +1,53 @@
+package config
+
+import (
+ "flag"
+
+ "github.com/Antonboom/testifylint/internal/checkers"
+)
+
+// NewDefault builds default testifylint config.
+func NewDefault() Config {
+ return Config{
+ EnableAll: false,
+ EnabledCheckers: checkers.EnabledByDefault(),
+ ExpectedActual: ExpectedActualConfig{
+ ExpVarPattern: RegexpValue{checkers.DefaultExpectedVarPattern},
+ },
+ SuiteExtraAssertCall: SuiteExtraAssertCallConfig{
+ Mode: checkers.DefaultSuiteExtraAssertCallMode,
+ },
+ }
+}
+
+// Config implements testifylint configuration.
+type Config struct {
+ EnableAll bool
+ EnabledCheckers KnownCheckersValue
+ ExpectedActual ExpectedActualConfig
+ SuiteExtraAssertCall SuiteExtraAssertCallConfig
+}
+
+// ExpectedActualConfig implements configuration of checkers.ExpectedActual.
+type ExpectedActualConfig struct {
+ ExpVarPattern RegexpValue
+}
+
+// SuiteExtraAssertCallConfig implements configuration of checkers.SuiteExtraAssertCall.
+type SuiteExtraAssertCallConfig struct {
+ Mode checkers.SuiteExtraAssertCallMode
+}
+
+// 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.ExpectedActual.ExpVarPattern, "expected-actual.pattern", "regexp for expected variable name")
+ fs.Var(NewEnumValue(suiteExtraAssertCallModeAsString, &cfg.SuiteExtraAssertCall.Mode),
+ "suite-extra-assert-call.mode", "to require or remove extra Assert() call")
+}
+
+var suiteExtraAssertCallModeAsString = map[string]checkers.SuiteExtraAssertCallMode{
+ "remove": checkers.SuiteExtraAssertCallModeRemove,
+ "require": checkers.SuiteExtraAssertCallModeRequire,
+}
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
new file mode 100644
index 000000000..2f0ee978f
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go
@@ -0,0 +1,105 @@
+package config
+
+import (
+ "flag"
+ "fmt"
+ "regexp"
+ "sort"
+ "strings"
+
+ "github.com/Antonboom/testifylint/internal/checkers"
+)
+
+var (
+ _ flag.Value = (*KnownCheckersValue)(nil)
+ _ flag.Value = (*RegexpValue)(nil)
+ _ flag.Value = (*EnumValue[checkers.SuiteExtraAssertCallMode])(nil)
+)
+
+// KnownCheckersValue implements comma separated list of testify checkers.
+type KnownCheckersValue []string
+
+func (kcv KnownCheckersValue) String() string {
+ return strings.Join(kcv, ",")
+}
+
+func (kcv *KnownCheckersValue) Set(v string) error {
+ chckrs := strings.Split(v, ",")
+ for _, checkerName := range chckrs {
+ if ok := checkers.IsKnown(checkerName); !ok {
+ return fmt.Errorf("unknown checker %q", checkerName)
+ }
+ }
+
+ *kcv = chckrs
+ return nil
+}
+
+// RegexpValue is a special wrapper for support of flag.FlagSet over regexp.Regexp.
+// Original regexp is available through RegexpValue.Regexp.
+type RegexpValue struct {
+ *regexp.Regexp
+}
+
+func (rv RegexpValue) String() string {
+ if rv.Regexp == nil {
+ return ""
+ }
+ return rv.Regexp.String()
+}
+
+func (rv *RegexpValue) Set(v string) error {
+ compiled, err := regexp.Compile(v)
+ if err != nil {
+ return err
+ }
+
+ rv.Regexp = compiled
+ return nil
+}
+
+// EnumValue is a special type for support of flag.FlagSet over user-defined constants.
+type EnumValue[EnumT comparable] struct {
+ mapping map[string]EnumT
+ keys []string
+ dst *EnumT
+}
+
+// NewEnumValue takes the "enum-value-name to enum-value" mapping and a destination for the value passed through the CLI.
+// Returns an EnumValue instance suitable for flag.FlagSet.Var.
+func NewEnumValue[EnumT comparable](mapping map[string]EnumT, dst *EnumT) *EnumValue[EnumT] {
+ keys := make([]string, 0, len(mapping))
+ for k := range mapping {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ return &EnumValue[EnumT]{
+ mapping: mapping,
+ keys: keys,
+ dst: dst,
+ }
+}
+
+func (e EnumValue[EnumT]) String() string {
+ if e.dst == nil {
+ return ""
+ }
+
+ for k, v := range e.mapping {
+ if v == *e.dst {
+ return k
+ }
+ }
+ return ""
+}
+
+func (e *EnumValue[EnumT]) Set(s string) error {
+ v, ok := e.mapping[s]
+ if !ok {
+ return fmt.Errorf("use one of (%v)", strings.Join(e.keys, " | "))
+ }
+
+ *e.dst = v
+ return nil
+}
diff --git a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go
new file mode 100644
index 000000000..45731aa97
--- /dev/null
+++ b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go
@@ -0,0 +1,13 @@
+package testify
+
+const (
+ ModulePath = "github.com/stretchr/testify"
+
+ AssertPkgName = "assert"
+ RequirePkgName = "require"
+ SuitePkgName = "suite"
+
+ AssertPkgPath = ModulePath + "/" + AssertPkgName
+ RequirePkgPath = ModulePath + "/" + RequirePkgName
+ SuitePkgPath = ModulePath + "/" + SuitePkgName
+)