aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Antonboom/testifylint/analyzer
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/Antonboom/testifylint/analyzer')
-rw-r--r--vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go175
-rw-r--r--vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go48
2 files changed, 223 insertions, 0 deletions
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
+}