aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/nunnatsa
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2024-11-11 11:41:38 +0100
committerTaras Madan <tarasmadan@google.com>2024-11-11 11:10:48 +0000
commit27e76fae2ee2d84dc7db63af1d9ed7358ba35b7a (patch)
treeed19c0e35e272b3c4cc5a2f2c595e035b2428337 /vendor/github.com/nunnatsa
parent621e84e063b0e15b23e17780338627c509e1b9e8 (diff)
vendor: update
Diffstat (limited to 'vendor/github.com/nunnatsa')
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/.gitignore1
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/Makefile12
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/README.md46
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/analyzer.go16
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/doc.go17
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actual.go118
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actualarg.go235
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncactual.go123
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncfuncarg.go38
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/comparisonAsserion.go260
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/expression.go315
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/bematchers.go77
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/benumericmatcher.go128
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/equalmatcher.go124
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/errormatchers.go199
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/lenmatchers.go11
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcher.go86
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherinfo.go148
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherwithnest.go66
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/multiplematchers.go62
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/expression/value/value.go221
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/formatter/formatter.go22
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/dothandler.go36
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/ginkgoinfo.go63
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go123
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handling.go195
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/namehandler.go49
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/dothandler.go99
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go215
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/namedhandler.go112
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/gomegainfo/gomegainfo.go113
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go2
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go339
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go28
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncfunccallrule.go41
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncsucceedrule.go30
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asynctimeintervalsrule.go79
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/caprule.go128
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparepointerrule.go64
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparisonrule.go75
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/doublenegativerule.go30
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalboolrule.go36
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equaldifferenttypesrule.go119
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalnilrule.go29
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/errorequalnilrule.go35
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/forceexpecttorule.go43
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/havelen0.go23
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/haveoccurredrule.go35
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/lenrule.go119
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcheronlyrule.go12
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcherrorrule.go110
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/missingassertionrule.go27
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/nilcomparerule.go75
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/rule.go61
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/internal/rules/succeedrule.go41
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go1604
-rw-r--r--vendor/github.com/nunnatsa/ginkgolinter/types/config.go2
57 files changed, 4387 insertions, 2130 deletions
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/.gitignore b/vendor/github.com/nunnatsa/ginkgolinter/.gitignore
index 7d7f8b10c..67467b717 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/.gitignore
+++ b/vendor/github.com/nunnatsa/ginkgolinter/.gitignore
@@ -1,2 +1,3 @@
ginkgolinter
bin/
+e2e \ No newline at end of file
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/Makefile b/vendor/github.com/nunnatsa/ginkgolinter/Makefile
index 586633006..8ddd8c42c 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/Makefile
+++ b/vendor/github.com/nunnatsa/ginkgolinter/Makefile
@@ -5,7 +5,7 @@ HASH_FLAG := -X github.com/nunnatsa/ginkgolinter/version.gitHash=$(COMMIT_HASH)
BUILD_ARGS := -ldflags "$(VERSION_FLAG) $(HASH_FLAG)"
-build: unit-test
+build: goimports
go build $(BUILD_ARGS) -o ginkgolinter ./cmd/ginkgolinter
unit-test:
@@ -23,5 +23,11 @@ build-for-linux:
build-all: build build-for-linux build-for-mac build-for-windows
-test: build
- ./tests/e2e.sh
+test-cli:
+ cd tests; go test -v ./
+
+test: unit-test test-cli
+
+goimports:
+ go install golang.org/x/tools/cmd/goimports@latest
+ goimports -w -local="github.com/nunnatsa/ginkgolinter" $(shell find . -type f -name '*.go' ! -path "*/vendor/*")
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/README.md b/vendor/github.com/nunnatsa/ginkgolinter/README.md
index 977cec903..536a65e7b 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/README.md
+++ b/vendor/github.com/nunnatsa/ginkgolinter/README.md
@@ -249,6 +249,28 @@ This will probably happen when using the old format:
Eventually(aFunc, 500 * time.Millisecond /*timeout*/, 10 * time.Second /*polling*/).Should(Succeed())
```
+### Correct usage of the `Succeed()` matcher [Bug]
+The `Succeed()` matcher only accepts a single error value. this rule validates that.
+
+For example:
+ ```go
+ Expect(42).To(Succeed())
+ ```
+
+But mostly, we want to avoid using this matcher with functions that return multiple values, even if their last
+returned value is an error, because this is not supported:
+ ```go
+ Expect(os.Open("myFile.txt")).To(Succeed())
+ ```
+
+In async assertions (like `Eventually()`), the `Succeed()` matcher may also been used with functions that accept
+a Gomega object as their first parameter, and returns nothing, e.g. this is a valid usage of `Eventually`
+ ```go
+ Eventually(func(g Gomega){
+ g.Expect(true).To(BeTrue())
+ }).WithTimeout(10 * time.Millisecond).WithPolling(time.Millisecond).Should(Succeed())
+ ```
+
### Avoid Spec Pollution: Don't Initialize Variables in Container Nodes [BUG/STYLE]:
***Note***: Only applied when the `--forbid-spec-pollution=true` flag is set (disabled by default).
@@ -476,6 +498,30 @@ will be changed to:
```go
Eventually(aFunc, time.Second*5, time.Second*polling)
```
+
+### Correct usage of the `Succeed()` and the `HaveOccurred()` matchers
+This rule enforces using the `Success()` matcher only for functions, and the `HaveOccurred()` matcher only for error
+values.
+
+For example:
+ ```go
+ Expect(err).To(Succeed())
+ ```
+will trigger a warning with a suggestion to replace the mather to
+ ```go
+ Expect(err).ToNot(HaveOccurred())
+ ```
+
+and vice versa:
+ ```go
+ Expect(myErrorFunc()).ToNot(HaveOccurred())
+ ```
+will trigger a warning with a suggestion to replace the mather to
+ ```go
+ Expect(myErrorFunc()).To(Succeed())
+ ```
+***This rule is disabled by default***. Use the `--force-succeed=true` command line flag to enable it.
+
## Suppress the linter
### Suppress warning from command line
* Use the `--suppress-len-assertion=true` flag to suppress the wrong length and cap assertions warning
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go b/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go
index edff57acd..dbc39aba5 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go
@@ -25,13 +25,14 @@ func NewAnalyzerWithConfig(config *types.Config) *analysis.Analyzer {
// NewAnalyzer returns an Analyzer - the package interface with nogo
func NewAnalyzer() *analysis.Analyzer {
config := &types.Config{
- SuppressLen: false,
- SuppressNil: false,
- SuppressErr: false,
- SuppressCompare: false,
- ForbidFocus: false,
- AllowHaveLen0: false,
- ForceExpectTo: false,
+ SuppressLen: false,
+ SuppressNil: false,
+ SuppressErr: false,
+ SuppressCompare: false,
+ ForbidFocus: false,
+ AllowHaveLen0: false,
+ ForceExpectTo: false,
+ ForceSucceedForFuncs: false,
}
a := NewAnalyzerWithConfig(config)
@@ -50,6 +51,7 @@ func NewAnalyzer() *analysis.Analyzer {
a.Flags.BoolVar(&ignored, "suppress-focus-container", true, "Suppress warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt. Deprecated and ignored: use --forbid-focus-container instead")
a.Flags.Var(&config.ForbidFocus, "forbid-focus-container", "trigger a warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt; default = false.")
a.Flags.Var(&config.ForbidSpecPollution, "forbid-spec-pollution", "trigger a warning for variable assignments in ginkgo containers like Describe, Context and When, instead of in BeforeEach(); default = false.")
+ a.Flags.Var(&config.ForceSucceedForFuncs, "force-succeed", "force using the Succeed matcher for error functions, and the HaveOccurred matcher for non-function error values")
return a
}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/doc.go b/vendor/github.com/nunnatsa/ginkgolinter/doc.go
index dd9ecf58a..c07b6a316 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/doc.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/doc.go
@@ -30,6 +30,14 @@ For example:
This will probably happen when using the old format:
Eventually(aFunc, 500 * time.Millisecond, 10 * time.Second).Should(Succeed())
+* Success matcher validation: [BUG]
+ The Success matcher expect that the actual argument will be a single error. In async actual assertions, It also allow
+ functions with Gomega object as the function first parameter.
+For example:
+ Expect(myInt).To(Succeed())
+or
+ Eventually(func() int { return 42 }).Should(Succeed())
+
* reject variable assignments in ginkgo containers [Bug/Style]:
For example:
var _ = Describe("description", func(){
@@ -96,4 +104,13 @@ methods.
For example:
Eventually(context.Background(), func() bool { return true }, "1s").Should(BeTrue())
Eventually(context.Background(), func() bool { return true }, time.Second*60, 15).Should(BeTrue())
+
+* Success <=> Eventually usage [Style]
+ enforce that the Succeed() matcher will be used for error functions, and the HaveOccurred() matcher will
+ be used for error values.
+
+For example:
+ Expect(err).ToNot(Succeed())
+or
+ Expect(funcRetError().ToNot(HaveOccurred())
`
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actual.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actual.go
new file mode 100644
index 000000000..8e3df5d3f
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actual.go
@@ -0,0 +1,118 @@
+package actual
+
+import (
+ "go/ast"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+)
+
+type Actual struct {
+ Orig *ast.CallExpr
+ Clone *ast.CallExpr
+ Arg ArgPayload
+ argType gotypes.Type
+ isTuple bool
+ isAsync bool
+ asyncArg *AsyncArg
+ actualOffset int
+}
+
+func New(origExpr, cloneExpr *ast.CallExpr, orig *ast.CallExpr, clone *ast.CallExpr, pass *analysis.Pass, handler gomegahandler.Handler, timePkg string) (*Actual, bool) {
+ funcName, ok := handler.GetActualFuncName(orig)
+ if !ok {
+ return nil, false
+ }
+
+ arg, actualOffset := getActualArgPayload(orig, clone, pass, funcName)
+ if arg == nil {
+ return nil, false
+ }
+
+ argType := pass.TypesInfo.TypeOf(orig.Args[actualOffset])
+ isTuple := false
+
+ if tpl, ok := argType.(*gotypes.Tuple); ok {
+ if tpl.Len() > 0 {
+ argType = tpl.At(0).Type()
+ } else {
+ argType = nil
+ }
+
+ isTuple = tpl.Len() > 1
+ }
+
+ isAsyncExpr := gomegainfo.IsAsyncActualMethod(funcName)
+
+ var asyncArg *AsyncArg
+ if isAsyncExpr {
+ asyncArg = newAsyncArg(origExpr, cloneExpr, orig, clone, argType, pass, actualOffset, timePkg)
+ }
+
+ return &Actual{
+ Orig: orig,
+ Clone: clone,
+ Arg: arg,
+ argType: argType,
+ isTuple: isTuple,
+ isAsync: isAsyncExpr,
+ asyncArg: asyncArg,
+ actualOffset: actualOffset,
+ }, true
+}
+
+func (a *Actual) ReplaceActual(newArgs ast.Expr) {
+ a.Clone.Args[a.actualOffset] = newArgs
+}
+
+func (a *Actual) ReplaceActualWithItsFirstArg() {
+ firstArgOfArg := a.Clone.Args[a.actualOffset].(*ast.CallExpr).Args[0]
+ a.ReplaceActual(firstArgOfArg)
+}
+
+func (a *Actual) IsAsync() bool {
+ return a.isAsync
+}
+
+func (a *Actual) IsTuple() bool {
+ return a.isTuple
+}
+
+func (a *Actual) ArgGOType() gotypes.Type {
+ return a.argType
+}
+
+func (a *Actual) GetAsyncArg() *AsyncArg {
+ return a.asyncArg
+}
+
+func (a *Actual) AppendWithArgsMethod() {
+ if a.asyncArg.fun != nil {
+ if len(a.asyncArg.fun.Args) > 0 {
+ actualOrigFunc := a.Clone.Fun
+ actualOrigArgs := a.Clone.Args
+
+ actualOrigArgs[a.actualOffset] = a.asyncArg.fun.Fun
+ call := &ast.SelectorExpr{
+ Sel: ast.NewIdent("WithArguments"),
+ X: &ast.CallExpr{
+ Fun: actualOrigFunc,
+ Args: actualOrigArgs,
+ },
+ }
+
+ a.Clone.Fun = call
+ a.Clone.Args = a.asyncArg.fun.Args
+ a.Clone = a.Clone.Fun.(*ast.SelectorExpr).X.(*ast.CallExpr)
+ } else {
+ a.Clone.Args[a.actualOffset] = a.asyncArg.fun.Fun
+ }
+ }
+}
+
+func (a *Actual) GetActualArg() ast.Expr {
+ return a.Clone.Args[a.actualOffset]
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actualarg.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actualarg.go
new file mode 100644
index 000000000..9d251c468
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/actualarg.go
@@ -0,0 +1,235 @@
+package actual
+
+import (
+ "go/ast"
+ "go/token"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+ "github.com/nunnatsa/ginkgolinter/internal/reverseassertion"
+)
+
+type ArgType uint64
+
+const (
+ UnknownActualArgType ArgType = 1 << iota
+ ErrActualArgType
+ LenFuncActualArgType
+ CapFuncActualArgType
+ ComparisonActualArgType
+ LenComparisonActualArgType
+ CapComparisonActualArgType
+ NilComparisonActualArgType
+ BinaryComparisonActualArgType
+ FuncSigArgType
+ ErrFuncActualArgType
+ GomegaParamArgType
+ MultiRetsArgType
+
+ ErrorTypeArgType
+
+ EqualZero
+ GreaterThanZero
+)
+
+func (a ArgType) Is(val ArgType) bool {
+ return a&val != 0
+}
+
+func getActualArgPayload(origActualExpr, actualExprClone *ast.CallExpr, pass *analysis.Pass, actualMethodName string) (ArgPayload, int) {
+ origArgExpr, argExprClone, actualOffset, isGomegaExpr := getActualArg(origActualExpr, actualExprClone, actualMethodName, pass)
+ if !isGomegaExpr {
+ return nil, 0
+ }
+
+ var arg ArgPayload
+
+ if value.IsExprError(pass, origArgExpr) {
+ arg = newErrPayload(origArgExpr, argExprClone, pass)
+ } else {
+ switch expr := origArgExpr.(type) {
+ case *ast.CallExpr:
+ arg = newFuncCallArgPayload(expr, argExprClone.(*ast.CallExpr))
+
+ case *ast.BinaryExpr:
+ arg = parseBinaryExpr(expr, argExprClone.(*ast.BinaryExpr), pass)
+
+ default:
+ t := pass.TypesInfo.TypeOf(origArgExpr)
+ if sig, ok := t.(*gotypes.Signature); ok {
+ arg = getAsyncFuncArg(sig)
+ }
+ }
+
+ }
+
+ if arg != nil {
+ return arg, actualOffset
+ }
+
+ return newRegularArgPayload(origArgExpr, argExprClone, pass), actualOffset
+}
+
+func getActualArg(origActualExpr *ast.CallExpr, actualExprClone *ast.CallExpr, actualMethodName string, pass *analysis.Pass) (ast.Expr, ast.Expr, int, bool) {
+ var (
+ origArgExpr ast.Expr
+ argExprClone ast.Expr
+ )
+
+ funcOffset := gomegainfo.ActualArgOffset(actualMethodName)
+ if funcOffset < 0 {
+ return nil, nil, 0, false
+ }
+
+ if len(origActualExpr.Args) <= funcOffset {
+ return nil, nil, 0, false
+ }
+
+ origArgExpr = origActualExpr.Args[funcOffset]
+ argExprClone = actualExprClone.Args[funcOffset]
+
+ if gomegainfo.IsAsyncActualMethod(actualMethodName) {
+ if pass.TypesInfo.TypeOf(origArgExpr).String() == "context.Context" {
+ funcOffset++
+ if len(origActualExpr.Args) <= funcOffset {
+ return nil, nil, 0, false
+ }
+
+ origArgExpr = origActualExpr.Args[funcOffset]
+ argExprClone = actualExprClone.Args[funcOffset]
+ }
+ }
+
+ return origArgExpr, argExprClone, funcOffset, true
+}
+
+type ArgPayload interface {
+ ArgType() ArgType
+}
+
+type RegularArgPayload struct {
+ value.Value
+}
+
+func newRegularArgPayload(orig, clone ast.Expr, pass *analysis.Pass) *RegularArgPayload {
+ return &RegularArgPayload{
+ Value: value.New(orig, clone, pass),
+ }
+}
+
+func (*RegularArgPayload) ArgType() ArgType {
+ return UnknownActualArgType
+}
+
+type FuncCallArgPayload struct {
+ argType ArgType
+
+ origFunc *ast.CallExpr
+ cloneFunc *ast.CallExpr
+
+ origVal ast.Expr
+ cloneVal ast.Expr
+}
+
+func newFuncCallArgPayload(orig, clone *ast.CallExpr) ArgPayload {
+ funcName, ok := builtinFuncName(orig)
+ if !ok {
+ return nil
+ }
+
+ if len(orig.Args) != 1 {
+ return nil
+ }
+
+ var argType ArgType
+ switch funcName {
+ case "len":
+ argType = LenFuncActualArgType
+ case "cap":
+ argType = CapFuncActualArgType
+ default:
+ return nil
+ }
+
+ return &FuncCallArgPayload{
+ argType: argType,
+ origFunc: orig,
+ cloneFunc: clone,
+ origVal: orig.Args[0],
+ cloneVal: clone.Args[0],
+ }
+}
+
+func (f *FuncCallArgPayload) ArgType() ArgType {
+ return f.argType
+}
+
+type ErrPayload struct {
+ value.Valuer
+}
+
+func newErrPayload(orig, clone ast.Expr, pass *analysis.Pass) *ErrPayload {
+ return &ErrPayload{
+ Valuer: value.GetValuer(orig, clone, pass),
+ }
+}
+
+func (*ErrPayload) ArgType() ArgType {
+ return ErrActualArgType | ErrorTypeArgType
+}
+
+func parseBinaryExpr(origActualExpr, argExprClone *ast.BinaryExpr, pass *analysis.Pass) ArgPayload {
+ left, right, op := origActualExpr.X, origActualExpr.Y, origActualExpr.Op
+ replace := false
+ switch realFirst := left.(type) {
+ case *ast.Ident: // check if const
+ info, ok := pass.TypesInfo.Types[realFirst]
+ if ok {
+ if value.Is[*gotypes.Basic](info.Type) && (info.Value != nil || info.IsNil()) {
+ replace = true
+ }
+ }
+
+ case *ast.BasicLit:
+ replace = true
+ }
+
+ if replace {
+ left, right = right, left
+ }
+
+ switch op {
+ case token.EQL:
+ case token.NEQ:
+ case token.GTR, token.GEQ, token.LSS, token.LEQ:
+ if replace {
+ op = reverseassertion.ChangeCompareOperator(op)
+ }
+ default:
+ return nil
+ }
+
+ leftClone, rightClone := argExprClone.X, argExprClone.Y
+ if replace {
+ leftClone, rightClone = rightClone, leftClone
+ }
+
+ leftVal := value.GetValuer(left, leftClone, pass)
+ rightVal := value.GetValuer(right, rightClone, pass)
+
+ if value.IsNil(right, pass) {
+ return newNilComparisonPayload(leftVal, rightVal, op)
+ }
+
+ leftVal.IsFunc()
+ if firstFunc, ok := left.(*ast.CallExpr); ok {
+ if payload, ok := newFuncComparisonPayload(firstFunc, leftClone.(*ast.CallExpr), right, rightClone, op, pass); ok {
+ return payload
+ }
+ }
+
+ return newComparisonArgPayload(leftVal, rightVal, op)
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncactual.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncactual.go
new file mode 100644
index 000000000..7c5df2a34
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncactual.go
@@ -0,0 +1,123 @@
+package actual
+
+import (
+ "go/ast"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/intervals"
+)
+
+type AsyncArg struct {
+ valid bool
+ fun *ast.CallExpr
+
+ timeoutInterval intervals.DurationValue
+ pollingInterval intervals.DurationValue
+ tooManyTimeouts bool
+ tooManyPolling bool
+}
+
+func newAsyncArg(origExpr, cloneExpr, orig, clone *ast.CallExpr, argType gotypes.Type, pass *analysis.Pass, actualOffset int, timePkg string) *AsyncArg {
+ var (
+ fun *ast.CallExpr
+ valid = true
+ timeout intervals.DurationValue
+ polling intervals.DurationValue
+ )
+
+ if _, isActualFuncCall := orig.Args[actualOffset].(*ast.CallExpr); isActualFuncCall {
+ fun = clone.Args[actualOffset].(*ast.CallExpr)
+ valid = isValidAsyncValueType(argType)
+ }
+
+ timeoutOffset := actualOffset + 1
+ //var err error
+ tooManyTimeouts := false
+ tooManyPolling := false
+
+ if len(orig.Args) > timeoutOffset {
+ timeout = intervals.GetDuration(pass, timeoutOffset, orig.Args[timeoutOffset], clone.Args[timeoutOffset], timePkg)
+ pollingOffset := actualOffset + 2
+ if len(orig.Args) > pollingOffset {
+ polling = intervals.GetDuration(pass, pollingOffset, orig.Args[pollingOffset], clone.Args[pollingOffset], timePkg)
+ }
+ }
+ selOrig := origExpr.Fun.(*ast.SelectorExpr)
+ selClone := cloneExpr.Fun.(*ast.SelectorExpr)
+
+ for {
+ callOrig, ok := selOrig.X.(*ast.CallExpr)
+ if !ok {
+ break
+ }
+ callClone := selClone.X.(*ast.CallExpr)
+
+ funOrig, ok := callOrig.Fun.(*ast.SelectorExpr)
+ if !ok {
+ break
+ }
+ funClone := callClone.Fun.(*ast.SelectorExpr)
+
+ switch funOrig.Sel.Name {
+ case "WithTimeout", "Within":
+ if timeout != nil {
+ tooManyTimeouts = true
+ } else if len(callOrig.Args) == 1 {
+ timeout = intervals.GetDurationFromValue(pass, callOrig.Args[0], callClone.Args[0])
+ }
+
+ case "WithPolling", "ProbeEvery":
+ if polling != nil {
+ tooManyPolling = true
+ } else if len(callOrig.Args) == 1 {
+ polling = intervals.GetDurationFromValue(pass, callOrig.Args[0], callClone.Args[0])
+ }
+ }
+
+ selOrig = funOrig
+ selClone = funClone
+ }
+
+ return &AsyncArg{
+ valid: valid,
+ fun: fun,
+ timeoutInterval: timeout,
+ pollingInterval: polling,
+ tooManyTimeouts: tooManyTimeouts,
+ tooManyPolling: tooManyPolling,
+ }
+}
+
+func (a *AsyncArg) IsValid() bool {
+ return a.valid
+}
+
+func (a *AsyncArg) Timeout() intervals.DurationValue {
+ return a.timeoutInterval
+}
+
+func (a *AsyncArg) Polling() intervals.DurationValue {
+ return a.pollingInterval
+}
+
+func (a *AsyncArg) TooManyTimeouts() bool {
+ return a.tooManyTimeouts
+}
+
+func (a *AsyncArg) TooManyPolling() bool {
+ return a.tooManyPolling
+}
+
+func isValidAsyncValueType(t gotypes.Type) bool {
+ switch t.(type) {
+ // allow functions that return function or channel.
+ case *gotypes.Signature, *gotypes.Chan, *gotypes.Pointer:
+ return true
+ case *gotypes.Named:
+ return isValidAsyncValueType(t.Underlying())
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncfuncarg.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncfuncarg.go
new file mode 100644
index 000000000..c777cd4a7
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/asyncfuncarg.go
@@ -0,0 +1,38 @@
+package actual
+
+import (
+ gotypes "go/types"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+ "github.com/nunnatsa/ginkgolinter/internal/interfaces"
+)
+
+func getAsyncFuncArg(sig *gotypes.Signature) ArgPayload {
+ argType := FuncSigArgType
+ if sig.Results().Len() == 1 {
+ if interfaces.ImplementsError(sig.Results().At(0).Type().Underlying()) {
+ argType |= ErrFuncActualArgType | ErrorTypeArgType
+ }
+ }
+
+ if sig.Params().Len() > 0 {
+ arg := sig.Params().At(0).Type()
+ if gomegainfo.IsGomegaType(arg) && sig.Results().Len() == 0 {
+ argType |= FuncSigArgType | GomegaParamArgType
+ }
+ }
+
+ if sig.Results().Len() > 1 {
+ argType |= FuncSigArgType | MultiRetsArgType
+ }
+
+ return &FuncSigArgPayload{argType: argType}
+}
+
+type FuncSigArgPayload struct {
+ argType ArgType
+}
+
+func (f FuncSigArgPayload) ArgType() ArgType {
+ return f.argType
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/comparisonAsserion.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/comparisonAsserion.go
new file mode 100644
index 000000000..2b16402db
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/actual/comparisonAsserion.go
@@ -0,0 +1,260 @@
+package actual
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/token"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+)
+
+type ComparisonActualPayload interface {
+ GetOp() token.Token
+ GetLeft() value.Valuer
+ GetRight() value.Valuer
+}
+
+type FuncComparisonPayload struct {
+ op token.Token
+ argType ArgType
+ val value.Valuer
+ left value.Valuer
+ arg ast.Expr
+}
+
+func newFuncComparisonPayload(origLeft, leftClone *ast.CallExpr, origRight, rightClone ast.Expr, op token.Token, pass *analysis.Pass) (*FuncComparisonPayload, bool) {
+
+ funcName, ok := builtinFuncName(origLeft)
+ if !ok {
+ return nil, false
+ }
+
+ if len(origLeft.Args) != 1 {
+ return nil, false
+ }
+
+ left := value.GetValuer(origLeft, leftClone, pass)
+ val := value.GetValuer(origRight, rightClone, pass)
+
+ argType := ComparisonActualArgType
+ switch funcName {
+ case "len":
+ argType |= LenComparisonActualArgType
+
+ if val.IsValueNumeric() {
+ if val.IsValueZero() {
+ switch op {
+ case token.EQL:
+ argType |= EqualZero
+
+ case token.NEQ, token.GTR:
+ argType |= GreaterThanZero
+ }
+ } else if val.GetValue().String() == "1" && op == token.GEQ {
+ argType |= GreaterThanZero
+ }
+ }
+
+ if !argType.Is(GreaterThanZero) && op != token.EQL && op != token.NEQ {
+ return nil, false
+ }
+
+ case "cap":
+ if op != token.EQL && op != token.NEQ {
+ return nil, false
+ }
+ argType |= CapComparisonActualArgType
+
+ default:
+ return nil, false
+ }
+
+ return &FuncComparisonPayload{
+ op: op,
+ argType: argType,
+ val: val,
+ left: left,
+ arg: leftClone.Args[0],
+ }, true
+}
+
+func (f *FuncComparisonPayload) GetLeft() value.Valuer {
+ return f.left
+}
+
+func (f *FuncComparisonPayload) GetRight() value.Valuer {
+ return f.val
+}
+
+func (f *FuncComparisonPayload) ArgType() ArgType {
+ return f.argType
+}
+
+func (f *FuncComparisonPayload) GetOp() token.Token {
+ return f.op
+}
+
+func (f *FuncComparisonPayload) GetValue() constant.Value {
+ return f.val.GetValue()
+}
+
+func (f *FuncComparisonPayload) GetType() gotypes.Type {
+ return f.val.GetType()
+}
+
+func (f *FuncComparisonPayload) GetValueExpr() ast.Expr {
+ return f.val.GetValueExpr()
+}
+
+func (f *FuncComparisonPayload) IsError() bool {
+ return f.val.IsError()
+}
+
+func (f *FuncComparisonPayload) IsValueZero() bool {
+ return f.val.IsValueZero()
+}
+
+func (f *FuncComparisonPayload) IsFunc() bool {
+ return true
+}
+
+func (f *FuncComparisonPayload) IsValueNumeric() bool {
+ return f.val.IsValueNumeric()
+}
+
+func (f *FuncComparisonPayload) IsValueInt() bool {
+ return f.val.IsValueInt()
+}
+
+func (f *FuncComparisonPayload) IsInterface() bool {
+ return f.val.IsInterface()
+}
+
+func (f *FuncComparisonPayload) IsPointer() bool {
+ return f.val.IsPointer()
+}
+
+func (f *FuncComparisonPayload) GetFuncArg() ast.Expr {
+ return f.arg
+}
+
+type ComparisonArgPayload struct {
+ left value.Valuer
+ right value.Valuer
+ op token.Token
+}
+
+func newComparisonArgPayload(left, right value.Valuer, op token.Token) *ComparisonArgPayload {
+ return &ComparisonArgPayload{
+ left: left,
+ right: right,
+ op: op,
+ }
+}
+
+func (*ComparisonArgPayload) ArgType() ArgType {
+ return BinaryComparisonActualArgType | ComparisonActualArgType
+}
+
+func (c *ComparisonArgPayload) GetOp() token.Token {
+ return c.op
+}
+
+func (c *ComparisonArgPayload) GetLeft() value.Valuer {
+ return c.left
+}
+
+func (c *ComparisonArgPayload) GetRight() value.Valuer {
+ return c.right
+}
+
+type NilComparisonPayload struct {
+ val value.Valuer
+ right value.Valuer
+ op token.Token
+}
+
+func newNilComparisonPayload(val, right value.Valuer, op token.Token) *NilComparisonPayload {
+ return &NilComparisonPayload{
+ val: val,
+ right: right,
+ op: op,
+ }
+}
+
+func (*NilComparisonPayload) ArgType() ArgType {
+ return NilComparisonActualArgType
+}
+
+func (n *NilComparisonPayload) GetLeft() value.Valuer {
+ return n.val
+}
+
+func (n *NilComparisonPayload) GetRight() value.Valuer {
+ return n.right
+}
+
+func (n *NilComparisonPayload) GetType() gotypes.Type {
+ return n.val.GetType()
+}
+
+func (n *NilComparisonPayload) GetValue() constant.Value {
+ return n.val.GetValue()
+}
+
+func (n *NilComparisonPayload) GetValueExpr() ast.Expr {
+ return n.val.GetValueExpr()
+}
+
+func (n *NilComparisonPayload) IsValueInt() bool {
+ return n.val.IsValueInt()
+}
+
+func (n *NilComparisonPayload) IsError() bool {
+ return n.val.IsError()
+}
+
+func (n *NilComparisonPayload) IsValueNumeric() bool {
+ return n.val.IsValueNumeric()
+}
+
+func (n *NilComparisonPayload) IsFunc() bool {
+ return n.val.IsFunc()
+}
+
+func (n *NilComparisonPayload) IsValueZero() bool {
+ return n.val.IsValueZero()
+}
+
+func (n *NilComparisonPayload) IsInterface() bool {
+ return n.val.IsInterface()
+}
+
+func (n *NilComparisonPayload) IsPointer() bool {
+ return n.val.IsPointer()
+}
+
+func (n *NilComparisonPayload) GetOp() token.Token {
+ return n.op
+}
+
+func builtinFuncName(callExpr *ast.CallExpr) (string, bool) {
+ argFunc, ok := callExpr.Fun.(*ast.Ident)
+ if !ok {
+ return "", false
+ }
+
+ if len(callExpr.Args) != 1 {
+ return "", false
+ }
+
+ switch name := argFunc.Name; name {
+ case "len", "cap", "min", "max":
+ return name, true
+ default:
+ return "", false
+ }
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/expression.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/expression.go
new file mode 100644
index 000000000..976e726fc
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/expression.go
@@ -0,0 +1,315 @@
+package expression
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ gotypes "go/types"
+
+ "github.com/nunnatsa/ginkgolinter/internal/formatter"
+
+ "github.com/go-toolsmith/astcopy"
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+ "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+ "github.com/nunnatsa/ginkgolinter/internal/reverseassertion"
+)
+
+type GomegaExpression struct {
+ orig *ast.CallExpr
+ clone *ast.CallExpr
+
+ assertionFuncName string
+ origAssertionFuncName string
+ actualFuncName string
+
+ isAsync bool
+
+ actual *actual.Actual
+ matcher *matcher.Matcher
+
+ handler gomegahandler.Handler
+}
+
+func New(origExpr *ast.CallExpr, pass *analysis.Pass, handler gomegahandler.Handler, timePkg string) (*GomegaExpression, bool) {
+ actualMethodName, ok := handler.GetActualFuncName(origExpr)
+ if !ok || !gomegainfo.IsActualMethod(actualMethodName) {
+ return nil, false
+ }
+
+ origSel, ok := origExpr.Fun.(*ast.SelectorExpr)
+ if !ok || !gomegainfo.IsAssertionFunc(origSel.Sel.Name) {
+ return &GomegaExpression{
+ orig: origExpr,
+ actualFuncName: actualMethodName,
+ }, true
+ }
+
+ exprClone := astcopy.CallExpr(origExpr)
+ selClone := exprClone.Fun.(*ast.SelectorExpr)
+
+ origActual := handler.GetActualExpr(origSel)
+ if origActual == nil {
+ return nil, false
+ }
+
+ actualClone := handler.GetActualExprClone(origSel, selClone)
+ if actualClone == nil {
+ return nil, false
+ }
+
+ actl, ok := actual.New(origExpr, exprClone, origActual, actualClone, pass, handler, timePkg)
+ if !ok {
+ return nil, false
+ }
+
+ origMatcher, ok := origExpr.Args[0].(*ast.CallExpr)
+ if !ok {
+ return nil, false
+ }
+
+ matcherClone := exprClone.Args[0].(*ast.CallExpr)
+
+ mtchr, ok := matcher.New(origMatcher, matcherClone, pass, handler)
+ if !ok {
+ return nil, false
+ }
+
+ exprClone.Args[0] = mtchr.Clone
+
+ gexp := &GomegaExpression{
+ orig: origExpr,
+ clone: exprClone,
+
+ assertionFuncName: origSel.Sel.Name,
+ origAssertionFuncName: origSel.Sel.Name,
+ actualFuncName: actualMethodName,
+
+ isAsync: actl.IsAsync(),
+
+ actual: actl,
+ matcher: mtchr,
+
+ handler: handler,
+ }
+
+ if mtchr.ShouldReverseLogic() {
+ gexp.ReverseAssertionFuncLogic()
+ }
+
+ return gexp, true
+}
+
+func (e *GomegaExpression) IsMissingAssertion() bool {
+ return e.matcher == nil
+}
+
+func (e *GomegaExpression) GetActualFuncName() string {
+ if e == nil {
+ return ""
+ }
+ return e.actualFuncName
+}
+
+func (e *GomegaExpression) GetAssertFuncName() string {
+ if e == nil {
+ return ""
+ }
+ return e.assertionFuncName
+}
+
+func (e *GomegaExpression) GetOrigAssertFuncName() string {
+ if e == nil {
+ return ""
+ }
+ return e.origAssertionFuncName
+}
+
+func (e *GomegaExpression) IsAsync() bool {
+ return e.isAsync
+}
+
+func (e *GomegaExpression) ReverseAssertionFuncLogic() {
+ assertionFunc := e.clone.Fun.(*ast.SelectorExpr).Sel
+ newName := reverseassertion.ChangeAssertionLogic(assertionFunc.Name)
+ assertionFunc.Name = newName
+ e.assertionFuncName = newName
+}
+
+func (e *GomegaExpression) ReplaceAssertionMethod(name string) {
+ e.clone.Fun.(*ast.SelectorExpr).Sel.Name = name
+}
+
+func (e *GomegaExpression) ReplaceMatcherFuncName(name string) {
+ e.matcher.ReplaceMatcherFuncName(name)
+}
+
+func (e *GomegaExpression) ReplaceMatcherArgs(newArgs []ast.Expr) {
+ e.matcher.ReplaceMatcherArgs(newArgs)
+}
+
+func (e *GomegaExpression) RemoveMatcherArgs() {
+ e.matcher.ReplaceMatcherArgs(nil)
+}
+
+func (e *GomegaExpression) ReplaceActual(newArg ast.Expr) {
+ e.actual.ReplaceActual(newArg)
+}
+
+func (e *GomegaExpression) ReplaceActualWithItsFirstArg() {
+ e.actual.ReplaceActualWithItsFirstArg()
+}
+
+func (e *GomegaExpression) replaceMathcerFuncNoArgs(name string) {
+ e.matcher.ReplaceMatcherFuncName(name)
+ e.RemoveMatcherArgs()
+}
+
+func (e *GomegaExpression) SetMatcherBeZero() {
+ e.replaceMathcerFuncNoArgs("BeZero")
+}
+
+func (e *GomegaExpression) SetMatcherBeEmpty() {
+ e.replaceMathcerFuncNoArgs("BeEmpty")
+}
+
+func (e *GomegaExpression) SetLenNumericMatcher() {
+ if m, ok := e.matcher.GetMatcherInfo().(value.Valuer); ok && m.IsValueZero() {
+ e.SetMatcherBeEmpty()
+ } else {
+ e.ReplaceMatcherFuncName("HaveLen")
+ e.ReplaceMatcherArgs([]ast.Expr{m.GetValueExpr()})
+ }
+}
+
+func (e *GomegaExpression) SetLenNumericActual() {
+ if m, ok := e.matcher.GetMatcherInfo().(value.Valuer); ok && m.IsValueZero() {
+ e.SetMatcherBeEmpty()
+ } else {
+ e.ReplaceMatcherFuncName("HaveLen")
+ e.ReplaceMatcherArgs([]ast.Expr{m.GetValueExpr()})
+ }
+}
+
+func (e *GomegaExpression) SetMatcherLen(arg ast.Expr) {
+ e.ReplaceMatcherFuncName("HaveLen")
+ e.ReplaceMatcherArgs([]ast.Expr{arg})
+}
+
+func (e *GomegaExpression) SetMatcherCap(arg ast.Expr) {
+ e.ReplaceMatcherFuncName("HaveCap")
+ e.ReplaceMatcherArgs([]ast.Expr{arg})
+}
+
+func (e *GomegaExpression) SetMatcherCapZero() {
+ e.ReplaceMatcherFuncName("HaveCap")
+ e.ReplaceMatcherArgs([]ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}})
+}
+
+func (e *GomegaExpression) SetMatcherSucceed() {
+ e.replaceMathcerFuncNoArgs("Succeed")
+}
+
+func (e *GomegaExpression) SetMatcherHaveOccurred() {
+ e.replaceMathcerFuncNoArgs("HaveOccurred")
+}
+
+func (e *GomegaExpression) SetMatcherBeNil() {
+ e.replaceMathcerFuncNoArgs("BeNil")
+}
+
+func (e *GomegaExpression) SetMatcherBeTrue() {
+ e.replaceMathcerFuncNoArgs("BeTrue")
+}
+
+func (e *GomegaExpression) SetMatcherBeFalse() {
+ e.replaceMathcerFuncNoArgs("BeFalse")
+}
+
+func (e *GomegaExpression) SetMatcherHaveValue() {
+ newMatcherExp := e.handler.GetNewWrapperMatcher("HaveValue", e.matcher.Clone)
+ e.clone.Args[0] = newMatcherExp
+ e.matcher.Clone = newMatcherExp
+}
+
+func (e *GomegaExpression) SetMatcherEqual(arg ast.Expr) {
+ e.ReplaceMatcherFuncName("Equal")
+ e.ReplaceMatcherArgs([]ast.Expr{arg})
+}
+
+func (e *GomegaExpression) SetMatcherBeIdenticalTo(arg ast.Expr) {
+ e.ReplaceMatcherFuncName("BeIdenticalTo")
+ e.ReplaceMatcherArgs([]ast.Expr{arg})
+}
+
+func (e *GomegaExpression) SetMatcherBeNumerically(op token.Token, arg ast.Expr) {
+ e.ReplaceMatcherFuncName("BeNumerically")
+ e.ReplaceMatcherArgs([]ast.Expr{
+ &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", op.String())},
+ arg,
+ })
+}
+
+func (e *GomegaExpression) IsNegativeAssertion() bool {
+ return reverseassertion.IsNegativeLogic(e.assertionFuncName)
+}
+
+func (e *GomegaExpression) GetClone() *ast.CallExpr {
+ return e.clone
+}
+
+// Actual proxies:
+
+func (e *GomegaExpression) GetActualClone() *ast.CallExpr {
+ return e.actual.Clone
+}
+
+func (e *GomegaExpression) AppendWithArgsToActual() {
+ e.actual.AppendWithArgsMethod()
+}
+
+func (e *GomegaExpression) GetAsyncActualArg() *actual.AsyncArg {
+ return e.actual.GetAsyncArg()
+}
+
+func (e *GomegaExpression) GetActualArg() actual.ArgPayload {
+ return e.actual.Arg
+}
+
+func (e *GomegaExpression) GetActualArgExpr() ast.Expr {
+ return e.actual.GetActualArg()
+}
+
+func (e *GomegaExpression) GetActualArgGOType() gotypes.Type {
+ return e.actual.ArgGOType()
+}
+
+func (e *GomegaExpression) ActualArgTypeIs(other actual.ArgType) bool {
+ return e.actual.Arg.ArgType().Is(other)
+}
+
+func (e *GomegaExpression) IsActualTuple() bool {
+ return e.actual.IsTuple()
+}
+
+// Matcher proxies
+
+func (e *GomegaExpression) GetMatcher() *matcher.Matcher {
+ return e.matcher
+}
+
+func (e *GomegaExpression) GetMatcherInfo() matcher.Info {
+ return e.matcher.GetMatcherInfo()
+}
+
+func (e *GomegaExpression) MatcherTypeIs(other matcher.Type) bool {
+ return e.matcher.GetMatcherInfo().Type().Is(other)
+}
+
+func (e *GomegaExpression) FormatOrig(frm *formatter.GoFmtFormatter) string {
+ return frm.Format(e.orig)
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/bematchers.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/bematchers.go
new file mode 100644
index 000000000..24272535d
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/bematchers.go
@@ -0,0 +1,77 @@
+package matcher
+
+import "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+
+type BeIdenticalToMatcher struct {
+ value.Value
+}
+
+func (BeIdenticalToMatcher) Type() Type {
+ return BeIdenticalToMatcherType
+}
+
+func (BeIdenticalToMatcher) MatcherName() string {
+ return beIdenticalTo
+}
+
+type BeEquivalentToMatcher struct {
+ value.Value
+}
+
+func (BeEquivalentToMatcher) Type() Type {
+ return BeEquivalentToMatcherType
+}
+
+func (BeEquivalentToMatcher) MatcherName() string {
+ return beEquivalentTo
+}
+
+type BeZeroMatcher struct{}
+
+func (BeZeroMatcher) Type() Type {
+ return BeZeroMatcherType
+}
+
+func (BeZeroMatcher) MatcherName() string {
+ return beZero
+}
+
+type BeEmptyMatcher struct{}
+
+func (BeEmptyMatcher) Type() Type {
+ return BeEmptyMatcherType
+}
+
+func (BeEmptyMatcher) MatcherName() string {
+ return beEmpty
+}
+
+type BeTrueMatcher struct{}
+
+func (BeTrueMatcher) Type() Type {
+ return BeTrueMatcherType | BoolValueTrue
+}
+
+func (BeTrueMatcher) MatcherName() string {
+ return beTrue
+}
+
+type BeFalseMatcher struct{}
+
+func (BeFalseMatcher) Type() Type {
+ return BeFalseMatcherType | BoolValueFalse
+}
+
+func (BeFalseMatcher) MatcherName() string {
+ return beFalse
+}
+
+type BeNilMatcher struct{}
+
+func (BeNilMatcher) Type() Type {
+ return BeNilMatcherType
+}
+
+func (BeNilMatcher) MatcherName() string {
+ return beNil
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/benumericmatcher.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/benumericmatcher.go
new file mode 100644
index 000000000..8683f0291
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/benumericmatcher.go
@@ -0,0 +1,128 @@
+package matcher
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/token"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+)
+
+type BeNumericallyMatcher struct {
+ op token.Token
+ value value.Valuer
+ argType Type
+}
+
+var compareOps = map[string]token.Token{
+ `"=="`: token.EQL,
+ `"<"`: token.LSS,
+ `">"`: token.GTR,
+ `"="`: token.ASSIGN,
+ `"!="`: token.NEQ,
+ `"<="`: token.LEQ,
+ `">="`: token.GEQ,
+}
+
+func getCompareOp(opExp ast.Expr) token.Token {
+ basic, ok := opExp.(*ast.BasicLit)
+ if !ok {
+ return token.ILLEGAL
+ }
+ if basic.Kind != token.STRING {
+ return token.ILLEGAL
+ }
+
+ if tk, ok := compareOps[basic.Value]; ok {
+ return tk
+ }
+
+ return token.ILLEGAL
+}
+
+func newBeNumericallyMatcher(opExp, orig, clone ast.Expr, pass *analysis.Pass) Info {
+ op := getCompareOp(opExp)
+ if op == token.ILLEGAL {
+ return &UnspecifiedMatcher{
+ matcherName: beNumerically,
+ }
+ }
+
+ val := value.GetValuer(orig, clone, pass)
+ argType := BeNumericallyMatcherType
+
+ if val.IsValueNumeric() {
+ if v := val.GetValue().String(); v == "0" {
+ switch op {
+ case token.EQL:
+ argType |= EqualZero
+
+ case token.NEQ, token.GTR:
+ argType |= GreaterThanZero
+ }
+ } else if v == "1" && op == token.GEQ {
+ argType |= GreaterThanZero
+ }
+ }
+
+ return &BeNumericallyMatcher{
+ op: op,
+ value: val,
+ argType: argType,
+ }
+}
+
+func (m BeNumericallyMatcher) Type() Type {
+ return m.argType
+}
+
+func (BeNumericallyMatcher) MatcherName() string {
+ return beNumerically
+}
+
+func (m BeNumericallyMatcher) GetValueExpr() ast.Expr {
+ return m.value.GetValueExpr()
+}
+
+func (m BeNumericallyMatcher) GetValue() constant.Value {
+ return m.value.GetValue()
+}
+
+func (m BeNumericallyMatcher) GetType() gotypes.Type {
+ return m.value.GetType()
+}
+
+func (m BeNumericallyMatcher) GetOp() token.Token {
+ return m.op
+}
+
+func (m BeNumericallyMatcher) IsValueZero() bool {
+ return m.value.IsValueZero()
+}
+
+func (m BeNumericallyMatcher) IsValueInt() bool {
+ return m.value.IsValueInt()
+}
+
+func (m BeNumericallyMatcher) IsValueNumeric() bool {
+ return m.value.IsValueNumeric()
+}
+
+func (m BeNumericallyMatcher) IsError() bool {
+ return m.value.IsError()
+}
+
+func (m BeNumericallyMatcher) IsFunc() bool {
+ return m.value.IsFunc()
+}
+
+func (m BeNumericallyMatcher) IsInterface() bool {
+ return m.value.IsInterface()
+}
+
+func (m BeNumericallyMatcher) IsPointer() bool {
+ return m.value.IsPointer()
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/equalmatcher.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/equalmatcher.go
new file mode 100644
index 000000000..8cee8e408
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/equalmatcher.go
@@ -0,0 +1,124 @@
+package matcher
+
+import (
+ "go/ast"
+ "go/constant"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+)
+
+func newEqualMatcher(orig, clone ast.Expr, pass *analysis.Pass) Info {
+ t := pass.TypesInfo.Types[orig]
+
+ if t.Value != nil {
+ if t.Value.Kind() == constant.Bool {
+ if t.Value.String() == "true" {
+ return &EqualTrueMatcher{}
+ }
+ return &EqualFalseMatcher{}
+ }
+ }
+
+ if value.IsNil(orig, pass) {
+ return &EqualNilMatcher{
+ gotype: pass.TypesInfo.TypeOf(orig),
+ }
+ }
+
+ val := value.GetValuer(orig, clone, pass)
+
+ return &EqualMatcher{
+ val: val,
+ }
+}
+
+type EqualMatcher struct {
+ val value.Valuer
+}
+
+func (EqualMatcher) Type() Type {
+ return EqualMatcherType
+}
+
+func (EqualMatcher) MatcherName() string {
+ return equal
+}
+
+func (m EqualMatcher) GetValue() constant.Value {
+ return m.val.GetValue()
+}
+
+func (m EqualMatcher) GetType() gotypes.Type {
+ return m.val.GetType()
+}
+
+func (m EqualMatcher) GetValueExpr() ast.Expr {
+ return m.val.GetValueExpr()
+}
+
+func (m EqualMatcher) IsValueZero() bool {
+ return m.val.IsValueZero()
+}
+
+func (m EqualMatcher) IsValueInt() bool {
+ return m.val.IsValueInt()
+}
+
+func (m EqualMatcher) IsValueNumeric() bool {
+ return m.val.IsValueNumeric()
+}
+
+func (m EqualMatcher) IsError() bool {
+ return m.val.IsError()
+}
+
+func (m EqualMatcher) IsFunc() bool {
+ return m.val.IsFunc()
+}
+
+func (m EqualMatcher) IsInterface() bool {
+ return m.val.IsInterface()
+}
+
+func (m EqualMatcher) IsPointer() bool {
+ return m.val.IsPointer()
+}
+
+type EqualNilMatcher struct {
+ gotype gotypes.Type
+}
+
+func (EqualNilMatcher) Type() Type {
+ return EqualNilMatcherType | EqualMatcherType | EqualValueMatcherType
+}
+
+func (EqualNilMatcher) MatcherName() string {
+ return equal
+}
+
+func (n EqualNilMatcher) GetType() gotypes.Type {
+ return n.gotype
+}
+
+type EqualTrueMatcher struct{}
+
+func (EqualTrueMatcher) Type() Type {
+ return EqualMatcherType | EqualBoolValueMatcherType | BoolValueTrue
+}
+
+func (EqualTrueMatcher) MatcherName() string {
+ return equal
+}
+
+type EqualFalseMatcher struct{}
+
+func (EqualFalseMatcher) Type() Type {
+ return EqualMatcherType | EqualBoolValueMatcherType | BoolValueFalse
+}
+
+func (EqualFalseMatcher) MatcherName() string {
+ return equal
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/errormatchers.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/errormatchers.go
new file mode 100644
index 000000000..a493287e0
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/errormatchers.go
@@ -0,0 +1,199 @@
+package matcher
+
+import (
+ "go/ast"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+ "github.com/nunnatsa/ginkgolinter/internal/interfaces"
+)
+
+type HaveOccurredMatcher struct{}
+
+func (m *HaveOccurredMatcher) Type() Type {
+ return HaveOccurredMatcherType
+}
+func (m *HaveOccurredMatcher) MatcherName() string {
+ return haveOccurred
+}
+
+type SucceedMatcher struct{}
+
+func (m *SucceedMatcher) Type() Type {
+ return SucceedMatcherType
+}
+func (m *SucceedMatcher) MatcherName() string {
+ return succeed
+}
+
+type MatchErrorMatcher interface {
+ Info
+ AllowedNumArgs() int
+ NumArgs() int
+}
+
+type InvalidMatchErrorMatcher struct {
+ firstAgr ast.Expr
+ numArgs int
+}
+
+func (m *InvalidMatchErrorMatcher) Type() Type {
+ return MatchErrorMatcherType
+}
+
+func (m *InvalidMatchErrorMatcher) MatcherName() string {
+ return matchError
+}
+
+func (m *InvalidMatchErrorMatcher) AllowedNumArgs() int {
+ return 1
+}
+
+func (m *InvalidMatchErrorMatcher) NumArgs() int {
+ return m.numArgs
+}
+
+func (m *InvalidMatchErrorMatcher) GetValueExpr() ast.Expr {
+ return m.firstAgr
+}
+
+type MatchErrorMatcherWithErr struct {
+ numArgs int
+}
+
+func (m *MatchErrorMatcherWithErr) Type() Type {
+ return MatchErrorMatcherType | ErrMatchWithErr
+}
+
+func (m *MatchErrorMatcherWithErr) MatcherName() string {
+ return matchError
+}
+
+func (m *MatchErrorMatcherWithErr) AllowedNumArgs() int {
+ return 1
+}
+
+func (m *MatchErrorMatcherWithErr) NumArgs() int {
+ return m.numArgs
+}
+
+type MatchErrorMatcherWithErrFunc struct {
+ numArgs int
+ secondArgIsString bool
+}
+
+func (m *MatchErrorMatcherWithErrFunc) Type() Type {
+ return MatchErrorMatcherType | ErrMatchWithErrFunc
+}
+
+func (m *MatchErrorMatcherWithErrFunc) MatcherName() string {
+ return matchError
+}
+
+func (m *MatchErrorMatcherWithErrFunc) AllowedNumArgs() int {
+ return 2
+}
+
+func (m *MatchErrorMatcherWithErrFunc) NumArgs() int {
+ return m.numArgs
+}
+
+func (m *MatchErrorMatcherWithErrFunc) IsSecondArgString() bool {
+ return m.secondArgIsString
+}
+
+type MatchErrorMatcherWithString struct {
+ numArgs int
+}
+
+func (m *MatchErrorMatcherWithString) Type() Type {
+ return MatchErrorMatcherType | ErrMatchWithString
+}
+
+func (m *MatchErrorMatcherWithString) MatcherName() string {
+ return matchError
+}
+
+func (m *MatchErrorMatcherWithString) AllowedNumArgs() int {
+ return 1
+}
+
+func (m *MatchErrorMatcherWithString) NumArgs() int {
+ return m.numArgs
+}
+
+type MatchErrorMatcherWithMatcher struct {
+ numArgs int
+}
+
+func (m *MatchErrorMatcherWithMatcher) Type() Type {
+ return MatchErrorMatcherType | ErrMatchWithMatcher
+}
+
+func (m *MatchErrorMatcherWithMatcher) MatcherName() string {
+ return matchError
+}
+
+func (m *MatchErrorMatcherWithMatcher) AllowedNumArgs() int {
+ return 1
+}
+
+func (m *MatchErrorMatcherWithMatcher) NumArgs() int {
+ return m.numArgs
+}
+
+func newMatchErrorMatcher(args []ast.Expr, pass *analysis.Pass) MatchErrorMatcher {
+ numArgs := len(args)
+ if value.IsExprError(pass, args[0]) {
+ return &MatchErrorMatcherWithErr{numArgs: numArgs}
+ }
+
+ t := pass.TypesInfo.TypeOf(args[0])
+ if isString(args[0], pass) {
+ return &MatchErrorMatcherWithString{numArgs: numArgs}
+ }
+
+ if interfaces.ImplementsGomegaMatcher(t) {
+ return &MatchErrorMatcherWithMatcher{numArgs: numArgs}
+ }
+
+ if isFuncErrBool(t) {
+ isString := false
+ if numArgs > 1 {
+ t2 := pass.TypesInfo.TypeOf(args[1])
+ isString = gotypes.Identical(t2, gotypes.Typ[gotypes.String])
+ }
+ return &MatchErrorMatcherWithErrFunc{numArgs: numArgs, secondArgIsString: isString}
+ }
+
+ return &InvalidMatchErrorMatcher{numArgs: numArgs}
+}
+
+func isString(exp ast.Expr, pass *analysis.Pass) bool {
+ t := pass.TypesInfo.TypeOf(exp)
+ return gotypes.Identical(t, gotypes.Typ[gotypes.String])
+}
+
+// isFuncErrBool checks if a function is with the signature `func(error) bool`
+func isFuncErrBool(t gotypes.Type) bool {
+ sig, ok := t.(*gotypes.Signature)
+ if !ok {
+ return false
+ }
+ if sig.Params().Len() != 1 || sig.Results().Len() != 1 {
+ return false
+ }
+
+ if !interfaces.ImplementsError(sig.Params().At(0).Type()) {
+ return false
+ }
+
+ b, ok := sig.Results().At(0).Type().(*gotypes.Basic)
+ if ok && b.Name() == "bool" && b.Info() == gotypes.IsBoolean && b.Kind() == gotypes.Bool {
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/lenmatchers.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/lenmatchers.go
new file mode 100644
index 000000000..8e4f438e8
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/lenmatchers.go
@@ -0,0 +1,11 @@
+package matcher
+
+type HaveLenZeroMatcher struct{}
+
+func (HaveLenZeroMatcher) Type() Type {
+ return HaveLenZeroMatcherType
+}
+
+func (HaveLenZeroMatcher) MatcherName() string {
+ return haveLen
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcher.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcher.go
new file mode 100644
index 000000000..0969b9551
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcher.go
@@ -0,0 +1,86 @@
+package matcher
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
+)
+
+const ( // gomega matchers
+ beEmpty = "BeEmpty"
+ beEquivalentTo = "BeEquivalentTo"
+ beFalse = "BeFalse"
+ beIdenticalTo = "BeIdenticalTo"
+ beNil = "BeNil"
+ beNumerically = "BeNumerically"
+ beTrue = "BeTrue"
+ beZero = "BeZero"
+ equal = "Equal"
+ haveLen = "HaveLen"
+ haveValue = "HaveValue"
+ and = "And"
+ or = "Or"
+ withTransform = "WithTransform"
+ matchError = "MatchError"
+ haveOccurred = "HaveOccurred"
+ succeed = "Succeed"
+)
+
+type Matcher struct {
+ funcName string
+ Orig *ast.CallExpr
+ Clone *ast.CallExpr
+ info Info
+ reverseLogic bool
+ handler gomegahandler.Handler
+}
+
+func New(origMatcher, matcherClone *ast.CallExpr, pass *analysis.Pass, handler gomegahandler.Handler) (*Matcher, bool) {
+ reverse := false
+ var assertFuncName string
+ for {
+ ok := false
+ assertFuncName, ok = handler.GetActualFuncName(origMatcher)
+ if !ok {
+ return nil, false
+ }
+
+ if assertFuncName != "Not" {
+ break
+ }
+
+ reverse = !reverse
+ origMatcher, ok = origMatcher.Args[0].(*ast.CallExpr)
+ if !ok {
+ return nil, false
+ }
+ matcherClone = matcherClone.Args[0].(*ast.CallExpr)
+ }
+
+ return &Matcher{
+ funcName: assertFuncName,
+ Orig: origMatcher,
+ Clone: matcherClone,
+ info: getMatcherInfo(origMatcher, matcherClone, assertFuncName, pass, handler),
+ reverseLogic: reverse,
+ handler: handler,
+ }, true
+}
+
+func (m *Matcher) ShouldReverseLogic() bool {
+ return m.reverseLogic
+}
+
+func (m *Matcher) GetMatcherInfo() Info {
+ return m.info
+}
+
+func (m *Matcher) ReplaceMatcherFuncName(name string) {
+ m.handler.ReplaceFunction(m.Clone, ast.NewIdent(name))
+}
+
+func (m *Matcher) ReplaceMatcherArgs(newArgs []ast.Expr) {
+ m.Clone.Args = newArgs
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherinfo.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherinfo.go
new file mode 100644
index 000000000..084226bcc
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherinfo.go
@@ -0,0 +1,148 @@
+package matcher
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+ "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
+)
+
+type Type uint64
+
+const (
+ Unspecified Type = 1 << iota
+ EqualMatcherType
+ BeZeroMatcherType
+ BeEmptyMatcherType
+ BeTrueMatcherType
+ BeFalseMatcherType
+ BeNumericallyMatcherType
+ HaveLenZeroMatcherType
+ BeEquivalentToMatcherType
+ BeIdenticalToMatcherType
+ BeNilMatcherType
+ MatchErrorMatcherType
+ MultipleMatcherMatherType
+ HaveValueMatherType
+ WithTransformMatherType
+ EqualBoolValueMatcherType
+ EqualValueMatcherType
+ HaveOccurredMatcherType
+ SucceedMatcherType
+ EqualNilMatcherType
+
+ BoolValueFalse
+ BoolValueTrue
+
+ OrMatherType
+ AndMatherType
+
+ ErrMatchWithErr
+ ErrMatchWithErrFunc
+ ErrMatchWithString
+ ErrMatchWithMatcher
+
+ EqualZero
+ GreaterThanZero
+)
+
+type Info interface {
+ Type() Type
+ MatcherName() string
+}
+
+func getMatcherInfo(orig, clone *ast.CallExpr, matcherName string, pass *analysis.Pass, handler gomegahandler.Handler) Info {
+ switch matcherName {
+ case equal:
+ return newEqualMatcher(orig.Args[0], clone.Args[0], pass)
+
+ case beZero:
+ return &BeZeroMatcher{}
+
+ case beEmpty:
+ return &BeEmptyMatcher{}
+
+ case beTrue:
+ return &BeTrueMatcher{}
+
+ case beFalse:
+ return &BeFalseMatcher{}
+
+ case beNil:
+ return &BeNilMatcher{}
+
+ case beNumerically:
+ if len(orig.Args) == 2 {
+ return newBeNumericallyMatcher(orig.Args[0], orig.Args[1], clone.Args[1], pass)
+ }
+
+ case haveLen:
+ if value.GetValuer(orig.Args[0], clone.Args[0], pass).IsValueZero() {
+ return &HaveLenZeroMatcher{}
+ }
+
+ case beEquivalentTo:
+ return &BeEquivalentToMatcher{
+ Value: value.New(orig.Args[0], clone.Args[0], pass),
+ }
+
+ case beIdenticalTo:
+ return &BeIdenticalToMatcher{
+ Value: value.New(orig.Args[0], clone.Args[0], pass),
+ }
+
+ case matchError:
+ return newMatchErrorMatcher(orig.Args, pass)
+
+ case haveValue:
+ if nestedMatcher, ok := getNestedMatcher(orig, clone, 0, pass, handler); ok {
+ return &HaveValueMatcher{
+ nested: nestedMatcher,
+ }
+ }
+
+ case withTransform:
+ if nestedMatcher, ok := getNestedMatcher(orig, clone, 1, pass, handler); ok {
+ return newWithTransformMatcher(orig.Args[0], nestedMatcher, pass)
+ }
+
+ case or, and:
+ matcherType := MultipleMatcherMatherType
+ if matcherName == or {
+ matcherType |= OrMatherType
+ } else {
+ matcherType |= AndMatherType
+ }
+
+ if m, ok := newMultipleMatchersMatcher(matcherType, orig.Args, clone.Args, pass, handler); ok {
+ return m
+ }
+
+ case succeed:
+ return &SucceedMatcher{}
+
+ case haveOccurred:
+ return &HaveOccurredMatcher{}
+
+ }
+
+ return &UnspecifiedMatcher{matcherName: matcherName}
+}
+
+type UnspecifiedMatcher struct {
+ matcherName string
+}
+
+func (UnspecifiedMatcher) Type() Type {
+ return Unspecified
+}
+
+func (u UnspecifiedMatcher) MatcherName() string {
+ return u.matcherName
+}
+
+func (t Type) Is(other Type) bool {
+ return t&other != 0
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherwithnest.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherwithnest.go
new file mode 100644
index 000000000..cc26e5ac2
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/matcherwithnest.go
@@ -0,0 +1,66 @@
+package matcher
+
+import (
+ "go/ast"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
+)
+
+type HaveValueMatcher struct {
+ nested *Matcher
+}
+
+func (m *HaveValueMatcher) Type() Type {
+ return HaveValueMatherType
+}
+func (m *HaveValueMatcher) MatcherName() string {
+ return haveValue
+}
+
+func (m *HaveValueMatcher) GetNested() *Matcher {
+ return m.nested
+}
+
+type WithTransformMatcher struct {
+ funcType gotypes.Type
+ nested *Matcher
+}
+
+func (m *WithTransformMatcher) Type() Type {
+ return WithTransformMatherType
+}
+func (m *WithTransformMatcher) MatcherName() string {
+ return withTransform
+}
+
+func (m *WithTransformMatcher) GetNested() *Matcher {
+ return m.nested
+}
+
+func (m *WithTransformMatcher) GetFuncType() gotypes.Type {
+ return m.funcType
+}
+
+func getNestedMatcher(orig, clone *ast.CallExpr, offset int, pass *analysis.Pass, handler gomegahandler.Handler) (*Matcher, bool) {
+ if origNested, ok := orig.Args[offset].(*ast.CallExpr); ok {
+ cloneNested := clone.Args[offset].(*ast.CallExpr)
+
+ return New(origNested, cloneNested, pass, handler)
+ }
+
+ return nil, false
+}
+
+func newWithTransformMatcher(fun ast.Expr, nested *Matcher, pass *analysis.Pass) *WithTransformMatcher {
+ funcType := pass.TypesInfo.TypeOf(fun)
+ if sig, ok := funcType.(*gotypes.Signature); ok && sig.Results().Len() > 0 {
+ funcType = sig.Results().At(0).Type()
+ }
+ return &WithTransformMatcher{
+ funcType: funcType,
+ nested: nested,
+ }
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/multiplematchers.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/multiplematchers.go
new file mode 100644
index 000000000..9ce0cf5b8
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/matcher/multiplematchers.go
@@ -0,0 +1,62 @@
+package matcher
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
+)
+
+type MultipleMatchersMatcher struct {
+ matherType Type
+ matchers []*Matcher
+}
+
+func (m *MultipleMatchersMatcher) Type() Type {
+ return m.matherType
+}
+
+func (m *MultipleMatchersMatcher) MatcherName() string {
+ if m.matherType.Is(OrMatherType) {
+ return or
+ }
+ return and
+}
+
+func newMultipleMatchersMatcher(matherType Type, orig, clone []ast.Expr, pass *analysis.Pass, handler gomegahandler.Handler) (*MultipleMatchersMatcher, bool) {
+ matchers := make([]*Matcher, len(orig))
+
+ for i := range orig {
+ nestedOrig, ok := orig[i].(*ast.CallExpr)
+ if !ok {
+ return nil, false
+ }
+
+ m, ok := New(nestedOrig, clone[i].(*ast.CallExpr), pass, handler)
+ if !ok {
+ return nil, false
+ }
+
+ m.reverseLogic = false
+
+ matchers[i] = m
+ }
+
+ return &MultipleMatchersMatcher{
+ matherType: matherType,
+ matchers: matchers,
+ }, true
+}
+
+func (m *MultipleMatchersMatcher) Len() int {
+ return len(m.matchers)
+}
+
+func (m *MultipleMatchersMatcher) At(i int) *Matcher {
+ if i >= len(m.matchers) {
+ panic("index out of range")
+ }
+
+ return m.matchers[i]
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/value/value.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/value/value.go
new file mode 100644
index 000000000..dda0dd73b
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/expression/value/value.go
@@ -0,0 +1,221 @@
+package value
+
+import (
+ "go/ast"
+ "go/constant"
+ gotypes "go/types"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/interfaces"
+)
+
+type Valuer interface {
+ GetValue() constant.Value
+ GetType() gotypes.Type
+ GetValueExpr() ast.Expr
+ IsValueZero() bool
+ IsValueInt() bool
+ IsValueNumeric() bool
+ IsError() bool
+ IsFunc() bool
+ IsInterface() bool
+ IsPointer() bool
+}
+
+func GetValuer(orig, clone ast.Expr, pass *analysis.Pass) Valuer {
+ val := New(orig, clone, pass)
+ unspecified := UnspecifiedValue{
+ Value: val,
+ }
+
+ if orig == nil {
+ return unspecified
+ }
+
+ if IsExprError(pass, orig) {
+ return &ErrValue{
+ Value: val,
+ err: clone,
+ }
+ }
+
+ if val.GetValue() == nil || !val.tv.IsValue() {
+ return unspecified
+ }
+
+ if val.GetValue().Kind() == constant.Int {
+ num, ok := constant.Int64Val(val.GetValue())
+ if !ok {
+ return unspecified
+ }
+ return &IntValue{
+ Value: val,
+ val: num,
+ }
+ }
+
+ return unspecified
+}
+
+type Value struct {
+ expr ast.Expr
+ tv gotypes.TypeAndValue
+}
+
+func New(orig, clone ast.Expr, pass *analysis.Pass) Value {
+ tv := pass.TypesInfo.Types[orig]
+
+ return Value{
+ expr: clone,
+ tv: tv,
+ }
+}
+
+func (v Value) GetValueExpr() ast.Expr {
+ return v.expr
+}
+
+func (v Value) GetValue() constant.Value {
+ return v.tv.Value
+}
+
+func (v Value) GetType() gotypes.Type {
+ return v.tv.Type
+}
+
+func (v Value) IsInterface() bool {
+ return gotypes.IsInterface(v.tv.Type)
+}
+
+func (v Value) IsPointer() bool {
+ return Is[*gotypes.Pointer](v.tv.Type)
+}
+
+func (v Value) IsNil() bool {
+ return v.tv.IsNil()
+}
+
+type UnspecifiedValue struct {
+ Value
+}
+
+func (u UnspecifiedValue) IsValueZero() bool {
+ return false
+}
+
+func (u UnspecifiedValue) IsValueInt() bool {
+ return false
+}
+
+func (u UnspecifiedValue) IsValueNumeric() bool {
+ return false
+}
+
+func (u UnspecifiedValue) IsError() bool {
+ return false
+}
+
+func (u UnspecifiedValue) IsFunc() bool {
+ return isFunc(u.GetValueExpr())
+}
+
+type ErrValue struct {
+ Value
+ err ast.Expr
+}
+
+func (e ErrValue) IsValueZero() bool {
+ return false
+}
+
+func (e ErrValue) IsValueInt() bool {
+ return false
+}
+
+func (e ErrValue) IsValueNumeric() bool {
+ return false
+}
+
+func (e ErrValue) IsError() bool {
+ return true
+}
+
+func (e ErrValue) IsFunc() bool {
+ return isFunc(e.GetValueExpr())
+}
+
+type IntValuer interface {
+ GetIntValue() int64
+}
+
+type IntValue struct {
+ Value
+ val int64
+}
+
+func (i IntValue) IsValueZero() bool {
+ return i.val == 0
+}
+
+func (i IntValue) IsValueInt() bool {
+ return i.val == 0
+}
+
+func (i IntValue) IsValueNumeric() bool {
+ return true
+}
+
+func (i IntValue) IsError() bool {
+ return false
+}
+
+func (i IntValue) IsFunc() bool {
+ return false
+}
+
+func (i IntValue) GetIntValue() int64 {
+ return i.val
+}
+
+func isFunc(exp ast.Expr) bool {
+ return Is[*ast.CallExpr](exp)
+}
+
+func Is[T any](x any) bool {
+ _, matchType := x.(T)
+ return matchType
+}
+
+func IsExprError(pass *analysis.Pass, expr ast.Expr) bool {
+ actualArgType := pass.TypesInfo.TypeOf(expr)
+ switch t := actualArgType.(type) {
+ case *gotypes.Named:
+ return interfaces.ImplementsError(actualArgType)
+
+ case *gotypes.Pointer:
+ if tt, ok := t.Elem().(*gotypes.Named); ok {
+ return interfaces.ImplementsError(tt)
+ }
+
+ case *gotypes.Tuple:
+ if t.Len() > 0 {
+ switch t0 := t.At(0).Type().(type) {
+ case *gotypes.Named, *gotypes.Pointer:
+ if interfaces.ImplementsError(t0) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+func IsNil(exp ast.Expr, pass *analysis.Pass) bool {
+ id, ok := exp.(*ast.Ident)
+ if !ok {
+ return false
+ }
+
+ return pass.TypesInfo.Types[id].IsNil()
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/formatter/formatter.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/formatter/formatter.go
new file mode 100644
index 000000000..64f3d99ad
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/formatter/formatter.go
@@ -0,0 +1,22 @@
+package formatter
+
+import (
+ "bytes"
+ "go/ast"
+ "go/printer"
+ "go/token"
+)
+
+type GoFmtFormatter struct {
+ fset *token.FileSet
+}
+
+func NewGoFmtFormatter(fset *token.FileSet) *GoFmtFormatter {
+ return &GoFmtFormatter{fset: fset}
+}
+
+func (f GoFmtFormatter) Format(exp ast.Expr) string {
+ var buf bytes.Buffer
+ _ = printer.Fprint(&buf, f.fset, exp)
+ return buf.String()
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/dothandler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/dothandler.go
new file mode 100644
index 000000000..9c54b4334
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/dothandler.go
@@ -0,0 +1,36 @@
+package ginkgohandler
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+// dotHandler is used when importing ginkgo with dot; i.e.
+// import . "github.com/onsi/ginkgo"
+type dotHandler struct{}
+
+func (h dotHandler) HandleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass) bool {
+ return handleGinkgoSpecs(expr, config, pass, h)
+}
+
+func (h dotHandler) getFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) {
+ if fun, ok := exp.Fun.(*ast.Ident); ok {
+ return isFocusContainer(fun.Name), fun
+ }
+ return false, nil
+}
+
+func (h dotHandler) isWrapContainer(exp *ast.CallExpr) bool {
+ if fun, ok := exp.Fun.(*ast.Ident); ok {
+ return isWrapContainer(fun.Name)
+ }
+ return false
+}
+
+func (h dotHandler) isFocusSpec(exp ast.Expr) bool {
+ id, ok := exp.(*ast.Ident)
+ return ok && id.Name == focusSpec
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/ginkgoinfo.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/ginkgoinfo.go
new file mode 100644
index 000000000..d8bb75399
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/ginkgoinfo.go
@@ -0,0 +1,63 @@
+package ginkgohandler
+
+const ( // container names
+ describe = "Describe"
+ pdescribe = "PDescribe"
+ xdescribe = "XDescribe"
+ fdescribe = "FDescribe"
+
+ when = "When"
+ pwhen = "PWhen"
+ xwhen = "XWhen"
+ fwhen = "FWhen"
+
+ contextContainer = "Context"
+ pcontext = "PContext"
+ xcontext = "XContext"
+ fcontext = "FContext"
+
+ it = "It"
+ pit = "PIt"
+ xit = "XIt"
+ fit = "FIt"
+
+ describeTable = "DescribeTable"
+ pdescribeTable = "PDescribeTable"
+ xdescribeTable = "XDescribeTable"
+ fdescribeTable = "FDescribeTable"
+
+ entry = "Entry"
+ pentry = "PEntry"
+ xentry = "XEntry"
+ fentry = "FEntry"
+)
+
+func isFocusContainer(name string) bool {
+ switch name {
+ case fdescribe, fcontext, fwhen, fit, fdescribeTable, fentry:
+ return true
+ }
+ return false
+}
+
+func isContainer(name string) bool {
+ switch name {
+ case it, when, contextContainer, describe, describeTable, entry,
+ pit, pwhen, pcontext, pdescribe, pdescribeTable, pentry,
+ xit, xwhen, xcontext, xdescribe, xdescribeTable, xentry:
+ return true
+ }
+ return isFocusContainer(name)
+}
+
+func isWrapContainer(name string) bool {
+ switch name {
+ case when, contextContainer, describe,
+ fwhen, fcontext, fdescribe,
+ pwhen, pcontext, pdescribe,
+ xwhen, xcontext, xdescribe:
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go
index f10d83184..c44e3e8d8 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go
@@ -2,6 +2,10 @@ package ginkgohandler
import (
"go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/types"
)
const (
@@ -14,116 +18,31 @@ const (
// Handler provide different handling, depend on the way ginkgo was imported, whether
// in imported with "." name, custom name or without any name.
type Handler interface {
- GetFocusContainerName(*ast.CallExpr) (bool, *ast.Ident)
- IsWrapContainer(*ast.CallExpr) bool
- IsFocusSpec(ident ast.Expr) bool
+ HandleGinkgoSpecs(ast.Expr, types.Config, *analysis.Pass) bool
+ getFocusContainerName(*ast.CallExpr) (bool, *ast.Ident)
+ isWrapContainer(*ast.CallExpr) bool
+ isFocusSpec(ident ast.Expr) bool
}
// GetGinkgoHandler returns a ginkgor handler according to the way ginkgo was imported in the specific file
func GetGinkgoHandler(file *ast.File) Handler {
for _, imp := range file.Imports {
- if imp.Path.Value != importPath && imp.Path.Value != importPathV2 {
- continue
- }
+ switch imp.Path.Value {
+
+ case importPath, importPathV2:
+ switch name := imp.Name.String(); {
+ case name == ".":
+ return dotHandler{}
+ case name == "<nil>": // import with no local name
+ return nameHandler("ginkgo")
+ default:
+ return nameHandler(name)
+ }
- switch name := imp.Name.String(); {
- case name == ".":
- return dotHandler{}
- case name == "<nil>": // import with no local name
- return nameHandler("ginkgo")
default:
- return nameHandler(name)
- }
- }
-
- return nil // no ginkgo import; this file does not use ginkgo
-}
-
-// dotHandler is used when importing ginkgo with dot; i.e.
-// import . "github.com/onsi/ginkgo"
-type dotHandler struct{}
-
-func (h dotHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) {
- if fun, ok := exp.Fun.(*ast.Ident); ok {
- return isFocusContainer(fun.Name), fun
- }
- return false, nil
-}
-
-func (h dotHandler) IsWrapContainer(exp *ast.CallExpr) bool {
- if fun, ok := exp.Fun.(*ast.Ident); ok {
- return IsWrapContainer(fun.Name)
- }
- return false
-}
-
-func (h dotHandler) IsFocusSpec(exp ast.Expr) bool {
- id, ok := exp.(*ast.Ident)
- return ok && id.Name == focusSpec
-}
-
-// nameHandler is used when importing ginkgo without name; i.e.
-// import "github.com/onsi/ginkgo"
-//
-// or with a custom name; e.g.
-// import customname "github.com/onsi/ginkgo"
-type nameHandler string
-
-func (h nameHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) {
- if sel, ok := exp.Fun.(*ast.SelectorExpr); ok {
- if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) {
- return isFocusContainer(sel.Sel.Name), sel.Sel
- }
- }
- return false, nil
-}
-
-func (h nameHandler) IsWrapContainer(exp *ast.CallExpr) bool {
- if sel, ok := exp.Fun.(*ast.SelectorExpr); ok {
- if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) {
- return IsWrapContainer(sel.Sel.Name)
- }
- }
- return false
-
-}
-
-func (h nameHandler) IsFocusSpec(exp ast.Expr) bool {
- if selExp, ok := exp.(*ast.SelectorExpr); ok {
- if x, ok := selExp.X.(*ast.Ident); ok && x.Name == string(h) {
- return selExp.Sel.Name == focusSpec
+ continue
}
}
- return false
-}
-
-func isFocusContainer(name string) bool {
- switch name {
- case "FDescribe", "FContext", "FWhen", "FIt", "FDescribeTable", "FEntry":
- return true
- }
- return false
-}
-
-func IsContainer(name string) bool {
- switch name {
- case "It", "When", "Context", "Describe", "DescribeTable", "Entry",
- "PIt", "PWhen", "PContext", "PDescribe", "PDescribeTable", "PEntry",
- "XIt", "XWhen", "XContext", "XDescribe", "XDescribeTable", "XEntry":
- return true
- }
- return isFocusContainer(name)
-}
-
-func IsWrapContainer(name string) bool {
- switch name {
- case "When", "Context", "Describe",
- "FWhen", "FContext", "FDescribe",
- "PWhen", "PContext", "PDescribe",
- "XWhen", "XContext", "XDescribe":
- return true
- }
-
- return false
+ return nil
}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handling.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handling.go
new file mode 100644
index 000000000..4b6de5767
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handling.go
@@ -0,0 +1,195 @@
+package ginkgohandler
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const (
+ linterName = "ginkgo-linter"
+ focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code. Consider to replace with %q"
+ focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code. Consider to remove it"
+ useBeforeEachTemplate = "use BeforeEach() to assign variable %s"
+)
+
+func handleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass, ginkgoHndlr Handler) bool {
+ goDeeper := false
+ if exp, ok := expr.(*ast.CallExpr); ok {
+ if bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, exp) {
+ goDeeper = true
+ }
+
+ if bool(config.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, exp) {
+ goDeeper = true
+ }
+ }
+ return goDeeper
+}
+
+func checkAssignmentsInContainer(pass *analysis.Pass, ginkgoHndlr Handler, exp *ast.CallExpr) bool {
+ foundSomething := false
+ if ginkgoHndlr.isWrapContainer(exp) {
+ for _, arg := range exp.Args {
+ if fn, ok := arg.(*ast.FuncLit); ok {
+ if fn.Body != nil {
+ if checkAssignments(pass, fn.Body.List) {
+ foundSomething = true
+ }
+ break
+ }
+ }
+ }
+ }
+
+ return foundSomething
+}
+
+func checkAssignments(pass *analysis.Pass, list []ast.Stmt) bool {
+ foundSomething := false
+ for _, stmt := range list {
+ switch st := stmt.(type) {
+ case *ast.DeclStmt:
+ if checkAssignmentDecl(pass, st) {
+ foundSomething = true
+ }
+
+ case *ast.AssignStmt:
+ if checkAssignmentAssign(pass, st) {
+ foundSomething = true
+ }
+
+ case *ast.IfStmt:
+ if checkAssignmentIf(pass, st) {
+ foundSomething = true
+ }
+ }
+ }
+
+ return foundSomething
+}
+
+func checkAssignmentsValues(pass *analysis.Pass, names []*ast.Ident, values []ast.Expr) bool {
+ foundSomething := false
+ for i, val := range values {
+ if !is[*ast.FuncLit](val) {
+ reportNoFix(pass, names[i].Pos(), useBeforeEachTemplate, names[i].Name)
+ foundSomething = true
+ }
+ }
+
+ return foundSomething
+}
+
+func checkAssignmentDecl(pass *analysis.Pass, ds *ast.DeclStmt) bool {
+ foundSomething := false
+ if gen, ok := ds.Decl.(*ast.GenDecl); ok {
+ if gen.Tok != token.VAR {
+ return false
+ }
+ for _, spec := range gen.Specs {
+ if valSpec, ok := spec.(*ast.ValueSpec); ok {
+ if checkAssignmentsValues(pass, valSpec.Names, valSpec.Values) {
+ foundSomething = true
+ }
+ }
+ }
+ }
+
+ return foundSomething
+}
+
+func checkAssignmentAssign(pass *analysis.Pass, as *ast.AssignStmt) bool {
+ foundSomething := false
+ for i, val := range as.Rhs {
+ if !is[*ast.FuncLit](val) {
+ if id, isIdent := as.Lhs[i].(*ast.Ident); isIdent && id.Name != "_" {
+ reportNoFix(pass, id.Pos(), useBeforeEachTemplate, id.Name)
+ foundSomething = true
+ }
+ }
+ }
+ return foundSomething
+}
+
+func checkAssignmentIf(pass *analysis.Pass, is *ast.IfStmt) bool {
+ foundSomething := false
+
+ if is.Body != nil {
+ if checkAssignments(pass, is.Body.List) {
+ foundSomething = true
+ }
+ }
+ if is.Else != nil {
+ if block, isBlock := is.Else.(*ast.BlockStmt); isBlock {
+ if checkAssignments(pass, block.List) {
+ foundSomething = true
+ }
+ }
+ }
+
+ return foundSomething
+}
+
+func checkFocusContainer(pass *analysis.Pass, handler Handler, exp *ast.CallExpr) bool {
+ foundFocus := false
+ isFocus, id := handler.getFocusContainerName(exp)
+ if isFocus {
+ reportNewName(pass, id, id.Name[1:], id.Name)
+ foundFocus = true
+ }
+
+ if id != nil && isContainer(id.Name) {
+ for _, arg := range exp.Args {
+ if handler.isFocusSpec(arg) {
+ reportNoFix(pass, arg.Pos(), focusSpecFound)
+ foundFocus = true
+ } else if callExp, ok := arg.(*ast.CallExpr); ok {
+ if checkFocusContainer(pass, handler, callExp) { // handle table entries
+ foundFocus = true
+ }
+ }
+ }
+ }
+
+ return foundFocus
+}
+
+func reportNewName(pass *analysis.Pass, id *ast.Ident, newName string, oldExpr string) {
+ pass.Report(analysis.Diagnostic{
+ Pos: id.Pos(),
+ Message: fmt.Sprintf(focusContainerFound, newName),
+ SuggestedFixes: []analysis.SuggestedFix{
+ {
+ Message: fmt.Sprintf("should replace %s with %s", oldExpr, newName),
+ TextEdits: []analysis.TextEdit{
+ {
+ Pos: id.Pos(),
+ End: id.End(),
+ NewText: []byte(newName),
+ },
+ },
+ },
+ },
+ })
+}
+
+func reportNoFix(pass *analysis.Pass, pos token.Pos, message string, args ...any) {
+ if len(args) > 0 {
+ message = fmt.Sprintf(message, args...)
+ }
+
+ pass.Report(analysis.Diagnostic{
+ Pos: pos,
+ Message: message,
+ })
+}
+
+func is[T any](x any) bool {
+ _, matchType := x.(T)
+ return matchType
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/namehandler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/namehandler.go
new file mode 100644
index 000000000..2ef9fe703
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/namehandler.go
@@ -0,0 +1,49 @@
+package ginkgohandler
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+// nameHandler is used when importing ginkgo without name; i.e.
+// import "github.com/onsi/ginkgo"
+//
+// or with a custom name; e.g.
+// import customname "github.com/onsi/ginkgo"
+type nameHandler string
+
+func (h nameHandler) HandleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass) bool {
+ return handleGinkgoSpecs(expr, config, pass, h)
+}
+
+func (h nameHandler) getFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) {
+ if sel, ok := exp.Fun.(*ast.SelectorExpr); ok {
+ if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) {
+ return isFocusContainer(sel.Sel.Name), sel.Sel
+ }
+ }
+ return false, nil
+}
+
+func (h nameHandler) isWrapContainer(exp *ast.CallExpr) bool {
+ if sel, ok := exp.Fun.(*ast.SelectorExpr); ok {
+ if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) {
+ return isWrapContainer(sel.Sel.Name)
+ }
+ }
+ return false
+
+}
+
+func (h nameHandler) isFocusSpec(exp ast.Expr) bool {
+ if selExp, ok := exp.(*ast.SelectorExpr); ok {
+ if x, ok := selExp.X.(*ast.Ident); ok && x.Name == string(h) {
+ return selExp.Sel.Name == focusSpec
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/dothandler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/dothandler.go
new file mode 100644
index 000000000..bd3b93992
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/dothandler.go
@@ -0,0 +1,99 @@
+package gomegahandler
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/analysis"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+)
+
+// dotHandler is used when importing gomega with dot; i.e.
+// import . "github.com/onsi/gomega"
+type dotHandler struct {
+ pass *analysis.Pass
+}
+
+// GetActualFuncName returns the name of the gomega function, e.g. `Expect`
+func (h dotHandler) GetActualFuncName(expr *ast.CallExpr) (string, bool) {
+ switch actualFunc := expr.Fun.(type) {
+ case *ast.Ident:
+ return actualFunc.Name, true
+ case *ast.SelectorExpr:
+ if h.isGomegaVar(actualFunc.X) {
+ return actualFunc.Sel.Name, true
+ }
+
+ if x, ok := actualFunc.X.(*ast.CallExpr); ok {
+ return h.GetActualFuncName(x)
+ }
+
+ case *ast.CallExpr:
+ return h.GetActualFuncName(actualFunc)
+ }
+ return "", false
+}
+
+// ReplaceFunction replaces the function with another one, for fix suggestions
+func (dotHandler) ReplaceFunction(caller *ast.CallExpr, newExpr *ast.Ident) {
+ switch f := caller.Fun.(type) {
+ case *ast.Ident:
+ caller.Fun = newExpr
+ case *ast.SelectorExpr:
+ f.Sel = newExpr
+ }
+}
+
+func (dotHandler) GetNewWrapperMatcher(name string, existing *ast.CallExpr) *ast.CallExpr {
+ return &ast.CallExpr{
+ Fun: ast.NewIdent(name),
+ Args: []ast.Expr{existing},
+ }
+}
+
+func (h dotHandler) GetActualExpr(assertionFunc *ast.SelectorExpr) *ast.CallExpr {
+ actualExpr, ok := assertionFunc.X.(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+
+ switch fun := actualExpr.Fun.(type) {
+ case *ast.Ident:
+ return actualExpr
+ case *ast.SelectorExpr:
+ if gomegainfo.IsActualMethod(fun.Sel.Name) {
+ if h.isGomegaVar(fun.X) {
+ return actualExpr
+ }
+ } else {
+ return h.GetActualExpr(fun)
+ }
+ }
+ return nil
+}
+
+func (h dotHandler) GetActualExprClone(origFunc, funcClone *ast.SelectorExpr) *ast.CallExpr {
+ actualExpr, ok := funcClone.X.(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+
+ switch funClone := actualExpr.Fun.(type) {
+ case *ast.Ident:
+ return actualExpr
+ case *ast.SelectorExpr:
+ origFun := origFunc.X.(*ast.CallExpr).Fun.(*ast.SelectorExpr)
+ if gomegainfo.IsActualMethod(funClone.Sel.Name) {
+ if h.isGomegaVar(origFun.X) {
+ return actualExpr
+ }
+ } else {
+ return h.GetActualExprClone(origFun, funClone)
+ }
+ }
+ return nil
+}
+
+func (h dotHandler) isGomegaVar(x ast.Expr) bool {
+ return gomegainfo.IsGomegaVar(x, h.pass)
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go
index 4290e7373..4dba604a4 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go
@@ -2,7 +2,8 @@ package gomegahandler
import (
"go/ast"
- "go/token"
+
+ "golang.org/x/tools/go/analysis"
)
const (
@@ -17,15 +18,15 @@ type Handler interface {
// ReplaceFunction replaces the function with another one, for fix suggestions
ReplaceFunction(*ast.CallExpr, *ast.Ident)
- getDefFuncName(expr *ast.CallExpr) string
+ GetActualExpr(assertionFunc *ast.SelectorExpr) *ast.CallExpr
- getFieldType(field *ast.Field) string
+ GetActualExprClone(origFunc, funcClone *ast.SelectorExpr) *ast.CallExpr
- GetActualExpr(assertionFunc *ast.SelectorExpr) *ast.CallExpr
+ GetNewWrapperMatcher(name string, existing *ast.CallExpr) *ast.CallExpr
}
// GetGomegaHandler returns a gomegar handler according to the way gomega was imported in the specific file
-func GetGomegaHandler(file *ast.File) Handler {
+func GetGomegaHandler(file *ast.File, pass *analysis.Pass) Handler {
for _, imp := range file.Imports {
if imp.Path.Value != importPath {
continue
@@ -33,209 +34,15 @@ func GetGomegaHandler(file *ast.File) Handler {
switch name := imp.Name.String(); {
case name == ".":
- return dotHandler{}
+ return &dotHandler{
+ pass: pass,
+ }
case name == "<nil>": // import with no local name
- return nameHandler("gomega")
+ return &nameHandler{name: "gomega", pass: pass}
default:
- return nameHandler(name)
+ return &nameHandler{name: name, pass: pass}
}
}
return nil // no gomega import; this file does not use gomega
}
-
-// dotHandler is used when importing gomega with dot; i.e.
-// import . "github.com/onsi/gomega"
-type dotHandler struct{}
-
-// GetActualFuncName returns the name of the gomega function, e.g. `Expect`
-func (h dotHandler) GetActualFuncName(expr *ast.CallExpr) (string, bool) {
- switch actualFunc := expr.Fun.(type) {
- case *ast.Ident:
- return actualFunc.Name, true
- case *ast.SelectorExpr:
- if isGomegaVar(actualFunc.X, h) {
- return actualFunc.Sel.Name, true
- }
-
- if x, ok := actualFunc.X.(*ast.CallExpr); ok {
- return h.GetActualFuncName(x)
- }
-
- case *ast.CallExpr:
- return h.GetActualFuncName(actualFunc)
- }
- return "", false
-}
-
-// ReplaceFunction replaces the function with another one, for fix suggestions
-func (dotHandler) ReplaceFunction(caller *ast.CallExpr, newExpr *ast.Ident) {
- switch f := caller.Fun.(type) {
- case *ast.Ident:
- caller.Fun = newExpr
- case *ast.SelectorExpr:
- f.Sel = newExpr
- }
-}
-
-func (dotHandler) getDefFuncName(expr *ast.CallExpr) string {
- if f, ok := expr.Fun.(*ast.Ident); ok {
- return f.Name
- }
- return ""
-}
-
-func (dotHandler) getFieldType(field *ast.Field) string {
- switch t := field.Type.(type) {
- case *ast.Ident:
- return t.Name
- case *ast.StarExpr:
- if name, ok := t.X.(*ast.Ident); ok {
- return name.Name
- }
- }
- return ""
-}
-
-// nameHandler is used when importing gomega without name; i.e.
-// import "github.com/onsi/gomega"
-//
-// or with a custom name; e.g.
-// import customname "github.com/onsi/gomega"
-type nameHandler string
-
-// GetActualFuncName returns the name of the gomega function, e.g. `Expect`
-func (g nameHandler) GetActualFuncName(expr *ast.CallExpr) (string, bool) {
- selector, ok := expr.Fun.(*ast.SelectorExpr)
- if !ok {
- return "", false
- }
-
- switch x := selector.X.(type) {
- case *ast.Ident:
- if x.Name != string(g) {
- if !isGomegaVar(x, g) {
- return "", false
- }
- }
-
- return selector.Sel.Name, true
-
- case *ast.CallExpr:
- return g.GetActualFuncName(x)
- }
-
- return "", false
-}
-
-// ReplaceFunction replaces the function with another one, for fix suggestions
-func (nameHandler) ReplaceFunction(caller *ast.CallExpr, newExpr *ast.Ident) {
- caller.Fun.(*ast.SelectorExpr).Sel = newExpr
-}
-
-func (g nameHandler) getDefFuncName(expr *ast.CallExpr) string {
- if sel, ok := expr.Fun.(*ast.SelectorExpr); ok {
- if f, ok := sel.X.(*ast.Ident); ok && f.Name == string(g) {
- return sel.Sel.Name
- }
- }
- return ""
-}
-
-func (g nameHandler) getFieldType(field *ast.Field) string {
- switch t := field.Type.(type) {
- case *ast.SelectorExpr:
- if id, ok := t.X.(*ast.Ident); ok {
- if id.Name == string(g) {
- return t.Sel.Name
- }
- }
- case *ast.StarExpr:
- if sel, ok := t.X.(*ast.SelectorExpr); ok {
- if x, ok := sel.X.(*ast.Ident); ok && x.Name == string(g) {
- return sel.Sel.Name
- }
- }
-
- }
- return ""
-}
-
-func isGomegaVar(x ast.Expr, handler Handler) bool {
- if i, ok := x.(*ast.Ident); ok {
- if i.Obj != nil && i.Obj.Kind == ast.Var {
- switch decl := i.Obj.Decl.(type) {
- case *ast.AssignStmt:
- if decl.Tok == token.DEFINE {
- if defFunc, ok := decl.Rhs[0].(*ast.CallExpr); ok {
- fName := handler.getDefFuncName(defFunc)
- switch fName {
- case "NewGomega", "NewWithT", "NewGomegaWithT":
- return true
- }
- }
- }
- case *ast.Field:
- name := handler.getFieldType(decl)
- switch name {
- case "Gomega", "WithT", "GomegaWithT":
- return true
- }
- }
- }
- }
- return false
-}
-
-func (h dotHandler) GetActualExpr(assertionFunc *ast.SelectorExpr) *ast.CallExpr {
- actualExpr, ok := assertionFunc.X.(*ast.CallExpr)
- if !ok {
- return nil
- }
-
- switch fun := actualExpr.Fun.(type) {
- case *ast.Ident:
- return actualExpr
- case *ast.SelectorExpr:
- if isHelperMethods(fun.Sel.Name) {
- return h.GetActualExpr(fun)
- }
- if isGomegaVar(fun.X, h) {
- return actualExpr
- }
- }
- return nil
-}
-
-func (g nameHandler) GetActualExpr(assertionFunc *ast.SelectorExpr) *ast.CallExpr {
- actualExpr, ok := assertionFunc.X.(*ast.CallExpr)
- if !ok {
- return nil
- }
-
- switch fun := actualExpr.Fun.(type) {
- case *ast.Ident:
- return actualExpr
- case *ast.SelectorExpr:
- if x, ok := fun.X.(*ast.Ident); ok && x.Name == string(g) {
- return actualExpr
- }
- if isHelperMethods(fun.Sel.Name) {
- return g.GetActualExpr(fun)
- }
-
- if isGomegaVar(fun.X, g) {
- return actualExpr
- }
- }
- return nil
-}
-
-func isHelperMethods(funcName string) bool {
- switch funcName {
- case "WithOffset", "WithTimeout", "WithPolling", "Within", "ProbeEvery", "WithContext", "WithArguments", "MustPassRepeatedly":
- return true
- }
-
- return false
-}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/namedhandler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/namedhandler.go
new file mode 100644
index 000000000..712442426
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/namedhandler.go
@@ -0,0 +1,112 @@
+package gomegahandler
+
+import (
+ "go/ast"
+
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// nameHandler is used when importing gomega without name; i.e.
+// import "github.com/onsi/gomega"
+//
+// or with a custom name; e.g.
+// import customname "github.com/onsi/gomega"
+type nameHandler struct {
+ name string
+ pass *analysis.Pass
+}
+
+// GetActualFuncName returns the name of the gomega function, e.g. `Expect`
+func (g nameHandler) GetActualFuncName(expr *ast.CallExpr) (string, bool) {
+ selector, ok := expr.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return "", false
+ }
+
+ switch x := selector.X.(type) {
+ case *ast.Ident:
+ if x.Name != g.name {
+ if !g.isGomegaVar(x) {
+ return "", false
+ }
+ }
+
+ return selector.Sel.Name, true
+
+ case *ast.CallExpr:
+ return g.GetActualFuncName(x)
+ }
+
+ return "", false
+}
+
+// ReplaceFunction replaces the function with another one, for fix suggestions
+func (nameHandler) ReplaceFunction(caller *ast.CallExpr, newExpr *ast.Ident) {
+ caller.Fun.(*ast.SelectorExpr).Sel = newExpr
+}
+
+func (g nameHandler) isGomegaVar(x ast.Expr) bool {
+ return gomegainfo.IsGomegaVar(x, g.pass)
+}
+
+func (g nameHandler) GetActualExpr(assertionFunc *ast.SelectorExpr) *ast.CallExpr {
+ actualExpr, ok := assertionFunc.X.(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+
+ switch fun := actualExpr.Fun.(type) {
+ case *ast.Ident:
+ return actualExpr
+ case *ast.SelectorExpr:
+ if x, ok := fun.X.(*ast.Ident); ok && x.Name == g.name {
+ return actualExpr
+ }
+ if gomegainfo.IsActualMethod(fun.Sel.Name) {
+ if g.isGomegaVar(fun.X) {
+ return actualExpr
+ }
+ } else {
+ return g.GetActualExpr(fun)
+ }
+ }
+ return nil
+}
+
+func (g nameHandler) GetActualExprClone(origFunc, funcClone *ast.SelectorExpr) *ast.CallExpr {
+ actualExpr, ok := funcClone.X.(*ast.CallExpr)
+ if !ok {
+ return nil
+ }
+
+ switch funClone := actualExpr.Fun.(type) {
+ case *ast.Ident:
+ return actualExpr
+ case *ast.SelectorExpr:
+ if x, ok := funClone.X.(*ast.Ident); ok && x.Name == g.name {
+ return actualExpr
+ }
+ origFun := origFunc.X.(*ast.CallExpr).Fun.(*ast.SelectorExpr)
+ if gomegainfo.IsActualMethod(funClone.Sel.Name) {
+ if g.isGomegaVar(origFun.X) {
+ return actualExpr
+ }
+ } else {
+ return g.GetActualExprClone(origFun, funClone)
+ }
+
+ }
+ return nil
+}
+
+func (g nameHandler) GetNewWrapperMatcher(name string, existing *ast.CallExpr) *ast.CallExpr {
+ return &ast.CallExpr{
+ Fun: &ast.SelectorExpr{
+ X: ast.NewIdent(g.name),
+ Sel: ast.NewIdent(name),
+ },
+ Args: []ast.Expr{existing},
+ }
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegainfo/gomegainfo.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegainfo/gomegainfo.go
new file mode 100644
index 000000000..ca45a34b2
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegainfo/gomegainfo.go
@@ -0,0 +1,113 @@
+package gomegainfo
+
+import (
+ "go/ast"
+ gotypes "go/types"
+ "regexp"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+const ( // gomega actual method names
+ expect = "Expect"
+ expectWithOffset = "ExpectWithOffset"
+ omega = "Ω"
+ eventually = "Eventually"
+ eventuallyWithOffset = "EventuallyWithOffset"
+ consistently = "Consistently"
+ consistentlyWithOffset = "ConsistentlyWithOffset"
+)
+
+const ( // assertion methods
+ to = "To"
+ toNot = "ToNot"
+ notTo = "NotTo"
+ should = "Should"
+ shouldNot = "ShouldNot"
+)
+
+var funcOffsetMap = map[string]int{
+ expect: 0,
+ expectWithOffset: 1,
+ omega: 0,
+ eventually: 0,
+ eventuallyWithOffset: 1,
+ consistently: 0,
+ consistentlyWithOffset: 1,
+}
+
+func IsActualMethod(name string) bool {
+ _, found := funcOffsetMap[name]
+ return found
+}
+
+func ActualArgOffset(methodName string) int {
+ funcOffset, ok := funcOffsetMap[methodName]
+ if !ok {
+ return -1
+ }
+ return funcOffset
+}
+
+func GetAllowedAssertionMethods(actualMethodName string) string {
+ switch actualMethodName {
+ case expect, expectWithOffset:
+ return `"To()", "ToNot()" or "NotTo()"`
+
+ case eventually, eventuallyWithOffset, consistently, consistentlyWithOffset:
+ return `"Should()" or "ShouldNot()"`
+
+ case omega:
+ return `"Should()", "To()", "ShouldNot()", "ToNot()" or "NotTo()"`
+
+ default:
+ return ""
+ }
+}
+
+var asyncFuncSet = map[string]struct{}{
+ eventually: {},
+ eventuallyWithOffset: {},
+ consistently: {},
+ consistentlyWithOffset: {},
+}
+
+func IsAsyncActualMethod(name string) bool {
+ _, ok := asyncFuncSet[name]
+ return ok
+}
+
+func IsAssertionFunc(name string) bool {
+ switch name {
+ case to, toNot, notTo, should, shouldNot:
+ return true
+ }
+ return false
+}
+
+var gomegaTypeRegex = regexp.MustCompile(`github\.com/onsi/gomega/(?:internal|types)\.Gomega`)
+
+func IsGomegaVar(x ast.Expr, pass *analysis.Pass) bool {
+ if tx, ok := pass.TypesInfo.Types[x]; ok {
+ return IsGomegaType(tx.Type)
+ }
+
+ return false
+}
+
+func IsGomegaType(t gotypes.Type) bool {
+ var typeStr string
+ switch ttx := t.(type) {
+ case *gotypes.Pointer:
+ tp := ttx.Elem()
+ typeStr = tp.String()
+
+ case *gotypes.Named:
+ typeStr = ttx.String()
+
+ default:
+ return false
+ }
+
+ return gomegaTypeRegex.MatchString(typeStr)
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go
index dafeacd4f..91849ca56 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go
@@ -72,5 +72,5 @@ func ImplementsError(t gotypes.Type) bool {
}
func ImplementsGomegaMatcher(t gotypes.Type) bool {
- return gotypes.Implements(t, gomegaMatcherType)
+ return t != nil && gotypes.Implements(t, gomegaMatcherType)
}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go
index b8166bdb2..51d55166d 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go
@@ -1,285 +1,166 @@
package intervals
import (
- "errors"
"go/ast"
"go/constant"
"go/token"
gotypes "go/types"
- "strconv"
"time"
"golang.org/x/tools/go/analysis"
-
- "github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
- "github.com/nunnatsa/ginkgolinter/internal/reports"
)
-type noDurationIntervalErr struct {
- value string
-}
-
-func (err noDurationIntervalErr) Error() string {
- return "only use time.Duration for timeout and polling in Eventually() or Consistently()"
-}
-
-func CheckIntervals(pass *analysis.Pass, expr *ast.CallExpr, actualExpr *ast.CallExpr, reportBuilder *reports.Builder, handler gomegahandler.Handler, timePkg string, funcIndex int) {
- var (
- timeout time.Duration
- polling time.Duration
- err error
- )
-
- timeoutOffset := funcIndex + 1
- if len(actualExpr.Args) > timeoutOffset {
- timeout, err = getDuration(pass, actualExpr.Args[timeoutOffset], timePkg)
- if err != nil {
- suggestFix := false
- if tryFixIntDuration(expr, err, handler, timePkg, timeoutOffset) {
- suggestFix = true
- }
- reportBuilder.AddIssue(suggestFix, err.Error())
- }
- pollingOffset := funcIndex + 2
- if len(actualExpr.Args) > pollingOffset {
- polling, err = getDuration(pass, actualExpr.Args[pollingOffset], timePkg)
- if err != nil {
- suggestFix := false
- if tryFixIntDuration(expr, err, handler, timePkg, pollingOffset) {
- suggestFix = true
+func GetDuration(pass *analysis.Pass, argOffset int, origInterval, intervalClone ast.Expr, timePkg string) DurationValue {
+ tv := pass.TypesInfo.Types[origInterval]
+ argType := tv.Type
+ if durType, ok := argType.(*gotypes.Named); ok {
+ if durType.String() == "time.Duration" {
+ if tv.Value != nil {
+ if val, ok := constant.Int64Val(tv.Value); ok {
+ return &RealDurationValue{
+ dur: time.Duration(val),
+ expr: intervalClone,
+ }
}
- reportBuilder.AddIssue(suggestFix, err.Error())
+ }
+ return &UnknownDurationTypeValue{
+ expr: intervalClone,
}
}
}
- selExp := expr.Fun.(*ast.SelectorExpr)
- for {
- call, ok := selExp.X.(*ast.CallExpr)
- if !ok {
- break
- }
-
- fun, ok := call.Fun.(*ast.SelectorExpr)
- if !ok {
- break
- }
-
- switch fun.Sel.Name {
- case "WithTimeout", "Within":
- if timeout != 0 {
- reportBuilder.AddIssue(false, "timeout defined more than once")
- } else if len(call.Args) == 1 {
- timeout, err = getDurationFromValue(pass, call.Args[0], timePkg)
- if err != nil {
- reportBuilder.AddIssue(false, err.Error())
+ if basic, ok := argType.(*gotypes.Basic); ok && tv.Value != nil {
+ if basic.Info()&gotypes.IsInteger != 0 {
+ if num, ok := constant.Int64Val(tv.Value); ok {
+ return &NumericDurationValue{
+ timePkg: timePkg,
+ numSeconds: num,
+ offset: argOffset,
+ dur: time.Duration(num) * time.Second,
+ expr: intervalClone,
}
}
+ }
- case "WithPolling", "ProbeEvery":
- if polling != 0 {
- reportBuilder.AddIssue(false, "polling defined more than once")
- } else if len(call.Args) == 1 {
- polling, err = getDurationFromValue(pass, call.Args[0], timePkg)
- if err != nil {
- reportBuilder.AddIssue(false, err.Error())
+ if basic.Info()&gotypes.IsFloat != 0 {
+ if num, ok := constant.Float64Val(tv.Value); ok {
+ return &NumericDurationValue{
+ timePkg: timePkg,
+ numSeconds: int64(num),
+ offset: argOffset,
+ dur: time.Duration(num) * time.Second,
+ expr: intervalClone,
}
}
}
-
- selExp = fun
}
- if timeout != 0 && polling != 0 && timeout < polling {
- reportBuilder.AddIssue(false, "timeout must not be shorter than the polling interval")
- }
+ return &UnknownDurationValue{expr: intervalClone}
}
-func tryFixIntDuration(expr *ast.CallExpr, err error, handler gomegahandler.Handler, timePkg string, offset int) bool {
- suggestFix := false
- var durErr noDurationIntervalErr
- if errors.As(err, &durErr) {
- if len(durErr.value) > 0 {
- actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr))
- var newArg ast.Expr
- second := &ast.SelectorExpr{
- Sel: ast.NewIdent("Second"),
- X: ast.NewIdent(timePkg),
+func GetDurationFromValue(pass *analysis.Pass, orig, clone ast.Expr) DurationValue {
+ tv := pass.TypesInfo.Types[orig]
+ interval := tv.Value
+ if interval != nil {
+ if val, ok := constant.Int64Val(interval); ok {
+ return RealDurationValue{
+ dur: time.Duration(val),
+ expr: orig,
}
- if durErr.value == "1" {
- newArg = second
- } else {
- newArg = &ast.BinaryExpr{
- X: second,
- Op: token.MUL,
- Y: actualExpr.Args[offset],
- }
- }
- actualExpr.Args[offset] = newArg
- suggestFix = true
}
}
-
- return suggestFix
+ return UnknownDurationTypeValue{expr: clone}
}
-func getDuration(pass *analysis.Pass, interval ast.Expr, timePkg string) (time.Duration, error) {
- argType := pass.TypesInfo.TypeOf(interval)
- if durType, ok := argType.(*gotypes.Named); ok {
- if durType.Obj().Name() == "Duration" && durType.Obj().Pkg().Name() == "time" {
- return getDurationFromValue(pass, interval, timePkg)
- }
- }
+type DurationValue interface {
+ Duration() time.Duration
+}
- value := ""
- switch val := interval.(type) {
- case *ast.BasicLit:
- if val.Kind == token.INT {
- value = val.Value
- }
- case *ast.Ident:
- i, err := getConstDuration(pass, val, timePkg)
- if err != nil || i == 0 {
- return 0, nil
- }
- value = val.Name
- }
+type NumericValue interface {
+ GetOffset() int
+ GetDurationExpr() ast.Expr
+}
+type RealDurationValue struct {
+ dur time.Duration
+ expr ast.Expr
+}
- return 0, noDurationIntervalErr{value: value}
+func (r RealDurationValue) Duration() time.Duration {
+ return r.dur
}
-func getDurationFromValue(pass *analysis.Pass, interval ast.Expr, timePkg string) (time.Duration, error) {
- switch dur := interval.(type) {
- case *ast.SelectorExpr:
- ident, ok := dur.X.(*ast.Ident)
- if ok {
- if ident.Name == timePkg {
- return getTimeDurationValue(dur)
- }
- return getDurationFromValue(pass, dur.Sel, timePkg)
- }
- case *ast.BinaryExpr:
- return getBinaryExprDuration(pass, dur, timePkg)
+type NumericDurationValue struct {
+ timePkg string
+ numSeconds int64
+ offset int
+ dur time.Duration
+ expr ast.Expr
+}
- case *ast.Ident:
- return getConstDuration(pass, dur, timePkg)
- }
+func (r *NumericDurationValue) Duration() time.Duration {
+ return r.dur
+}
- return 0, nil
+func (r *NumericDurationValue) GetOffset() int {
+ return r.offset
}
-func getConstDuration(pass *analysis.Pass, ident *ast.Ident, timePkg string) (time.Duration, error) {
- o := pass.TypesInfo.ObjectOf(ident)
- if o != nil {
- if c, ok := o.(*gotypes.Const); ok {
- if c.Val().Kind() == constant.Int {
- i, err := strconv.Atoi(c.Val().String())
- if err != nil {
- return 0, nil
- }
- return time.Duration(i), nil
- }
- }
+func (r *NumericDurationValue) GetDurationExpr() ast.Expr {
+ var newArg ast.Expr
+ second := &ast.SelectorExpr{
+ Sel: ast.NewIdent("Second"),
+ X: ast.NewIdent(r.timePkg),
}
- if ident.Obj != nil && ident.Obj.Kind == ast.Con && ident.Obj.Decl != nil {
- if vals, ok := ident.Obj.Decl.(*ast.ValueSpec); ok {
- if len(vals.Values) == 1 {
- switch val := vals.Values[0].(type) {
- case *ast.BasicLit:
- if val.Kind == token.INT {
- i, err := strconv.Atoi(val.Value)
- if err != nil {
- return 0, nil
- }
- return time.Duration(i), nil
- }
- return 0, nil
- case *ast.BinaryExpr:
- return getBinaryExprDuration(pass, val, timePkg)
- }
- }
+ if r.numSeconds == 1 {
+ newArg = second
+ } else {
+ newArg = &ast.BinaryExpr{
+ X: second,
+ Op: token.MUL,
+ Y: r.expr,
}
}
- return 0, nil
+ return newArg
}
-func getTimeDurationValue(dur *ast.SelectorExpr) (time.Duration, error) {
- switch dur.Sel.Name {
- case "Nanosecond":
- return time.Nanosecond, nil
- case "Microsecond":
- return time.Microsecond, nil
- case "Millisecond":
- return time.Millisecond, nil
- case "Second":
- return time.Second, nil
- case "Minute":
- return time.Minute, nil
- case "Hour":
- return time.Hour, nil
- default:
- return 0, errors.New("unknown duration value") // should never happen
- }
+type UnknownDurationValue struct {
+ expr ast.Expr
}
-func getBinaryExprDuration(pass *analysis.Pass, expr *ast.BinaryExpr, timePkg string) (time.Duration, error) {
- x, err := getBinaryDurValue(pass, expr.X, timePkg)
- if err != nil || x == 0 {
- return 0, nil
- }
- y, err := getBinaryDurValue(pass, expr.Y, timePkg)
- if err != nil || y == 0 {
- return 0, nil
- }
+func (r UnknownDurationValue) Duration() time.Duration {
+ return 0
+}
- switch expr.Op {
- case token.ADD:
- return x + y, nil
- case token.SUB:
- val := x - y
- if val > 0 {
- return val, nil
- }
- return 0, nil
- case token.MUL:
- return x * y, nil
- case token.QUO:
- if y == 0 {
- return 0, nil
- }
- return x / y, nil
- case token.REM:
- if y == 0 {
- return 0, nil
- }
- return x % y, nil
- default:
- return 0, nil
- }
+type UnknownNumericValue struct {
+ expr ast.Expr
+ offset int
}
-func getBinaryDurValue(pass *analysis.Pass, expr ast.Expr, timePkg string) (time.Duration, error) {
- switch x := expr.(type) {
- case *ast.SelectorExpr:
- return getDurationFromValue(pass, x, timePkg)
- case *ast.BinaryExpr:
- return getBinaryExprDuration(pass, x, timePkg)
- case *ast.BasicLit:
- if x.Kind == token.INT {
- val, err := strconv.Atoi(x.Value)
- if err != nil {
- return 0, err
- }
- return time.Duration(val), nil
- }
- case *ast.ParenExpr:
- return getBinaryDurValue(pass, x.X, timePkg)
+func (r UnknownNumericValue) Duration() time.Duration {
+ return 0
+}
- case *ast.Ident:
- return getConstDuration(pass, x, timePkg)
+func (r UnknownNumericValue) GetDurationExpr() ast.Expr {
+ return &ast.BinaryExpr{
+ X: &ast.SelectorExpr{
+ Sel: ast.NewIdent("Second"),
+ X: ast.NewIdent("time"),
+ },
+ Op: token.MUL,
+ Y: r.expr,
}
+}
+
+func (r UnknownNumericValue) GetOffset() int {
+ return r.offset
+}
+
+type UnknownDurationTypeValue struct {
+ expr ast.Expr
+}
- return 0, nil
+func (r UnknownDurationTypeValue) Duration() time.Duration {
+ return 0
}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go
index c7f931ca7..dee88bd2c 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go
@@ -1,13 +1,13 @@
package reports
import (
- "bytes"
"fmt"
"go/ast"
- "go/printer"
"go/token"
"strings"
+ "github.com/nunnatsa/ginkgolinter/internal/formatter"
+
"golang.org/x/tools/go/analysis"
)
@@ -18,19 +18,25 @@ type Builder struct {
issues []string
fixOffer string
suggestFix bool
+ formatter *formatter.GoFmtFormatter
}
-func NewBuilder(fset *token.FileSet, oldExpr ast.Expr) *Builder {
+func NewBuilder(oldExpr ast.Expr, expFormatter *formatter.GoFmtFormatter) *Builder {
b := &Builder{
pos: oldExpr.Pos(),
end: oldExpr.End(),
- oldExpr: goFmt(fset, oldExpr),
+ oldExpr: expFormatter.Format(oldExpr),
suggestFix: false,
+ formatter: expFormatter,
}
return b
}
+func (b *Builder) OldExp() string {
+ return b.oldExpr
+}
+
func (b *Builder) AddIssue(suggestFix bool, issue string, args ...any) {
if len(args) > 0 {
issue = fmt.Sprintf(issue, args...)
@@ -42,9 +48,11 @@ func (b *Builder) AddIssue(suggestFix bool, issue string, args ...any) {
}
}
-func (b *Builder) SetFixOffer(fset *token.FileSet, fixOffer ast.Expr) {
- if offer := goFmt(fset, fixOffer); offer != b.oldExpr {
- b.fixOffer = offer
+func (b *Builder) SetFixOffer(fixOffer ast.Expr) {
+ if b.suggestFix {
+ if offer := b.formatter.Format(fixOffer); offer != b.oldExpr {
+ b.fixOffer = offer
+ }
}
}
@@ -76,10 +84,8 @@ func (b *Builder) Build() analysis.Diagnostic {
return diagnostic
}
-func goFmt(fset *token.FileSet, x ast.Expr) string {
- var b bytes.Buffer
- _ = printer.Fprint(&b, fset, x)
- return b.String()
+func (b *Builder) FormatExpr(expr ast.Expr) string {
+ return b.formatter.Format(expr)
}
func (b *Builder) getMessage() string {
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncfunccallrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncfunccallrule.go
new file mode 100644
index 000000000..e4eda7f6c
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncfunccallrule.go
@@ -0,0 +1,41 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const valueInEventually = "use a function call in %[1]s. This actually checks nothing, because %[1]s receives the function returned value, instead of function itself, and this value is never changed"
+
+// AsyncFuncCallRule checks that there is no function call actual parameter,
+// in an async actual method (e.g. Eventually).
+//
+// Async actual methods should get the function itself, not a function call, because
+// then there is no async operation at all, and we're waiting for the function to be
+// returned before calling the assertion.
+//
+// We do allow functions that return a function, a channel or a pointer.
+type AsyncFuncCallRule struct{}
+
+func (r AsyncFuncCallRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ if bool(config.SuppressAsync) || !gexp.IsAsync() {
+ return false
+ }
+
+ if asyncArg := gexp.GetAsyncActualArg(); asyncRules != nil {
+ return !asyncArg.IsValid()
+ }
+
+ return false
+}
+
+func (r AsyncFuncCallRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if r.isApplied(gexp, config) {
+
+ gexp.AppendWithArgsToActual()
+
+ reportBuilder.AddIssue(true, valueInEventually, gexp.GetActualFuncName())
+ }
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncsucceedrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncsucceedrule.go
new file mode 100644
index 000000000..803c705de
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asyncsucceedrule.go
@@ -0,0 +1,30 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+type AsyncSucceedRule struct{}
+
+func (AsyncSucceedRule) isApply(gexp *expression.GomegaExpression) bool {
+ return gexp.IsAsync() &&
+ gexp.MatcherTypeIs(matcher.SucceedMatcherType) &&
+ gexp.ActualArgTypeIs(actual.FuncSigArgType) &&
+ !gexp.ActualArgTypeIs(actual.ErrorTypeArgType|actual.GomegaParamArgType)
+}
+
+func (r AsyncSucceedRule) Apply(gexp *expression.GomegaExpression, _ types.Config, reportBuilder *reports.Builder) bool {
+ if r.isApply(gexp) {
+ if gexp.ActualArgTypeIs(actual.MultiRetsArgType) {
+ reportBuilder.AddIssue(false, "Success matcher does not support multiple values")
+ } else {
+ reportBuilder.AddIssue(false, "Success matcher only support a single error value, or function with Gomega as its first parameter")
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asynctimeintervalsrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asynctimeintervalsrule.go
new file mode 100644
index 000000000..45953ec01
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/asynctimeintervalsrule.go
@@ -0,0 +1,79 @@
+package rules
+
+import (
+ "go/ast"
+ "time"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/intervals"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const (
+ multipleTimeouts = "timeout defined more than once"
+ multiplePolling = "polling defined more than once"
+ onlyUseTimeDurationForInterval = "only use time.Duration for timeout and polling in Eventually() or Consistently()"
+ pollingGreaterThanTimeout = "timeout must not be shorter than the polling interval"
+)
+
+type AsyncTimeIntervalsRule struct{}
+
+func (r AsyncTimeIntervalsRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ return !bool(config.SuppressAsync) && bool(config.ValidateAsyncIntervals) && gexp.IsAsync()
+}
+
+func (r AsyncTimeIntervalsRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if r.isApplied(gexp, config) {
+ asyncArg := gexp.GetAsyncActualArg()
+ if asyncArg.TooManyTimeouts() {
+ reportBuilder.AddIssue(false, multipleTimeouts)
+ }
+
+ if asyncArg.TooManyPolling() {
+ reportBuilder.AddIssue(false, multiplePolling)
+ }
+
+ timeoutDuration := checkInterval(gexp, asyncArg.Timeout(), reportBuilder)
+ pollingDuration := checkInterval(gexp, asyncArg.Polling(), reportBuilder)
+
+ if timeoutDuration > 0 && pollingDuration > 0 && pollingDuration > timeoutDuration {
+ reportBuilder.AddIssue(false, pollingGreaterThanTimeout)
+ }
+ }
+
+ return false
+}
+
+func checkInterval(gexp *expression.GomegaExpression, durVal intervals.DurationValue, reportBuilder *reports.Builder) time.Duration {
+ if durVal != nil {
+ switch to := durVal.(type) {
+ case *intervals.RealDurationValue, *intervals.UnknownDurationTypeValue:
+
+ case *intervals.NumericDurationValue:
+ if checkNumericInterval(gexp.GetActualClone(), to) {
+ reportBuilder.AddIssue(true, onlyUseTimeDurationForInterval)
+ }
+
+ case *intervals.UnknownDurationValue:
+ reportBuilder.AddIssue(true, onlyUseTimeDurationForInterval)
+ }
+
+ return durVal.Duration()
+ }
+
+ return 0
+}
+
+func checkNumericInterval(intervalMethod *ast.CallExpr, interval intervals.DurationValue) bool {
+ if interval != nil {
+ if numVal, ok := interval.(intervals.NumericValue); ok {
+ if offset := numVal.GetOffset(); offset > 0 {
+ intervalMethod.Args[offset] = numVal.GetDurationExpr()
+ return true
+ }
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/caprule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/caprule.go
new file mode 100644
index 000000000..e3ad45d96
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/caprule.go
@@ -0,0 +1,128 @@
+package rules
+
+import (
+ "go/token"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const wrongCapWarningTemplate = "wrong cap assertion"
+
+// CapRule does not allow using the cap() function in actual with numeric comparison.
+// it suggests to use the HaveLen matcher, instead.
+type CapRule struct{}
+
+func (r *CapRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+
+ if r.fixExpression(gexp) {
+ reportBuilder.AddIssue(true, wrongCapWarningTemplate)
+ return true
+ }
+ return false
+}
+
+func (r *CapRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ if config.SuppressLen {
+ return false
+ }
+
+ //matcherType := gexp.matcher.GetMatcherInfo().Type()
+ if gexp.ActualArgTypeIs(actual.CapFuncActualArgType) {
+ if gexp.MatcherTypeIs(matcher.EqualMatcherType | matcher.BeZeroMatcherType) {
+ return true
+ }
+
+ if gexp.MatcherTypeIs(matcher.BeNumericallyMatcherType) {
+ mtchr := gexp.GetMatcherInfo().(*matcher.BeNumericallyMatcher)
+ return mtchr.GetOp() == token.EQL || mtchr.GetOp() == token.NEQ || gexp.MatcherTypeIs(matcher.EqualZero|matcher.GreaterThanZero)
+ }
+ }
+
+ if gexp.ActualArgTypeIs(actual.CapComparisonActualArgType) && gexp.MatcherTypeIs(matcher.BeTrueMatcherType|matcher.BeFalseMatcherType|matcher.EqualBoolValueMatcherType) {
+ return true
+ }
+
+ return false
+}
+
+func (r *CapRule) fixExpression(gexp *expression.GomegaExpression) bool {
+ if gexp.ActualArgTypeIs(actual.CapFuncActualArgType) {
+ return r.fixEqual(gexp)
+ }
+
+ if gexp.ActualArgTypeIs(actual.CapComparisonActualArgType) {
+ return r.fixComparison(gexp)
+ }
+
+ return false
+}
+
+func (r *CapRule) fixEqual(gexp *expression.GomegaExpression) bool {
+ matcherInfo := gexp.GetMatcherInfo()
+ switch mtchr := matcherInfo.(type) {
+ case *matcher.EqualMatcher:
+ gexp.SetMatcherCap(mtchr.GetValueExpr())
+
+ case *matcher.BeZeroMatcher:
+ gexp.SetMatcherCapZero()
+
+ case *matcher.BeNumericallyMatcher:
+ if !r.handleBeNumerically(gexp, mtchr) {
+ return false
+ }
+
+ default:
+ return false
+ }
+
+ gexp.ReplaceActualWithItsFirstArg()
+
+ return true
+}
+
+func (r *CapRule) fixComparison(gexp *expression.GomegaExpression) bool {
+ actl := gexp.GetActualArg().(*actual.FuncComparisonPayload)
+ if op := actl.GetOp(); op == token.NEQ {
+ gexp.ReverseAssertionFuncLogic()
+ } else if op != token.EQL {
+ return false
+ }
+
+ gexp.SetMatcherCap(actl.GetValueExpr())
+ gexp.ReplaceActual(actl.GetFuncArg())
+
+ if gexp.MatcherTypeIs(matcher.BoolValueFalse) {
+ gexp.ReverseAssertionFuncLogic()
+ }
+
+ return true
+}
+
+func (r *CapRule) handleBeNumerically(gexp *expression.GomegaExpression, matcher *matcher.BeNumericallyMatcher) bool {
+ op := matcher.GetOp()
+ val := matcher.GetValue()
+ isValZero := val.String() == "0"
+ isValOne := val.String() == "1"
+
+ if (op == token.GTR && isValZero) || (op == token.GEQ && isValOne) {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherCapZero()
+ } else if op == token.EQL {
+ gexp.SetMatcherCap(matcher.GetValueExpr())
+ } else if op == token.NEQ {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherCap(matcher.GetValueExpr())
+ } else {
+ return false
+ }
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparepointerrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparepointerrule.go
new file mode 100644
index 000000000..dcbea1bc9
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparepointerrule.go
@@ -0,0 +1,64 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const comparePointerToValue = "comparing a pointer to a value will always fail"
+
+type ComparePointRule struct{}
+
+func (r ComparePointRule) isApplied(gexp *expression.GomegaExpression) bool {
+ actl, ok := gexp.GetActualArg().(*actual.RegularArgPayload)
+ if !ok {
+ return false
+ }
+
+ return actl.IsPointer()
+}
+
+func (r ComparePointRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ switch mtchr := gexp.GetMatcherInfo().(type) {
+ case *matcher.EqualMatcher:
+ if mtchr.IsPointer() || mtchr.IsInterface() {
+ return false
+ }
+
+ case *matcher.BeEquivalentToMatcher:
+ if mtchr.IsPointer() || mtchr.IsInterface() || mtchr.IsNil() {
+ return false
+ }
+
+ case *matcher.BeIdenticalToMatcher:
+ if mtchr.IsPointer() || mtchr.IsInterface() || mtchr.IsNil() {
+ return false
+ }
+
+ case *matcher.EqualNilMatcher:
+ return false
+
+ case *matcher.BeTrueMatcher,
+ *matcher.BeFalseMatcher,
+ *matcher.BeNumericallyMatcher,
+ *matcher.EqualTrueMatcher,
+ *matcher.EqualFalseMatcher:
+
+ default:
+ return false
+ }
+
+ getMatcherOnlyRules().Apply(gexp, config, reportBuilder)
+
+ gexp.SetMatcherHaveValue()
+ reportBuilder.AddIssue(true, comparePointerToValue)
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparisonrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparisonrule.go
new file mode 100644
index 000000000..fb38529e0
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/comparisonrule.go
@@ -0,0 +1,75 @@
+package rules
+
+import (
+ "go/token"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const wrongCompareWarningTemplate = "wrong comparison assertion"
+
+type ComparisonRule struct{}
+
+func (r ComparisonRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ if config.SuppressCompare {
+ return false
+ }
+
+ return gexp.ActualArgTypeIs(actual.ComparisonActualArgType)
+}
+
+func (r ComparisonRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+
+ actl, ok := gexp.GetActualArg().(actual.ComparisonActualPayload)
+ if !ok {
+ return false
+ }
+
+ switch actl.GetOp() {
+ case token.EQL:
+ r.handleEqualComparison(gexp, actl)
+
+ case token.NEQ:
+ gexp.ReverseAssertionFuncLogic()
+ r.handleEqualComparison(gexp, actl)
+ case token.GTR, token.GEQ, token.LSS, token.LEQ:
+ if !actl.GetRight().IsValueNumeric() {
+ return false
+ }
+
+ gexp.SetMatcherBeNumerically(actl.GetOp(), actl.GetRight().GetValueExpr())
+
+ default:
+ return false
+ }
+
+ if gexp.MatcherTypeIs(matcher.BoolValueFalse) {
+ gexp.ReverseAssertionFuncLogic()
+ }
+
+ gexp.ReplaceActual(actl.GetLeft().GetValueExpr())
+
+ reportBuilder.AddIssue(true, wrongCompareWarningTemplate)
+ return true
+}
+
+func (r ComparisonRule) handleEqualComparison(gexp *expression.GomegaExpression, actual actual.ComparisonActualPayload) {
+ if actual.GetRight().IsValueZero() {
+ gexp.SetMatcherBeZero()
+ } else {
+ left := actual.GetLeft()
+ arg := actual.GetRight().GetValueExpr()
+ if left.IsInterface() || left.IsPointer() {
+ gexp.SetMatcherBeIdenticalTo(arg)
+ } else {
+ gexp.SetMatcherEqual(arg)
+ }
+ }
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/doublenegativerule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/doublenegativerule.go
new file mode 100644
index 000000000..6ce7be5a5
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/doublenegativerule.go
@@ -0,0 +1,30 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const doubleNegativeWarningTemplate = "avoid double negative assertion"
+
+type DoubleNegativeRule struct{}
+
+func (DoubleNegativeRule) isApplied(gexp *expression.GomegaExpression) bool {
+ return gexp.MatcherTypeIs(matcher.BeFalseMatcherType) &&
+ gexp.IsNegativeAssertion()
+}
+
+func (r DoubleNegativeRule) Apply(gexp *expression.GomegaExpression, _ types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherBeTrue()
+
+ reportBuilder.AddIssue(true, doubleNegativeWarningTemplate)
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalboolrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalboolrule.go
new file mode 100644
index 000000000..e9eaa1b80
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalboolrule.go
@@ -0,0 +1,36 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const wrongBoolWarningTemplate = "wrong boolean assertion"
+
+type EqualBoolRule struct{}
+
+func (r EqualBoolRule) isApplied(gexp *expression.GomegaExpression) bool {
+ return gexp.MatcherTypeIs(matcher.EqualBoolValueMatcherType)
+}
+
+func (r EqualBoolRule) Apply(gexp *expression.GomegaExpression, _ types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ if gexp.MatcherTypeIs(matcher.BoolValueTrue) {
+ gexp.SetMatcherBeTrue()
+ } else {
+ if gexp.IsNegativeAssertion() {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherBeTrue()
+ } else {
+ gexp.SetMatcherBeFalse()
+ }
+ }
+
+ reportBuilder.AddIssue(true, wrongBoolWarningTemplate)
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equaldifferenttypesrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equaldifferenttypesrule.go
new file mode 100644
index 000000000..81d703bb8
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equaldifferenttypesrule.go
@@ -0,0 +1,119 @@
+package rules
+
+import (
+ gotypes "go/types"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const compareDifferentTypes = "use %[1]s with different types: Comparing %[2]s with %[3]s; either change the expected value type if possible, or use the BeEquivalentTo() matcher, instead of %[1]s()"
+
+type EqualDifferentTypesRule struct{}
+
+func (r EqualDifferentTypesRule) isApplied(config types.Config) bool {
+ return !bool(config.SuppressTypeCompare)
+}
+
+func (r EqualDifferentTypesRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(config) {
+ return false
+ }
+
+ return r.checkEqualDifferentTypes(gexp, gexp.GetMatcher(), false, reportBuilder)
+}
+
+func (r EqualDifferentTypesRule) checkEqualDifferentTypes(gexp *expression.GomegaExpression, mtchr *matcher.Matcher, parentPointer bool, reportBuilder *reports.Builder) bool {
+ actualType := gexp.GetActualArgGOType()
+
+ if parentPointer {
+ if t, ok := actualType.(*gotypes.Pointer); ok {
+ actualType = t.Elem()
+ }
+ }
+
+ var (
+ matcherType gotypes.Type
+ matcherName string
+ )
+
+ switch specificMatcher := mtchr.GetMatcherInfo().(type) {
+ case *matcher.EqualMatcher:
+ matcherType = specificMatcher.GetType()
+ matcherName = specificMatcher.MatcherName()
+
+ case *matcher.BeIdenticalToMatcher:
+ matcherType = specificMatcher.GetType()
+ matcherName = specificMatcher.MatcherName()
+
+ case *matcher.HaveValueMatcher:
+ return r.checkEqualDifferentTypes(gexp, specificMatcher.GetNested(), true, reportBuilder)
+
+ case *matcher.MultipleMatchersMatcher:
+ foundIssue := false
+ for i := range specificMatcher.Len() {
+ if r.checkEqualDifferentTypes(gexp, specificMatcher.At(i), parentPointer, reportBuilder) {
+ foundIssue = true
+ }
+
+ }
+ return foundIssue
+
+ case *matcher.EqualNilMatcher:
+ matcherType = specificMatcher.GetType()
+ matcherName = specificMatcher.MatcherName()
+
+ case *matcher.WithTransformMatcher:
+ nested := specificMatcher.GetNested()
+ switch specificNested := nested.GetMatcherInfo().(type) {
+ case *matcher.EqualMatcher:
+ matcherType = specificNested.GetType()
+ matcherName = specificNested.MatcherName()
+
+ case *matcher.BeIdenticalToMatcher:
+ matcherType = specificNested.GetType()
+ matcherName = specificNested.MatcherName()
+
+ default:
+ return false
+ }
+
+ actualType = specificMatcher.GetFuncType()
+ default:
+ return false
+ }
+
+ if !gotypes.Identical(matcherType, actualType) {
+ if r.isImplementing(matcherType, actualType) || r.isImplementing(actualType, matcherType) {
+ return false
+ }
+
+ reportBuilder.AddIssue(false, compareDifferentTypes, matcherName, actualType, matcherType)
+ return true
+ }
+
+ return false
+}
+
+func (r EqualDifferentTypesRule) isImplementing(ifs, impl gotypes.Type) bool {
+ if gotypes.IsInterface(ifs) {
+
+ var (
+ theIfs *gotypes.Interface
+ ok bool
+ )
+
+ for {
+ theIfs, ok = ifs.(*gotypes.Interface)
+ if ok {
+ break
+ }
+ ifs = ifs.Underlying()
+ }
+
+ return gotypes.Implements(impl, theIfs)
+ }
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalnilrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalnilrule.go
new file mode 100644
index 000000000..5b28e7d9b
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/equalnilrule.go
@@ -0,0 +1,29 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+// EqualNilRule validate that there is no use of Equal(nil) in the code
+// It is part of assertion only rules
+type EqualNilRule struct{}
+
+func (r EqualNilRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ return !bool(config.SuppressNil) &&
+ gexp.MatcherTypeIs(matcher.EqualValueMatcherType)
+}
+
+func (r EqualNilRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+
+ gexp.SetMatcherBeNil()
+
+ reportBuilder.AddIssue(true, wrongNilWarningTemplate)
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/errorequalnilrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/errorequalnilrule.go
new file mode 100644
index 000000000..7aaf7631b
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/errorequalnilrule.go
@@ -0,0 +1,35 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/value"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+type ErrorEqualNilRule struct{}
+
+func (ErrorEqualNilRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ return !bool(config.SuppressErr) &&
+ gexp.ActualArgTypeIs(actual.ErrorTypeArgType) &&
+ gexp.MatcherTypeIs(matcher.BeNilMatcherType|matcher.EqualNilMatcherType)
+}
+
+func (r ErrorEqualNilRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+
+ if v, ok := gexp.GetActualArg().(value.Valuer); ok && v.IsFunc() || gexp.ActualArgTypeIs(actual.ErrFuncActualArgType) {
+ gexp.SetMatcherSucceed()
+ } else {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherHaveOccurred()
+ }
+
+ reportBuilder.AddIssue(true, wrongErrWarningTemplate)
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/forceexpecttorule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/forceexpecttorule.go
new file mode 100644
index 000000000..391d1d449
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/forceexpecttorule.go
@@ -0,0 +1,43 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const forceExpectToTemplate = "must not use %s with %s"
+
+type ForceExpectToRule struct{}
+
+func (ForceExpectToRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ if !config.ForceExpectTo {
+ return false
+ }
+
+ actlName := gexp.GetActualFuncName()
+ return actlName == "Expect" || actlName == "ExpectWithOffset"
+}
+
+func (r ForceExpectToRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+
+ var newName string
+
+ switch gexp.GetAssertFuncName() {
+ case "Should":
+ newName = "To"
+ case "ShouldNot":
+ newName = "ToNot"
+ default:
+ return false
+ }
+
+ gexp.ReplaceAssertionMethod(newName)
+ reportBuilder.AddIssue(true, forceExpectToTemplate, gexp.GetActualFuncName(), gexp.GetOrigAssertFuncName())
+
+ // always return false, to keep checking another rules.
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/havelen0.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/havelen0.go
new file mode 100644
index 000000000..20bcb7211
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/havelen0.go
@@ -0,0 +1,23 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+type HaveLen0 struct{}
+
+func (r *HaveLen0) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ return gexp.MatcherTypeIs(matcher.HaveLenZeroMatcherType) && !bool(config.AllowHaveLen0)
+}
+
+func (r *HaveLen0) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+ gexp.SetMatcherBeEmpty()
+ reportBuilder.AddIssue(true, wrongLengthWarningTemplate)
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/haveoccurredrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/haveoccurredrule.go
new file mode 100644
index 000000000..437d3ee23
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/haveoccurredrule.go
@@ -0,0 +1,35 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+type HaveOccurredRule struct{}
+
+func (r HaveOccurredRule) isApplied(gexp *expression.GomegaExpression) bool {
+ return gexp.MatcherTypeIs(matcher.HaveOccurredMatcherType)
+}
+
+func (r HaveOccurredRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ if !gexp.ActualArgTypeIs(actual.ErrorTypeArgType) {
+ reportBuilder.AddIssue(false, "asserting a non-error type with HaveOccurred matcher")
+ return true
+ }
+
+ if bool(config.ForceSucceedForFuncs) && gexp.GetActualArg().(*actual.ErrPayload).IsFunc() {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherSucceed()
+ reportBuilder.AddIssue(true, "prefer using the Succeed matcher for error function, instead of HaveOccurred")
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/lenrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/lenrule.go
new file mode 100644
index 000000000..06d6f2c68
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/lenrule.go
@@ -0,0 +1,119 @@
+package rules
+
+import (
+ "go/token"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const wrongLengthWarningTemplate = "wrong length assertion"
+
+// LenRule does not allow using the len() function in actual with numeric comparison. Instead,
+// it suggests to use the HaveLen matcher, or the BeEmpty matcher, if comparing to zero.
+type LenRule struct{}
+
+func (r *LenRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+
+ if !r.isApplied(gexp, config) {
+ return false
+ }
+
+ if r.fixExpression(gexp) {
+ reportBuilder.AddIssue(true, wrongLengthWarningTemplate)
+ return true
+ }
+ return false
+}
+
+func (r *LenRule) isApplied(gexp *expression.GomegaExpression, config types.Config) bool {
+ if config.SuppressLen {
+ return false
+ }
+
+ if gexp.ActualArgTypeIs(actual.LenFuncActualArgType) {
+ if gexp.MatcherTypeIs(matcher.EqualMatcherType | matcher.BeZeroMatcherType) {
+ return true
+ }
+
+ if gexp.MatcherTypeIs(matcher.BeNumericallyMatcherType) {
+ mtchr := gexp.GetMatcherInfo().(*matcher.BeNumericallyMatcher)
+ return mtchr.GetOp() == token.EQL || mtchr.GetOp() == token.NEQ || gexp.MatcherTypeIs(matcher.EqualZero|matcher.GreaterThanZero)
+ }
+ }
+
+ if gexp.ActualArgTypeIs(actual.LenComparisonActualArgType) && gexp.MatcherTypeIs(matcher.BeTrueMatcherType|matcher.BeFalseMatcherType|matcher.EqualBoolValueMatcherType) {
+ return true
+ }
+
+ return false
+}
+
+func (r *LenRule) fixExpression(gexp *expression.GomegaExpression) bool {
+ if gexp.ActualArgTypeIs(actual.LenFuncActualArgType) {
+ return r.fixEqual(gexp)
+ }
+
+ if gexp.ActualArgTypeIs(actual.LenComparisonActualArgType) {
+ return r.fixComparison(gexp)
+ }
+
+ return false
+}
+
+func (r *LenRule) fixEqual(gexp *expression.GomegaExpression) bool {
+
+ if gexp.MatcherTypeIs(matcher.EqualMatcherType) {
+ gexp.SetLenNumericMatcher()
+
+ } else if gexp.MatcherTypeIs(matcher.BeZeroMatcherType) {
+ gexp.SetMatcherBeEmpty()
+
+ } else if gexp.MatcherTypeIs(matcher.BeNumericallyMatcherType) {
+ mtchr := gexp.GetMatcherInfo().(*matcher.BeNumericallyMatcher)
+ op := mtchr.GetOp()
+
+ if op == token.EQL {
+ gexp.SetLenNumericMatcher()
+ } else if op == token.NEQ {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetLenNumericMatcher()
+ } else if gexp.MatcherTypeIs(matcher.GreaterThanZero) {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherBeEmpty()
+ } else {
+ return false
+ }
+ } else {
+ return false
+ }
+
+ gexp.ReplaceActualWithItsFirstArg()
+ return true
+}
+
+func (r *LenRule) fixComparison(gexp *expression.GomegaExpression) bool {
+ actl := gexp.GetActualArg().(*actual.FuncComparisonPayload)
+ if op := actl.GetOp(); op == token.NEQ {
+ gexp.ReverseAssertionFuncLogic()
+ } else if op != token.EQL {
+ return false
+ }
+
+ if gexp.MatcherTypeIs(matcher.BoolValueFalse) {
+ gexp.ReverseAssertionFuncLogic()
+ }
+
+ if actl.IsValueZero() {
+ gexp.SetMatcherBeEmpty()
+ } else {
+ gexp.SetMatcherLen(actl.GetValueExpr())
+ }
+
+ gexp.ReplaceActual(actl.GetFuncArg())
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcheronlyrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcheronlyrule.go
new file mode 100644
index 000000000..1174393c6
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcheronlyrule.go
@@ -0,0 +1,12 @@
+package rules
+
+var matcherOnlyRules = Rules{
+ &HaveLen0{},
+ &EqualBoolRule{},
+ &EqualNilRule{},
+ &DoubleNegativeRule{},
+}
+
+func getMatcherOnlyRules() Rules {
+ return matcherOnlyRules
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcherrorrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcherrorrule.go
new file mode 100644
index 000000000..767b4b621
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/matcherrorrule.go
@@ -0,0 +1,110 @@
+package rules
+
+import (
+ "go/ast"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const (
+ matchErrorArgWrongType = "the MatchError matcher used to assert a non error type (%s)"
+ matchErrorWrongTypeAssertion = "MatchError first parameter (%s) must be error, string, GomegaMatcher or func(error)bool are allowed"
+ matchErrorMissingDescription = "missing function description as second parameter of MatchError"
+ matchErrorRedundantArg = "redundant MatchError arguments; consider removing them"
+ matchErrorNoFuncDescription = "The second parameter of MatchError must be the function description (string)"
+)
+
+// MatchErrorRule validates the usage of the MatchError matcher.
+//
+// # First, it checks that the actual value is actually an error
+//
+// Then, it checks the matcher itself: this matcher can be used in 3 different ways:
+// 1. With error type variable
+// 2. With another gomega matcher, to check the actual err.Error() value
+// 3. With function with a signature of func(error) bool. In this case, additional description
+// string variable is required.
+type MatchErrorRule struct{}
+
+func (r MatchErrorRule) isApplied(gexp *expression.GomegaExpression) bool {
+ return gexp.MatcherTypeIs(matcher.MatchErrorMatcherType | matcher.MultipleMatcherMatherType)
+}
+
+func (r MatchErrorRule) Apply(gexp *expression.GomegaExpression, _ types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ return checkMatchError(gexp, reportBuilder)
+}
+
+func checkMatchError(gexp *expression.GomegaExpression, reportBuilder *reports.Builder) bool {
+ mtchr := gexp.GetMatcherInfo()
+ switch m := mtchr.(type) {
+ case matcher.MatchErrorMatcher:
+ return checkMatchErrorMatcher(gexp, gexp.GetMatcher(), m, reportBuilder)
+
+ case *matcher.MultipleMatchersMatcher:
+ res := false
+ for i := range m.Len() {
+ nested := m.At(i)
+ if specific, ok := nested.GetMatcherInfo().(matcher.MatchErrorMatcher); ok {
+ if valid := checkMatchErrorMatcher(gexp, gexp.GetMatcher(), specific, reportBuilder); valid {
+ res = true
+ }
+ }
+ }
+ return res
+ default:
+ return false
+ }
+}
+
+func checkMatchErrorMatcher(gexp *expression.GomegaExpression, mtchr *matcher.Matcher, mtchrInfo matcher.MatchErrorMatcher, reportBuilder *reports.Builder) bool {
+ if !gexp.ActualArgTypeIs(actual.ErrorTypeArgType) {
+ reportBuilder.AddIssue(false, matchErrorArgWrongType, reportBuilder.FormatExpr(gexp.GetActualArgExpr()))
+ }
+
+ switch m := mtchrInfo.(type) {
+ case *matcher.InvalidMatchErrorMatcher:
+ reportBuilder.AddIssue(false, matchErrorWrongTypeAssertion, reportBuilder.FormatExpr(mtchr.Clone.Args[0]))
+
+ case *matcher.MatchErrorMatcherWithErrFunc:
+ if m.NumArgs() == m.AllowedNumArgs() {
+ if !m.IsSecondArgString() {
+ reportBuilder.AddIssue(false, matchErrorNoFuncDescription)
+ }
+ return true
+ }
+
+ if m.NumArgs() == 1 {
+ reportBuilder.AddIssue(false, matchErrorMissingDescription)
+ return true
+ }
+
+ case *matcher.MatchErrorMatcherWithErr,
+ *matcher.MatchErrorMatcherWithMatcher,
+ *matcher.MatchErrorMatcherWithString:
+ // continue
+ default:
+ return false
+ }
+
+ if mtchrInfo.NumArgs() == mtchrInfo.AllowedNumArgs() {
+ return true
+ }
+
+ if mtchrInfo.NumArgs() > mtchrInfo.AllowedNumArgs() {
+ var newArgsSuggestion []ast.Expr
+ for i := 0; i < mtchrInfo.AllowedNumArgs(); i++ {
+ newArgsSuggestion = append(newArgsSuggestion, mtchr.Clone.Args[i])
+ }
+ mtchr.Clone.Args = newArgsSuggestion
+ reportBuilder.AddIssue(false, matchErrorRedundantArg)
+ return true
+ }
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/missingassertionrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/missingassertionrule.go
new file mode 100644
index 000000000..43fc58bf6
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/missingassertionrule.go
@@ -0,0 +1,27 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/gomegainfo"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const missingAssertionMessage = `%q: missing assertion method. Expected %s`
+
+type MissingAssertionRule struct{}
+
+func (r MissingAssertionRule) isApplied(gexp *expression.GomegaExpression) bool {
+ return gexp.IsMissingAssertion()
+}
+
+func (r MissingAssertionRule) Apply(gexp *expression.GomegaExpression, _ types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ actualMethodName := gexp.GetActualFuncName()
+ reportBuilder.AddIssue(false, missingAssertionMessage, actualMethodName, gomegainfo.GetAllowedAssertionMethods(actualMethodName))
+
+ return true
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/nilcomparerule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/nilcomparerule.go
new file mode 100644
index 000000000..fc3cd49e5
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/nilcomparerule.go
@@ -0,0 +1,75 @@
+package rules
+
+import (
+ "go/token"
+
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+const (
+ wrongNilWarningTemplate = "wrong nil assertion"
+ wrongErrWarningTemplate = "wrong error assertion"
+)
+
+type NilCompareRule struct{}
+
+func (r NilCompareRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ isErr, ruleApplied := r.isApplied(gexp, config)
+ if !ruleApplied {
+ return false
+ }
+
+ if gexp.MatcherTypeIs(matcher.BoolValueFalse) {
+ gexp.ReverseAssertionFuncLogic()
+ }
+
+ r.handleNilBeBoolMatcher(gexp, gexp.GetActualArg().(*actual.NilComparisonPayload), reportBuilder, isErr)
+
+ return true
+}
+
+func (r NilCompareRule) isApplied(gexp *expression.GomegaExpression, config types.Config) (bool, bool) {
+ if !gexp.MatcherTypeIs(matcher.EqualBoolValueMatcherType | matcher.BeTrueMatcherType | matcher.BeFalseMatcherType) {
+ return false, false
+ }
+
+ actl, ok := gexp.GetActualArg().(*actual.NilComparisonPayload)
+ if !ok {
+ return false, false
+ }
+
+ isErr := actl.IsError() && !bool(config.SuppressErr)
+
+ if !isErr && bool(config.SuppressNil) {
+ return isErr, false
+ }
+
+ return isErr, true
+}
+
+func (r NilCompareRule) handleNilBeBoolMatcher(gexp *expression.GomegaExpression, actl *actual.NilComparisonPayload, reportBuilder *reports.Builder, isErr bool) {
+ template := wrongNilWarningTemplate
+ if isErr {
+ template = wrongErrWarningTemplate
+ if actl.IsFunc() {
+ gexp.SetMatcherSucceed()
+ } else {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherHaveOccurred()
+ }
+ } else {
+ gexp.SetMatcherBeNil()
+ }
+
+ gexp.ReplaceActual(actl.GetValueExpr())
+
+ if actl.GetOp() == token.NEQ {
+ gexp.ReverseAssertionFuncLogic()
+ }
+
+ reportBuilder.AddIssue(true, template)
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/rule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/rule.go
new file mode 100644
index 000000000..cf331c21c
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/rule.go
@@ -0,0 +1,61 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+type Rule interface {
+ Apply(*expression.GomegaExpression, types.Config, *reports.Builder) bool
+}
+
+var rules = Rules{
+ &ForceExpectToRule{},
+ &LenRule{},
+ &CapRule{},
+ &ComparisonRule{},
+ &NilCompareRule{},
+ &ComparePointRule{},
+ &ErrorEqualNilRule{},
+ &MatchErrorRule{},
+ getMatcherOnlyRules(),
+ &EqualDifferentTypesRule{},
+ &HaveOccurredRule{},
+ &SucceedRule{},
+}
+
+var asyncRules = Rules{
+ &AsyncFuncCallRule{},
+ &AsyncTimeIntervalsRule{},
+ &ErrorEqualNilRule{},
+ &MatchErrorRule{},
+ &AsyncSucceedRule{},
+ getMatcherOnlyRules(),
+}
+
+func GetRules() Rules {
+ return rules
+}
+
+func GetAsyncRules() Rules {
+ return asyncRules
+}
+
+type Rules []Rule
+
+func (r Rules) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ for _, rule := range r {
+ if rule.Apply(gexp, config, reportBuilder) {
+ return true
+ }
+ }
+
+ return false
+}
+
+var missingAssertionRule = MissingAssertionRule{}
+
+func GetMissingAssertionRule() Rule {
+ return missingAssertionRule
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/succeedrule.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/succeedrule.go
new file mode 100644
index 000000000..6a5167a8a
--- /dev/null
+++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/rules/succeedrule.go
@@ -0,0 +1,41 @@
+package rules
+
+import (
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/actual"
+ "github.com/nunnatsa/ginkgolinter/internal/expression/matcher"
+ "github.com/nunnatsa/ginkgolinter/internal/reports"
+ "github.com/nunnatsa/ginkgolinter/types"
+)
+
+type SucceedRule struct{}
+
+func (r SucceedRule) isApplied(gexp *expression.GomegaExpression) bool {
+ return !gexp.IsAsync() && gexp.MatcherTypeIs(matcher.SucceedMatcherType)
+}
+
+func (r SucceedRule) Apply(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder) bool {
+ if !r.isApplied(gexp) {
+ return false
+ }
+
+ if !gexp.ActualArgTypeIs(actual.ErrorTypeArgType) {
+ if gexp.IsActualTuple() {
+ reportBuilder.AddIssue(false, "the Success matcher does not support multiple values")
+ } else {
+ reportBuilder.AddIssue(false, "asserting a non-error type with Succeed matcher")
+ }
+ return true
+ }
+
+ if bool(config.ForceSucceedForFuncs) && !gexp.GetActualArg().(*actual.ErrPayload).IsFunc() {
+ gexp.ReverseAssertionFuncLogic()
+ gexp.SetMatcherHaveOccurred()
+
+ reportBuilder.AddIssue(true, "prefer using the HaveOccurred matcher for non-function error value, instead of Succeed")
+
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go b/vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go
index 574fdfadf..188b2b5f9 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go
@@ -1,24 +1,16 @@
package linter
import (
- "bytes"
- "fmt"
"go/ast"
- "go/constant"
- "go/printer"
- "go/token"
- gotypes "go/types"
- "reflect"
- "github.com/go-toolsmith/astcopy"
"golang.org/x/tools/go/analysis"
+ "github.com/nunnatsa/ginkgolinter/internal/expression"
+ "github.com/nunnatsa/ginkgolinter/internal/formatter"
"github.com/nunnatsa/ginkgolinter/internal/ginkgohandler"
"github.com/nunnatsa/ginkgolinter/internal/gomegahandler"
- "github.com/nunnatsa/ginkgolinter/internal/interfaces"
- "github.com/nunnatsa/ginkgolinter/internal/intervals"
"github.com/nunnatsa/ginkgolinter/internal/reports"
- "github.com/nunnatsa/ginkgolinter/internal/reverseassertion"
+ "github.com/nunnatsa/ginkgolinter/internal/rules"
"github.com/nunnatsa/ginkgolinter/types"
)
@@ -26,62 +18,6 @@ import (
//
// For more details, look at the README.md file
-const (
- linterName = "ginkgo-linter"
- wrongLengthWarningTemplate = "wrong length assertion"
- wrongCapWarningTemplate = "wrong cap assertion"
- wrongNilWarningTemplate = "wrong nil assertion"
- wrongBoolWarningTemplate = "wrong boolean assertion"
- wrongErrWarningTemplate = "wrong error assertion"
- wrongCompareWarningTemplate = "wrong comparison assertion"
- doubleNegativeWarningTemplate = "avoid double negative assertion"
- valueInEventually = "use a function call in %s. This actually checks nothing, because %s receives the function returned value, instead of function itself, and this value is never changed"
- comparePointerToValue = "comparing a pointer to a value will always fail"
- missingAssertionMessage = linterName + `: %q: missing assertion method. Expected %s`
- focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code. Consider to replace with %q"
- focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code. Consider to remove it"
- compareDifferentTypes = "use %[1]s with different types: Comparing %[2]s with %[3]s; either change the expected value type if possible, or use the BeEquivalentTo() matcher, instead of %[1]s()"
- matchErrorArgWrongType = "the MatchError matcher used to assert a non error type (%s)"
- matchErrorWrongTypeAssertion = "MatchError first parameter (%s) must be error, string, GomegaMatcher or func(error)bool are allowed"
- matchErrorMissingDescription = "missing function description as second parameter of MatchError"
- matchErrorRedundantArg = "redundant MatchError arguments; consider removing them"
- matchErrorNoFuncDescription = "The second parameter of MatchError must be the function description (string)"
- forceExpectToTemplate = "must not use Expect with %s"
- useBeforeEachTemplate = "use BeforeEach() to assign variable %s"
-)
-
-const ( // gomega matchers
- beEmpty = "BeEmpty"
- beEquivalentTo = "BeEquivalentTo"
- beFalse = "BeFalse"
- beIdenticalTo = "BeIdenticalTo"
- beNil = "BeNil"
- beNumerically = "BeNumerically"
- beTrue = "BeTrue"
- beZero = "BeZero"
- equal = "Equal"
- haveLen = "HaveLen"
- haveCap = "HaveCap"
- haveOccurred = "HaveOccurred"
- haveValue = "HaveValue"
- not = "Not"
- omega = "Ω"
- succeed = "Succeed"
- and = "And"
- or = "Or"
- withTransform = "WithTransform"
- matchError = "MatchError"
-)
-
-const ( // gomega actuals
- expect = "Expect"
- expectWithOffset = "ExpectWithOffset"
- eventually = "Eventually"
- eventuallyWithOffset = "EventuallyWithOffset"
- consistently = "Consistently"
- consistentlyWithOffset = "ConsistentlyWithOffset"
-)
-
type GinkgoLinter struct {
config *types.Config
}
@@ -94,7 +30,7 @@ func NewGinkgoLinter(config *types.Config) *GinkgoLinter {
}
// Run is the main assertion function
-func (l *GinkgoLinter) Run(pass *analysis.Pass) (interface{}, error) {
+func (l *GinkgoLinter) Run(pass *analysis.Pass) (any, error) {
for _, file := range pass.Files {
fileConfig := l.config.Clone()
@@ -102,39 +38,20 @@ func (l *GinkgoLinter) Run(pass *analysis.Pass) (interface{}, error) {
fileConfig.UpdateFromFile(cm)
- gomegaHndlr := gomegahandler.GetGomegaHandler(file)
+ gomegaHndlr := gomegahandler.GetGomegaHandler(file, pass)
ginkgoHndlr := ginkgohandler.GetGinkgoHandler(file)
if gomegaHndlr == nil && ginkgoHndlr == nil { // no gomega or ginkgo imports => no use in gomega in this file; nothing to do here
continue
}
- timePks := ""
- for _, imp := range file.Imports {
- if imp.Path.Value == `"time"` {
- if imp.Name == nil {
- timePks = "time"
- } else {
- timePks = imp.Name.Name
- }
- }
- }
-
ast.Inspect(file, func(n ast.Node) bool {
if ginkgoHndlr != nil {
goDeeper := false
spec, ok := n.(*ast.ValueSpec)
if ok {
for _, val := range spec.Values {
- if exp, ok := val.(*ast.CallExpr); ok {
- if bool(fileConfig.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, exp) {
- goDeeper = true
- }
-
- if bool(fileConfig.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, exp) {
- goDeeper = true
- }
- }
+ goDeeper = ginkgoHndlr.HandleGinkgoSpecs(val, fileConfig, pass) || goDeeper
}
}
if goDeeper {
@@ -147,1527 +64,68 @@ func (l *GinkgoLinter) Run(pass *analysis.Pass) (interface{}, error) {
return true
}
- config := fileConfig.Clone()
-
- if comments, ok := cm[stmt]; ok {
- config.UpdateFromComment(comments)
- }
-
// search for function calls
assertionExp, ok := stmt.X.(*ast.CallExpr)
if !ok {
return true
}
+ config := fileConfig.Clone()
+ if comments, ok := cm[stmt]; ok {
+ config.UpdateFromComment(comments)
+ }
+
if ginkgoHndlr != nil {
- goDeeper := false
- if bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, assertionExp) {
- goDeeper = true
- }
- if bool(config.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, assertionExp) {
- goDeeper = true
- }
- if goDeeper {
+ if ginkgoHndlr.HandleGinkgoSpecs(assertionExp, config, pass) {
return true
}
}
- // no more ginkgo checks. From here it's only gomega. So if there is no gomega handler, exit here. This is
- // mostly to prevent nil pointer error.
+ // no more ginkgo checks. From here it's only gomega. So if there is no gomega handler, exit here.
if gomegaHndlr == nil {
return true
}
- assertionFunc, ok := assertionExp.Fun.(*ast.SelectorExpr)
- if !ok {
- checkNoAssertion(pass, assertionExp, gomegaHndlr)
- return true
- }
-
- if !isAssertionFunc(assertionFunc.Sel.Name) {
- checkNoAssertion(pass, assertionExp, gomegaHndlr)
+ gexp, ok := expression.New(assertionExp, pass, gomegaHndlr, getTimePkg(file))
+ if !ok || gexp == nil {
return true
}
- actualExpr := gomegaHndlr.GetActualExpr(assertionFunc)
- if actualExpr == nil {
- return true
- }
-
- return checkExpression(pass, config, assertionExp, actualExpr, gomegaHndlr, timePks)
+ reportBuilder := reports.NewBuilder(assertionExp, formatter.NewGoFmtFormatter(pass.Fset))
+ return checkGomegaExpression(gexp, config, reportBuilder, pass)
})
}
return nil, nil
}
-func checkAssignmentsInContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool {
- foundSomething := false
- if ginkgoHndlr.IsWrapContainer(exp) {
- for _, arg := range exp.Args {
- if fn, ok := arg.(*ast.FuncLit); ok {
- if fn.Body != nil {
- if checkAssignments(pass, fn.Body.List) {
- foundSomething = true
- }
- break
- }
- }
- }
- }
-
- return foundSomething
-}
-
-func checkAssignments(pass *analysis.Pass, list []ast.Stmt) bool {
- foundSomething := false
- for _, stmt := range list {
- switch st := stmt.(type) {
- case *ast.DeclStmt:
- if gen, ok := st.Decl.(*ast.GenDecl); ok {
- if gen.Tok != token.VAR {
- continue
- }
- for _, spec := range gen.Specs {
- if valSpec, ok := spec.(*ast.ValueSpec); ok {
- if checkAssignmentsValues(pass, valSpec.Names, valSpec.Values) {
- foundSomething = true
- }
- }
- }
- }
-
- case *ast.AssignStmt:
- for i, val := range st.Rhs {
- if !is[*ast.FuncLit](val) {
- if id, isIdent := st.Lhs[i].(*ast.Ident); isIdent && id.Name != "_" {
- reportNoFix(pass, id.Pos(), useBeforeEachTemplate, id.Name)
- foundSomething = true
- }
- }
- }
-
- case *ast.IfStmt:
- if st.Body != nil {
- if checkAssignments(pass, st.Body.List) {
- foundSomething = true
- }
- }
- if st.Else != nil {
- if block, isBlock := st.Else.(*ast.BlockStmt); isBlock {
- if checkAssignments(pass, block.List) {
- foundSomething = true
- }
- }
- }
- }
- }
-
- return foundSomething
-}
-
-func checkAssignmentsValues(pass *analysis.Pass, names []*ast.Ident, values []ast.Expr) bool {
- foundSomething := false
- for i, val := range values {
- if !is[*ast.FuncLit](val) {
- reportNoFix(pass, names[i].Pos(), useBeforeEachTemplate, names[i].Name)
- foundSomething = true
- }
- }
-
- return foundSomething
-}
-
-func checkFocusContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool {
- foundFocus := false
- isFocus, id := ginkgoHndlr.GetFocusContainerName(exp)
- if isFocus {
- reportNewName(pass, id, id.Name[1:], focusContainerFound, id.Name)
- foundFocus = true
- }
-
- if id != nil && ginkgohandler.IsContainer(id.Name) {
- for _, arg := range exp.Args {
- if ginkgoHndlr.IsFocusSpec(arg) {
- reportNoFix(pass, arg.Pos(), focusSpecFound)
- foundFocus = true
- } else if callExp, ok := arg.(*ast.CallExpr); ok {
- if checkFocusContainer(pass, ginkgoHndlr, callExp) { // handle table entries
- foundFocus = true
- }
- }
- }
- }
-
- return foundFocus
-}
-
-func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler, timePkg string) bool {
- expr := astcopy.CallExpr(assertionExp)
-
- reportBuilder := reports.NewBuilder(pass.Fset, expr)
-
+func checkGomegaExpression(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder, pass *analysis.Pass) bool {
goNested := false
- if checkAsyncAssertion(pass, config, expr, actualExpr, handler, reportBuilder, timePkg) {
+ if rules.GetMissingAssertionRule().Apply(gexp, config, reportBuilder) {
goNested = true
} else {
-
- actualArg := getActualArg(actualExpr, handler)
- if actualArg == nil {
- return true
- }
-
- if config.ForceExpectTo {
- goNested = forceExpectTo(expr, handler, reportBuilder) || goNested
+ if gexp.IsAsync() {
+ rules.GetAsyncRules().Apply(gexp, config, reportBuilder)
+ goNested = true
+ } else {
+ rules.GetRules().Apply(gexp, config, reportBuilder)
}
-
- goNested = doCheckExpression(pass, config, assertionExp, actualArg, expr, handler, reportBuilder) || goNested
}
if reportBuilder.HasReport() {
- reportBuilder.SetFixOffer(pass.Fset, expr)
+ reportBuilder.SetFixOffer(gexp.GetClone())
pass.Report(reportBuilder.Build())
}
return goNested
}
-func forceExpectTo(expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- if asrtFun, ok := expr.Fun.(*ast.SelectorExpr); ok {
- if actualFuncName, ok := handler.GetActualFuncName(expr); ok && actualFuncName == expect {
- var (
- name string
- newIdent *ast.Ident
- )
-
- switch name = asrtFun.Sel.Name; name {
- case "Should":
- newIdent = ast.NewIdent("To")
- case "ShouldNot":
- newIdent = ast.NewIdent("ToNot")
- default:
- return false
- }
-
- handler.ReplaceFunction(expr, newIdent)
- reportBuilder.AddIssue(true, fmt.Sprintf(forceExpectToTemplate, name))
- return true
- }
- }
-
- return false
-}
-
-func doCheckExpression(pass *analysis.Pass, config types.Config, assertionExp *ast.CallExpr, actualArg ast.Expr, expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- if !bool(config.SuppressLen) && isActualIsLenFunc(actualArg) {
- return checkLengthMatcher(expr, pass, handler, reportBuilder)
-
- } else if !bool(config.SuppressLen) && isActualIsCapFunc(actualArg) {
- return checkCapMatcher(expr, handler, reportBuilder)
-
- } else if nilable, compOp := getNilableFromComparison(actualArg); nilable != nil {
- if isExprError(pass, nilable) {
- if config.SuppressErr {
- return true
- }
- } else if config.SuppressNil {
- return true
- }
-
- return checkNilMatcher(expr, pass, nilable, handler, compOp == token.NEQ, reportBuilder)
-
- } else if first, second, op, ok := isComparison(pass, actualArg); ok {
- matcher, shouldContinue := startCheckComparison(expr, handler)
- if !shouldContinue {
- return false
- }
- if !config.SuppressLen {
- if isActualIsLenFunc(first) {
- if handleLenComparison(pass, expr, matcher, first, second, op, handler, reportBuilder) {
- return false
- }
- }
- if isActualIsCapFunc(first) {
- if handleCapComparison(expr, matcher, first, second, op, handler, reportBuilder) {
- return false
- }
- }
- }
- return bool(config.SuppressCompare) || checkComparison(expr, pass, matcher, handler, first, second, op, reportBuilder)
-
- } else if checkMatchError(pass, assertionExp, actualArg, handler, reportBuilder) {
- return false
- } else if isExprError(pass, actualArg) {
- return bool(config.SuppressErr) || checkNilError(pass, expr, handler, actualArg, reportBuilder)
-
- } else if checkPointerComparison(pass, config, assertionExp, expr, actualArg, handler, reportBuilder) {
- return false
- } else if !handleAssertionOnly(pass, config, expr, handler, actualArg, reportBuilder) {
- return false
- } else if !config.SuppressTypeCompare {
- return !checkEqualWrongType(pass, assertionExp, actualArg, handler, reportBuilder)
- }
-
- return true
-}
-
-func checkMatchError(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- matcher, ok := origExp.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- return doCheckMatchError(pass, origExp, matcher, actualArg, handler, reportBuilder)
-}
-
-func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- name, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return false
- }
- switch name {
- case matchError:
- case not:
- nested, ok := matcher.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- return doCheckMatchError(pass, origExp, nested, actualArg, handler, reportBuilder)
- case and, or:
- res := false
- for _, arg := range matcher.Args {
- if nested, ok := arg.(*ast.CallExpr); ok {
- if valid := doCheckMatchError(pass, origExp, nested, actualArg, handler, reportBuilder); valid {
- res = true
- }
- }
- }
- return res
- default:
- return false
- }
-
- if !isExprError(pass, actualArg) {
- reportBuilder.AddIssue(false, matchErrorArgWrongType, goFmt(pass.Fset, actualArg))
- }
-
- expr := astcopy.CallExpr(matcher)
-
- validAssertion, requiredParams := checkMatchErrorAssertion(pass, matcher)
- if !validAssertion {
- reportBuilder.AddIssue(false, matchErrorWrongTypeAssertion, goFmt(pass.Fset, matcher.Args[0]))
- }
-
- numParams := len(matcher.Args)
- if numParams == requiredParams {
- if numParams == 2 {
- t := pass.TypesInfo.TypeOf(matcher.Args[1])
- if !gotypes.Identical(t, gotypes.Typ[gotypes.String]) {
- reportBuilder.AddIssue(false, matchErrorNoFuncDescription)
- return true
- }
+func getTimePkg(file *ast.File) string {
+ timePkg := "time"
+ for _, imp := range file.Imports {
+ if imp.Path.Value == `"time"` && imp.Name != nil {
+ timePkg = imp.Name.Name
}
- return true
}
- if requiredParams == 2 && numParams == 1 {
- reportBuilder.AddIssue(false, matchErrorMissingDescription)
- return true
- }
-
- var newArgsSuggestion = []ast.Expr{expr.Args[0]}
- if requiredParams == 2 {
- newArgsSuggestion = append(newArgsSuggestion, expr.Args[1])
- }
- expr.Args = newArgsSuggestion
-
- reportBuilder.AddIssue(true, matchErrorRedundantArg)
- return true
-}
-
-func checkMatchErrorAssertion(pass *analysis.Pass, matcher *ast.CallExpr) (bool, int) {
- if isErrorMatcherValidArg(pass, matcher.Args[0]) {
- return true, 1
- }
-
- t1 := pass.TypesInfo.TypeOf(matcher.Args[0])
- if isFuncErrBool(t1) {
- return true, 2
- }
-
- return false, 0
-}
-
-// isFuncErrBool checks if a function is with the signature `func(error) bool`
-func isFuncErrBool(t gotypes.Type) bool {
- sig, ok := t.(*gotypes.Signature)
- if !ok {
- return false
- }
- if sig.Params().Len() != 1 || sig.Results().Len() != 1 {
- return false
- }
-
- if !interfaces.ImplementsError(sig.Params().At(0).Type()) {
- return false
- }
-
- b, ok := sig.Results().At(0).Type().(*gotypes.Basic)
- if ok && b.Name() == "bool" && b.Info() == gotypes.IsBoolean && b.Kind() == gotypes.Bool {
- return true
- }
-
- return false
-}
-
-func isErrorMatcherValidArg(pass *analysis.Pass, arg ast.Expr) bool {
- if isExprError(pass, arg) {
- return true
- }
-
- if t, ok := pass.TypesInfo.TypeOf(arg).(*gotypes.Basic); ok && t.Kind() == gotypes.String {
- return true
- }
-
- t := pass.TypesInfo.TypeOf(arg)
-
- return interfaces.ImplementsGomegaMatcher(t)
-}
-
-func checkEqualWrongType(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- matcher, ok := origExp.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- return checkEqualDifferentTypes(pass, matcher, actualArg, handler, false, reportBuilder)
-}
-
-func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, parentPointer bool, reportBuilder *reports.Builder) bool {
- matcherFuncName, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return false
- }
-
- actualType := pass.TypesInfo.TypeOf(actualArg)
-
- switch matcherFuncName {
- case equal, beIdenticalTo: // continue
- case and, or:
- foundIssue := false
- for _, nestedExp := range matcher.Args {
- nested, ok := nestedExp.(*ast.CallExpr)
- if !ok {
- continue
- }
- if checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder) {
- foundIssue = true
- }
- }
-
- return foundIssue
- case withTransform:
- nested, ok := matcher.Args[1].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- matcherFuncName, ok = handler.GetActualFuncName(nested)
- switch matcherFuncName {
- case equal, beIdenticalTo:
- case not:
- return checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder)
- default:
- return false
- }
-
- if t := getFuncType(pass, matcher.Args[0]); t != nil {
- actualType = t
- matcher = nested
-
- if !ok {
- return false
- }
- } else {
- return checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder)
- }
-
- case not:
- nested, ok := matcher.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- return checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder)
-
- case haveValue:
- nested, ok := matcher.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- return checkEqualDifferentTypes(pass, nested, actualArg, handler, true, reportBuilder)
- default:
- return false
- }
-
- matcherValue := matcher.Args[0]
-
- switch act := actualType.(type) {
- case *gotypes.Tuple:
- actualType = act.At(0).Type()
- case *gotypes.Pointer:
- if parentPointer {
- actualType = act.Elem()
- }
- }
-
- matcherType := pass.TypesInfo.TypeOf(matcherValue)
-
- if !reflect.DeepEqual(matcherType, actualType) {
- // Equal can handle comparison of interface and a value that implements it
- if isImplementing(matcherType, actualType) || isImplementing(actualType, matcherType) {
- return false
- }
-
- reportBuilder.AddIssue(false, compareDifferentTypes, matcherFuncName, actualType, matcherType)
- return true
- }
-
- return false
-}
-
-func getFuncType(pass *analysis.Pass, expr ast.Expr) gotypes.Type {
- switch f := expr.(type) {
- case *ast.FuncLit:
- if f.Type != nil && f.Type.Results != nil && len(f.Type.Results.List) > 0 {
- return pass.TypesInfo.TypeOf(f.Type.Results.List[0].Type)
- }
- case *ast.Ident:
- a := pass.TypesInfo.TypeOf(f)
- if sig, ok := a.(*gotypes.Signature); ok && sig.Results().Len() > 0 {
- return sig.Results().At(0).Type()
- }
- }
-
- return nil
-}
-
-func isImplementing(ifs, impl gotypes.Type) bool {
- if gotypes.IsInterface(ifs) {
-
- var (
- theIfs *gotypes.Interface
- ok bool
- )
-
- for {
- theIfs, ok = ifs.(*gotypes.Interface)
- if ok {
- break
- }
- ifs = ifs.Underlying()
- }
-
- return gotypes.Implements(impl, theIfs)
- }
- return false
-}
-
-// be careful - never change origExp!!! only modify its clone, expr!!!
-func checkPointerComparison(pass *analysis.Pass, config types.Config, origExp *ast.CallExpr, expr *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- if !isPointer(pass, actualArg) {
- return false
- }
- matcher, ok := origExp.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- matcherFuncName, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return false
- }
-
- // not using recurse here, since we need the original expression, in order to get the TypeInfo, while we should not
- // modify it.
- for matcherFuncName == not {
- reverseAssertionFuncLogic(expr)
- expr.Args[0] = expr.Args[0].(*ast.CallExpr).Args[0]
- matcher, ok = matcher.Args[0].(*ast.CallExpr)
- if !ok {
- return false
- }
-
- matcherFuncName, ok = handler.GetActualFuncName(matcher)
- if !ok {
- return false
- }
- }
-
- switch matcherFuncName {
- case equal, beIdenticalTo, beEquivalentTo:
- arg := matcher.Args[0]
- if isPointer(pass, arg) {
- return false
- }
- if isNil(arg) {
- return false
- }
- if isInterface(pass, arg) {
- return false
- }
- case beFalse, beTrue, beNumerically:
- default:
- return false
- }
-
- handleAssertionOnly(pass, config, expr, handler, actualArg, reportBuilder)
-
- args := []ast.Expr{astcopy.CallExpr(expr.Args[0].(*ast.CallExpr))}
- handler.ReplaceFunction(expr.Args[0].(*ast.CallExpr), ast.NewIdent(haveValue))
- expr.Args[0].(*ast.CallExpr).Args = args
-
- reportBuilder.AddIssue(true, comparePointerToValue)
- return true
-}
-
-// check async assertion does not assert function call. This is a real bug in the test. In this case, the assertion is
-// done on the returned value, instead of polling the result of a function, for instance.
-func checkAsyncAssertion(pass *analysis.Pass, config types.Config, expr *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder, timePkg string) bool {
- funcName, ok := handler.GetActualFuncName(actualExpr)
- if !ok {
- return false
- }
-
- var funcIndex int
- switch funcName {
- case eventually, consistently:
- funcIndex = 0
- case eventuallyWithOffset, consistentlyWithOffset:
- funcIndex = 1
- default:
- return false
- }
-
- if !config.SuppressAsync && len(actualExpr.Args) > funcIndex {
- t := pass.TypesInfo.TypeOf(actualExpr.Args[funcIndex])
-
- // skip context variable, if used as first argument
- if "context.Context" == t.String() {
- funcIndex++
- }
-
- if len(actualExpr.Args) > funcIndex {
- if fun, funcCall := actualExpr.Args[funcIndex].(*ast.CallExpr); funcCall {
- t = pass.TypesInfo.TypeOf(fun)
- if !isValidAsyncValueType(t) {
- actualExpr = handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr))
-
- if len(fun.Args) > 0 {
- origArgs := actualExpr.Args
- origFunc := actualExpr.Fun
- actualExpr.Args = fun.Args
-
- origArgs[funcIndex] = fun.Fun
- call := &ast.SelectorExpr{
- Sel: ast.NewIdent("WithArguments"),
- X: &ast.CallExpr{
- Fun: origFunc,
- Args: origArgs,
- },
- }
-
- actualExpr.Fun = call
- actualExpr.Args = fun.Args
- actualExpr = actualExpr.Fun.(*ast.SelectorExpr).X.(*ast.CallExpr)
- } else {
- actualExpr.Args[funcIndex] = fun.Fun
- }
-
- reportBuilder.AddIssue(true, valueInEventually, funcName, funcName)
- }
- }
- }
-
- if config.ValidateAsyncIntervals {
- intervals.CheckIntervals(pass, expr, actualExpr, reportBuilder, handler, timePkg, funcIndex)
- }
- }
-
- handleAssertionOnly(pass, config, expr, handler, actualExpr, reportBuilder)
- return true
-}
-
-func isValidAsyncValueType(t gotypes.Type) bool {
- switch t.(type) {
- // allow functions that return function or channel.
- case *gotypes.Signature, *gotypes.Chan, *gotypes.Pointer:
- return true
- case *gotypes.Named:
- return isValidAsyncValueType(t.Underlying())
- }
-
- return false
-}
-
-func startCheckComparison(exp *ast.CallExpr, handler gomegahandler.Handler) (*ast.CallExpr, bool) {
- matcher, ok := exp.Args[0].(*ast.CallExpr)
- if !ok {
- return nil, false
- }
-
- matcherFuncName, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return nil, false
- }
-
- switch matcherFuncName {
- case beTrue:
- case beFalse:
- reverseAssertionFuncLogic(exp)
- case equal:
- boolean, found := matcher.Args[0].(*ast.Ident)
- if !found {
- return nil, false
- }
-
- if boolean.Name == "false" {
- reverseAssertionFuncLogic(exp)
- } else if boolean.Name != "true" {
- return nil, false
- }
-
- case not:
- reverseAssertionFuncLogic(exp)
- exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0]
- return startCheckComparison(exp, handler)
-
- default:
- return nil, false
- }
-
- return matcher, true
-}
-
-func checkComparison(exp *ast.CallExpr, pass *analysis.Pass, matcher *ast.CallExpr, handler gomegahandler.Handler, first ast.Expr, second ast.Expr, op token.Token, reportBuilder *reports.Builder) bool {
- fun, ok := exp.Fun.(*ast.SelectorExpr)
- if !ok {
- return true
- }
-
- call := handler.GetActualExpr(fun)
- if call == nil {
- return true
- }
-
- switch op {
- case token.EQL:
- handleEqualComparison(pass, matcher, first, second, handler)
-
- case token.NEQ:
- reverseAssertionFuncLogic(exp)
- handleEqualComparison(pass, matcher, first, second, handler)
- case token.GTR, token.GEQ, token.LSS, token.LEQ:
- if !isNumeric(pass, first) {
- return true
- }
- handler.ReplaceFunction(matcher, ast.NewIdent(beNumerically))
- matcher.Args = []ast.Expr{
- &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s"`, op.String())},
- second,
- }
- default:
- return true
- }
-
- call.Args = []ast.Expr{first}
- reportBuilder.AddIssue(true, wrongCompareWarningTemplate)
- return false
-}
-
-func handleEqualComparison(pass *analysis.Pass, matcher *ast.CallExpr, first ast.Expr, second ast.Expr, handler gomegahandler.Handler) {
- if isZero(pass, second) {
- handler.ReplaceFunction(matcher, ast.NewIdent(beZero))
- matcher.Args = nil
- } else {
- t := pass.TypesInfo.TypeOf(first)
- if gotypes.IsInterface(t) {
- handler.ReplaceFunction(matcher, ast.NewIdent(beIdenticalTo))
- } else if is[*gotypes.Pointer](t) {
- handler.ReplaceFunction(matcher, ast.NewIdent(beIdenticalTo))
- } else {
- handler.ReplaceFunction(matcher, ast.NewIdent(equal))
- }
-
- matcher.Args = []ast.Expr{second}
- }
-}
-
-func handleLenComparison(pass *analysis.Pass, exp *ast.CallExpr, matcher *ast.CallExpr, first ast.Expr, second ast.Expr, op token.Token, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- switch op {
- case token.EQL:
- case token.NEQ:
- reverseAssertionFuncLogic(exp)
- default:
- return false
- }
-
- var eql *ast.Ident
- if isZero(pass, second) {
- eql = ast.NewIdent(beEmpty)
- } else {
- eql = ast.NewIdent(haveLen)
- matcher.Args = []ast.Expr{second}
- }
-
- handler.ReplaceFunction(matcher, eql)
- firstLen, ok := first.(*ast.CallExpr) // assuming it's len()
- if !ok {
- return false // should never happen
- }
-
- val := firstLen.Args[0]
- fun := handler.GetActualExpr(exp.Fun.(*ast.SelectorExpr))
- fun.Args = []ast.Expr{val}
-
- reportBuilder.AddIssue(true, wrongLengthWarningTemplate)
- return true
-}
-
-func handleCapComparison(exp *ast.CallExpr, matcher *ast.CallExpr, first ast.Expr, second ast.Expr, op token.Token, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- switch op {
- case token.EQL:
- case token.NEQ:
- reverseAssertionFuncLogic(exp)
- default:
- return false
- }
-
- eql := ast.NewIdent(haveCap)
- matcher.Args = []ast.Expr{second}
-
- handler.ReplaceFunction(matcher, eql)
- firstLen, ok := first.(*ast.CallExpr) // assuming it's len()
- if !ok {
- return false // should never happen
- }
-
- val := firstLen.Args[0]
- fun := handler.GetActualExpr(exp.Fun.(*ast.SelectorExpr))
- fun.Args = []ast.Expr{val}
-
- reportBuilder.AddIssue(true, wrongCapWarningTemplate)
- return true
-}
-
-// Check if the "actual" argument is a call to the golang built-in len() function
-func isActualIsLenFunc(actualArg ast.Expr) bool {
- return checkActualFuncName(actualArg, "len")
-}
-
-// Check if the "actual" argument is a call to the golang built-in len() function
-func isActualIsCapFunc(actualArg ast.Expr) bool {
- return checkActualFuncName(actualArg, "cap")
-}
-
-func checkActualFuncName(actualArg ast.Expr, name string) bool {
- lenArgExp, ok := actualArg.(*ast.CallExpr)
- if !ok {
- return false
- }
-
- lenFunc, ok := lenArgExp.Fun.(*ast.Ident)
- return ok && lenFunc.Name == name
-}
-
-// Check if matcher function is in one of the patterns we want to avoid
-func checkLengthMatcher(exp *ast.CallExpr, pass *analysis.Pass, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- matcher, ok := exp.Args[0].(*ast.CallExpr)
- if !ok {
- return true
- }
-
- matcherFuncName, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return true
- }
-
- switch matcherFuncName {
- case equal:
- handleEqualLenMatcher(matcher, pass, exp, handler, reportBuilder)
- return false
-
- case beZero:
- handleBeZero(exp, handler, reportBuilder)
- return false
-
- case beNumerically:
- return handleBeNumerically(matcher, pass, exp, handler, reportBuilder)
-
- case not:
- reverseAssertionFuncLogic(exp)
- exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0]
- return checkLengthMatcher(exp, pass, handler, reportBuilder)
-
- default:
- return true
- }
-}
-
-// Check if matcher function is in one of the patterns we want to avoid
-func checkCapMatcher(exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- matcher, ok := exp.Args[0].(*ast.CallExpr)
- if !ok {
- return true
- }
-
- matcherFuncName, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return true
- }
-
- switch matcherFuncName {
- case equal:
- handleEqualCapMatcher(matcher, exp, handler, reportBuilder)
- return false
-
- case beZero:
- handleCapBeZero(exp, handler, reportBuilder)
- return false
-
- case beNumerically:
- return handleCapBeNumerically(matcher, exp, handler, reportBuilder)
-
- case not:
- reverseAssertionFuncLogic(exp)
- exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0]
- return checkCapMatcher(exp, handler, reportBuilder)
-
- default:
- return true
- }
-}
-
-// Check if matcher function is in one of the patterns we want to avoid
-func checkNilMatcher(exp *ast.CallExpr, pass *analysis.Pass, nilable ast.Expr, handler gomegahandler.Handler, notEqual bool, reportBuilder *reports.Builder) bool {
- matcher, ok := exp.Args[0].(*ast.CallExpr)
- if !ok {
- return true
- }
-
- matcherFuncName, ok := handler.GetActualFuncName(matcher)
- if !ok {
- return true
- }
-
- switch matcherFuncName {
- case equal:
- handleEqualNilMatcher(matcher, pass, exp, handler, nilable, notEqual, reportBuilder)
-
- case beTrue:
- handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, reportBuilder)
-
- case beFalse:
- reverseAssertionFuncLogic(exp)
- handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, reportBuilder)
-
- case not:
- reverseAssertionFuncLogic(exp)
- exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0]
- return checkNilMatcher(exp, pass, nilable, handler, notEqual, reportBuilder)
-
- default:
- return true
- }
- return false
-}
-
-func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, reportBuilder *reports.Builder) bool {
- if len(assertionExp.Args) == 0 {
- return true
- }
-
- equalFuncExpr, ok := assertionExp.Args[0].(*ast.CallExpr)
- if !ok {
- return true
- }
-
- funcName, ok := handler.GetActualFuncName(equalFuncExpr)
- if !ok {
- return true
- }
-
- switch funcName {
- case beNil: // no additional processing needed.
- case equal:
-
- if len(equalFuncExpr.Args) == 0 {
- return true
- }
-
- nilable, ok := equalFuncExpr.Args[0].(*ast.Ident)
- if !ok || nilable.Name != "nil" {
- return true
- }
-
- case not:
- reverseAssertionFuncLogic(assertionExp)
- assertionExp.Args[0] = assertionExp.Args[0].(*ast.CallExpr).Args[0]
- return checkNilError(pass, assertionExp, handler, actualArg, reportBuilder)
- default:
- return true
- }
-
- var newFuncName string
- if is[*ast.CallExpr](actualArg) {
- newFuncName = succeed
- } else {
- reverseAssertionFuncLogic(assertionExp)
- newFuncName = haveOccurred
- }
-
- handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(newFuncName))
- equalFuncExpr.Args = nil
-
- reportBuilder.AddIssue(true, wrongErrWarningTemplate)
- return false
-}
-
-// handleAssertionOnly checks use-cases when the actual value is valid, but only the assertion should be fixed
-// it handles:
-//
-// Equal(nil) => BeNil()
-// Equal(true) => BeTrue()
-// Equal(false) => BeFalse()
-// HaveLen(0) => BeEmpty()
-func handleAssertionOnly(pass *analysis.Pass, config types.Config, expr *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, reportBuilder *reports.Builder) bool {
- if len(expr.Args) == 0 {
- return true
- }
-
- equalFuncExpr, ok := expr.Args[0].(*ast.CallExpr)
- if !ok {
- return true
- }
-
- funcName, ok := handler.GetActualFuncName(equalFuncExpr)
- if !ok {
- return true
- }
-
- switch funcName {
- case equal:
- if len(equalFuncExpr.Args) == 0 {
- return true
- }
-
- tkn, ok := equalFuncExpr.Args[0].(*ast.Ident)
- if !ok {
- return true
- }
-
- var replacement string
- var template string
- switch tkn.Name {
- case "nil":
- if config.SuppressNil {
- return true
- }
- replacement = beNil
- template = wrongNilWarningTemplate
- case "true":
- replacement = beTrue
- template = wrongBoolWarningTemplate
- case "false":
- if isNegativeAssertion(expr) {
- reverseAssertionFuncLogic(expr)
- replacement = beTrue
- } else {
- replacement = beFalse
- }
- template = wrongBoolWarningTemplate
- default:
- return true
- }
-
- handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(replacement))
- equalFuncExpr.Args = nil
-
- reportBuilder.AddIssue(true, template)
- return false
-
- case beFalse:
- if isNegativeAssertion(expr) {
- reverseAssertionFuncLogic(expr)
- handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(beTrue))
- reportBuilder.AddIssue(true, doubleNegativeWarningTemplate)
- return false
- }
- return false
-
- case haveLen:
- if config.AllowHaveLen0 {
- return true
- }
-
- if len(equalFuncExpr.Args) > 0 {
- if isZero(pass, equalFuncExpr.Args[0]) {
- handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(beEmpty))
- equalFuncExpr.Args = nil
- reportBuilder.AddIssue(true, wrongLengthWarningTemplate)
- return false
- }
- }
-
- return true
-
- case not:
- reverseAssertionFuncLogic(expr)
- expr.Args[0] = expr.Args[0].(*ast.CallExpr).Args[0]
- return handleAssertionOnly(pass, config, expr, handler, actualArg, reportBuilder)
- default:
- return true
- }
-}
-
-func isZero(pass *analysis.Pass, arg ast.Expr) bool {
- if val, ok := arg.(*ast.BasicLit); ok && val.Kind == token.INT && val.Value == "0" {
- return true
- }
- info, ok := pass.TypesInfo.Types[arg]
- if ok {
- if t, ok := info.Type.(*gotypes.Basic); ok && t.Kind() == gotypes.Int && info.Value != nil {
- if i, ok := constant.Int64Val(info.Value); ok && i == 0 {
- return true
- }
- }
- } else if val, ok := arg.(*ast.Ident); ok && val.Obj != nil && val.Obj.Kind == ast.Con {
- if spec, ok := val.Obj.Decl.(*ast.ValueSpec); ok {
- if len(spec.Values) == 1 {
- if value, ok := spec.Values[0].(*ast.BasicLit); ok && value.Kind == token.INT && value.Value == "0" {
- return true
- }
- }
- }
- }
-
- return false
-}
-
-// getActualArg checks that the function is an assertion's actual function and return the "actual" parameter. If the
-// function is not assertion's actual function, return nil.
-func getActualArg(actualExpr *ast.CallExpr, handler gomegahandler.Handler) ast.Expr {
- funcName, ok := handler.GetActualFuncName(actualExpr)
- if !ok {
- return nil
- }
-
- switch funcName {
- case expect, omega:
- return actualExpr.Args[0]
- case expectWithOffset:
- return actualExpr.Args[1]
- default:
- return nil
- }
-}
-
-// Replace the len function call by its parameter, to create a fix suggestion
-func replaceLenActualArg(actualExpr *ast.CallExpr, handler gomegahandler.Handler) {
- name, ok := handler.GetActualFuncName(actualExpr)
- if !ok {
- return
- }
-
- switch name {
- case expect, omega:
- arg := actualExpr.Args[0]
- if isActualIsLenFunc(arg) || isActualIsCapFunc(arg) {
- // replace the len function call by its parameter, to create a fix suggestion
- actualExpr.Args[0] = arg.(*ast.CallExpr).Args[0]
- }
- case expectWithOffset:
- arg := actualExpr.Args[1]
- if isActualIsLenFunc(arg) || isActualIsCapFunc(arg) {
- // replace the len function call by its parameter, to create a fix suggestion
- actualExpr.Args[1] = arg.(*ast.CallExpr).Args[0]
- }
- }
-}
-
-// Replace the nil comparison with the compared object, to create a fix suggestion
-func replaceNilActualArg(actualExpr *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr) bool {
- actualFuncName, ok := handler.GetActualFuncName(actualExpr)
- if !ok {
- return false
- }
-
- switch actualFuncName {
- case expect, omega:
- actualExpr.Args[0] = nilable
- return true
-
- case expectWithOffset:
- actualExpr.Args[1] = nilable
- return true
-
- default:
- return false
- }
-}
-
-// For the BeNumerically matcher, we want to avoid the assertion of length to be > 0 or >= 1, or just == number
-func handleBeNumerically(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- opExp, ok1 := matcher.Args[0].(*ast.BasicLit)
- valExp, ok2 := matcher.Args[1].(*ast.BasicLit)
-
- if ok1 && ok2 {
- op := opExp.Value
- val := valExp.Value
-
- if (op == `">"` && val == "0") || (op == `">="` && val == "1") {
- reverseAssertionFuncLogic(exp)
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(beEmpty))
- exp.Args[0].(*ast.CallExpr).Args = nil
- } else if op == `"=="` {
- chooseNumericMatcher(pass, exp, handler, valExp)
- } else if op == `"!="` {
- reverseAssertionFuncLogic(exp)
- chooseNumericMatcher(pass, exp, handler, valExp)
- } else {
- return true
- }
-
- reportLengthAssertion(exp, handler, reportBuilder)
- return false
- }
- return true
-}
-
-// For the BeNumerically matcher, we want to avoid the assertion of length to be > 0 or >= 1, or just == number
-func handleCapBeNumerically(matcher *ast.CallExpr, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool {
- opExp, ok1 := matcher.Args[0].(*ast.BasicLit)
- valExp, ok2 := matcher.Args[1].(*ast.BasicLit)
-
- if ok1 && ok2 {
- op := opExp.Value
- val := valExp.Value
-
- if (op == `">"` && val == "0") || (op == `">="` && val == "1") {
- reverseAssertionFuncLogic(exp)
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveCap))
- exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}}
- } else if op == `"=="` {
- replaceNumericCapMatcher(exp, handler, valExp)
- } else if op == `"!="` {
- reverseAssertionFuncLogic(exp)
- replaceNumericCapMatcher(exp, handler, valExp)
- } else {
- return true
- }
-
- reportCapAssertion(exp, handler, reportBuilder)
- return false
- }
- return true
-}
-
-func chooseNumericMatcher(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, valExp ast.Expr) {
- caller := exp.Args[0].(*ast.CallExpr)
- if isZero(pass, valExp) {
- handler.ReplaceFunction(caller, ast.NewIdent(beEmpty))
- exp.Args[0].(*ast.CallExpr).Args = nil
- } else {
- handler.ReplaceFunction(caller, ast.NewIdent(haveLen))
- exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{valExp}
- }
-}
-
-func replaceNumericCapMatcher(exp *ast.CallExpr, handler gomegahandler.Handler, valExp ast.Expr) {
- caller := exp.Args[0].(*ast.CallExpr)
- handler.ReplaceFunction(caller, ast.NewIdent(haveCap))
- exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{valExp}
-}
-
-func reverseAssertionFuncLogic(exp *ast.CallExpr) {
- assertionFunc := exp.Fun.(*ast.SelectorExpr).Sel
- assertionFunc.Name = reverseassertion.ChangeAssertionLogic(assertionFunc.Name)
-}
-
-func isNegativeAssertion(exp *ast.CallExpr) bool {
- assertionFunc := exp.Fun.(*ast.SelectorExpr).Sel
- return reverseassertion.IsNegativeLogic(assertionFunc.Name)
-}
-
-func handleEqualLenMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) {
- equalTo, ok := matcher.Args[0].(*ast.BasicLit)
- if ok {
- chooseNumericMatcher(pass, exp, handler, equalTo)
- } else {
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveLen))
- exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{matcher.Args[0]}
- }
- reportLengthAssertion(exp, handler, reportBuilder)
-}
-
-func handleEqualCapMatcher(matcher *ast.CallExpr, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) {
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveCap))
- exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{matcher.Args[0]}
- reportCapAssertion(exp, handler, reportBuilder)
-}
-
-func handleBeZero(exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) {
- exp.Args[0].(*ast.CallExpr).Args = nil
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(beEmpty))
- reportLengthAssertion(exp, handler, reportBuilder)
-}
-
-func handleCapBeZero(exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) {
- exp.Args[0].(*ast.CallExpr).Args = nil
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveCap))
- exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}}
- reportCapAssertion(exp, handler, reportBuilder)
-}
-
-func handleEqualNilMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, reportBuilder *reports.Builder) {
- equalTo, ok := matcher.Args[0].(*ast.Ident)
- if !ok {
- return
- }
-
- if equalTo.Name == "false" {
- reverseAssertionFuncLogic(exp)
- } else if equalTo.Name != "true" {
- return
- }
-
- newFuncName, isItError := handleNilComparisonErr(pass, exp, nilable)
-
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(newFuncName))
- exp.Args[0].(*ast.CallExpr).Args = nil
-
- reportNilAssertion(exp, handler, nilable, notEqual, isItError, reportBuilder)
-}
-
-func handleNilBeBoolMatcher(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, reportBuilder *reports.Builder) {
- newFuncName, isItError := handleNilComparisonErr(pass, exp, nilable)
- handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(newFuncName))
- exp.Args[0].(*ast.CallExpr).Args = nil
-
- reportNilAssertion(exp, handler, nilable, notEqual, isItError, reportBuilder)
-}
-
-func handleNilComparisonErr(pass *analysis.Pass, exp *ast.CallExpr, nilable ast.Expr) (string, bool) {
- newFuncName := beNil
- isItError := isExprError(pass, nilable)
- if isItError {
- if is[*ast.CallExpr](nilable) {
- newFuncName = succeed
- } else {
- reverseAssertionFuncLogic(exp)
- newFuncName = haveOccurred
- }
- }
-
- return newFuncName, isItError
-}
-
-func isAssertionFunc(name string) bool {
- switch name {
- case "To", "ToNot", "NotTo", "Should", "ShouldNot":
- return true
- }
- return false
-}
-
-func reportLengthAssertion(expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) {
- actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr))
- replaceLenActualArg(actualExpr, handler)
-
- reportBuilder.AddIssue(true, wrongLengthWarningTemplate)
-}
-
-func reportCapAssertion(expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) {
- actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr))
- replaceLenActualArg(actualExpr, handler)
-
- reportBuilder.AddIssue(true, wrongCapWarningTemplate)
-}
-
-func reportNilAssertion(expr *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, isItError bool, reportBuilder *reports.Builder) {
- actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr))
- changed := replaceNilActualArg(actualExpr, handler, nilable)
- if !changed {
- return
- }
-
- if notEqual {
- reverseAssertionFuncLogic(expr)
- }
- template := wrongNilWarningTemplate
- if isItError {
- template = wrongErrWarningTemplate
- }
-
- reportBuilder.AddIssue(true, template)
-}
-
-func reportNewName(pass *analysis.Pass, id *ast.Ident, newName string, messageTemplate, oldExpr string) {
- pass.Report(analysis.Diagnostic{
- Pos: id.Pos(),
- Message: fmt.Sprintf(messageTemplate, newName),
- SuggestedFixes: []analysis.SuggestedFix{
- {
- Message: fmt.Sprintf("should replace %s with %s", oldExpr, newName),
- TextEdits: []analysis.TextEdit{
- {
- Pos: id.Pos(),
- End: id.End(),
- NewText: []byte(newName),
- },
- },
- },
- },
- })
-}
-
-func reportNoFix(pass *analysis.Pass, pos token.Pos, message string, args ...any) {
- if len(args) > 0 {
- message = fmt.Sprintf(message, args...)
- }
-
- pass.Report(analysis.Diagnostic{
- Pos: pos,
- Message: message,
- })
-}
-
-func getNilableFromComparison(actualArg ast.Expr) (ast.Expr, token.Token) {
- bin, ok := actualArg.(*ast.BinaryExpr)
- if !ok {
- return nil, token.ILLEGAL
- }
-
- if bin.Op == token.EQL || bin.Op == token.NEQ {
- if isNil(bin.Y) {
- return bin.X, bin.Op
- } else if isNil(bin.X) {
- return bin.Y, bin.Op
- }
- }
-
- return nil, token.ILLEGAL
-}
-
-func isNil(expr ast.Expr) bool {
- nilObject, ok := expr.(*ast.Ident)
- return ok && nilObject.Name == "nil" && nilObject.Obj == nil
-}
-
-func isComparison(pass *analysis.Pass, actualArg ast.Expr) (ast.Expr, ast.Expr, token.Token, bool) {
- bin, ok := actualArg.(*ast.BinaryExpr)
- if !ok {
- return nil, nil, token.ILLEGAL, false
- }
-
- first, second, op := bin.X, bin.Y, bin.Op
- replace := false
- switch realFirst := first.(type) {
- case *ast.Ident: // check if const
- info, ok := pass.TypesInfo.Types[realFirst]
- if ok {
- if is[*gotypes.Basic](info.Type) && info.Value != nil {
- replace = true
- }
- }
-
- case *ast.BasicLit:
- replace = true
- }
-
- if replace {
- first, second = second, first
- }
-
- switch op {
- case token.EQL:
- case token.NEQ:
- case token.GTR, token.GEQ, token.LSS, token.LEQ:
- if replace {
- op = reverseassertion.ChangeCompareOperator(op)
- }
- default:
- return nil, nil, token.ILLEGAL, false
- }
- return first, second, op, true
-}
-
-func goFmt(fset *token.FileSet, x ast.Expr) string {
- var b bytes.Buffer
- _ = printer.Fprint(&b, fset, x)
- return b.String()
-}
-
-func isExprError(pass *analysis.Pass, expr ast.Expr) bool {
- actualArgType := pass.TypesInfo.TypeOf(expr)
- switch t := actualArgType.(type) {
- case *gotypes.Named:
- if interfaces.ImplementsError(actualArgType) {
- return true
- }
- case *gotypes.Tuple:
- if t.Len() > 0 {
- switch t0 := t.At(0).Type().(type) {
- case *gotypes.Named, *gotypes.Pointer:
- if interfaces.ImplementsError(t0) {
- return true
- }
- }
- }
- }
- return false
-}
-
-func isPointer(pass *analysis.Pass, expr ast.Expr) bool {
- t := pass.TypesInfo.TypeOf(expr)
- return is[*gotypes.Pointer](t)
-}
-
-func isInterface(pass *analysis.Pass, expr ast.Expr) bool {
- t := pass.TypesInfo.TypeOf(expr)
- return gotypes.IsInterface(t)
-}
-
-func isNumeric(pass *analysis.Pass, node ast.Expr) bool {
- t := pass.TypesInfo.TypeOf(node)
-
- switch t.String() {
- case "int", "uint", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "float32", "float64":
- return true
- }
- return false
-}
-
-func checkNoAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegahandler.Handler) {
- funcName, ok := handler.GetActualFuncName(expr)
- if ok {
- var allowedFunction string
- switch funcName {
- case expect, expectWithOffset:
- allowedFunction = `"To()", "ToNot()" or "NotTo()"`
- case eventually, eventuallyWithOffset, consistently, consistentlyWithOffset:
- allowedFunction = `"Should()" or "ShouldNot()"`
- case omega:
- allowedFunction = `"Should()", "To()", "ShouldNot()", "ToNot()" or "NotTo()"`
- default:
- return
- }
- reportNoFix(pass, expr.Pos(), missingAssertionMessage, funcName, allowedFunction)
- }
-}
-
-func is[T any](x any) bool {
- _, matchType := x.(T)
- return matchType
+ return timePkg
}
diff --git a/vendor/github.com/nunnatsa/ginkgolinter/types/config.go b/vendor/github.com/nunnatsa/ginkgolinter/types/config.go
index b6838e524..0aadd3416 100644
--- a/vendor/github.com/nunnatsa/ginkgolinter/types/config.go
+++ b/vendor/github.com/nunnatsa/ginkgolinter/types/config.go
@@ -28,6 +28,7 @@ type Config struct {
ForceExpectTo Boolean
ValidateAsyncIntervals Boolean
ForbidSpecPollution Boolean
+ ForceSucceedForFuncs Boolean
}
func (s *Config) AllTrue() bool {
@@ -47,6 +48,7 @@ func (s *Config) Clone() Config {
ForceExpectTo: s.ForceExpectTo,
ValidateAsyncIntervals: s.ValidateAsyncIntervals,
ForbidSpecPollution: s.ForbidSpecPollution,
+ ForceSucceedForFuncs: s.ForceSucceedForFuncs,
}
}