From 4165372ec8fd142475a4e35fd0cf4f8042132208 Mon Sep 17 00:00:00 2001 From: Taras Madan Date: Wed, 22 Feb 2023 22:16:50 +0100 Subject: dependencies: update set go min requirements to 1.19 update dependencies update vendor --- vendor/github.com/nunnatsa/ginkgolinter/.gitignore | 2 + vendor/github.com/nunnatsa/ginkgolinter/LICENSE | 21 + vendor/github.com/nunnatsa/ginkgolinter/Makefile | 14 + vendor/github.com/nunnatsa/ginkgolinter/README.md | 181 ++++++ .../nunnatsa/ginkgolinter/ginkgo_linter.go | 662 +++++++++++++++++++++ .../nunnatsa/ginkgolinter/gomegahandler/handler.go | 167 ++++++ .../reverseassertion/reverse_assertion.go | 17 + .../nunnatsa/ginkgolinter/types/boolean.go | 32 + .../nunnatsa/ginkgolinter/types/suppress.go | 67 +++ 9 files changed, 1163 insertions(+) create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/.gitignore create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/LICENSE create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/Makefile create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/README.md create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/reverseassertion/reverse_assertion.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/types/boolean.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/types/suppress.go (limited to 'vendor/github.com/nunnatsa') diff --git a/vendor/github.com/nunnatsa/ginkgolinter/.gitignore b/vendor/github.com/nunnatsa/ginkgolinter/.gitignore new file mode 100644 index 000000000..7d7f8b10c --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/.gitignore @@ -0,0 +1,2 @@ +ginkgolinter +bin/ diff --git a/vendor/github.com/nunnatsa/ginkgolinter/LICENSE b/vendor/github.com/nunnatsa/ginkgolinter/LICENSE new file mode 100644 index 000000000..11096c5c8 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Nahshon Unna Tsameret + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/nunnatsa/ginkgolinter/Makefile b/vendor/github.com/nunnatsa/ginkgolinter/Makefile new file mode 100644 index 000000000..f37079839 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/Makefile @@ -0,0 +1,14 @@ +build: + go build -o ginkgolinter ./cmd/ginkgolinter + +build-for-windows: + GOOS=windows GOARCH=amd64 go build -o bin/ginkgolinter-amd64.exe ./cmd/ginkgolinter + +build-for-mac: + GOOS=darwin GOARCH=amd64 go build -o bin/ginkgolinter-amd64-darwin ./cmd/ginkgolinter + +build-for-linux: + GOOS=linux GOARCH=amd64 go build -o bin/ginkgolinter-amd64-linux ./cmd/ginkgolinter + GOOS=linux GOARCH=386 go build -o bin/ginkgolinter-386-linux ./cmd/ginkgolinter + +build-all: build build-for-linux build-for-mac build-for-windows diff --git a/vendor/github.com/nunnatsa/ginkgolinter/README.md b/vendor/github.com/nunnatsa/ginkgolinter/README.md new file mode 100644 index 000000000..fa31117af --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/README.md @@ -0,0 +1,181 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/nunnatsa/ginkgolinter)](https://goreportcard.com/report/github.com/nunnatsa/ginkgolinter) +[![Coverage Status](https://coveralls.io/repos/github/nunnatsa/ginkgolinter/badge.svg?branch=main)](https://coveralls.io/github/nunnatsa/ginkgolinter?branch=main) +# ginkgo-linter +[ginkgo](https://onsi.github.io/ginkgo/) is a popular testing framework and [gomega](https://onsi.github.io/gomega/) is its assertion package. + +This is a golang linter to enforce some standards while using the ginkgo and gomega packages. + +## Install the CLI +Download the right executable from the latest release, according to your OS. + +Another option is to use go: +```shell +go install github.com/nunnatsa/ginkgolinter/cmd/ginkgolinter@latest +``` +Then add the new executable to your PATH. + +## usage +```shell +ginkgolinter [-fix] ./... +``` +Use the `-fix` flag to apply the fix suggestions to the source code. + +## Linter Checks +The linter checks the gomega assertions in golang test code. Gomega may be used together with ginkgo tests, For example: +```go +It("should test something", func() { // It is ginkgo test case function + Expect("abcd").To(HaveLen(4), "the string should have a length of 4") // Expect is the gomega assertion +}) +``` +or within a classic golang test code, like this: +```go +func TestWithGomega(t *testing.T) { + g := NewWithT(t) + g.Expect("abcd").To(HaveLen(4), "the string should have a length of 4") +} +``` + +In some cases, the gomega will be passed as a variable to function by ginkgo, for example: +```go +Eventually(func(g Gomega) error { + g.Expect("abcd").To(HaveLen(4), "the string should have a length of 4") + return nil +}).Should(Succeed()) +``` + +The linter checks the `Expect`, `ExpectWithOffset` and the `Ω` "actual" functions, with the `Should`, `ShouldNot`, `To`, `ToNot` and `NotTo` assertion functions. + +It also supports the embedded `Not()` matcher + +### Wrong Length Assertion +The linter finds assertion of the golang built-in `len` function, with all kind of matchers, while there are already gomega matchers for these usecases; We want to assert the item, rather than its length. + +There are several wrong patterns: +```go +Expect(len(x)).To(Equal(0)) // should be: Expect(x).To(BeEmpty()) +Expect(len(x)).To(BeZero()) // should be: Expect(x).To(BeEmpty()) +Expect(len(x)).To(BeNumeric(">", 0)) // should be: Expect(x).ToNot(BeEmpty()) +Expect(len(x)).To(BeNumeric(">=", 1)) // should be: Expect(x).ToNot(BeEmpty()) +Expect(len(x)).To(BeNumeric("==", 0)) // should be: Expect(x).To(BeEmpty()) +Expect(len(x)).To(BeNumeric("!=", 0)) // should be: Expect(x).ToNot(BeEmpty()) + +Expect(len(x)).To(Equal(1)) // should be: Expect(x).To(HaveLen(1)) +Expect(len(x)).To(BeNumeric("==", 2)) // should be: Expect(x).To(HaveLen(2)) +Expect(len(x)).To(BeNumeric("!=", 3)) // should be: Expect(x).ToNot(HaveLen(3)) +``` + +It also supports the embedded `Not()` matcher; e.g. + +`Ω(len(x)).Should(Not(Equal(4)))` => `Ω(x).ShouldNot(HaveLen(4))` + +Or even (double negative): + +`Ω(len(x)).To(Not(BeNumeric(">", 0)))` => `Ω(x).To(BeEmpty())` + +The output of the linter,when finding issues, looks like this: +``` +./testdata/src/a/a.go:14:5: ginkgo-linter: wrong length assertion; consider using `Expect("abcd").Should(HaveLen(4))` instead +./testdata/src/a/a.go:18:5: ginkgo-linter: wrong length assertion; consider using `Expect("").Should(BeEmpty())` instead +./testdata/src/a/a.go:22:5: ginkgo-linter: wrong length assertion; consider using `Expect("").Should(BeEmpty())` instead +``` + +### Wrong `nil` Assertion +The linter finds assertion of the comparison to nil, with all kind of matchers, instead of using the existing `BeNil()` matcher; We want to assert the item, rather than a comparison result. + +There are several wrong patterns: + +```go +Expect(x == nil).To(Equal(true)) // should be: Expect(x).To(BeNil()) +Expect(nil == x).To(Equal(true)) // should be: Expect(x).To(BeNil()) +Expect(x != nil).To(Equal(true)) // should be: Expect(x).ToNot(BeNil()) +Expect(nil != nil).To(Equal(true)) // should be: Expect(x).ToNot(BeNil()) + +Expect(x == nil).To(BeTrue()) // should be: Expect(x).To(BeNil()) +Expect(x == nil).To(BeFalse()) // should be: Expect(x).ToNot(BeNil()) +``` +It also supports the embedded `Not()` matcher; e.g. + +`Ω(x == nil).Should(Not(BeTrue()))` => `Ω(x).ShouldNot(BeNil())` + +Or even (double negative): + +`Ω(x != nil).Should(Not(BeTrue()))` => `Ω(x).Should(BeNil())` + +### Wrong boolean Assertion +The linter finds assertion using the `Equal` method, with the values of to `true` or `false`, instead +of using the existing `BeTrue()` or `BeFalse()` matcher. + +There are several wrong patterns: + +```go +Expect(x).To(Equal(true)) // should be: Expect(x).To(BeTrue()) +Expect(x).To(Equal(false)) // should be: Expect(x).To(BeFalse()) +``` +It also supports the embedded `Not()` matcher; e.g. + +`Ω(x).Should(Not(Equal(True)))` => `Ω(x).ShouldNot(BeBeTrue())` + +### Wrong Error Assertion +The linter finds assertion of errors compared with nil, or to be equal nil, or to be nil. The linter suggests to use `Succeed` for functions or `HaveOccurred` for error values.. + +There are several wrong patterns: + +```go +Expect(err).To(BeNil()) // should be: Expect(err).ToNot(HaveOccurred()) +Expect(err == nil).To(Equal(true)) // should be: Expect(err).ToNot(HaveOccurred()) +Expect(err == nil).To(BeFalse()) // should be: Expect(err).To(HaveOccurred()) +Expect(err != nil).To(BeTrue()) // should be: Expect(err).To(HaveOccurred()) +Expect(funcReturnsError()).To(BeNil()) // should be: Expect(funcReturnsError()).To(Succeed()) + +and so on +``` +It also supports the embedded `Not()` matcher; e.g. + +`Ω(err == nil).Should(Not(BeTrue()))` => `Ω(x).Should(HaveOccurred())` + +## Suppress the linter +### Suppress warning from command line +* Use the `--suppress-len-assertion=true` flag to suppress the wrong length assertion warning +* Use the `--suppress-nil-assertion=true` flag to suppress the wrong nil assertion warning +* Use the `--suppress-err-assertion=true` flag to suppress the wrong error assertion warning + +### Suppress warning from the code +To suppress the wrong length assertion warning, add a comment with (only) + +`ginkgo-linter:ignore-len-assert-warning`. + +To suppress the wrong nil assertion warning, add a comment with (only) + +`ginkgo-linter:ignore-nil-assert-warning`. + +To suppress the wrong error assertion warning, add a comment with (only) + +`ginkgo-linter:ignore-err-assert-warning`. + +There are two options to use these comments: +1. If the comment is at the top of the file, supress the warning for the whole file; e.g.: + ```go + package mypackage + + // ginkgo-linter:ignore-len-assert-warning + + import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + ) + + var _ = Describe("my test", func() { + It("should do something", func() { + Expect(len("abc")).Should(Equal(3)) // nothing in this file will trigger the warning + }) + }) + ``` + +2. If the comment is before a wrong length check expression, the warning is suppressed for this expression only; for example: + ```golang + It("should test something", func() { + // ginkgo-linter:ignore-nil-assert-warning + Expect(x == nil).Should(BeTrue()) // this line will not trigger the warning + Expect(x == nil).Should(BeTrue()) // this line will trigger the warning + } + ``` diff --git a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go b/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go new file mode 100644 index 000000000..fc089be10 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go @@ -0,0 +1,662 @@ +package ginkgolinter + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/printer" + "go/token" + gotypes "go/types" + + "github.com/go-toolsmith/astcopy" + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/gomegahandler" + "github.com/nunnatsa/ginkgolinter/reverseassertion" + "github.com/nunnatsa/ginkgolinter/types" +) + +// The ginkgolinter enforces standards of using ginkgo and gomega. +// +// The current checks are: +// * enforce right length assertion - warn for assertion of len(something): +// +// This check finds the following patterns and suggests an alternative +// * Expect(len(something)).To(Equal(number)) ===> Expect(x).To(HaveLen(number)) +// * ExpectWithOffset(1, len(something)).ShouldNot(Equal(0)) ===> ExpectWithOffset(1, something).ShouldNot(BeEmpty()) +// * Ω(len(something)).NotTo(BeZero()) ===> Ω(something).NotTo(BeEmpty()) +// * Expect(len(something)).To(BeNumerically(">", 0)) ===> Expect(something).ToNot(BeEmpty()) +// * Expect(len(something)).To(BeNumerically(">=", 1)) ===> Expect(something).ToNot(BeEmpty()) +// * Expect(len(something)).To(BeNumerically("==", number)) ===> Expect(something).To(HaveLen(number)) +// +// * enforce right nil assertion - warn for assertion of x == nil: +// This check finds the following patterns and suggests an alternative +// * Expect(x == nil).Should(Equal(true)) ===> Expect(x).Should(BeNil()) +// * Expect(nil == x).Should(BeTrue()) ===> Expect(x).Should(BeNil()) +// * Expect(x != nil).Should(Equal(false)) ===> Expect(x).Should(BeNil()) +// * Expect(nil == x).Should(BeFalse()) ===> Expect(x).Should(BeNil()) +// * Expect(x).Should(Equal(nil) // ===> Expect(x).Should(BeNil()) + +const ( + linterName = "ginkgo-linter" + wrongLengthWarningTemplate = linterName + ": wrong length assertion; consider using `%s` instead" + wrongNilWarningTemplate = linterName + ": wrong nil assertion; consider using `%s` instead" + wrongBoolWarningTemplate = linterName + ": wrong boolean assertion; consider using `%s` instead" + wrongErrWarningTemplate = linterName + ": wrong error assertion; consider using `%s` instead" + beEmpty = "BeEmpty" + beNil = "BeNil" + beTrue = "BeTrue" + beFalse = "BeFalse" + equal = "Equal" + not = "Not" + haveLen = "HaveLen" + succeed = "Succeed" + haveOccurred = "HaveOccurred" + expect = "Expect" + omega = "Ω" + expectWithOffset = "ExpectWithOffset" +) + +// Analyzer is the interface to go_vet +var Analyzer = NewAnalyzer() + +type ginkgoLinter struct { + suppress *types.Suppress +} + +// NewAnalyzer returns an Analyzer - the package interface with nogo +func NewAnalyzer() *analysis.Analyzer { + linter := ginkgoLinter{ + suppress: &types.Suppress{ + Len: false, + Nil: false, + Err: false, + }, + } + + a := &analysis.Analyzer{ + Name: "ginkgolinter", + Doc: `enforces standards of using ginkgo and gomega +currently, the linter searches for following: +* wrong length assertions. We want to assert the item rather than its length. +For example: + Expect(len(x)).Should(Equal(1)) +This should be replaced with: + Expect(x)).Should(HavelLen(1)) + +* wrong nil assertions. We want to assert the item rather than a comparison result. +For example: + Expect(x == nil).Should(BeTrue()) +This should be replaced with: + Expect(x).Should(BeNil()) + `, + Run: linter.run, + RunDespiteErrors: true, + } + + a.Flags.Init("ginkgolinter", flag.ExitOnError) + a.Flags.Var(&linter.suppress.Len, "suppress-len-assertion", "Suppress warning for wrong length assertions") + a.Flags.Var(&linter.suppress.Nil, "suppress-nil-assertion", "Suppress warning for wrong nil assertions") + a.Flags.Var(&linter.suppress.Err, "suppress-err-assertion", "Suppress warning for wrong error assertions") + + return a +} + +// main assertion function +func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { + if l.suppress.AllTrue() { + return nil, nil + } + + for _, file := range pass.Files { + fileSuppress := l.suppress.Clone() + + cm := ast.NewCommentMap(pass.Fset, file, file.Comments) + + fileSuppress.UpdateFromFile(cm) + if fileSuppress.AllTrue() { + continue + } + + handler := gomegahandler.GetGomegaHandler(file) + if handler == nil { // no gomega import => no use in gomega in this file; nothing to do here + continue + } + + ast.Inspect(file, func(n ast.Node) bool { + + stmt, ok := n.(*ast.ExprStmt) + if !ok { + return true + } + + exprSuppress := fileSuppress.Clone() + + if comments, ok := cm[stmt]; ok { + exprSuppress.UpdateFromComment(comments) + } + + // search for function calls + assertionExp, ok := stmt.X.(*ast.CallExpr) + if !ok { + return true + } + + assertionFunc, ok := assertionExp.Fun.(*ast.SelectorExpr) + if !ok || !isAssertionFunc(assertionFunc.Sel.Name) { + return true + } + + actualArg := getActualArg(assertionFunc, handler) + if actualArg == nil { + return true + } + + return checkExpression(pass, exprSuppress, actualArg, assertionExp, handler) + + }) + } + return nil, nil +} + +func checkExpression(pass *analysis.Pass, exprSuppress types.Suppress, actualArg ast.Expr, assertionExp *ast.CallExpr, handler gomegahandler.Handler) bool { + assertionExp = astcopy.CallExpr(assertionExp) + oldExpr := goFmt(pass.Fset, assertionExp) + if !bool(exprSuppress.Len) && isActualIsLenFunc(actualArg) { + + return checkLengthMatcher(assertionExp, pass, handler, oldExpr) + } else { + if nilable, compOp := getNilableFromComparison(actualArg); nilable != nil { + if isExprError(pass, nilable) { + if exprSuppress.Err { + return true + } + } else if exprSuppress.Nil { + return true + } + + return checkNilMatcher(assertionExp, pass, nilable, handler, compOp == token.NEQ, oldExpr) + + } else if isExprError(pass, actualArg) { + return bool(exprSuppress.Err) || checkNilError(pass, assertionExp, handler, actualArg, oldExpr) + + } else { + return simplifyEqual(pass, exprSuppress, assertionExp, handler, actualArg, oldExpr) + } + } +} + +// Check if the "actual" argument is a call to the golang built-in len() function +func isActualIsLenFunc(actualArg ast.Expr) bool { + lenArgExp, ok := actualArg.(*ast.CallExpr) + if !ok { + return false + } + + lenFunc, ok := lenArgExp.Fun.(*ast.Ident) + return ok && lenFunc.Name == "len" +} + +// 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, oldExp string) 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: + handleEqualMatcher(matcher, pass, exp, handler, oldExp) + return false + + case "BeZero": + handleBeZero(pass, exp, handler, oldExp) + return false + + case "BeNumerically": + return handleBeNumerically(matcher, pass, exp, handler, oldExp) + + case not: + reverseAssertionFuncLogic(exp) + exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0] + return checkLengthMatcher(exp, pass, handler, oldExp) + + 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, oldExp string) 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, oldExp) + + case beTrue: + handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, oldExp) + + case beFalse: + reverseAssertionFuncLogic(exp) + handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, oldExp) + + case not: + reverseAssertionFuncLogic(exp) + exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0] + return checkNilMatcher(exp, pass, nilable, handler, notEqual, oldExp) + + default: + return true + } + return false +} + +func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, oldExpr string) 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, oldExpr) + default: + return true + } + + var newFuncName string + if _, ok := actualArg.(*ast.CallExpr); ok { + newFuncName = succeed + } else { + reverseAssertionFuncLogic(assertionExp) + newFuncName = haveOccurred + } + + handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(newFuncName)) + equalFuncExpr.Args = nil + + report(pass, assertionExp, wrongErrWarningTemplate, oldExpr) + return false +} + +// handle Equal(nil), Equal(true) and Equal(false) +func simplifyEqual(pass *analysis.Pass, exprSuppress types.Suppress, assertionExp *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, oldExpr string) 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 equal: + if len(equalFuncExpr.Args) == 0 { + return true + } + + token, ok := equalFuncExpr.Args[0].(*ast.Ident) + if !ok { + return true + } + + var replacement string + var template string + switch token.Name { + case "nil": + if exprSuppress.Nil { + return true + } + replacement = beNil + template = wrongNilWarningTemplate + case "true": + replacement = beTrue + template = wrongBoolWarningTemplate + case "false": + replacement = beFalse + template = wrongBoolWarningTemplate + default: + return true + } + + handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(replacement)) + equalFuncExpr.Args = nil + + report(pass, assertionExp, template, oldExpr) + + return false + + case not: + reverseAssertionFuncLogic(assertionExp) + assertionExp.Args[0] = assertionExp.Args[0].(*ast.CallExpr).Args[0] + return simplifyEqual(pass, exprSuppress, assertionExp, handler, actualArg, oldExpr) + default: + return true + } +} + +// 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(assertionFunc *ast.SelectorExpr, handler gomegahandler.Handler) ast.Expr { + actualExpr, ok := assertionFunc.X.(*ast.CallExpr) + if !ok { + return nil + } + + 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) { + // 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) { + // 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, oldExp string) 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 + reportLengthAssertion(pass, exp, handler, oldExp) + return false + } else if op == `"=="` { + chooseNumericMatcher(exp, handler, valExp) + reportLengthAssertion(pass, exp, handler, oldExp) + + return false + } else if op == `"!="` { + reverseAssertionFuncLogic(exp) + chooseNumericMatcher(exp, handler, valExp) + reportLengthAssertion(pass, exp, handler, oldExp) + + return false + } + } + return true +} + +func chooseNumericMatcher(exp *ast.CallExpr, handler gomegahandler.Handler, valExp *ast.BasicLit) { + caller := exp.Args[0].(*ast.CallExpr) + if valExp.Value == "0" { + 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 reverseAssertionFuncLogic(exp *ast.CallExpr) { + assertionFunc := exp.Fun.(*ast.SelectorExpr).Sel + assertionFunc.Name = reverseassertion.ChangeAssertionLogic(assertionFunc.Name) +} + +func handleEqualMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, oldExp string) { + equalTo, ok := matcher.Args[0].(*ast.BasicLit) + if ok { + chooseNumericMatcher(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(pass, exp, handler, oldExp) +} + +func handleBeZero(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, oldExp string) { + exp.Args[0].(*ast.CallExpr).Args = nil + handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(beEmpty)) + reportLengthAssertion(pass, exp, handler, oldExp) +} + +func handleEqualNilMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, oldExp string) { + 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(pass, exp, handler, nilable, notEqual, oldExp, isItError) +} + +func handleNilBeBoolMatcher(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, oldExp string) { + newFuncName, isItError := handleNilComparisonErr(pass, exp, nilable) + handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(newFuncName)) + exp.Args[0].(*ast.CallExpr).Args = nil + + reportNilAssertion(pass, exp, handler, nilable, notEqual, oldExp, isItError) +} + +func handleNilComparisonErr(pass *analysis.Pass, exp *ast.CallExpr, nilable ast.Expr) (string, bool) { + newFuncName := beNil + isItError := isExprError(pass, nilable) + if isItError { + if _, ok := nilable.(*ast.CallExpr); ok { + 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(pass *analysis.Pass, expr *ast.CallExpr, handler gomegahandler.Handler, oldExpr string) { + replaceLenActualArg(expr.Fun.(*ast.SelectorExpr).X.(*ast.CallExpr), handler) + + report(pass, expr, wrongLengthWarningTemplate, oldExpr) +} + +func reportNilAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, oldExpr string, isItError bool) { + changed := replaceNilActualArg(expr.Fun.(*ast.SelectorExpr).X.(*ast.CallExpr), handler, nilable) + if !changed { + return + } + + if notEqual { + reverseAssertionFuncLogic(expr) + } + template := wrongNilWarningTemplate + if isItError { + template = wrongErrWarningTemplate + } + + report(pass, expr, template, oldExpr) +} + +func report(pass *analysis.Pass, expr *ast.CallExpr, messageTemplate, oldExpr string) { + newExp := goFmt(pass.Fset, expr) + pass.Report(analysis.Diagnostic{ + Pos: expr.Pos(), + Message: fmt.Sprintf(messageTemplate, newExp), + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: fmt.Sprintf("should replace %s with %s", oldExpr, newExp), + TextEdits: []analysis.TextEdit{ + { + Pos: expr.Pos(), + End: expr.End(), + NewText: []byte(newExp), + }, + }, + }, + }, + }) +} + +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 goFmt(fset *token.FileSet, x ast.Expr) string { + var b bytes.Buffer + _ = printer.Fprint(&b, fset, x) + return b.String() +} + +var errorType *gotypes.Interface + +func init() { + errorType = gotypes.Universe.Lookup("error").Type().Underlying().(*gotypes.Interface) +} + +func isError(t gotypes.Type) bool { + return gotypes.Implements(t, errorType) +} + +func isExprError(pass *analysis.Pass, expr ast.Expr) bool { + actualArgType := pass.TypesInfo.TypeOf(expr) + switch t := actualArgType.(type) { + case *gotypes.Named: + if isError(actualArgType) { + return true + } + case *gotypes.Tuple: + if t.Len() > 0 { + switch t0 := t.At(0).Type().(type) { + case *gotypes.Named, *gotypes.Pointer: + if isError(t0) { + return true + } + } + } + } + return false +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go new file mode 100644 index 000000000..d57ba2913 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go @@ -0,0 +1,167 @@ +package gomegahandler + +import ( + "go/ast" + "go/token" +) + +// Handler provide different handling, depend on the way gomega was imported, whether +// in imported with "." name, custom name or without any name. +type Handler interface { + // GetActualFuncName returns the name of the gomega function, e.g. `Expect` + GetActualFuncName(*ast.CallExpr) (string, bool) + // ReplaceFunction replaces the function with another one, for fix suggestions + ReplaceFunction(*ast.CallExpr, *ast.Ident) + + getDefFuncName(expr *ast.CallExpr) string + + getFieldType(field *ast.Field) string +} + +// GetGomegaHandler returns a gomegar handler according to the way gomega was imported in the specific file +func GetGomegaHandler(file *ast.File) Handler { + for _, imp := range file.Imports { + if imp.Path.Value != `"github.com/onsi/gomega"` { + continue + } + + switch name := imp.Name.String(); { + case name == ".": + return dotHandler{} + case name == "": // import with no local name + return nameHandler("gomega") + default: + return nameHandler(name) + } + } + + 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 + } + } + return "", false +} + +// ReplaceFunction replaces the function with another one, for fix suggestions +func (dotHandler) ReplaceFunction(caller *ast.CallExpr, newExpr *ast.Ident) { + caller.Fun = 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 + } + + x, ok := selector.X.(*ast.Ident) + if !ok { + return "", false + } + + if x.Name != string(g) { + if !isGomegaVar(x, g) { + return "", false + } + } + + return selector.Sel.Name, true +} + +// 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 +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/reverseassertion/reverse_assertion.go b/vendor/github.com/nunnatsa/ginkgolinter/reverseassertion/reverse_assertion.go new file mode 100644 index 000000000..42c914e50 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/reverseassertion/reverse_assertion.go @@ -0,0 +1,17 @@ +package reverseassertion + +var reverseLogicAssertions = map[string]string{ + "To": "ToNot", + "ToNot": "To", + "NotTo": "To", + "Should": "ShouldNot", + "ShouldNot": "Should", +} + +// ChangeAssertionLogic get gomega assertion function name, and returns the reverse logic function name +func ChangeAssertionLogic(funcName string) string { + if revFunc, ok := reverseLogicAssertions[funcName]; ok { + return revFunc + } + return funcName +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/types/boolean.go b/vendor/github.com/nunnatsa/ginkgolinter/types/boolean.go new file mode 100644 index 000000000..be510c4e9 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/types/boolean.go @@ -0,0 +1,32 @@ +package types + +import ( + "errors" + "strings" +) + +// Boolean is a bool, implementing the flag.Value interface, to be used as a flag var. +type Boolean bool + +func (b *Boolean) Set(value string) error { + if b == nil { + return errors.New("trying to set nil parameter") + } + switch strings.ToLower(value) { + case "true": + *b = true + case "false": + *b = false + default: + return errors.New(value + " is not a Boolean value") + + } + return nil +} + +func (b Boolean) String() string { + if b { + return "true" + } + return "false" +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/types/suppress.go b/vendor/github.com/nunnatsa/ginkgolinter/types/suppress.go new file mode 100644 index 000000000..a703eb2d3 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/types/suppress.go @@ -0,0 +1,67 @@ +package types + +import ( + "strings" + + "go/ast" +) + +const ( + suppressPrefix = "ginkgo-linter:" + suppressLengthAssertionWarning = suppressPrefix + "ignore-len-assert-warning" + suppressNilAssertionWarning = suppressPrefix + "ignore-nil-assert-warning" + suppressErrAssertionWarning = suppressPrefix + "ignore-err-assert-warning" +) + +type Suppress struct { + Len Boolean + Nil Boolean + Err Boolean +} + +func (s Suppress) AllTrue() bool { + return bool(s.Len && s.Nil && s.Err) +} + +func (s Suppress) Clone() Suppress { + return Suppress{ + Len: s.Len, + Nil: s.Nil, + Err: s.Err, + } +} + +func (s *Suppress) UpdateFromComment(commentGroup []*ast.CommentGroup) { + for _, cmntList := range commentGroup { + if s.AllTrue() { + break + } + + for _, cmnt := range cmntList.List { + commentLines := strings.Split(cmnt.Text, "\n") + for _, comment := range commentLines { + comment = strings.TrimPrefix(comment, "//") + comment = strings.TrimPrefix(comment, "/*") + comment = strings.TrimSuffix(comment, "*/") + comment = strings.TrimSpace(comment) + + s.Len = s.Len || (comment == suppressLengthAssertionWarning) + s.Nil = s.Nil || (comment == suppressNilAssertionWarning) + s.Err = s.Err || (comment == suppressErrAssertionWarning) + } + } + } +} + +func (s *Suppress) UpdateFromFile(cm ast.CommentMap) { + + for key, commentGroup := range cm { + if s.AllTrue() { + break + } + + if _, ok := key.(*ast.GenDecl); ok { + s.UpdateFromComment(commentGroup) + } + } +} -- cgit mrf-deployment