aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/go-critic
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-07-04 11:12:55 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-07-04 15:05:30 +0200
commitc7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch)
tree0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/go-critic
parent9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff)
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/go-critic')
-rw-r--r--vendor/github.com/go-critic/go-critic/LICENSE22
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go102
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go102
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go98
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go102
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go63
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/badCond_checker.go147
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go344
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/builtinShadow_checker.go36
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/captLocal_checker.go49
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/caseOrder_checker.go88
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/checkers.go10
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/codegenComment_checker.go61
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go77
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go157
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/commentedOutImport_checker.go76
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/defaultCaseOrder_checker.go65
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go149
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/docStub_checker.go95
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go133
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupBranchBody_checker.go58
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go57
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go63
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupSubExpr_checker.go102
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go71
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go70
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go58
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go87
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/evalOrder_checker.go87
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/exitAfterDefer_checker.go78
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go65
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go68
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/hexLiteral_checker.go60
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go63
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/ifElseChain_checker.go99
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/importShadow_checker.go47
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go50
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/initClause_checker.go56
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go27
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go86
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astset.go44
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/zero_value.go94
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go124
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/methodExprCall_checker.go57
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/nestingReduce_checker.go73
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/newDeref_checker.go45
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go64
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go82
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go66
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go86
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/ptrToRefParam_checker.go70
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go80
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go75
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go47
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/regexpPattern_checker.go68
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/singleCaseSwitch_checker.go61
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go72
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/sloppyReassign_checker.go80
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go47
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go49
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go117
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/typeAssertChain_checker.go132
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go88
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go86
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/underef_checker.go127
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go170
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go79
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unnamedResult_checker.go103
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go69
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go59
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/utils.go309
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go64
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/weakCond_checker.go77
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go52
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go224
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go66
76 files changed, 6534 insertions, 0 deletions
diff --git a/vendor/github.com/go-critic/go-critic/LICENSE b/vendor/github.com/go-critic/go-critic/LICENSE
new file mode 100644
index 000000000..b944b4bbd
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2018-2019 Alekseev Artem
+Copyright (c) 2018-2019 Ravil Bikbulatov
+
+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/go-critic/go-critic/checkers/appendAssign_checker.go b/vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go
new file mode 100644
index 000000000..47d12f014
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go
@@ -0,0 +1,102 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/astp"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "appendAssign"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects suspicious append result assignments"
+ info.Before = `
+p.positives = append(p.negatives, x)
+p.negatives = append(p.negatives, y)`
+ info.After = `
+p.positives = append(p.positives, x)
+p.negatives = append(p.negatives, y)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&appendAssignChecker{ctx: ctx})
+ })
+}
+
+type appendAssignChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *appendAssignChecker) VisitStmt(stmt ast.Stmt) {
+ assign, ok := stmt.(*ast.AssignStmt)
+ if !ok || assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) {
+ return
+ }
+ for i, rhs := range assign.Rhs {
+ call, ok := rhs.(*ast.CallExpr)
+ if !ok || qualifiedName(call.Fun) != "append" {
+ continue
+ }
+ c.checkAppend(assign.Lhs[i], call)
+ }
+}
+
+func (c *appendAssignChecker) checkAppend(x ast.Expr, call *ast.CallExpr) {
+ if call.Ellipsis != token.NoPos {
+ // Try to detect `xs = append(ys, xs...)` idiom.
+ for _, arg := range call.Args[1:] {
+ y := arg
+ if arg, ok := arg.(*ast.SliceExpr); ok {
+ y = arg.X
+ }
+ if astequal.Expr(x, y) {
+ return
+ }
+ }
+ }
+
+ switch x := x.(type) {
+ case *ast.Ident:
+ if x.Name == "_" {
+ return // Don't check assignments to blank ident
+ }
+ case *ast.IndexExpr:
+ if !astp.IsIndexExpr(call.Args[0]) {
+ // Most likely `m[k] = append(x, ...)`
+ // pattern, where x was retrieved by m[k] before.
+ //
+ // TODO: it's possible to record such map/slice reads
+ // and check whether it was done before this call.
+ // But for now, treat it like x belongs to m[k].
+ return
+ }
+ }
+
+ switch y := call.Args[0].(type) {
+ case *ast.SliceExpr:
+ if _, ok := c.ctx.TypesInfo.TypeOf(y.X).(*types.Array); ok {
+ // Arrays are frequently used as scratch storages.
+ return
+ }
+ c.matchSlices(call, x, y.X)
+ case *ast.IndexExpr, *ast.Ident, *ast.SelectorExpr:
+ c.matchSlices(call, x, y)
+ }
+}
+
+func (c *appendAssignChecker) matchSlices(cause ast.Node, x, y ast.Expr) {
+ if !astequal.Expr(x, astutil.Unparen(y)) {
+ c.warn(cause)
+ }
+}
+
+func (c *appendAssignChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "append result not assigned to the same slice")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go b/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go
new file mode 100644
index 000000000..63f5d9fea
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go
@@ -0,0 +1,102 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "appendCombine"
+ info.Tags = []string{"performance"}
+ info.Summary = "Detects `append` chains to the same slice that can be done in a single `append` call"
+ info.Before = `
+xs = append(xs, 1)
+xs = append(xs, 2)`
+ info.After = `xs = append(xs, 1, 2)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmtList(&appendCombineChecker{ctx: ctx})
+ })
+}
+
+type appendCombineChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *appendCombineChecker) VisitStmtList(list []ast.Stmt) {
+ var cause ast.Node // First append
+ var slice ast.Expr // Slice being appended to
+ chain := 0 // How much appends in a row we've seen
+
+ // Break the chain.
+ // If enough appends are in chain, print warning.
+ flush := func() {
+ if chain > 1 {
+ c.warn(cause, chain)
+ }
+ chain = 0
+ slice = nil
+ }
+
+ for _, stmt := range list {
+ call := c.matchAppend(stmt, slice)
+ if call == nil {
+ flush()
+ continue
+ }
+
+ if chain == 0 {
+ // First append in a chain.
+ chain = 1
+ slice = call.Args[0]
+ cause = stmt
+ } else {
+ chain++
+ }
+ }
+
+ // Required for printing chains that consist of trailing
+ // statements from the list.
+ flush()
+}
+
+func (c *appendCombineChecker) matchAppend(stmt ast.Stmt, slice ast.Expr) *ast.CallExpr {
+ // Seeking for:
+ // slice = append(slice, xs...)
+ // xs are 0-N append arguments, but not variadic argument,
+ // because it makes append combining impossible.
+
+ assign := astcast.ToAssignStmt(stmt)
+ if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
+ return nil
+ }
+
+ call, ok := assign.Rhs[0].(*ast.CallExpr)
+ {
+ cond := ok &&
+ qualifiedName(call.Fun) == "append" &&
+ call.Ellipsis == token.NoPos &&
+ astequal.Expr(assign.Lhs[0], call.Args[0])
+ if !cond {
+ return nil
+ }
+ }
+
+ // Check that current append slice match previous append slice.
+ // Otherwise we should break the chain.
+ if slice == nil || astequal.Expr(slice, call.Args[0]) {
+ return call
+ }
+ return nil
+}
+
+func (c *appendCombineChecker) warn(cause ast.Node, chain int) {
+ c.ctx.Warn(cause, "can combine chain of %d appends into one", chain)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go b/vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go
new file mode 100644
index 000000000..85a6f7c66
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go
@@ -0,0 +1,98 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astp"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "argOrder"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects suspicious arguments order"
+ info.Before = `strings.HasPrefix("#", userpass)`
+ info.After = `strings.HasPrefix(userpass, "#")`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&argOrderChecker{ctx: ctx})
+ })
+}
+
+type argOrderChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *argOrderChecker) VisitExpr(expr ast.Expr) {
+ call := astcast.ToCallExpr(expr)
+
+ // For now only handle functions of 2 args.
+ // TODO(Quasilyte): generalize the algorithm and add more patterns.
+ if len(call.Args) != 2 {
+ return
+ }
+
+ calledExpr := astcast.ToSelectorExpr(call.Fun)
+ obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
+ if !ok || !isStdlibPkg(obj.Imported()) {
+ return
+ }
+
+ x := call.Args[0]
+ y := call.Args[1]
+ switch calledExpr.Sel.Name {
+ case "HasPrefix", "HasSuffix", "Contains", "TrimPrefix", "TrimSuffix", "Split":
+ if obj.Name() != "bytes" && obj.Name() != "strings" {
+ return
+ }
+ if c.isConstLiteral(x) && !c.isConstLiteral(y) {
+ c.warn(call)
+ }
+ }
+}
+
+func (c *argOrderChecker) isConstLiteral(x ast.Expr) bool {
+ if c.ctx.TypesInfo.Types[x].Value != nil {
+ return true
+ }
+
+ // Also permit byte slices.
+ switch x := x.(type) {
+ case *ast.CallExpr:
+ // Handle `[]byte("abc")` as well.
+ if len(x.Args) != 1 || !astp.IsBasicLit(x.Args[0]) {
+ return false
+ }
+ typ, ok := c.ctx.TypesInfo.TypeOf(x.Fun).(*types.Slice)
+ return ok && typep.HasUint8Kind(typ.Elem())
+
+ case *ast.CompositeLit:
+ // Check if it's a const byte slice.
+ typ, ok := c.ctx.TypesInfo.TypeOf(x).(*types.Slice)
+ if !ok || !typep.HasUint8Kind(typ.Elem()) {
+ return false
+ }
+ for _, elt := range x.Elts {
+ if !astp.IsBasicLit(elt) {
+ return false
+ }
+ }
+ return true
+
+ default:
+ return false
+ }
+}
+
+func (c *argOrderChecker) warn(call *ast.CallExpr) {
+ fixed := astcopy.CallExpr(call)
+ fixed.Args[0], fixed.Args[1] = fixed.Args[1], fixed.Args[0]
+ c.ctx.Warn(call, "probably meant `%s`", fixed)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go b/vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go
new file mode 100644
index 000000000..eb3428663
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go
@@ -0,0 +1,102 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "assignOp"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects assignments that can be simplified by using assignment operators"
+ info.Before = `x = x * 2`
+ info.After = `x *= 2`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&assignOpChecker{ctx: ctx})
+ })
+}
+
+type assignOpChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *assignOpChecker) VisitStmt(stmt ast.Stmt) {
+ assign, ok := stmt.(*ast.AssignStmt)
+ cond := ok &&
+ assign.Tok == token.ASSIGN &&
+ len(assign.Lhs) == 1 &&
+ len(assign.Rhs) == 1 &&
+ typep.SideEffectFree(c.ctx.TypesInfo, assign.Lhs[0])
+ if !cond {
+ return
+ }
+
+ // TODO(quasilyte): can take commutativity into account.
+ expr, ok := assign.Rhs[0].(*ast.BinaryExpr)
+ if !ok || !astequal.Expr(assign.Lhs[0], expr.X) {
+ return
+ }
+
+ // TODO(quasilyte): perform unparen?
+ switch expr.Op {
+ case token.MUL:
+ c.warn(assign, token.MUL_ASSIGN, expr.Y)
+ case token.QUO:
+ c.warn(assign, token.QUO_ASSIGN, expr.Y)
+ case token.REM:
+ c.warn(assign, token.REM_ASSIGN, expr.Y)
+ case token.ADD:
+ c.warn(assign, token.ADD_ASSIGN, expr.Y)
+ case token.SUB:
+ c.warn(assign, token.SUB_ASSIGN, expr.Y)
+ case token.AND:
+ c.warn(assign, token.AND_ASSIGN, expr.Y)
+ case token.OR:
+ c.warn(assign, token.OR_ASSIGN, expr.Y)
+ case token.XOR:
+ c.warn(assign, token.XOR_ASSIGN, expr.Y)
+ case token.SHL:
+ c.warn(assign, token.SHL_ASSIGN, expr.Y)
+ case token.SHR:
+ c.warn(assign, token.SHR_ASSIGN, expr.Y)
+ case token.AND_NOT:
+ c.warn(assign, token.AND_NOT_ASSIGN, expr.Y)
+ }
+}
+
+func (c *assignOpChecker) warn(cause *ast.AssignStmt, op token.Token, rhs ast.Expr) {
+ suggestion := c.simplify(cause, op, rhs)
+ c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion)
+}
+
+func (c *assignOpChecker) simplify(cause *ast.AssignStmt, op token.Token, rhs ast.Expr) ast.Stmt {
+ if lit, ok := rhs.(*ast.BasicLit); ok && lit.Kind == token.INT && lit.Value == "1" {
+ switch op {
+ case token.ADD_ASSIGN:
+ return &ast.IncDecStmt{
+ X: cause.Lhs[0],
+ TokPos: cause.TokPos,
+ Tok: token.INC,
+ }
+ case token.SUB_ASSIGN:
+ return &ast.IncDecStmt{
+ X: cause.Lhs[0],
+ TokPos: cause.TokPos,
+ Tok: token.DEC,
+ }
+ }
+ }
+ suggestion := astcopy.AssignStmt(cause)
+ suggestion.Tok = op
+ suggestion.Rhs[0] = rhs
+ return suggestion
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go b/vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go
new file mode 100644
index 000000000..150cc6904
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go
@@ -0,0 +1,63 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "badCall"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects suspicious function calls"
+ info.Before = `strings.Replace(s, from, to, 0)`
+ info.After = `strings.Replace(s, from, to, -1)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&badCallChecker{ctx: ctx})
+ })
+}
+
+type badCallChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *badCallChecker) VisitExpr(expr ast.Expr) {
+ call := astcast.ToCallExpr(expr)
+ if len(call.Args) == 0 {
+ return
+ }
+
+ // TODO(quasilyte): handle methods.
+
+ switch qualifiedName(call.Fun) {
+ case "strings.Replace", "bytes.Replace":
+ if n := astcast.ToBasicLit(call.Args[3]); n.Value == "0" {
+ c.warnBadArg(n, "-1")
+ }
+ case "strings.SplitN", "bytes.SplitN":
+ if n := astcast.ToBasicLit(call.Args[2]); n.Value == "0" {
+ c.warnBadArg(n, "-1")
+ }
+ case "append":
+ if len(call.Args) == 1 {
+ c.warnAppend(call)
+ }
+ }
+}
+
+func (c *badCallChecker) warnBadArg(badArg *ast.BasicLit, correction string) {
+ goodArg := astcopy.BasicLit(badArg)
+ goodArg.Value = correction
+ c.ctx.Warn(badArg, "suspicious arg %s, probably meant %s",
+ badArg, goodArg)
+}
+
+func (c *badCallChecker) warnAppend(call *ast.CallExpr) {
+ c.ctx.Warn(call, "no-op append call, probably missing arguments")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/badCond_checker.go b/vendor/github.com/go-critic/go-critic/checkers/badCond_checker.go
new file mode 100644
index 000000000..466a89cc3
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/badCond_checker.go
@@ -0,0 +1,147 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/token"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "badCond"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects suspicious condition expressions"
+ info.Before = `
+for i := 0; i > n; i++ {
+ xs[i] = 0
+}`
+ info.After = `
+for i := 0; i < n; i++ {
+ xs[i] = 0
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForFuncDecl(&badCondChecker{ctx: ctx})
+ })
+}
+
+type badCondChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *badCondChecker) VisitFuncDecl(decl *ast.FuncDecl) {
+ ast.Inspect(decl.Body, func(n ast.Node) bool {
+ switch n := n.(type) {
+ case *ast.ForStmt:
+ c.checkForStmt(n)
+ case ast.Expr:
+ c.checkExpr(n)
+ }
+ return true
+ })
+}
+
+func (c *badCondChecker) checkExpr(expr ast.Expr) {
+ // TODO(Quasilyte): recognize more patterns.
+
+ cond := astcast.ToBinaryExpr(expr)
+ lhs := astcast.ToBinaryExpr(astutil.Unparen(cond.X))
+ rhs := astcast.ToBinaryExpr(astutil.Unparen(cond.Y))
+
+ if cond.Op != token.LAND {
+ return
+ }
+
+ // Notes:
+ // `x != a || x != b` handled by go vet.
+
+ // Pattern 1.
+ // `x < a && x > b`; Where `a` is less than `b`.
+ if c.lessAndGreater(lhs, rhs) {
+ c.warnCond(cond, "always false")
+ return
+ }
+
+ // Pattern 2.
+ // `x == a && x == b`
+ //
+ // Valid when `b == a` is intended, but still reported.
+ // We can disable "just suspicious" warnings by default
+ // is users are upset with the current behavior.
+ if c.equalToBoth(lhs, rhs) {
+ c.warnCond(cond, "suspicious")
+ return
+ }
+}
+
+func (c *badCondChecker) equalToBoth(lhs, rhs *ast.BinaryExpr) bool {
+ return lhs.Op == token.EQL && rhs.Op == token.EQL &&
+ astequal.Expr(lhs.X, rhs.X)
+}
+
+func (c *badCondChecker) lessAndGreater(lhs, rhs *ast.BinaryExpr) bool {
+ if lhs.Op != token.LSS || rhs.Op != token.GTR {
+ return false
+ }
+ if !astequal.Expr(lhs.X, rhs.X) {
+ return false
+ }
+ a := c.ctx.TypesInfo.Types[lhs.Y].Value
+ b := c.ctx.TypesInfo.Types[rhs.Y].Value
+ return a != nil && b != nil && constant.Compare(a, token.LSS, b)
+}
+
+func (c *badCondChecker) checkForStmt(stmt *ast.ForStmt) {
+ // TODO(Quasilyte): handle other kinds of bad conditionals.
+
+ init := astcast.ToAssignStmt(stmt.Init)
+ if init.Tok != token.DEFINE || len(init.Lhs) != 1 || len(init.Rhs) != 1 {
+ return
+ }
+ if astcast.ToBasicLit(init.Rhs[0]).Value != "0" {
+ return
+ }
+
+ iter := astcast.ToIdent(init.Lhs[0])
+ cond := astcast.ToBinaryExpr(stmt.Cond)
+ if cond.Op != token.GTR || !astequal.Expr(iter, cond.X) {
+ return
+ }
+ if !typep.SideEffectFree(c.ctx.TypesInfo, cond.Y) {
+ return
+ }
+
+ post := astcast.ToIncDecStmt(stmt.Post)
+ if post.Tok != token.INC || !astequal.Expr(iter, post.X) {
+ return
+ }
+
+ mutated := lintutil.CouldBeMutated(c.ctx.TypesInfo, stmt.Body, cond.Y) ||
+ lintutil.CouldBeMutated(c.ctx.TypesInfo, stmt.Body, iter)
+ if mutated {
+ return
+ }
+
+ c.warnForStmt(stmt, cond)
+}
+
+func (c *badCondChecker) warnForStmt(cause ast.Node, cond *ast.BinaryExpr) {
+ suggest := astcopy.BinaryExpr(cond)
+ suggest.Op = token.LSS
+ c.ctx.Warn(cause, "`%s` in loop; probably meant `%s`?",
+ cond, suggest)
+}
+
+func (c *badCondChecker) warnCond(cond *ast.BinaryExpr, tag string) {
+ c.ctx.Warn(cond, "`%s` condition is %s", cond, tag)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go b/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go
new file mode 100644
index 000000000..f4eb9ed73
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go
@@ -0,0 +1,344 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "strconv"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/astp"
+ "github.com/go-toolsmith/typep"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "boolExprSimplify"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects bool expressions that can be simplified"
+ info.Before = `
+a := !(elapsed >= expectElapsedMin)
+b := !(x) == !(y)`
+ info.After = `
+a := elapsed < expectElapsedMin
+b := (x) == (y)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&boolExprSimplifyChecker{ctx: ctx})
+ })
+}
+
+type boolExprSimplifyChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+ hasFloats bool
+}
+
+func (c *boolExprSimplifyChecker) VisitExpr(x ast.Expr) {
+ if !astp.IsBinaryExpr(x) && !astp.IsUnaryExpr(x) {
+ return
+ }
+
+ // Throw away non-bool expressions and avoid redundant
+ // AST copying below.
+ if typ := c.ctx.TypesInfo.TypeOf(x); typ == nil || !typep.HasBoolKind(typ.Underlying()) {
+ return
+ }
+
+ // We'll loose all types info after a copy,
+ // this is why we record valuable info before doing it.
+ c.hasFloats = lintutil.ContainsNode(x, func(n ast.Node) bool {
+ if x, ok := n.(*ast.BinaryExpr); ok {
+ return typep.HasFloatProp(c.ctx.TypesInfo.TypeOf(x.X).Underlying()) ||
+ typep.HasFloatProp(c.ctx.TypesInfo.TypeOf(x.Y).Underlying())
+ }
+ return false
+ })
+
+ y := c.simplifyBool(astcopy.Expr(x))
+ if !astequal.Expr(x, y) {
+ c.warn(x, y)
+ }
+}
+
+func (c *boolExprSimplifyChecker) simplifyBool(x ast.Expr) ast.Expr {
+ return astutil.Apply(x, nil, func(cur *astutil.Cursor) bool {
+ return c.doubleNegation(cur) ||
+ c.negatedEquals(cur) ||
+ c.invertComparison(cur) ||
+ c.combineChecks(cur) ||
+ c.removeIncDec(cur) ||
+ c.foldRanges(cur) ||
+ true
+ }).(ast.Expr)
+}
+
+func (c *boolExprSimplifyChecker) doubleNegation(cur *astutil.Cursor) bool {
+ neg1 := astcast.ToUnaryExpr(cur.Node())
+ neg2 := astcast.ToUnaryExpr(astutil.Unparen(neg1.X))
+ if neg1.Op == token.NOT && neg2.Op == token.NOT {
+ cur.Replace(astutil.Unparen(neg2.X))
+ return true
+ }
+ return false
+}
+
+func (c *boolExprSimplifyChecker) negatedEquals(cur *astutil.Cursor) bool {
+ x, ok := cur.Node().(*ast.BinaryExpr)
+ if !ok || x.Op != token.EQL {
+ return false
+ }
+ neg1 := astcast.ToUnaryExpr(x.X)
+ neg2 := astcast.ToUnaryExpr(x.Y)
+ if neg1.Op == token.NOT && neg2.Op == token.NOT {
+ x.X = neg1.X
+ x.Y = neg2.X
+ return true
+ }
+ return false
+}
+
+func (c *boolExprSimplifyChecker) invertComparison(cur *astutil.Cursor) bool {
+ if c.hasFloats { // See #673
+ return false
+ }
+
+ neg := astcast.ToUnaryExpr(cur.Node())
+ cmp := astcast.ToBinaryExpr(astutil.Unparen(neg.X))
+ if neg.Op != token.NOT {
+ return false
+ }
+
+ // Replace operator to its negated form.
+ switch cmp.Op {
+ case token.EQL:
+ cmp.Op = token.NEQ
+ case token.NEQ:
+ cmp.Op = token.EQL
+ case token.LSS:
+ cmp.Op = token.GEQ
+ case token.GTR:
+ cmp.Op = token.LEQ
+ case token.LEQ:
+ cmp.Op = token.GTR
+ case token.GEQ:
+ cmp.Op = token.LSS
+
+ default:
+ return false
+ }
+ cur.Replace(cmp)
+ return true
+}
+
+func (c *boolExprSimplifyChecker) isSafe(x ast.Expr) bool {
+ return typep.SideEffectFree(c.ctx.TypesInfo, x)
+}
+
+func (c *boolExprSimplifyChecker) combineChecks(cur *astutil.Cursor) bool {
+ or, ok := cur.Node().(*ast.BinaryExpr)
+ if !ok || or.Op != token.LOR {
+ return false
+ }
+
+ lhs := astcast.ToBinaryExpr(astutil.Unparen(or.X))
+ rhs := astcast.ToBinaryExpr(astutil.Unparen(or.Y))
+
+ if !astequal.Expr(lhs.X, rhs.X) || !astequal.Expr(lhs.Y, rhs.Y) {
+ return false
+ }
+ if !c.isSafe(lhs.X) || !c.isSafe(lhs.Y) {
+ return false
+ }
+
+ combTable := [...]struct {
+ x token.Token
+ y token.Token
+ result token.Token
+ }{
+ {token.GTR, token.EQL, token.GEQ},
+ {token.EQL, token.GTR, token.GEQ},
+ {token.LSS, token.EQL, token.LEQ},
+ {token.EQL, token.LSS, token.LEQ},
+ }
+ for _, comb := range &combTable {
+ if comb.x == lhs.Op && comb.y == rhs.Op {
+ lhs.Op = comb.result
+ cur.Replace(lhs)
+ return true
+ }
+ }
+ return false
+}
+
+func (c *boolExprSimplifyChecker) removeIncDec(cur *astutil.Cursor) bool {
+ cmp := astcast.ToBinaryExpr(cur.Node())
+
+ matchOneWay := func(op token.Token, x, y *ast.BinaryExpr) bool {
+ if x.Op != op || astcast.ToBasicLit(x.Y).Value != "1" {
+ return false
+ }
+ if y.Op == op && astcast.ToBasicLit(y.Y).Value == "1" {
+ return false
+ }
+ return true
+ }
+ replace := func(lhsOp, rhsOp, replacement token.Token) bool {
+ lhs := astcast.ToBinaryExpr(cmp.X)
+ rhs := astcast.ToBinaryExpr(cmp.Y)
+ switch {
+ case matchOneWay(lhsOp, lhs, rhs):
+ cmp.X = lhs.X
+ cmp.Op = replacement
+ cur.Replace(cmp)
+ return true
+ case matchOneWay(rhsOp, rhs, lhs):
+ cmp.Y = rhs.X
+ cmp.Op = replacement
+ cur.Replace(cmp)
+ return true
+ default:
+ return false
+ }
+ }
+
+ switch cmp.Op {
+ case token.GTR:
+ // `x > y-1` => `x >= y`
+ // `x+1 > y` => `x >= y`
+ return replace(token.ADD, token.SUB, token.GEQ)
+
+ case token.GEQ:
+ // `x >= y+1` => `x > y`
+ // `x-1 >= y` => `x > y`
+ return replace(token.SUB, token.ADD, token.GTR)
+
+ case token.LSS:
+ // `x < y+1` => `x <= y`
+ // `x-1 < y` => `x <= y`
+ return replace(token.SUB, token.ADD, token.LEQ)
+
+ case token.LEQ:
+ // `x <= y-1` => `x < y`
+ // `x+1 <= y` => `x < y`
+ return replace(token.ADD, token.SUB, token.LSS)
+
+ default:
+ return false
+ }
+}
+
+func (c *boolExprSimplifyChecker) foldRanges(cur *astutil.Cursor) bool {
+ if c.hasFloats { // See #848
+ return false
+ }
+
+ e, ok := cur.Node().(*ast.BinaryExpr)
+ if !ok {
+ return false
+ }
+ lhs := astcast.ToBinaryExpr(e.X)
+ rhs := astcast.ToBinaryExpr(e.Y)
+ if !c.isSafe(lhs.X) || !c.isSafe(rhs.X) {
+ return false
+ }
+ if !astequal.Expr(lhs.X, rhs.X) {
+ return false
+ }
+
+ c1, ok := c.int64val(lhs.Y)
+ if !ok {
+ return false
+ }
+ c2, ok := c.int64val(rhs.Y)
+ if !ok {
+ return false
+ }
+
+ type combination struct {
+ lhsOp token.Token
+ rhsOp token.Token
+ rhsDiff int64
+ resDelta int64
+ }
+ match := func(comb *combination) bool {
+ if lhs.Op != comb.lhsOp || rhs.Op != comb.rhsOp {
+ return false
+ }
+ if c2-c1 != comb.rhsDiff {
+ return false
+ }
+ return true
+ }
+
+ switch e.Op {
+ case token.LAND:
+ combTable := [...]combination{
+ // `x > c && x < c+2` => `x == c+1`
+ {token.GTR, token.LSS, 2, 1},
+ // `x >= c && x < c+1` => `x == c`
+ {token.GEQ, token.LSS, 1, 0},
+ // `x > c && x <= c+1` => `x == c+1`
+ {token.GTR, token.LEQ, 1, 1},
+ // `x >= c && x <= c` => `x == c`
+ {token.GEQ, token.LEQ, 0, 0},
+ }
+ for _, comb := range combTable {
+ if match(&comb) {
+ lhs.Op = token.EQL
+ v := c1 + comb.resDelta
+ lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v)
+ cur.Replace(lhs)
+ return true
+ }
+ }
+
+ case token.LOR:
+ combTable := [...]combination{
+ // `x < c || x > c` => `x != c`
+ {token.LSS, token.GTR, 0, 0},
+ // `x <= c || x > c+1` => `x != c+1`
+ {token.LEQ, token.GTR, 1, 1},
+ // `x < c || x >= c+1` => `x != c`
+ {token.LSS, token.GEQ, 1, 0},
+ // `x <= c || x >= c+2` => `x != c+1`
+ {token.LEQ, token.GEQ, 2, 1},
+ }
+ for _, comb := range combTable {
+ if match(&comb) {
+ lhs.Op = token.NEQ
+ v := c1 + comb.resDelta
+ lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v)
+ cur.Replace(lhs)
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func (c *boolExprSimplifyChecker) int64val(x ast.Expr) (int64, bool) {
+ // TODO(Quasilyte): if we had types info, we could use TypesInfo.Types[x].Value,
+ // but since copying erases leaves us without it, only basic literals are handled.
+ lit, ok := x.(*ast.BasicLit)
+ if !ok {
+ return 0, false
+ }
+ v, err := strconv.ParseInt(lit.Value, 10, 64)
+ if err != nil {
+ return 0, false
+ }
+ return v, true
+}
+
+func (c *boolExprSimplifyChecker) warn(cause, suggestion ast.Expr) {
+ c.SkipChilds = true
+ c.ctx.Warn(cause, "can simplify `%s` to `%s`", cause, suggestion)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/builtinShadow_checker.go b/vendor/github.com/go-critic/go-critic/checkers/builtinShadow_checker.go
new file mode 100644
index 000000000..24d8b7fff
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/builtinShadow_checker.go
@@ -0,0 +1,36 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "builtinShadow"
+ info.Tags = []string{"style", "opinionated"}
+ info.Summary = "Detects when predeclared identifiers shadowed in assignments"
+ info.Before = `len := 10`
+ info.After = `length := 10`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForLocalDef(&builtinShadowChecker{ctx: ctx}, ctx.TypesInfo)
+ })
+}
+
+type builtinShadowChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *builtinShadowChecker) VisitLocalDef(name astwalk.Name, _ ast.Expr) {
+ if isBuiltin(name.ID.Name) {
+ c.warn(name.ID)
+ }
+}
+
+func (c *builtinShadowChecker) warn(ident *ast.Ident) {
+ c.ctx.Warn(ident, "shadowing of predeclared identifier: %s", ident)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/captLocal_checker.go b/vendor/github.com/go-critic/go-critic/checkers/captLocal_checker.go
new file mode 100644
index 000000000..bc9a2115f
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/captLocal_checker.go
@@ -0,0 +1,49 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "captLocal"
+ info.Tags = []string{"style"}
+ info.Params = lintpack.CheckerParams{
+ "paramsOnly": {
+ Value: true,
+ Usage: "whether to restrict checker to params only",
+ },
+ }
+ info.Summary = "Detects capitalized names for local variables"
+ info.Before = `func f(IN int, OUT *int) (ERR error) {}`
+ info.After = `func f(in int, out *int) (err error) {}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &captLocalChecker{ctx: ctx}
+ c.paramsOnly = info.Params.Bool("paramsOnly")
+ return astwalk.WalkerForLocalDef(c, ctx.TypesInfo)
+ })
+}
+
+type captLocalChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ paramsOnly bool
+}
+
+func (c *captLocalChecker) VisitLocalDef(def astwalk.Name, _ ast.Expr) {
+ if c.paramsOnly && def.Kind != astwalk.NameParam {
+ return
+ }
+ if ast.IsExported(def.ID.Name) {
+ c.warn(def.ID)
+ }
+}
+
+func (c *captLocalChecker) warn(id ast.Node) {
+ c.ctx.Warn(id, "`%s' should not be capitalized", id)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/caseOrder_checker.go b/vendor/github.com/go-critic/go-critic/checkers/caseOrder_checker.go
new file mode 100644
index 000000000..1ef4b53b7
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/caseOrder_checker.go
@@ -0,0 +1,88 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "caseOrder"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects erroneous case order inside switch statements"
+ info.Before = `
+switch x.(type) {
+case ast.Expr:
+ fmt.Println("expr")
+case *ast.BasicLit:
+ fmt.Println("basic lit") // Never executed
+}`
+ info.After = `
+switch x.(type) {
+case *ast.BasicLit:
+ fmt.Println("basic lit") // Now reachable
+case ast.Expr:
+ fmt.Println("expr")
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&caseOrderChecker{ctx: ctx})
+ })
+}
+
+type caseOrderChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *caseOrderChecker) VisitStmt(stmt ast.Stmt) {
+ switch stmt := stmt.(type) {
+ case *ast.TypeSwitchStmt:
+ c.checkTypeSwitch(stmt)
+ case *ast.SwitchStmt:
+ c.checkSwitch(stmt)
+ }
+}
+
+func (c *caseOrderChecker) checkTypeSwitch(s *ast.TypeSwitchStmt) {
+ type ifaceType struct {
+ node ast.Node
+ typ *types.Interface
+ }
+ var ifaces []ifaceType // Interfaces seen so far
+ for _, cc := range s.Body.List {
+ cc := cc.(*ast.CaseClause)
+ for _, x := range cc.List {
+ typ := c.ctx.TypesInfo.TypeOf(x)
+ if typ == nil {
+ c.warnTypeImpl(cc, x)
+ return
+ }
+ for _, iface := range ifaces {
+ if types.Implements(typ, iface.typ) {
+ c.warnTypeSwitch(cc, x, iface.node)
+ break
+ }
+ }
+ if iface, ok := typ.Underlying().(*types.Interface); ok {
+ ifaces = append(ifaces, ifaceType{node: x, typ: iface})
+ }
+ }
+ }
+}
+
+func (c *caseOrderChecker) warnTypeSwitch(cause, concrete, iface ast.Node) {
+ c.ctx.Warn(cause, "case %s must go before the %s case", concrete, iface)
+}
+
+func (c *caseOrderChecker) warnTypeImpl(cause, concrete ast.Node) {
+ c.ctx.Warn(cause, "type is not defined %s", concrete)
+}
+
+func (c *caseOrderChecker) checkSwitch(s *ast.SwitchStmt) {
+ // TODO(Quasilyte): can handle expression cases that overlap.
+ // Cases that have narrower value range should go before wider ones.
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/checkers.go b/vendor/github.com/go-critic/go-critic/checkers/checkers.go
new file mode 100644
index 000000000..96202221e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/checkers.go
@@ -0,0 +1,10 @@
+// Package checkers is a gocritic linter main checkers collection.
+package checkers
+
+import (
+ "github.com/go-lintpack/lintpack"
+)
+
+var collection = &lintpack.CheckerCollection{
+ URL: "https://github.com/go-critic/go-critic/checkers",
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/codegenComment_checker.go b/vendor/github.com/go-critic/go-critic/checkers/codegenComment_checker.go
new file mode 100644
index 000000000..14d89da37
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/codegenComment_checker.go
@@ -0,0 +1,61 @@
+package checkers
+
+import (
+ "go/ast"
+ "regexp"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "codegenComment"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects malformed 'code generated' file comments"
+ info.Before = `// This file was automatically generated by foogen`
+ info.After = `// Code generated by foogen. DO NOT EDIT.`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ patterns := []string{
+ "this (?:file|code) (?:was|is) auto(?:matically)? generated",
+ "this (?:file|code) (?:was|is) generated automatically",
+ "this (?:file|code) (?:was|is) generated by",
+ "this (?:file|code) (?:was|is) (?:auto(?:matically)? )?generated",
+ "this (?:file|code) (?:was|is) generated",
+ "code in this file (?:was|is) auto(?:matically)? generated",
+ "generated (?:file|code) - do not edit",
+ // TODO(Quasilyte): more of these.
+ }
+ re := regexp.MustCompile("(?i)" + strings.Join(patterns, "|"))
+ return &codegenCommentChecker{
+ ctx: ctx,
+ badCommentRE: re,
+ }
+ })
+}
+
+type codegenCommentChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ badCommentRE *regexp.Regexp
+}
+
+func (c *codegenCommentChecker) WalkFile(f *ast.File) {
+ if f.Doc == nil {
+ return
+ }
+
+ for _, comment := range f.Doc.List {
+ if c.badCommentRE.MatchString(comment.Text) {
+ c.warn(comment)
+ return
+ }
+ }
+}
+
+func (c *codegenCommentChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "comment should match `Code generated .* DO NOT EDIT.` regexp")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go b/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go
new file mode 100644
index 000000000..ed75015e0
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go
@@ -0,0 +1,77 @@
+package checkers
+
+import (
+ "go/ast"
+ "regexp"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "commentFormatting"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects comments with non-idiomatic formatting"
+ info.Before = `//This is a comment`
+ info.After = `// This is a comment`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ parts := []string{
+ `^//\w+:.*$`, //key: value
+ `^//nolint$`, //nolint
+ `^//line /.*:\d+`, //line /path/to/file:123
+ }
+ pat := "(?m)" + strings.Join(parts, "|")
+ pragmaRE := regexp.MustCompile(pat)
+ return astwalk.WalkerForComment(&commentFormattingChecker{
+ ctx: ctx,
+ pragmaRE: pragmaRE,
+ })
+ })
+}
+
+type commentFormattingChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ pragmaRE *regexp.Regexp
+}
+
+func (c *commentFormattingChecker) VisitComment(cg *ast.CommentGroup) {
+ if strings.HasPrefix(cg.List[0].Text, "/*") {
+ return
+ }
+ for _, comment := range cg.List {
+ if len(comment.Text) <= len("// ") {
+ continue
+ }
+ if c.pragmaRE.MatchString(comment.Text) {
+ continue
+ }
+
+ // Make a decision based on a first comment text rune.
+ r, _ := utf8.DecodeRuneInString(comment.Text[len("//"):])
+ if !c.specialChar(r) && !unicode.IsSpace(r) {
+ c.warn(cg)
+ return
+ }
+ }
+}
+
+func (c *commentFormattingChecker) specialChar(r rune) bool {
+ // Permitted list to avoid false-positives.
+ switch r {
+ case '+', '-', '#', '!':
+ return true
+ default:
+ return false
+ }
+}
+
+func (c *commentFormattingChecker) warn(cg *ast.CommentGroup) {
+ c.ctx.Warn(cg, "put a space between `//` and comment text")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go b/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go
new file mode 100644
index 000000000..0554e365e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go
@@ -0,0 +1,157 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "regexp"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/strparse"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "commentedOutCode"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects commented-out code inside function bodies"
+ info.Before = `
+// fmt.Println("Debugging hard")
+foo(1, 2)`
+ info.After = `foo(1, 2)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForLocalComment(&commentedOutCodeChecker{
+ ctx: ctx,
+ notQuiteFuncCall: regexp.MustCompile(`\w+\s+\([^)]*\)\s*$`),
+ })
+ })
+}
+
+type commentedOutCodeChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+ fn *ast.FuncDecl
+
+ notQuiteFuncCall *regexp.Regexp
+}
+
+func (c *commentedOutCodeChecker) EnterFunc(fn *ast.FuncDecl) bool {
+ c.fn = fn // Need to store current function inside checker context
+ return fn.Body != nil
+}
+
+func (c *commentedOutCodeChecker) VisitLocalComment(cg *ast.CommentGroup) {
+ s := cg.Text() // Collect text once
+
+ // We do multiple heuristics to avoid false positives.
+ // Many things can be improved here.
+
+ markers := []string{
+ "TODO", // TODO comments with code are permitted.
+
+ // "http://" is interpreted as a label with comment.
+ // There are other protocols we might want to include.
+ "http://",
+ "https://",
+
+ "e.g. ", // Clearly not a "selector expr" (mostly due to extra space)
+ }
+ for _, m := range markers {
+ if strings.Contains(s, m) {
+ return
+ }
+ }
+
+ // Some very short comment that can be skipped.
+ // Usually triggering on these results in false positive.
+ // Unless there is a very popular call like print/println.
+ cond := len(s) < len("quite too short") &&
+ !strings.Contains(s, "print") &&
+ !strings.Contains(s, "fmt.") &&
+ !strings.Contains(s, "log.")
+ if cond {
+ return
+ }
+
+ // Almost looks like a commented-out function call,
+ // but there is a whitespace between function name and
+ // parameters list. Skip these to avoid false positives.
+ if c.notQuiteFuncCall.MatchString(s) {
+ return
+ }
+
+ stmt := strparse.Stmt(s)
+
+ if c.isPermittedStmt(stmt) {
+ return
+ }
+
+ if stmt != strparse.BadStmt {
+ c.warn(cg)
+ return
+ }
+
+ // Don't try to parse one-liner as block statement
+ if len(cg.List) == 1 && !strings.Contains(s, "\n") {
+ return
+ }
+
+ // Some attempts to avoid false positives.
+ if c.skipBlock(s) {
+ return
+ }
+
+ // Add braces to make block statement from
+ // multiple statements.
+ stmt = strparse.Stmt(fmt.Sprintf("{ %s }", s))
+
+ if stmt, ok := stmt.(*ast.BlockStmt); ok && len(stmt.List) != 0 {
+ c.warn(cg)
+ }
+}
+
+func (c *commentedOutCodeChecker) skipBlock(s string) bool {
+ lines := strings.Split(s, "\n") // There is at least 1 line, that's invariant
+
+ // Special example test block.
+ if isExampleTestFunc(c.fn) && strings.Contains(lines[0], "Output:") {
+ return true
+ }
+
+ return false
+}
+
+func (c *commentedOutCodeChecker) isPermittedStmt(stmt ast.Stmt) bool {
+ switch stmt := stmt.(type) {
+ case *ast.ExprStmt:
+ return c.isPermittedExpr(stmt.X)
+ case *ast.LabeledStmt:
+ return c.isPermittedStmt(stmt.Stmt)
+ case *ast.DeclStmt:
+ decl := stmt.Decl.(*ast.GenDecl)
+ return decl.Tok == token.TYPE
+ default:
+ return false
+ }
+}
+
+func (c *commentedOutCodeChecker) isPermittedExpr(x ast.Expr) bool {
+ // Permit anything except expressions that can be used
+ // with complete result discarding.
+ switch x := x.(type) {
+ case *ast.CallExpr:
+ return false
+ case *ast.UnaryExpr:
+ // "<-" channel receive is not permitted.
+ return x.Op != token.ARROW
+ default:
+ return true
+ }
+}
+
+func (c *commentedOutCodeChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "may want to remove commented-out code")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/commentedOutImport_checker.go b/vendor/github.com/go-critic/go-critic/checkers/commentedOutImport_checker.go
new file mode 100644
index 000000000..5aeb86c07
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/commentedOutImport_checker.go
@@ -0,0 +1,76 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "regexp"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "commentedOutImport"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects commented-out imports"
+ info.Before = `
+import (
+ "fmt"
+ //"os"
+)`
+ info.After = `
+import (
+ "fmt"
+)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ const pattern = `(?m)^(?://|/\*)?\s*"([a-zA-Z0-9_/]+)"\s*(?:\*/)?$`
+ return &commentedOutImportChecker{
+ ctx: ctx,
+ importStringRE: regexp.MustCompile(pattern),
+ }
+ })
+}
+
+type commentedOutImportChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ importStringRE *regexp.Regexp
+}
+
+func (c *commentedOutImportChecker) WalkFile(f *ast.File) {
+ // TODO(Quasilyte): handle commented-out import spec,
+ // for example: // import "errors".
+
+ for _, decl := range f.Decls {
+ decl, ok := decl.(*ast.GenDecl)
+ if !ok || decl.Tok != token.IMPORT {
+ // Import decls can only be in the beginning of the file.
+ // If we've met some other decl, there will be no more
+ // import decls.
+ break
+ }
+
+ // Find comments inside this import decl span.
+ for _, cg := range f.Comments {
+ if cg.Pos() > decl.Rparen {
+ break // Below the decl, stop.
+ }
+ if cg.Pos() < decl.Lparen {
+ continue // Before the decl, skip.
+ }
+
+ for _, comment := range cg.List {
+ for _, m := range c.importStringRE.FindAllStringSubmatch(comment.Text, -1) {
+ c.warn(comment, m[1])
+ }
+ }
+ }
+ }
+}
+
+func (c *commentedOutImportChecker) warn(cause ast.Node, path string) {
+ c.ctx.Warn(cause, "remove commented-out %q import", path)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/defaultCaseOrder_checker.go b/vendor/github.com/go-critic/go-critic/checkers/defaultCaseOrder_checker.go
new file mode 100644
index 000000000..caa0de657
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/defaultCaseOrder_checker.go
@@ -0,0 +1,65 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "defaultCaseOrder"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects when default case in switch isn't on 1st or last position"
+ info.Before = `
+switch {
+case x > y:
+ // ...
+default: // <- not the best position
+ // ...
+case x == 10:
+ // ...
+}`
+ info.After = `
+switch {
+case x > y:
+ // ...
+case x == 10:
+ // ...
+default: // <- last case (could also be the first one)
+ // ...
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&defaultCaseOrderChecker{ctx: ctx})
+ })
+}
+
+type defaultCaseOrderChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *defaultCaseOrderChecker) VisitStmt(stmt ast.Stmt) {
+ swtch, ok := stmt.(*ast.SwitchStmt)
+ if !ok {
+ return
+ }
+ for i, stmt := range swtch.Body.List {
+ caseStmt, ok := stmt.(*ast.CaseClause)
+ if !ok {
+ continue
+ }
+ // is `default` case
+ if caseStmt.List == nil {
+ if i != 0 && i != len(swtch.Body.List)-1 {
+ c.warn(caseStmt)
+ }
+ }
+ }
+}
+
+func (c *defaultCaseOrderChecker) warn(cause *ast.CaseClause) {
+ c.ctx.Warn(cause, "consider to make `default` case as first or as last case")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go b/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go
new file mode 100644
index 000000000..37675735b
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go
@@ -0,0 +1,149 @@
+package checkers
+
+import (
+ "go/ast"
+ "regexp"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "deprecatedComment"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects malformed 'deprecated' doc-comments"
+ info.Before = `
+// deprecated, use FuncNew instead
+func FuncOld() int`
+ info.After = `
+// Deprecated: use FuncNew instead
+func FuncOld() int`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &deprecatedCommentChecker{ctx: ctx}
+
+ c.commonPatterns = []*regexp.Regexp{
+ regexp.MustCompile(`(?i)this (?:function|type) is deprecated`),
+ regexp.MustCompile(`(?i)deprecated[.!]? use \S* instead`),
+ regexp.MustCompile(`(?i)\[\[deprecated\]\].*`),
+ regexp.MustCompile(`(?i)note: deprecated\b.*`),
+ regexp.MustCompile(`(?i)deprecated in.*`),
+ // TODO(quasilyte): more of these?
+ }
+
+ // TODO(quasilyte): may want to generate this list programmatically.
+ //
+ // TODO(quasilyte): currently it only handles a single missing letter.
+ // Might want to handle other kinds of common misspell/typo kinds.
+ c.commonTypos = []string{
+ "Dprecated: ",
+ "Derecated: ",
+ "Depecated: ",
+ "Deprcated: ",
+ "Depreated: ",
+ "Deprected: ",
+ "Deprecaed: ",
+ "Deprecatd: ",
+ "Deprecate: ",
+ "Derpecate: ",
+ "Derpecated: ",
+ "Depreacted: ",
+ }
+ for i := range c.commonTypos {
+ c.commonTypos[i] = strings.ToUpper(c.commonTypos[i])
+ }
+
+ return astwalk.WalkerForDocComment(c)
+ })
+}
+
+type deprecatedCommentChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ commonPatterns []*regexp.Regexp
+ commonTypos []string
+}
+
+func (c *deprecatedCommentChecker) VisitDocComment(doc *ast.CommentGroup) {
+ // There are 3 accepted forms of deprecation comments:
+ //
+ // 1. inline, that can't be handled with a DocCommentVisitor.
+ // Note that "Deprecated: " may not even be the comment prefix there.
+ // Example: "The line number in the input. Deprecated: Kept for compatibility."
+ // TODO(quasilyte): fix it.
+ //
+ // 2. Longer form-1. It's a doc-comment that only contains "deprecation" notice.
+ //
+ // 3. Like form-2, but may also include doc-comment text.
+ // Distinguished by an empty line.
+ //
+ // See https://github.com/golang/go/issues/10909#issuecomment-136492606.
+ //
+ // It's desirable to see how people make mistakes with the format,
+ // this is why there is currently no special treatment for these cases.
+ // TODO(quasilyte): do more audits and grow the negative tests suite.
+ //
+ // TODO(quasilyte): there are also multi-line deprecation comments.
+
+ for _, comment := range doc.List {
+ if strings.HasPrefix(comment.Text, "/*") {
+ // TODO(quasilyte): handle multi-line doc comments.
+ continue
+ }
+ l := comment.Text[len("//"):]
+ if len(l) < len("Deprecated: ") {
+ continue
+ }
+ l = strings.TrimSpace(l)
+
+ // Check whether someone messed up with a prefix casing.
+ upcase := strings.ToUpper(l)
+ if strings.HasPrefix(upcase, "DEPRECATED: ") && !strings.HasPrefix(l, "Deprecated: ") {
+ c.warnCasing(comment, l)
+ return
+ }
+
+ // Check is someone used comma instead of a colon.
+ if strings.HasPrefix(l, "Deprecated, ") {
+ c.warnComma(comment)
+ return
+ }
+
+ // Check for other commonly used patterns.
+ for _, pat := range c.commonPatterns {
+ if pat.MatchString(l) {
+ c.warnPattern(comment)
+ return
+ }
+ }
+
+ // Detect some simple typos.
+ for _, prefixWithTypo := range c.commonTypos {
+ if strings.HasPrefix(upcase, prefixWithTypo) {
+ c.warnTypo(comment, l)
+ return
+ }
+ }
+ }
+}
+
+func (c *deprecatedCommentChecker) warnCasing(cause ast.Node, line string) {
+ prefix := line[:len("DEPRECATED: ")]
+ c.ctx.Warn(cause, "use `Deprecated: ` (note the casing) instead of `%s`", prefix)
+}
+
+func (c *deprecatedCommentChecker) warnPattern(cause ast.Node) {
+ c.ctx.Warn(cause, "the proper format is `Deprecated: <text>`")
+}
+
+func (c *deprecatedCommentChecker) warnComma(cause ast.Node) {
+ c.ctx.Warn(cause, "use `:` instead of `,` in `Deprecated, `")
+}
+
+func (c *deprecatedCommentChecker) warnTypo(cause ast.Node, line string) {
+ word := strings.Split(line, ":")[0]
+ c.ctx.Warn(cause, "typo in `%s`; should be `Deprecated`", word)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/docStub_checker.go b/vendor/github.com/go-critic/go-critic/checkers/docStub_checker.go
new file mode 100644
index 000000000..5c771b31c
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/docStub_checker.go
@@ -0,0 +1,95 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "regexp"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "docStub"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects comments that silence go lint complaints about doc-comment"
+ info.Before = `
+// Foo ...
+func Foo() {
+}`
+ info.After = `
+// (A) - remove the doc-comment stub
+func Foo() {}
+// (B) - replace it with meaningful comment
+// Foo is a demonstration-only function.
+func Foo() {}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ re := `(?i)^\.\.\.$|^\.$|^xxx\.?$|^whatever\.?$`
+ c := &docStubChecker{
+ ctx: ctx,
+ stubCommentRE: regexp.MustCompile(re),
+ }
+ return c
+ })
+}
+
+type docStubChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ stubCommentRE *regexp.Regexp
+}
+
+func (c *docStubChecker) WalkFile(f *ast.File) {
+ for _, decl := range f.Decls {
+ switch decl := decl.(type) {
+ case *ast.FuncDecl:
+ c.visitDoc(decl, decl.Name, decl.Doc, false)
+ case *ast.GenDecl:
+ if decl.Tok != token.TYPE {
+ continue
+ }
+ if len(decl.Specs) == 1 {
+ spec := decl.Specs[0].(*ast.TypeSpec)
+ // Only 1 spec, use doc from the decl itself.
+ c.visitDoc(spec, spec.Name, decl.Doc, true)
+ }
+ // N specs, use per-spec doc.
+ for _, spec := range decl.Specs {
+ spec := spec.(*ast.TypeSpec)
+ c.visitDoc(spec, spec.Name, spec.Doc, true)
+ }
+ }
+ }
+}
+
+func (c *docStubChecker) visitDoc(decl ast.Node, sym *ast.Ident, doc *ast.CommentGroup, article bool) {
+ if !sym.IsExported() || doc == nil {
+ return
+ }
+ line := strings.TrimSpace(doc.List[0].Text[len("//"):])
+ if article {
+ // Skip optional article.
+ for _, a := range []string{"The ", "An ", "A "} {
+ if strings.HasPrefix(line, a) {
+ line = line[len(a):]
+ break
+ }
+ }
+ }
+ if !strings.HasPrefix(line, sym.Name) {
+ return
+ }
+ line = strings.TrimSpace(line[len(sym.Name):])
+ // Now try to detect the "stub" part.
+ if c.stubCommentRE.MatchString(line) {
+ c.warn(decl)
+ }
+}
+
+func (c *docStubChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "silencing go lint doc-comment warnings is unadvised")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go
new file mode 100644
index 000000000..819759403
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go
@@ -0,0 +1,133 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "dupArg"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects suspicious duplicated arguments"
+ info.Before = `copy(dst, dst)`
+ info.After = `copy(dst, src)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &dupArgChecker{ctx: ctx}
+ // newMatcherFunc returns a function that matches a call if
+ // args[xIndex] and args[yIndex] are equal.
+ newMatcherFunc := func(xIndex, yIndex int) func(*ast.CallExpr) bool {
+ return func(call *ast.CallExpr) bool {
+ if len(call.Args) <= xIndex || len(call.Args) <= yIndex {
+ return false
+ }
+ x := call.Args[xIndex]
+ y := call.Args[yIndex]
+ return astequal.Expr(x, y)
+ }
+ }
+
+ // m maps pattern string to a matching function.
+ // String patterns are used for documentation purposes (readability).
+ m := map[string]func(*ast.CallExpr) bool{
+ "(x, x, ...)": newMatcherFunc(0, 1),
+ "(x, _, x, ...)": newMatcherFunc(0, 2),
+ "(_, x, x, ...)": newMatcherFunc(1, 2),
+ }
+
+ // TODO(quasilyte): handle x.Equal(x) cases.
+ // Example: *math/Big.Int.Cmp method.
+
+ // TODO(quasilyte): more perky mode that will also
+ // report things like io.Copy(x, x).
+ // Probably safe thing to do even without that option
+ // if `x` is not interface (requires type checks
+ // that are not incorporated into this checker yet).
+
+ c.matchers = map[string]func(*ast.CallExpr) bool{
+ "copy": m["(x, x, ...)"],
+
+ "math.Max": m["(x, x, ...)"],
+ "math.Min": m["(x, x, ...)"],
+
+ "reflect.Copy": m["(x, x, ...)"],
+ "reflect.DeepEqual": m["(x, x, ...)"],
+
+ "strings.Contains": m["(x, x, ...)"],
+ "strings.Compare": m["(x, x, ...)"],
+ "strings.EqualFold": m["(x, x, ...)"],
+ "strings.HasPrefix": m["(x, x, ...)"],
+ "strings.HasSuffix": m["(x, x, ...)"],
+ "strings.Index": m["(x, x, ...)"],
+ "strings.LastIndex": m["(x, x, ...)"],
+ "strings.Split": m["(x, x, ...)"],
+ "strings.SplitAfter": m["(x, x, ...)"],
+ "strings.SplitAfterN": m["(x, x, ...)"],
+ "strings.SplitN": m["(x, x, ...)"],
+ "strings.Replace": m["(_, x, x, ...)"],
+ "strings.ReplaceAll": m["(_, x, x, ...)"],
+
+ "bytes.Contains": m["(x, x, ...)"],
+ "bytes.Compare": m["(x, x, ...)"],
+ "bytes.Equal": m["(x, x, ...)"],
+ "bytes.EqualFold": m["(x, x, ...)"],
+ "bytes.HasPrefix": m["(x, x, ...)"],
+ "bytes.HasSuffix": m["(x, x, ...)"],
+ "bytes.Index": m["(x, x, ...)"],
+ "bytes.LastIndex": m["(x, x, ...)"],
+ "bytes.Split": m["(x, x, ...)"],
+ "bytes.SplitAfter": m["(x, x, ...)"],
+ "bytes.SplitAfterN": m["(x, x, ...)"],
+ "bytes.SplitN": m["(x, x, ...)"],
+ "bytes.Replace": m["(_, x, x, ...)"],
+ "bytes.ReplaceAll": m["(_, x, x, ...)"],
+
+ "types.Identical": m["(x, x, ...)"],
+ "types.IdenticalIgnoreTags": m["(x, x, ...)"],
+
+ "draw.Draw": m["(x, _, x, ...)"],
+
+ // TODO(quasilyte): more of these.
+ }
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type dupArgChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ matchers map[string]func(*ast.CallExpr) bool
+}
+
+func (c *dupArgChecker) VisitExpr(expr ast.Expr) {
+ call, ok := expr.(*ast.CallExpr)
+ if !ok {
+ return
+ }
+
+ // TODO(quasilyte): this kind of check is needed in multiple
+ // places and the code is somewhat duplicated around.
+ // We probably need to stop using qualifiedName for non-experimental checkers.
+ if calledExpr, ok := call.Fun.(*ast.SelectorExpr); ok {
+ obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
+ if !ok || !isStdlibPkg(obj.Imported()) {
+ return
+ }
+ }
+
+ m := c.matchers[qualifiedName(call.Fun)]
+ if m != nil && m(call) {
+ c.warn(call)
+ }
+}
+
+func (c *dupArgChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "suspicious duplicated args in `%s`", cause)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupBranchBody_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupBranchBody_checker.go
new file mode 100644
index 000000000..a13884873
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/dupBranchBody_checker.go
@@ -0,0 +1,58 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "dupBranchBody"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects duplicated branch bodies inside conditional statements"
+ info.Before = `
+if cond {
+ println("cond=true")
+} else {
+ println("cond=true")
+}`
+ info.After = `
+if cond {
+ println("cond=true")
+} else {
+ println("cond=false")
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&dupBranchBodyChecker{ctx: ctx})
+ })
+}
+
+type dupBranchBodyChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *dupBranchBodyChecker) VisitStmt(stmt ast.Stmt) {
+ // TODO(quasilyte): extend to check switch statements as well.
+ // Should be very careful with type switches.
+
+ if stmt, ok := stmt.(*ast.IfStmt); ok {
+ c.checkIf(stmt)
+ }
+}
+
+func (c *dupBranchBodyChecker) checkIf(stmt *ast.IfStmt) {
+ thenBody := stmt.Body
+ elseBody, ok := stmt.Else.(*ast.BlockStmt)
+ if ok && astequal.Stmt(thenBody, elseBody) {
+ c.warnIf(stmt)
+ }
+}
+
+func (c *dupBranchBodyChecker) warnIf(cause ast.Node) {
+ c.ctx.Warn(cause, "both branches in if statement has same body")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go
new file mode 100644
index 000000000..26ef17398
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go
@@ -0,0 +1,57 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "dupCase"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects duplicated case clauses inside switch statements"
+ info.Before = `
+switch x {
+case ys[0], ys[1], ys[2], ys[0], ys[4]:
+}`
+ info.After = `
+switch x {
+case ys[0], ys[1], ys[2], ys[3], ys[4]:
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&dupCaseChecker{ctx: ctx})
+ })
+}
+
+type dupCaseChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ astSet lintutil.AstSet
+}
+
+func (c *dupCaseChecker) VisitStmt(stmt ast.Stmt) {
+ if stmt, ok := stmt.(*ast.SwitchStmt); ok {
+ c.checkSwitch(stmt)
+ }
+}
+
+func (c *dupCaseChecker) checkSwitch(stmt *ast.SwitchStmt) {
+ c.astSet.Clear()
+ for i := range stmt.Body.List {
+ cc := stmt.Body.List[i].(*ast.CaseClause)
+ for _, x := range cc.List {
+ if !c.astSet.Insert(x) {
+ c.warn(x)
+ }
+ }
+ }
+}
+
+func (c *dupCaseChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "'case %s' is duplicated", cause)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go
new file mode 100644
index 000000000..d531413a1
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go
@@ -0,0 +1,63 @@
+package checkers
+
+import (
+ "fmt"
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "dupImport"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects multiple imports of the same package under different aliases"
+ info.Before = `
+import (
+ "fmt"
+ priting "fmt" // Imported the second time
+)`
+ info.After = `
+import(
+ "fmt"
+)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return &dupImportChecker{ctx: ctx}
+ })
+}
+
+type dupImportChecker struct {
+ ctx *lintpack.CheckerContext
+}
+
+func (c *dupImportChecker) WalkFile(f *ast.File) {
+ imports := make(map[string][]*ast.ImportSpec)
+ for _, importDcl := range f.Imports {
+ pkg := importDcl.Path.Value
+ imports[pkg] = append(imports[pkg], importDcl)
+ }
+
+ for _, importList := range imports {
+ if len(importList) == 1 {
+ continue
+ }
+ c.warn(importList)
+ }
+}
+
+func (c *dupImportChecker) warn(importList []*ast.ImportSpec) {
+ msg := fmt.Sprintf("package is imported %d times under different aliases on lines", len(importList))
+ for idx, importDcl := range importList {
+ switch {
+ case idx == len(importList)-1:
+ msg += " and"
+ case idx > 0:
+ msg += ","
+ }
+ msg += fmt.Sprintf(" %d", c.ctx.FileSet.Position(importDcl.Pos()).Line)
+ }
+ for _, importDcl := range importList {
+ c.ctx.Warn(importDcl, msg)
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupSubExpr_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupSubExpr_checker.go
new file mode 100644
index 000000000..24bb52434
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/dupSubExpr_checker.go
@@ -0,0 +1,102 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "dupSubExpr"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects suspicious duplicated sub-expressions"
+ info.Before = `
+sort.Slice(xs, func(i, j int) bool {
+ return xs[i].v < xs[i].v // Duplicated index
+})`
+ info.After = `
+sort.Slice(xs, func(i, j int) bool {
+ return xs[i].v < xs[j].v
+})`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &dupSubExprChecker{ctx: ctx}
+
+ ops := []struct {
+ op token.Token
+ float bool // Whether float args require special care
+ }{
+ {op: token.LOR}, // x || x
+ {op: token.LAND}, // x && x
+ {op: token.OR}, // x | x
+ {op: token.AND}, // x & x
+ {op: token.XOR}, // x ^ x
+ {op: token.LSS}, // x < x
+ {op: token.GTR}, // x > x
+ {op: token.AND_NOT}, // x &^ x
+ {op: token.REM}, // x % x
+
+ {op: token.EQL, float: true}, // x == x
+ {op: token.NEQ, float: true}, // x != x
+ {op: token.LEQ, float: true}, // x <= x
+ {op: token.GEQ, float: true}, // x >= x
+ {op: token.QUO, float: true}, // x / x
+ {op: token.SUB, float: true}, // x - x
+ }
+
+ c.opSet = make(map[token.Token]bool)
+ c.floatOpsSet = make(map[token.Token]bool)
+ for _, opInfo := range ops {
+ c.opSet[opInfo.op] = true
+ if opInfo.float {
+ c.floatOpsSet[opInfo.op] = true
+ }
+ }
+
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type dupSubExprChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ // opSet is a set of binary operations that do not make
+ // sense with duplicated (same) RHS and LHS.
+ opSet map[token.Token]bool
+
+ floatOpsSet map[token.Token]bool
+}
+
+func (c *dupSubExprChecker) VisitExpr(expr ast.Expr) {
+ if expr, ok := expr.(*ast.BinaryExpr); ok {
+ c.checkBinaryExpr(expr)
+ }
+}
+
+func (c *dupSubExprChecker) checkBinaryExpr(expr *ast.BinaryExpr) {
+ if !c.opSet[expr.Op] {
+ return
+ }
+ if c.resultIsFloat(expr.X) && c.floatOpsSet[expr.Op] {
+ return
+ }
+ if typep.SideEffectFree(c.ctx.TypesInfo, expr) && c.opSet[expr.Op] && astequal.Expr(expr.X, expr.Y) {
+ c.warn(expr)
+ }
+}
+
+func (c *dupSubExprChecker) resultIsFloat(expr ast.Expr) bool {
+ typ, ok := c.ctx.TypesInfo.TypeOf(expr).(*types.Basic)
+ return ok && typ.Info()&types.IsFloat != 0
+}
+
+func (c *dupSubExprChecker) warn(cause *ast.BinaryExpr) {
+ c.ctx.Warn(cause, "suspicious identical LHS and RHS for `%s` operator", cause.Op)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go b/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go
new file mode 100644
index 000000000..c3a9546bf
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go
@@ -0,0 +1,71 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "elseif"
+ info.Tags = []string{"style"}
+ info.Params = lintpack.CheckerParams{
+ "skipBalanced": {
+ Value: true,
+ Usage: "whether to skip balanced if-else pairs",
+ },
+ }
+ info.Summary = "Detects else with nested if statement that can be replaced with else-if"
+ info.Before = `
+if cond1 {
+} else {
+ if x := cond2; x {
+ }
+}`
+ info.After = `
+if cond1 {
+} else if x := cond2; x {
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &elseifChecker{ctx: ctx}
+ c.skipBalanced = info.Params.Bool("skipBalanced")
+ return astwalk.WalkerForStmt(c)
+ })
+}
+
+type elseifChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ skipBalanced bool
+}
+
+func (c *elseifChecker) VisitStmt(stmt ast.Stmt) {
+ if stmt, ok := stmt.(*ast.IfStmt); ok {
+ elseBody, ok := stmt.Else.(*ast.BlockStmt)
+ if !ok || len(elseBody.List) != 1 {
+ return
+ }
+ innerIfStmt, ok := elseBody.List[0].(*ast.IfStmt)
+ if !ok {
+ return
+ }
+ balanced := len(stmt.Body.List) == 1 &&
+ astp.IsIfStmt(stmt.Body.List[0])
+ if balanced && c.skipBalanced {
+ return // Configured to skip balanced statements
+ }
+ if innerIfStmt.Else != nil {
+ return
+ }
+ c.warn(stmt.Else)
+ }
+}
+
+func (c *elseifChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "can replace 'else {if cond {}}' with 'else if cond {}'")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go b/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go
new file mode 100644
index 000000000..5908dfa31
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go
@@ -0,0 +1,70 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "emptyFallthrough"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects fallthrough that can be avoided by using multi case values"
+ info.Before = `switch kind {
+case reflect.Int:
+ fallthrough
+case reflect.Int32:
+ return Int
+}`
+ info.After = `switch kind {
+case reflect.Int, reflect.Int32:
+ return Int
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&emptyFallthroughChecker{ctx: ctx})
+ })
+}
+
+type emptyFallthroughChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *emptyFallthroughChecker) VisitStmt(stmt ast.Stmt) {
+ ss, ok := stmt.(*ast.SwitchStmt)
+ if !ok {
+ return
+ }
+
+ prevCaseDefault := false
+ for i := len(ss.Body.List) - 1; i >= 0; i-- {
+ if cc, ok := ss.Body.List[i].(*ast.CaseClause); ok {
+ warn := false
+ if len(cc.Body) == 1 {
+ if bs, ok := cc.Body[0].(*ast.BranchStmt); ok && bs.Tok == token.FALLTHROUGH {
+ warn = true
+ if prevCaseDefault {
+ c.warnDefault(bs)
+ } else {
+ c.warn(bs)
+ }
+ }
+ }
+ if !warn {
+ prevCaseDefault = cc.List == nil
+ }
+ }
+ }
+}
+
+func (c *emptyFallthroughChecker) warnDefault(cause ast.Node) {
+ c.ctx.Warn(cause, "remove empty case containing only fallthrough to default case")
+}
+
+func (c *emptyFallthroughChecker) warn(cause ast.Node) {
+ c.ctx.Warn(cause, "replace empty case containing only fallthrough with expression list")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go b/vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go
new file mode 100644
index 000000000..a7be906ed
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go
@@ -0,0 +1,58 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "emptyStringTest"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects empty string checks that can be written more idiomatically"
+ info.Before = `len(s) == 0`
+ info.After = `s == ""`
+ info.Note = "See https://dmitri.shuralyov.com/idiomatic-go#empty-string-check."
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&emptyStringTestChecker{ctx: ctx})
+ })
+}
+
+type emptyStringTestChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *emptyStringTestChecker) VisitExpr(e ast.Expr) {
+ cmp := astcast.ToBinaryExpr(e)
+ if cmp.Op != token.EQL && cmp.Op != token.NEQ {
+ return
+ }
+ lenCall := astcast.ToCallExpr(cmp.X)
+ if astcast.ToIdent(lenCall.Fun).Name != "len" {
+ return
+ }
+ s := lenCall.Args[0]
+ if !typep.HasStringProp(c.ctx.TypesInfo.TypeOf(s)) {
+ return
+ }
+ zero := astcast.ToBasicLit(cmp.Y)
+ if zero.Value != "0" {
+ return
+ }
+ c.warn(cmp, s)
+}
+
+func (c *emptyStringTestChecker) warn(cmp *ast.BinaryExpr, s ast.Expr) {
+ suggest := astcopy.BinaryExpr(cmp)
+ suggest.X = s
+ suggest.Y = &ast.BasicLit{Value: `""`}
+ c.ctx.Warn(cmp, "replace `%s` with `%s`", cmp, suggest)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go b/vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go
new file mode 100644
index 000000000..265b2f79b
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go
@@ -0,0 +1,87 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "equalFold"
+ info.Tags = []string{"performance", "experimental"}
+ info.Summary = "Detects unoptimal strings/bytes case-insensitive comparison"
+ info.Before = `strings.ToLower(x) == strings.ToLower(y)`
+ info.After = `strings.EqualFold(x, y)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&equalFoldChecker{ctx: ctx})
+ })
+}
+
+type equalFoldChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *equalFoldChecker) VisitExpr(e ast.Expr) {
+ switch e := e.(type) {
+ case *ast.CallExpr:
+ c.checkBytes(e)
+ case *ast.BinaryExpr:
+ c.checkStrings(e)
+ }
+}
+
+// uncaseCall simplifies lower(x) or upper(x) to x.
+// If no simplification is applied, second return value is false.
+func (c *equalFoldChecker) uncaseCall(x ast.Expr, lower, upper string) (ast.Expr, bool) {
+ call := astcast.ToCallExpr(x)
+ name := qualifiedName(call.Fun)
+ if name != lower && name != upper {
+ return x, false
+ }
+ return call.Args[0], true
+}
+
+func (c *equalFoldChecker) checkBytes(expr *ast.CallExpr) {
+ if qualifiedName(expr.Fun) != "bytes.Equal" {
+ return
+ }
+
+ x, ok1 := c.uncaseCall(expr.Args[0], "bytes.ToLower", "bytes.ToUpper")
+ y, ok2 := c.uncaseCall(expr.Args[1], "bytes.ToLower", "bytes.ToUpper")
+ if !ok1 && !ok2 {
+ return
+ }
+ if !astequal.Expr(x, y) {
+ c.warnBytes(expr, x, y)
+ }
+}
+
+func (c *equalFoldChecker) checkStrings(expr *ast.BinaryExpr) {
+ if expr.Op != token.EQL && expr.Op != token.NEQ {
+ return
+ }
+
+ x, ok1 := c.uncaseCall(expr.X, "strings.ToLower", "strings.ToUpper")
+ y, ok2 := c.uncaseCall(expr.Y, "strings.ToLower", "strings.ToUpper")
+ if !ok1 && !ok2 {
+ return
+ }
+ if !astequal.Expr(x, y) {
+ c.warnStrings(expr, x, y)
+ }
+}
+
+func (c *equalFoldChecker) warnStrings(cause ast.Node, x, y ast.Expr) {
+ c.ctx.Warn(cause, "consider replacing with strings.EqualFold(%s, %s)", x, y)
+}
+
+func (c *equalFoldChecker) warnBytes(cause ast.Node, x, y ast.Expr) {
+ c.ctx.Warn(cause, "consider replacing with bytes.EqualFold(%s, %s)", x, y)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/evalOrder_checker.go b/vendor/github.com/go-critic/go-critic/checkers/evalOrder_checker.go
new file mode 100644
index 000000000..f76519cd7
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/evalOrder_checker.go
@@ -0,0 +1,87 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "evalOrder"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects unwanted dependencies on the evaluation order"
+ info.Before = `return x, f(&x)`
+ info.After = `
+err := f(&x)
+return x, err
+`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&evalOrderChecker{ctx: ctx})
+ })
+}
+
+type evalOrderChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *evalOrderChecker) VisitStmt(stmt ast.Stmt) {
+ ret := astcast.ToReturnStmt(stmt)
+ if len(ret.Results) < 2 {
+ return
+ }
+
+ // TODO(quasilyte): handle selector expressions like o.val in addition
+ // to bare identifiers.
+ addrTake := &ast.UnaryExpr{Op: token.AND}
+ for _, res := range ret.Results {
+ id, ok := res.(*ast.Ident)
+ if !ok {
+ continue
+ }
+ addrTake.X = id // addrTake is &id now
+ for _, res := range ret.Results {
+ call, ok := res.(*ast.CallExpr)
+ if !ok {
+ continue
+ }
+
+ // 1. Check if there is a call in form of id.method() where
+ // method takes id by a pointer.
+ if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
+ if astequal.Node(sel.X, id) && c.hasPtrRecv(sel.Sel) {
+ c.warn(call)
+ }
+ }
+
+ // 2. Check that there is no call that uses &id as an argument.
+ dependency := lintutil.ContainsNode(call, func(n ast.Node) bool {
+ return astequal.Node(addrTake, n)
+ })
+ if dependency {
+ c.warn(call)
+ }
+ }
+ }
+}
+
+func (c *evalOrderChecker) hasPtrRecv(fn *ast.Ident) bool {
+ sig, ok := c.ctx.TypesInfo.TypeOf(fn).(*types.Signature)
+ if !ok {
+ return false
+ }
+ return typep.IsPointer(sig.Recv().Type())
+}
+
+func (c *evalOrderChecker) warn(call *ast.CallExpr) {
+ c.ctx.Warn(call, "may want to evaluate %s before the return statement", call)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/exitAfterDefer_checker.go b/vendor/github.com/go-critic/go-critic/checkers/exitAfterDefer_checker.go
new file mode 100644
index 000000000..05ed6ae9e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/exitAfterDefer_checker.go
@@ -0,0 +1,78 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astfmt"
+ "github.com/go-toolsmith/astp"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "exitAfterDefer"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects calls to exit/fatal inside functions that use defer"
+ info.Before = `
+defer os.Remove(filename)
+if bad {
+ log.Fatalf("something bad happened")
+}`
+ info.After = `
+defer os.Remove(filename)
+if bad {
+ log.Printf("something bad happened")
+ return
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForFuncDecl(&exitAfterDeferChecker{ctx: ctx})
+ })
+}
+
+type exitAfterDeferChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *exitAfterDeferChecker) VisitFuncDecl(fn *ast.FuncDecl) {
+ // TODO(Quasilyte): handle goto and other kinds of flow that break
+ // the algorithm below that expects the latter statement to be
+ // executed after the ones that come before it.
+
+ var deferStmt *ast.DeferStmt
+ pre := func(cur *astutil.Cursor) bool {
+ // Don't recurse into local anonymous functions.
+ return !astp.IsFuncLit(cur.Node())
+ }
+ post := func(cur *astutil.Cursor) bool {
+ switch n := cur.Node().(type) {
+ case *ast.DeferStmt:
+ deferStmt = n
+ case *ast.CallExpr:
+ if deferStmt != nil {
+ switch qualifiedName(n.Fun) {
+ case "log.Fatal", "log.Fatalf", "log.Fatalln", "os.Exit":
+ c.warn(n, deferStmt)
+ return false
+ }
+ }
+ }
+ return true
+ }
+ astutil.Apply(fn.Body, pre, post)
+}
+
+func (c *exitAfterDeferChecker) warn(cause *ast.CallExpr, deferStmt *ast.DeferStmt) {
+ var s string
+ if fnlit, ok := deferStmt.Call.Fun.(*ast.FuncLit); ok {
+ // To avoid long and multi-line warning messages,
+ // collapse the function literals.
+ s = "defer " + astfmt.Sprint(fnlit.Type) + "{...}(...)"
+ } else {
+ s = astfmt.Sprint(deferStmt)
+ }
+ c.ctx.Warn(cause, "%s clutters `%s`", cause.Fun, s)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go b/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go
new file mode 100644
index 000000000..cb9faee71
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go
@@ -0,0 +1,65 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "flagDeref"
+ info.Tags = []string{"diagnostic"}
+ info.Summary = "Detects immediate dereferencing of `flag` package pointers"
+ info.Details = "Suggests to use pointer to array to avoid the copy using `&` on range expression."
+ info.Before = `b := *flag.Bool("b", false, "b docs")`
+ info.After = `
+var b bool
+flag.BoolVar(&b, "b", false, "b docs")`
+ info.Note = `
+Dereferencing returned pointers will lead to hard to find errors
+where flag values are not updated after flag.Parse().`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &flagDerefChecker{
+ ctx: ctx,
+ flagPtrFuncs: map[string]bool{
+ "flag.Bool": true,
+ "flag.Duration": true,
+ "flag.Float64": true,
+ "flag.Int": true,
+ "flag.Int64": true,
+ "flag.String": true,
+ "flag.Uint": true,
+ "flag.Uint64": true,
+ },
+ }
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type flagDerefChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ flagPtrFuncs map[string]bool
+}
+
+func (c *flagDerefChecker) VisitExpr(expr ast.Expr) {
+ if expr, ok := expr.(*ast.StarExpr); ok {
+ call, ok := expr.X.(*ast.CallExpr)
+ if !ok {
+ return
+ }
+ called := qualifiedName(call.Fun)
+ if c.flagPtrFuncs[called] {
+ c.warn(expr, called+"Var")
+ }
+ }
+}
+
+func (c *flagDerefChecker) warn(x ast.Node, suggestion string) {
+ c.ctx.Warn(x, "immediate deref in %s is most likely an error; consider using %s",
+ x, suggestion)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go b/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go
new file mode 100644
index 000000000..1d43ba521
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go
@@ -0,0 +1,68 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/types"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "flagName"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects flag names with whitespace"
+ info.Before = `b := flag.Bool(" foo ", false, "description")`
+ info.After = `b := flag.Bool("foo", false, "description")`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&flagNameChecker{ctx: ctx})
+ })
+}
+
+type flagNameChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *flagNameChecker) VisitExpr(expr ast.Expr) {
+ call := astcast.ToCallExpr(expr)
+ calledExpr := astcast.ToSelectorExpr(call.Fun)
+ obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
+ if !ok {
+ return
+ }
+ sym := calledExpr.Sel
+ pkg := obj.Imported()
+ if pkg.Path() != "flag" {
+ return
+ }
+
+ switch sym.Name {
+ case "Bool", "Duration", "Float64", "String",
+ "Int", "Int64", "Uint", "Uint64":
+ c.checkFlagName(call, call.Args[0])
+ case "BoolVar", "DurationVar", "Float64Var", "StringVar",
+ "IntVar", "Int64Var", "UintVar", "Uint64Var":
+ c.checkFlagName(call, call.Args[1])
+ }
+}
+
+func (c *flagNameChecker) checkFlagName(call *ast.CallExpr, arg ast.Expr) {
+ cv := c.ctx.TypesInfo.Types[arg].Value
+ if cv == nil {
+ return // Non-constant name
+ }
+ name := constant.StringVal(cv)
+ if strings.Contains(name, " ") {
+ c.warnWhitespace(call, name)
+ }
+}
+
+func (c *flagNameChecker) warnWhitespace(cause ast.Node, name string) {
+ c.ctx.Warn(cause, "flag name %q contains whitespace", name)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/hexLiteral_checker.go b/vendor/github.com/go-critic/go-critic/checkers/hexLiteral_checker.go
new file mode 100644
index 000000000..a700314cf
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/hexLiteral_checker.go
@@ -0,0 +1,60 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "hexLiteral"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects hex literals that have mixed case letter digits"
+ info.Before = `
+x := 0X12
+y := 0xfF`
+ info.After = `
+x := 0x12
+// (A)
+y := 0xff
+// (B)
+y := 0xFF`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&hexLiteralChecker{ctx: ctx})
+ })
+}
+
+type hexLiteralChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *hexLiteralChecker) warn0X(lit *ast.BasicLit) {
+ suggest := "0x" + lit.Value[len("0X"):]
+ c.ctx.Warn(lit, "prefer 0x over 0X, s/%s/%s/", lit.Value, suggest)
+}
+
+func (c *hexLiteralChecker) warnMixedDigits(lit *ast.BasicLit) {
+ c.ctx.Warn(lit, "don't mix hex literal letter digits casing")
+}
+
+func (c *hexLiteralChecker) VisitExpr(expr ast.Expr) {
+ lit := astcast.ToBasicLit(expr)
+ if lit.Kind != token.INT || len(lit.Value) < 3 {
+ return
+ }
+ if strings.HasPrefix(lit.Value, "0X") {
+ c.warn0X(lit)
+ return
+ }
+ digits := lit.Value[len("0x"):]
+ if strings.ToLower(digits) != digits && strings.ToUpper(digits) != digits {
+ c.warnMixedDigits(lit)
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go
new file mode 100644
index 000000000..656b4cc2d
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go
@@ -0,0 +1,63 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "hugeParam"
+ info.Tags = []string{"performance"}
+ info.Params = lintpack.CheckerParams{
+ "sizeThreshold": {
+ Value: 80,
+ Usage: "size in bytes that makes the warning trigger",
+ },
+ }
+ info.Summary = "Detects params that incur excessive amount of copying"
+ info.Before = `func f(x [1024]int) {}`
+ info.After = `func f(x *[1024]int) {}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForFuncDecl(&hugeParamChecker{
+ ctx: ctx,
+ sizeThreshold: int64(info.Params.Int("sizeThreshold")),
+ })
+ })
+}
+
+type hugeParamChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ sizeThreshold int64
+}
+
+func (c *hugeParamChecker) VisitFuncDecl(decl *ast.FuncDecl) {
+ // TODO(quasilyte): maybe it's worthwhile to permit skipping
+ // test files for this checker?
+ if decl.Recv != nil {
+ c.checkParams(decl.Recv.List)
+ }
+ c.checkParams(decl.Type.Params.List)
+}
+
+func (c *hugeParamChecker) checkParams(params []*ast.Field) {
+ for _, p := range params {
+ for _, id := range p.Names {
+ typ := c.ctx.TypesInfo.TypeOf(id)
+ size := c.ctx.SizesInfo.Sizeof(typ)
+ if size >= c.sizeThreshold {
+ c.warn(id, size)
+ }
+ }
+ }
+}
+
+func (c *hugeParamChecker) warn(cause *ast.Ident, size int64) {
+ c.ctx.Warn(cause, "%s is heavy (%d bytes); consider passing it by pointer",
+ cause, size)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/ifElseChain_checker.go b/vendor/github.com/go-critic/go-critic/checkers/ifElseChain_checker.go
new file mode 100644
index 000000000..c0a456afd
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/ifElseChain_checker.go
@@ -0,0 +1,99 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "ifElseChain"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects repeated if-else statements and suggests to replace them with switch statement"
+ info.Before = `
+if cond1 {
+ // Code A.
+} else if cond2 {
+ // Code B.
+} else {
+ // Code C.
+}`
+ info.After = `
+switch {
+case cond1:
+ // Code A.
+case cond2:
+ // Code B.
+default:
+ // Code C.
+}`
+ info.Note = `
+Permits single else or else-if; repeated else-if or else + else-if
+will trigger suggestion to use switch statement.
+See [EffectiveGo#switch](https://golang.org/doc/effective_go.html#switch).`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&ifElseChainChecker{ctx: ctx})
+ })
+}
+
+type ifElseChainChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ cause *ast.IfStmt
+ visited map[*ast.IfStmt]bool
+}
+
+func (c *ifElseChainChecker) EnterFunc(fn *ast.FuncDecl) bool {
+ if fn.Body == nil {
+ return false
+ }
+ c.visited = make(map[*ast.IfStmt]bool)
+ return true
+}
+
+func (c *ifElseChainChecker) VisitStmt(stmt ast.Stmt) {
+ if stmt, ok := stmt.(*ast.IfStmt); ok {
+ if c.visited[stmt] {
+ return
+ }
+ c.cause = stmt
+ c.checkIfStmt(stmt)
+ }
+}
+
+func (c *ifElseChainChecker) checkIfStmt(stmt *ast.IfStmt) {
+ const minThreshold = 2
+ if c.countIfelseLen(stmt) >= minThreshold {
+ c.warn()
+ }
+}
+
+func (c *ifElseChainChecker) countIfelseLen(stmt *ast.IfStmt) int {
+ count := 0
+ for {
+ switch e := stmt.Else.(type) {
+ case *ast.IfStmt:
+ if e.Init != nil {
+ return 0 // Give up
+ }
+ // Else if.
+ stmt = e
+ count++
+ c.visited[e] = true
+ case *ast.BlockStmt:
+ // Else branch.
+ return count + 1
+ default:
+ // No else or else if.
+ return count
+ }
+ }
+}
+
+func (c *ifElseChainChecker) warn() {
+ c.ctx.Warn(c.cause, "rewrite if-else to switch statement")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/importShadow_checker.go b/vendor/github.com/go-critic/go-critic/checkers/importShadow_checker.go
new file mode 100644
index 000000000..9a2ccc55e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/importShadow_checker.go
@@ -0,0 +1,47 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "importShadow"
+ info.Tags = []string{"style", "opinionated"}
+ info.Summary = "Detects when imported package names shadowed in the assignments"
+ info.Before = `
+// "path/filepath" is imported.
+filepath := "foo.txt"`
+ info.After = `
+filename := "foo.txt"`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ ctx.Require.PkgObjects = true
+ return astwalk.WalkerForLocalDef(&importShadowChecker{ctx: ctx}, ctx.TypesInfo)
+ })
+}
+
+type importShadowChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *importShadowChecker) VisitLocalDef(def astwalk.Name, _ ast.Expr) {
+ for pkgObj, name := range c.ctx.PkgObjects {
+ if name == def.ID.Name && name != "_" {
+ c.warn(def.ID, name, pkgObj.Imported())
+ }
+ }
+}
+
+func (c *importShadowChecker) warn(id ast.Node, importedName string, pkg *types.Package) {
+ if isStdlibPkg(pkg) {
+ c.ctx.Warn(id, "shadow of imported package '%s'", importedName)
+ } else {
+ c.ctx.Warn(id, "shadow of imported from '%s' package '%s'", pkg.Path(), importedName)
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go b/vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go
new file mode 100644
index 000000000..8fbe98c9d
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go
@@ -0,0 +1,50 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "indexAlloc"
+ info.Tags = []string{"performance"}
+ info.Summary = "Detects strings.Index calls that may cause unwanted allocs"
+ info.Before = `strings.Index(string(x), y)`
+ info.After = `bytes.Index(x, []byte(y))`
+ info.Note = `See Go issue for details: https://github.com/golang/go/issues/25864`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&indexAllocChecker{ctx: ctx})
+ })
+}
+
+type indexAllocChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *indexAllocChecker) VisitExpr(e ast.Expr) {
+ call := astcast.ToCallExpr(e)
+ if qualifiedName(call.Fun) != "strings.Index" {
+ return
+ }
+ stringConv := astcast.ToCallExpr(call.Args[0])
+ if qualifiedName(stringConv.Fun) != "string" {
+ return
+ }
+ x := stringConv.Args[0]
+ y := call.Args[1]
+ if typep.SideEffectFree(c.ctx.TypesInfo, x) && typep.SideEffectFree(c.ctx.TypesInfo, y) {
+ c.warn(e, x, y)
+ }
+}
+
+func (c *indexAllocChecker) warn(cause ast.Node, x, y ast.Expr) {
+ c.ctx.Warn(cause, "consider replacing %s with bytes.Index(%s, []byte(%s))",
+ cause, x, y)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/initClause_checker.go b/vendor/github.com/go-critic/go-critic/checkers/initClause_checker.go
new file mode 100644
index 000000000..bfbd661b2
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/initClause_checker.go
@@ -0,0 +1,56 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "initClause"
+ info.Tags = []string{"style", "opinionated", "experimental"}
+ info.Summary = "Detects non-assignment statements inside if/switch init clause"
+ info.Before = `if sideEffect(); cond {
+}`
+ info.After = `sideEffect()
+if cond {
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&initClauseChecker{ctx: ctx})
+ })
+}
+
+type initClauseChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *initClauseChecker) VisitStmt(stmt ast.Stmt) {
+ initClause := c.getInitClause(stmt)
+ if initClause != nil && !astp.IsAssignStmt(initClause) {
+ c.warn(stmt, initClause)
+ }
+}
+
+func (c *initClauseChecker) getInitClause(x ast.Stmt) ast.Stmt {
+ switch x := x.(type) {
+ case *ast.IfStmt:
+ return x.Init
+ case *ast.SwitchStmt:
+ return x.Init
+ default:
+ return nil
+ }
+}
+
+func (c *initClauseChecker) warn(stmt, clause ast.Stmt) {
+ name := "if"
+ if astp.IsSwitchStmt(stmt) {
+ name = "switch"
+ }
+ c.ctx.Warn(stmt, "consider to move `%s` before %s", clause, name)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go
new file mode 100644
index 000000000..3c0a95afc
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go
@@ -0,0 +1,27 @@
+package lintutil
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+// FindNode applies pred for root and all it's childs until it returns true.
+// Matched node is returned.
+// If none of the nodes matched predicate, nil is returned.
+func FindNode(root ast.Node, pred func(ast.Node) bool) ast.Node {
+ var found ast.Node
+ astutil.Apply(root, nil, func(cur *astutil.Cursor) bool {
+ if pred(cur.Node()) {
+ found = cur.Node()
+ return false
+ }
+ return true
+ })
+ return found
+}
+
+// ContainsNode reports whether `FindNode(root, pred)!=nil`.
+func ContainsNode(root ast.Node, pred func(ast.Node) bool) bool {
+ return FindNode(root, pred) != nil
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go
new file mode 100644
index 000000000..63d181e5e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go
@@ -0,0 +1,86 @@
+package lintutil
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/astp"
+ "github.com/go-toolsmith/typep"
+)
+
+// Different utilities to make simple analysis over typed ast values flow.
+//
+// It's primitive and can't replace SSA, but the bright side is that
+// it does not require building an additional IR eagerly.
+// Expected to be used sparingly inside a few checkers.
+//
+// If proven really useful, can be moved to go-toolsmith library.
+
+// IsImmutable reports whether n can be midified through any operation.
+func IsImmutable(info *types.Info, n ast.Expr) bool {
+ if astp.IsBasicLit(n) {
+ return true
+ }
+ tv, ok := info.Types[n]
+ return ok && !tv.Assignable() && !tv.Addressable()
+}
+
+// CouldBeMutated reports whether dst can be modified inside body.
+//
+// Note that it does not take already existing pointers to dst.
+// An example of safe and correct usage is checking of something
+// that was just defined, so the dst is a result of that definition.
+func CouldBeMutated(info *types.Info, body ast.Node, dst ast.Expr) bool {
+ if IsImmutable(info, dst) { // Fast path.
+ return false
+ }
+
+ // We don't track pass-by-value.
+ // If it's already a pointer, passing it by value
+ // means that there can be a potential indirect modification.
+ //
+ // It's possible to be less conservative here and find at least
+ // one such value pass before giving up.
+ if typep.IsPointer(info.TypeOf(dst)) {
+ return true
+ }
+
+ var isDst func(x ast.Expr) bool
+ if dst, ok := dst.(*ast.Ident); ok {
+ // Identifier can be shadowed,
+ // so we need to check the object as well.
+ obj := info.ObjectOf(dst)
+ if obj == nil {
+ return true // Being conservative
+ }
+ isDst = func(x ast.Expr) bool {
+ id, ok := x.(*ast.Ident)
+ return ok && id.Name == dst.Name && info.ObjectOf(id) == obj
+ }
+ } else {
+ isDst = func(x ast.Expr) bool {
+ return astequal.Expr(dst, x)
+ }
+ }
+
+ return ContainsNode(body, func(n ast.Node) bool {
+ switch n := n.(type) {
+ case *ast.UnaryExpr:
+ if n.Op == token.AND && isDst(n.X) {
+ return true // Address taken
+ }
+ case *ast.AssignStmt:
+ for _, lhs := range n.Lhs {
+ if isDst(lhs) {
+ return true
+ }
+ }
+ case *ast.IncDecStmt:
+ // Incremented or decremented.
+ return isDst(n.X)
+ }
+ return false
+ })
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astset.go b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astset.go
new file mode 100644
index 000000000..ebe7835e5
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astset.go
@@ -0,0 +1,44 @@
+package lintutil
+
+import (
+ "go/ast"
+
+ "github.com/go-toolsmith/astequal"
+)
+
+// AstSet is a simple ast.Node set.
+// Zero value is ready to use set.
+// Can be reused after Clear call.
+type AstSet struct {
+ items []ast.Node
+}
+
+// Contains reports whether s contains x.
+func (s *AstSet) Contains(x ast.Node) bool {
+ for i := range s.items {
+ if astequal.Node(s.items[i], x) {
+ return true
+ }
+ }
+ return false
+}
+
+// Insert pushes x in s if it's not already there.
+// Returns true if element was inserted.
+func (s *AstSet) Insert(x ast.Node) bool {
+ if s.Contains(x) {
+ return false
+ }
+ s.items = append(s.items, x)
+ return true
+}
+
+// Clear removes all element from set.
+func (s *AstSet) Clear() {
+ s.items = s.items[:0]
+}
+
+// Len returns the number of elements contained inside s.
+func (s *AstSet) Len() int {
+ return len(s.items)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/zero_value.go b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/zero_value.go
new file mode 100644
index 000000000..4370f5818
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/zero_value.go
@@ -0,0 +1,94 @@
+package lintutil
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/token"
+ "go/types"
+)
+
+// IsZeroValue reports whether x represents zero value of its type.
+//
+// The functions is conservative and may return false for zero values
+// if some cases are not handled in a comprehensive way
+// but is should never return true for something that's not a proper zv.
+func IsZeroValue(info *types.Info, x ast.Expr) bool {
+ switch x := x.(type) {
+ case *ast.BasicLit:
+ typ := info.TypeOf(x).Underlying().(*types.Basic)
+ v := info.Types[x].Value
+ var z constant.Value
+ switch {
+ case typ.Kind() == types.String:
+ z = constant.MakeString("")
+ case typ.Info()&types.IsInteger != 0:
+ z = constant.MakeInt64(0)
+ case typ.Info()&types.IsUnsigned != 0:
+ z = constant.MakeUint64(0)
+ case typ.Info()&types.IsFloat != 0:
+ z = constant.MakeFloat64(0)
+ default:
+ return false
+ }
+ return constant.Compare(v, token.EQL, z)
+
+ case *ast.CompositeLit:
+ return len(x.Elts) == 0
+
+ default:
+ // Note that this function is not comprehensive.
+ return false
+ }
+}
+
+// ZeroValueOf returns a zero value expression for typeExpr of type typ.
+// If function can't find such a value, nil is returned.
+func ZeroValueOf(typeExpr ast.Expr, typ types.Type) ast.Expr {
+ switch utyp := typ.Underlying().(type) {
+ case *types.Basic:
+ info := utyp.Info()
+ var zv ast.Expr
+ switch {
+ case info&types.IsInteger != 0:
+ zv = &ast.BasicLit{Kind: token.INT, Value: "0"}
+ case info&types.IsFloat != 0:
+ zv = &ast.BasicLit{Kind: token.FLOAT, Value: "0.0"}
+ case info&types.IsString != 0:
+ zv = &ast.BasicLit{Kind: token.STRING, Value: `""`}
+ case info&types.IsBoolean != 0:
+ zv = &ast.Ident{Name: "false"}
+ }
+ if isDefaultLiteralType(typ) {
+ return zv
+ }
+ return &ast.CallExpr{
+ Fun: typeExpr,
+ Args: []ast.Expr{zv},
+ }
+
+ case *types.Slice, *types.Map, *types.Pointer, *types.Interface:
+ return &ast.CallExpr{
+ Fun: typeExpr,
+ Args: []ast.Expr{&ast.Ident{Name: "nil"}},
+ }
+
+ case *types.Array, *types.Struct:
+ return &ast.CompositeLit{Type: typeExpr}
+
+ default:
+ return nil
+ }
+}
+
+func isDefaultLiteralType(typ types.Type) bool {
+ btyp, ok := typ.(*types.Basic)
+ if !ok {
+ return false
+ }
+ switch btyp.Kind() {
+ case types.Bool, types.Int, types.Float64, types.String:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go b/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go
new file mode 100644
index 000000000..de3e781e5
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go
@@ -0,0 +1,124 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+ "strings"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astp"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "mapKey"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects suspicious map literal keys"
+ info.Before = `
+_ = map[string]int{
+ "foo": 1,
+ "bar ": 2,
+}`
+ info.After = `
+_ = map[string]int{
+ "foo": 1,
+ "bar": 2,
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&mapKeyChecker{ctx: ctx})
+ })
+}
+
+type mapKeyChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ astSet lintutil.AstSet
+}
+
+func (c *mapKeyChecker) VisitExpr(expr ast.Expr) {
+ lit := astcast.ToCompositeLit(expr)
+ if len(lit.Elts) < 2 {
+ return
+ }
+
+ typ, ok := c.ctx.TypesInfo.TypeOf(lit).Underlying().(*types.Map)
+ if !ok {
+ return
+ }
+ if !typep.HasStringKind(typ.Key().Underlying()) {
+ return
+ }
+
+ c.checkWhitespace(lit)
+ c.checkDuplicates(lit)
+}
+
+func (c *mapKeyChecker) checkDuplicates(lit *ast.CompositeLit) {
+ c.astSet.Clear()
+
+ for _, elt := range lit.Elts {
+ kv := astcast.ToKeyValueExpr(elt)
+ if astp.IsBasicLit(kv.Key) {
+ // Basic lits are handled by the compiler.
+ continue
+ }
+ if !typep.SideEffectFree(c.ctx.TypesInfo, kv.Key) {
+ continue
+ }
+ if !c.astSet.Insert(kv.Key) {
+ c.warnDupKey(kv.Key)
+ }
+ }
+}
+
+func (c *mapKeyChecker) checkWhitespace(lit *ast.CompositeLit) {
+ var whitespaceKey ast.Node
+ for _, elt := range lit.Elts {
+ key := astcast.ToBasicLit(astcast.ToKeyValueExpr(elt).Key)
+ if len(key.Value) < len(`" "`) {
+ continue
+ }
+ // s is unquoted string literal value.
+ s := key.Value[len(`"`) : len(key.Value)-len(`"`)]
+ if !strings.Contains(s, " ") {
+ continue
+ }
+ if whitespaceKey != nil {
+ // Already seen something with a whitespace.
+ // More than one entry => not suspicious.
+ return
+ }
+ if s == " " {
+ // If space is used as a key, maybe this map
+ // has something to do with spaces. Give up.
+ return
+ }
+ // Check if it has exactly 1 space prefix or suffix.
+ bad := strings.HasPrefix(s, " ") && !strings.HasPrefix(s, " ") ||
+ strings.HasSuffix(s, " ") && !strings.HasSuffix(s, " ")
+ if !bad {
+ // These spaces can be a padding,
+ // or a legitimate part of a key. Give up.
+ return
+ }
+ whitespaceKey = key
+ }
+
+ if whitespaceKey != nil {
+ c.warnWhitespace(whitespaceKey)
+ }
+}
+
+func (c *mapKeyChecker) warnWhitespace(key ast.Node) {
+ c.ctx.Warn(key, "suspucious whitespace in %s key", key)
+}
+
+func (c *mapKeyChecker) warnDupKey(key ast.Node) {
+ c.ctx.Warn(key, "suspicious duplicate %s key", key)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/methodExprCall_checker.go b/vendor/github.com/go-critic/go-critic/checkers/methodExprCall_checker.go
new file mode 100644
index 000000000..60da11655
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/methodExprCall_checker.go
@@ -0,0 +1,57 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "methodExprCall"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects method expression call that can be replaced with a method call"
+ info.Before = `f := foo{}
+foo.bar(f)`
+ info.After = `f := foo{}
+f.bar()`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&methodExprCallChecker{ctx: ctx})
+ })
+}
+
+type methodExprCallChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *methodExprCallChecker) VisitExpr(x ast.Expr) {
+ call := astcast.ToCallExpr(x)
+ s := astcast.ToSelectorExpr(call.Fun)
+
+ if len(call.Args) < 1 || astcast.ToIdent(call.Args[0]).Name == "nil" {
+ return
+ }
+
+ if typep.IsTypeExpr(c.ctx.TypesInfo, s.X) {
+ c.warn(call, s)
+ }
+}
+
+func (c *methodExprCallChecker) warn(cause *ast.CallExpr, s *ast.SelectorExpr) {
+ selector := astcopy.SelectorExpr(s)
+ selector.X = cause.Args[0]
+
+ // Remove "&" from the receiver (if any).
+ if u, ok := selector.X.(*ast.UnaryExpr); ok && u.Op == token.AND {
+ selector.X = u.X
+ }
+
+ c.ctx.Warn(cause, "consider to change `%s` to `%s`", cause.Fun, selector)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/nestingReduce_checker.go b/vendor/github.com/go-critic/go-critic/checkers/nestingReduce_checker.go
new file mode 100644
index 000000000..4a0331d5c
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/nestingReduce_checker.go
@@ -0,0 +1,73 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "nestingReduce"
+ info.Tags = []string{"style", "opinionated", "experimental"}
+ info.Params = lintpack.CheckerParams{
+ "bodyWidth": {
+ Value: 5,
+ Usage: "min number of statements inside a branch to trigger a warning",
+ },
+ }
+ info.Summary = "Finds where nesting level could be reduced"
+ info.Before = `
+for _, v := range a {
+ if v.Bool {
+ body()
+ }
+}`
+ info.After = `
+for _, v := range a {
+ if !v.Bool {
+ continue
+ }
+ body()
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &nestingReduceChecker{ctx: ctx}
+ c.bodyWidth = info.Params.Int("bodyWidth")
+ return astwalk.WalkerForStmt(c)
+ })
+}
+
+type nestingReduceChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ bodyWidth int
+}
+
+func (c *nestingReduceChecker) VisitStmt(stmt ast.Stmt) {
+ switch stmt := stmt.(type) {
+ case *ast.ForStmt:
+ c.checkLoopBody(stmt.Body.List)
+ case *ast.RangeStmt:
+ c.checkLoopBody(stmt.Body.List)
+ }
+}
+
+func (c *nestingReduceChecker) checkLoopBody(body []ast.Stmt) {
+ if len(body) != 1 {
+ return
+ }
+ stmt, ok := body[0].(*ast.IfStmt)
+ if !ok {
+ return
+ }
+ if len(stmt.Body.List) >= c.bodyWidth && stmt.Else == nil {
+ c.warnLoop(stmt)
+ }
+}
+
+func (c *nestingReduceChecker) warnLoop(cause ast.Node) {
+ c.ctx.Warn(cause, "invert if cond, replace body with `continue`, move old body after the statement")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/newDeref_checker.go b/vendor/github.com/go-critic/go-critic/checkers/newDeref_checker.go
new file mode 100644
index 000000000..75e7f6428
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/newDeref_checker.go
@@ -0,0 +1,45 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "newDeref"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects immediate dereferencing of `new` expressions"
+ info.Before = `x := *new(bool)`
+ info.After = `x := false`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&newDerefChecker{ctx: ctx})
+ })
+}
+
+type newDerefChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *newDerefChecker) VisitExpr(expr ast.Expr) {
+ deref := astcast.ToStarExpr(expr)
+ call := astcast.ToCallExpr(deref.X)
+ if astcast.ToIdent(call.Fun).Name == "new" {
+ typ := c.ctx.TypesInfo.TypeOf(call.Args[0])
+ zv := lintutil.ZeroValueOf(astutil.Unparen(call.Args[0]), typ)
+ if zv != nil {
+ c.warn(expr, zv)
+ }
+ }
+}
+
+func (c *newDerefChecker) warn(cause, suggestion ast.Expr) {
+ c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go b/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go
new file mode 100644
index 000000000..231e25800
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go
@@ -0,0 +1,64 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "nilValReturn"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects return statements those results evaluate to nil"
+ info.Before = `
+if err == nil {
+ return err
+}`
+ info.After = `
+// (A) - return nil explicitly
+if err == nil {
+ return nil
+}
+// (B) - typo in "==", change to "!="
+if err != nil {
+ return err
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&nilValReturnChecker{ctx: ctx})
+ })
+}
+
+type nilValReturnChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *nilValReturnChecker) VisitStmt(stmt ast.Stmt) {
+ ifStmt, ok := stmt.(*ast.IfStmt)
+ if !ok || len(ifStmt.Body.List) != 1 {
+ return
+ }
+ ret, ok := ifStmt.Body.List[0].(*ast.ReturnStmt)
+ if !ok || len(ret.Results) != 1 {
+ return
+ }
+ expr, ok := ifStmt.Cond.(*ast.BinaryExpr)
+ cond := ok &&
+ expr.Op == token.EQL &&
+ typep.SideEffectFree(c.ctx.TypesInfo, expr.X) &&
+ qualifiedName(expr.Y) == "nil" &&
+ astequal.Expr(expr.X, ret.Results[0])
+ if cond {
+ c.warn(ret, expr.X)
+ }
+}
+
+func (c *nilValReturnChecker) warn(cause, val ast.Node) {
+ c.ctx.Warn(cause, "returned expr is always nil; replace %s with nil", val)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go b/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go
new file mode 100644
index 000000000..e40ec6db5
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go
@@ -0,0 +1,82 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "octalLiteral"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects octal literals passed to functions"
+ info.Before = `foo(02)`
+ info.After = `foo(2)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &octalLiteralChecker{
+ ctx: ctx,
+ octFriendlyPkg: map[string]bool{
+ "os": true,
+ "io/ioutil": true,
+ },
+ }
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type octalLiteralChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ octFriendlyPkg map[string]bool
+}
+
+func (c *octalLiteralChecker) VisitExpr(expr ast.Expr) {
+ call := astcast.ToCallExpr(expr)
+ calledExpr := astcast.ToSelectorExpr(call.Fun)
+ ident := astcast.ToIdent(calledExpr.X)
+
+ if obj, ok := c.ctx.TypesInfo.ObjectOf(ident).(*types.PkgName); ok {
+ pkg := obj.Imported()
+ if c.octFriendlyPkg[pkg.Path()] {
+ return
+ }
+ }
+
+ for _, arg := range call.Args {
+ if lit := astcast.ToBasicLit(c.unsign(arg)); len(lit.Value) > 1 &&
+ c.isIntLiteral(lit) &&
+ c.isOctalLiteral(lit) {
+ c.warn(call)
+ return
+ }
+ }
+}
+
+func (c *octalLiteralChecker) unsign(e ast.Expr) ast.Expr {
+ u, ok := e.(*ast.UnaryExpr)
+ if !ok {
+ return e
+ }
+ return u.X
+}
+
+func (c *octalLiteralChecker) isIntLiteral(lit *ast.BasicLit) bool {
+ return lit.Kind == token.INT
+}
+
+func (c *octalLiteralChecker) isOctalLiteral(lit *ast.BasicLit) bool {
+ return lit.Value[0] == '0' &&
+ lit.Value[1] != 'x' &&
+ lit.Value[1] != 'X'
+}
+
+func (c *octalLiteralChecker) warn(expr ast.Expr) {
+ c.ctx.Warn(expr, "suspicious octal args in `%s`", expr)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go b/vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go
new file mode 100644
index 000000000..d5c8de0b7
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go
@@ -0,0 +1,66 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "offBy1"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects various off-by-one kind of errors"
+ info.Before = `xs[len(xs)]`
+ info.After = `xs[len(xs)-1]`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&offBy1Checker{ctx: ctx})
+ })
+}
+
+type offBy1Checker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *offBy1Checker) VisitExpr(e ast.Expr) {
+ // TODO(Quasilyte): handle more off-by-1 patterns.
+ // TODO(Quasilyte): check whether go/analysis can help here.
+
+ // Detect s[len(s)] expressions that always panic.
+ // The correct form is s[len(s)-1].
+
+ indexExpr := astcast.ToIndexExpr(e)
+ indexed := indexExpr.X
+ if !typep.IsSlice(c.ctx.TypesInfo.TypeOf(indexed)) {
+ return
+ }
+ if !typep.SideEffectFree(c.ctx.TypesInfo, indexed) {
+ return
+ }
+ call := astcast.ToCallExpr(indexExpr.Index)
+ if astcast.ToIdent(call.Fun).Name != "len" {
+ return
+ }
+ if len(call.Args) != 1 || !astequal.Expr(call.Args[0], indexed) {
+ return
+ }
+ c.warnLenIndex(indexExpr)
+}
+
+func (c *offBy1Checker) warnLenIndex(cause *ast.IndexExpr) {
+ suggest := astcopy.IndexExpr(cause)
+ suggest.Index = &ast.BinaryExpr{
+ Op: token.SUB,
+ X: cause.Index,
+ Y: &ast.BasicLit{Value: "1"},
+ }
+ c.ctx.Warn(cause, "index expr always panics; maybe you wanted %s?", suggest)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go b/vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go
new file mode 100644
index 000000000..ffa74061e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go
@@ -0,0 +1,86 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "paramTypeCombine"
+ info.Tags = []string{"style", "opinionated"}
+ info.Summary = "Detects if function parameters could be combined by type and suggest the way to do it"
+ info.Before = `func foo(a, b int, c, d int, e, f int, g int) {}`
+ info.After = `func foo(a, b, c, d, e, f, g int) {}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForFuncDecl(&paramTypeCombineChecker{ctx: ctx})
+ })
+}
+
+type paramTypeCombineChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *paramTypeCombineChecker) EnterFunc(*ast.FuncDecl) bool {
+ return true
+}
+
+func (c *paramTypeCombineChecker) VisitFuncDecl(decl *ast.FuncDecl) {
+ typ := c.optimizeFuncType(decl.Type)
+ if !astequal.Expr(typ, decl.Type) {
+ c.warn(decl.Type, typ)
+ }
+}
+
+func (c *paramTypeCombineChecker) optimizeFuncType(f *ast.FuncType) *ast.FuncType {
+ return &ast.FuncType{
+ Params: c.optimizeParams(f.Params),
+ Results: c.optimizeParams(f.Results),
+ }
+}
+func (c *paramTypeCombineChecker) optimizeParams(params *ast.FieldList) *ast.FieldList {
+ // To avoid false positives, skip unnamed param lists.
+ //
+ // We're using a property that Go only permits unnamed params
+ // for the whole list, so it's enough to check whether any of
+ // ast.Field have empty name list.
+ skip := params == nil ||
+ len(params.List) < 2 ||
+ len(params.List[0].Names) == 0
+ if skip {
+ return params
+ }
+
+ list := []*ast.Field{}
+ names := make([]*ast.Ident, len(params.List[0].Names))
+ copy(names, params.List[0].Names)
+ list = append(list, &ast.Field{
+ Names: names,
+ Type: params.List[0].Type,
+ })
+ for i, p := range params.List[1:] {
+ names = make([]*ast.Ident, len(p.Names))
+ copy(names, p.Names)
+ if astequal.Expr(p.Type, params.List[i].Type) {
+ list[len(list)-1].Names =
+ append(list[len(list)-1].Names, names...)
+ } else {
+ list = append(list, &ast.Field{
+ Names: names,
+ Type: params.List[i+1].Type,
+ })
+ }
+ }
+ return &ast.FieldList{
+ List: list,
+ }
+}
+
+func (c *paramTypeCombineChecker) warn(f1, f2 *ast.FuncType) {
+ c.ctx.Warn(f1, "%s could be replaced with %s", f1, f2)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/ptrToRefParam_checker.go b/vendor/github.com/go-critic/go-critic/checkers/ptrToRefParam_checker.go
new file mode 100644
index 000000000..dacffc85a
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/ptrToRefParam_checker.go
@@ -0,0 +1,70 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "ptrToRefParam"
+ info.Tags = []string{"style", "opinionated", "experimental"}
+ info.Summary = "Detects input and output parameters that have a type of pointer to referential type"
+ info.Before = `func f(m *map[string]int) (*chan *int)`
+ info.After = `func f(m map[string]int) (chan *int)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForFuncDecl(&ptrToRefParamChecker{ctx: ctx})
+ })
+}
+
+type ptrToRefParamChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *ptrToRefParamChecker) VisitFuncDecl(fn *ast.FuncDecl) {
+ c.checkParams(fn.Type.Params.List)
+ if fn.Type.Results != nil {
+ c.checkParams(fn.Type.Results.List)
+ }
+}
+
+func (c *ptrToRefParamChecker) checkParams(params []*ast.Field) {
+ for _, param := range params {
+ ptr, ok := c.ctx.TypesInfo.TypeOf(param.Type).(*types.Pointer)
+ if !ok {
+ continue
+ }
+
+ if c.isRefType(ptr.Elem()) {
+ if len(param.Names) == 0 {
+ c.ctx.Warn(param, "consider to make non-pointer type for `%s`", param.Type)
+ } else {
+ for i := range param.Names {
+ c.warn(param.Names[i])
+ }
+ }
+ }
+ }
+}
+
+func (c *ptrToRefParamChecker) isRefType(x types.Type) bool {
+ switch typ := x.(type) {
+ case *types.Map, *types.Chan, *types.Interface:
+ return true
+ case *types.Named:
+ // Handle underlying type only for interfaces.
+ if _, ok := typ.Underlying().(*types.Interface); ok {
+ return true
+ }
+ }
+ return false
+}
+
+func (c *ptrToRefParamChecker) warn(id *ast.Ident) {
+ c.ctx.Warn(id, "consider `%s' to be of non-pointer type", id)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go b/vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go
new file mode 100644
index 000000000..387d1bbbc
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go
@@ -0,0 +1,80 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "rangeExprCopy"
+ info.Tags = []string{"performance"}
+ info.Params = lintpack.CheckerParams{
+ "sizeThreshold": {
+ Value: 512,
+ Usage: "size in bytes that makes the warning trigger",
+ },
+ "skipTestFuncs": {
+ Value: true,
+ Usage: "whether to check test functions",
+ },
+ }
+ info.Summary = "Detects expensive copies of `for` loop range expressions"
+ info.Details = "Suggests to use pointer to array to avoid the copy using `&` on range expression."
+ info.Before = `
+var xs [2048]byte
+for _, x := range xs { // Copies 2048 bytes
+ // Loop body.
+}`
+ info.After = `
+var xs [2048]byte
+for _, x := range &xs { // No copy
+ // Loop body.
+}`
+ info.Note = "See Go issue for details: https://github.com/golang/go/issues/15812."
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &rangeExprCopyChecker{ctx: ctx}
+ c.sizeThreshold = int64(info.Params.Int("sizeThreshold"))
+ c.skipTestFuncs = info.Params.Bool("skipTestFuncs")
+ return astwalk.WalkerForStmt(c)
+ })
+}
+
+type rangeExprCopyChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ sizeThreshold int64
+ skipTestFuncs bool
+}
+
+func (c *rangeExprCopyChecker) EnterFunc(fn *ast.FuncDecl) bool {
+ return fn.Body != nil &&
+ !(c.skipTestFuncs && isUnitTestFunc(c.ctx, fn))
+}
+
+func (c *rangeExprCopyChecker) VisitStmt(stmt ast.Stmt) {
+ rng, ok := stmt.(*ast.RangeStmt)
+ if !ok || rng.Key == nil || rng.Value == nil {
+ return
+ }
+ tv := c.ctx.TypesInfo.Types[rng.X]
+ if !tv.Addressable() {
+ return
+ }
+ if _, ok := tv.Type.(*types.Array); !ok {
+ return
+ }
+ if size := c.ctx.SizesInfo.Sizeof(tv.Type); size >= c.sizeThreshold {
+ c.warn(rng, size)
+ }
+}
+
+func (c *rangeExprCopyChecker) warn(rng *ast.RangeStmt, size int64) {
+ c.ctx.Warn(rng, "copy of %s (%d bytes) can be avoided with &%s",
+ rng.X, size, rng.X)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go b/vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go
new file mode 100644
index 000000000..182538a9f
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go
@@ -0,0 +1,75 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "rangeValCopy"
+ info.Tags = []string{"performance"}
+ info.Params = lintpack.CheckerParams{
+ "sizeThreshold": {
+ Value: 128,
+ Usage: "size in bytes that makes the warning trigger",
+ },
+ "skipTestFuncs": {
+ Value: true,
+ Usage: "whether to check test functions",
+ },
+ }
+ info.Summary = "Detects loops that copy big objects during each iteration"
+ info.Details = "Suggests to use index access or take address and make use pointer instead."
+ info.Before = `
+xs := make([][1024]byte, length)
+for _, x := range xs {
+ // Loop body.
+}`
+ info.After = `
+xs := make([][1024]byte, length)
+for i := range xs {
+ x := &xs[i]
+ // Loop body.
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &rangeValCopyChecker{ctx: ctx}
+ c.sizeThreshold = int64(info.Params.Int("sizeThreshold"))
+ c.skipTestFuncs = info.Params.Bool("skipTestFuncs")
+ return astwalk.WalkerForStmt(c)
+ })
+}
+
+type rangeValCopyChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ sizeThreshold int64
+ skipTestFuncs bool
+}
+
+func (c *rangeValCopyChecker) EnterFunc(fn *ast.FuncDecl) bool {
+ return fn.Body != nil &&
+ !(c.skipTestFuncs && isUnitTestFunc(c.ctx, fn))
+}
+
+func (c *rangeValCopyChecker) VisitStmt(stmt ast.Stmt) {
+ rng, ok := stmt.(*ast.RangeStmt)
+ if !ok || rng.Value == nil {
+ return
+ }
+ typ := c.ctx.TypesInfo.TypeOf(rng.Value)
+ if typ == nil {
+ return
+ }
+ if size := c.ctx.SizesInfo.Sizeof(typ); size >= c.sizeThreshold {
+ c.warn(rng, size)
+ }
+}
+
+func (c *rangeValCopyChecker) warn(n ast.Node, size int64) {
+ c.ctx.Warn(n, "each iteration copies %d bytes (consider pointers or indexing)", size)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go b/vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go
new file mode 100644
index 000000000..ef7a39787
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go
@@ -0,0 +1,47 @@
+package checkers
+
+import (
+ "go/ast"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astp"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "regexpMust"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects `regexp.Compile*` that can be replaced with `regexp.MustCompile*`"
+ info.Before = `re, _ := regexp.Compile("const pattern")`
+ info.After = `re := regexp.MustCompile("const pattern")`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&regexpMustChecker{ctx: ctx})
+ })
+}
+
+type regexpMustChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *regexpMustChecker) VisitExpr(x ast.Expr) {
+ if x, ok := x.(*ast.CallExpr); ok {
+ switch name := qualifiedName(x.Fun); name {
+ case "regexp.Compile", "regexp.CompilePOSIX":
+ // Only check for trivial string args, permit parenthesis.
+ if !astp.IsBasicLit(astutil.Unparen(x.Args[0])) {
+ return
+ }
+ c.warn(x, strings.Replace(name, "Compile", "MustCompile", 1))
+ }
+ }
+}
+
+func (c *regexpMustChecker) warn(cause *ast.CallExpr, suggestion string) {
+ c.ctx.Warn(cause, "for const patterns like %s, use %s",
+ cause.Args[0], suggestion)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/regexpPattern_checker.go b/vendor/github.com/go-critic/go-critic/checkers/regexpPattern_checker.go
new file mode 100644
index 000000000..383deb5d4
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/regexpPattern_checker.go
@@ -0,0 +1,68 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/constant"
+ "regexp"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "regexpPattern"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects suspicious regexp patterns"
+ info.Before = "regexp.MustCompile(`google.com|yandex.ru`)"
+ info.After = "regexp.MustCompile(`google\\.com|yandex\\.ru`)"
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ domains := []string{
+ "com",
+ "org",
+ "info",
+ "net",
+ "ru",
+ "de",
+ }
+
+ allDomains := strings.Join(domains, "|")
+ domainRE := regexp.MustCompile(`[^\\]\.(` + allDomains + `)\b`)
+ return astwalk.WalkerForExpr(&regexpPatternChecker{
+ ctx: ctx,
+ domainRE: domainRE,
+ })
+ })
+}
+
+type regexpPatternChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ domainRE *regexp.Regexp
+}
+
+func (c *regexpPatternChecker) VisitExpr(x ast.Expr) {
+ call, ok := x.(*ast.CallExpr)
+ if !ok {
+ return
+ }
+
+ switch qualifiedName(call.Fun) {
+ case "regexp.Compile", "regexp.CompilePOSIX", "regexp.MustCompile", "regexp.MustCompilePosix":
+ cv := c.ctx.TypesInfo.Types[call.Args[0]].Value
+ if cv == nil || cv.Kind() != constant.String {
+ return
+ }
+ s := constant.StringVal(cv)
+ if m := c.domainRE.FindStringSubmatch(s); m != nil {
+ c.warnDomain(call.Args[0], m[1])
+ }
+ }
+}
+
+func (c *regexpPatternChecker) warnDomain(cause ast.Expr, domain string) {
+ c.ctx.Warn(cause, "'.%s' should probably be '\\.%s'", domain, domain)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/singleCaseSwitch_checker.go b/vendor/github.com/go-critic/go-critic/checkers/singleCaseSwitch_checker.go
new file mode 100644
index 000000000..6cdb06aef
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/singleCaseSwitch_checker.go
@@ -0,0 +1,61 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "singleCaseSwitch"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects switch statements that could be better written as if statement"
+ info.Before = `
+switch x := x.(type) {
+case int:
+ body()
+}`
+ info.After = `
+if x, ok := x.(int); ok {
+ body()
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&singleCaseSwitchChecker{ctx: ctx})
+ })
+}
+
+type singleCaseSwitchChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *singleCaseSwitchChecker) VisitStmt(stmt ast.Stmt) {
+ switch stmt := stmt.(type) {
+ case *ast.SwitchStmt:
+ c.checkSwitchStmt(stmt, stmt.Body)
+ case *ast.TypeSwitchStmt:
+ c.checkSwitchStmt(stmt, stmt.Body)
+ }
+}
+
+func (c *singleCaseSwitchChecker) checkSwitchStmt(stmt ast.Stmt, body *ast.BlockStmt) {
+ if len(body.List) == 1 {
+ if body.List[0].(*ast.CaseClause).List == nil {
+ // default case.
+ c.warnDefault(stmt)
+ } else if len(body.List[0].(*ast.CaseClause).List) == 1 {
+ c.warn(stmt)
+ }
+ }
+}
+
+func (c *singleCaseSwitchChecker) warn(stmt ast.Stmt) {
+ c.ctx.Warn(stmt, "should rewrite switch statement to if statement")
+}
+
+func (c *singleCaseSwitchChecker) warnDefault(stmt ast.Stmt) {
+ c.ctx.Warn(stmt, "found switch with default case only")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go b/vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go
new file mode 100644
index 000000000..45123ec6b
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go
@@ -0,0 +1,72 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astfmt"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "sloppyLen"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects usage of `len` when result is obvious or doesn't make sense"
+ info.Before = `
+len(arr) >= 0 // Sloppy
+len(arr) <= 0 // Sloppy
+len(arr) < 0 // Doesn't make sense at all`
+ info.After = `
+len(arr) > 0
+len(arr) == 0`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&sloppyLenChecker{ctx: ctx})
+ })
+}
+
+type sloppyLenChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *sloppyLenChecker) VisitExpr(x ast.Expr) {
+ expr, ok := x.(*ast.BinaryExpr)
+ if !ok {
+ return
+ }
+
+ if expr.Op == token.LSS || expr.Op == token.GEQ || expr.Op == token.LEQ {
+ if c.isLenCall(expr.X) && c.isZero(expr.Y) {
+ c.warn(expr)
+ }
+ }
+}
+
+func (c *sloppyLenChecker) isLenCall(x ast.Expr) bool {
+ call, ok := x.(*ast.CallExpr)
+ return ok && qualifiedName(call.Fun) == "len" && len(call.Args) == 1
+}
+
+func (c *sloppyLenChecker) isZero(x ast.Expr) bool {
+ value, ok := x.(*ast.BasicLit)
+ return ok && value.Value == "0"
+}
+
+func (c *sloppyLenChecker) warn(cause *ast.BinaryExpr) {
+ info := ""
+ switch cause.Op {
+ case token.LSS:
+ info = "is always false"
+ case token.GEQ:
+ info = "is always true"
+ case token.LEQ:
+ expr := astcopy.BinaryExpr(cause)
+ expr.Op = token.EQL
+ info = astfmt.Sprintf("can be %s", expr)
+ }
+ c.ctx.Warn(cause, "%s %s", cause, info)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/sloppyReassign_checker.go b/vendor/github.com/go-critic/go-critic/checkers/sloppyReassign_checker.go
new file mode 100644
index 000000000..1a7c19877
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/sloppyReassign_checker.go
@@ -0,0 +1,80 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "sloppyReassign"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects suspicious/confusing re-assignments"
+ info.Before = `if err = f(); err != nil { return err }`
+ info.After = `if err := f(); err != nil { return err }`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&sloppyReassignChecker{ctx: ctx})
+ })
+}
+
+type sloppyReassignChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *sloppyReassignChecker) VisitStmt(stmt ast.Stmt) {
+ // Right now only check assignments in if statements init.
+ ifStmt := astcast.ToIfStmt(stmt)
+ assign := astcast.ToAssignStmt(ifStmt.Init)
+ if assign.Tok != token.ASSIGN {
+ return
+ }
+
+ // TODO(Quasilyte): is handling of multi-value assignments worthwhile?
+ if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
+ return
+ }
+
+ // TODO(Quasilyte): handle not only the simplest, return-only case.
+ body := ifStmt.Body.List
+ if len(body) != 1 {
+ return
+ }
+
+ // Variable that is being re-assigned.
+ reAssigned := astcast.ToIdent(assign.Lhs[0])
+ if reAssigned.Name == "" {
+ return
+ }
+
+ // TODO(Quasilyte): handle not only nil comparisons.
+ eqToNil := &ast.BinaryExpr{
+ Op: token.NEQ,
+ X: reAssigned,
+ Y: &ast.Ident{Name: "nil"},
+ }
+ if !astequal.Expr(ifStmt.Cond, eqToNil) {
+ return
+ }
+
+ results := astcast.ToReturnStmt(body[0]).Results
+ for _, res := range results {
+ if astequal.Expr(reAssigned, res) {
+ c.warnAssignToDefine(assign, reAssigned.Name)
+ break
+ }
+ }
+}
+
+func (c *sloppyReassignChecker) warnAssignToDefine(assign *ast.AssignStmt, name string) {
+ suggest := astcopy.AssignStmt(assign)
+ suggest.Tok = token.DEFINE
+ c.ctx.Warn(assign, "re-assignment to `%s` can be replaced with `%s`", name, suggest)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go b/vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go
new file mode 100644
index 000000000..74570108e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go
@@ -0,0 +1,47 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/typep"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "stringXbytes"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects redundant conversions between string and []byte"
+ info.Before = `copy(b, []byte(s))`
+ info.After = `copy(b, s)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&stringXbytes{ctx: ctx})
+ })
+}
+
+type stringXbytes struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *stringXbytes) VisitExpr(expr ast.Expr) {
+ x, ok := expr.(*ast.CallExpr)
+ if !ok || qualifiedName(x.Fun) != "copy" {
+ return
+ }
+
+ src := x.Args[1]
+
+ byteCast, ok := src.(*ast.CallExpr)
+ if ok && typep.IsTypeExpr(c.ctx.TypesInfo, byteCast.Fun) &&
+ typep.HasStringProp(c.ctx.TypesInfo.TypeOf(byteCast.Args[0])) {
+
+ c.warn(byteCast, byteCast.Args[0])
+ }
+}
+
+func (c *stringXbytes) warn(cause *ast.CallExpr, suggestion ast.Expr) {
+ c.ctx.Warn(cause, "can simplify `%s` to `%s`", cause, suggestion)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go b/vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go
new file mode 100644
index 000000000..3b2766276
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go
@@ -0,0 +1,49 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "switchTrue"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects switch-over-bool statements that use explicit `true` tag value"
+ info.Before = `
+switch true {
+case x > y:
+}`
+ info.After = `
+switch {
+case x > y:
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&switchTrueChecker{ctx: ctx})
+ })
+}
+
+type switchTrueChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *switchTrueChecker) VisitStmt(stmt ast.Stmt) {
+ if stmt, ok := stmt.(*ast.SwitchStmt); ok {
+ if qualifiedName(stmt.Tag) == "true" {
+ c.warn(stmt)
+ }
+ }
+}
+
+func (c *switchTrueChecker) warn(cause *ast.SwitchStmt) {
+ if cause.Init == nil {
+ c.ctx.Warn(cause, "replace 'switch true {}' with 'switch {}'")
+ } else {
+ c.ctx.Warn(cause, "replace 'switch %s; true {}' with 'switch %s; {}'",
+ cause.Init, cause.Init)
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go b/vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go
new file mode 100644
index 000000000..f4cb9e866
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go
@@ -0,0 +1,117 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "truncateCmp"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Params = lintpack.CheckerParams{
+ "skipArchDependent": {
+ Value: true,
+ Usage: "whether to skip int/uint/uintptr types",
+ },
+ }
+ info.Summary = "Detects potential truncation issues when comparing ints of different sizes"
+ info.Before = `
+func f(x int32, y int16) bool {
+ return int16(x) < y
+}`
+ info.After = `
+func f(x int32, int16) bool {
+ return x < int32(y)
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &truncateCmpChecker{ctx: ctx}
+ c.skipArchDependent = info.Params.Bool("skipArchDependent")
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type truncateCmpChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ skipArchDependent bool
+}
+
+func (c *truncateCmpChecker) VisitExpr(expr ast.Expr) {
+ cmp := astcast.ToBinaryExpr(expr)
+ switch cmp.Op {
+ case token.LSS, token.GTR, token.LEQ, token.GEQ, token.EQL, token.NEQ:
+ if astp.IsBasicLit(cmp.X) || astp.IsBasicLit(cmp.Y) {
+ return // Don't bother about untyped consts
+ }
+ leftCast := c.isTruncCast(cmp.X)
+ rightCast := c.isTruncCast(cmp.Y)
+ switch {
+ case leftCast && rightCast:
+ return
+ case leftCast:
+ c.checkCmp(cmp.X, cmp.Y)
+ case rightCast:
+ c.checkCmp(cmp.Y, cmp.X)
+ }
+ default:
+ return
+ }
+}
+
+func (c *truncateCmpChecker) isTruncCast(x ast.Expr) bool {
+ switch astcast.ToIdent(astcast.ToCallExpr(x).Fun).Name {
+ case "int8", "int16", "int32", "uint8", "uint16", "uint32":
+ return true
+ default:
+ return false
+ }
+}
+
+func (c *truncateCmpChecker) checkCmp(cmpX, cmpY ast.Expr) {
+ // Check if we have a cast to a type that can truncate.
+ xcast := astcast.ToCallExpr(cmpX)
+ if len(xcast.Args) != 1 {
+ return // Just in case of the shadowed builtin
+ }
+
+ x := xcast.Args[0]
+ y := cmpY
+
+ // Check that both x and y are signed or unsigned int-typed.
+ xtyp, ok := c.ctx.TypesInfo.TypeOf(x).Underlying().(*types.Basic)
+ if !ok || xtyp.Info()&types.IsInteger == 0 {
+ return
+ }
+ ytyp, ok := c.ctx.TypesInfo.TypeOf(y).Underlying().(*types.Basic)
+ if !ok || xtyp.Info() != ytyp.Info() {
+ return
+ }
+
+ xsize := c.ctx.SizesInfo.Sizeof(xtyp)
+ ysize := c.ctx.SizesInfo.Sizeof(ytyp)
+ if xsize <= ysize {
+ return
+ }
+
+ if c.skipArchDependent {
+ switch xtyp.Kind() {
+ case types.Int, types.Uint, types.Uintptr:
+ return
+ }
+ }
+
+ c.warn(xcast, xsize*8, ysize*8, xtyp.String())
+}
+
+func (c *truncateCmpChecker) warn(cause ast.Expr, xsize, ysize int64, suggest string) {
+ c.ctx.Warn(cause, "truncation in comparison %d->%d bit; cast the other operand to %s instead", xsize, ysize, suggest)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/typeAssertChain_checker.go b/vendor/github.com/go-critic/go-critic/checkers/typeAssertChain_checker.go
new file mode 100644
index 000000000..c0c42e351
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/typeAssertChain_checker.go
@@ -0,0 +1,132 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "typeAssertChain"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects repeated type assertions and suggests to replace them with type switch statement"
+ info.Before = `
+if x, ok := v.(T1); ok {
+ // Code A, uses x.
+} else if x, ok := v.(T2); ok {
+ // Code B, uses x.
+} else if x, ok := v.(T3); ok {
+ // Code C, uses x.
+}`
+ info.After = `
+switch x := v.(T1) {
+case cond1:
+ // Code A, uses x.
+case cond2:
+ // Code B, uses x.
+default:
+ // Code C, uses x.
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&typeAssertChainChecker{ctx: ctx})
+ })
+}
+
+type typeAssertChainChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ cause *ast.IfStmt
+ visited map[*ast.IfStmt]bool
+ typeSet lintutil.AstSet
+}
+
+func (c *typeAssertChainChecker) EnterFunc(fn *ast.FuncDecl) bool {
+ if fn.Body == nil {
+ return false
+ }
+ c.visited = make(map[*ast.IfStmt]bool)
+ return true
+}
+
+func (c *typeAssertChainChecker) VisitStmt(stmt ast.Stmt) {
+ ifstmt, ok := stmt.(*ast.IfStmt)
+ if !ok || c.visited[ifstmt] || ifstmt.Init == nil {
+ return
+ }
+ assertion := c.getTypeAssert(ifstmt)
+ if assertion == nil {
+ return
+ }
+ c.cause = ifstmt
+ c.checkIfStmt(ifstmt, assertion)
+}
+
+func (c *typeAssertChainChecker) getTypeAssert(ifstmt *ast.IfStmt) *ast.TypeAssertExpr {
+ assign := astcast.ToAssignStmt(ifstmt.Init)
+ if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 {
+ return nil
+ }
+ if !astp.IsIdent(assign.Lhs[0]) || assign.Tok != token.DEFINE {
+ return nil
+ }
+ if !astequal.Expr(assign.Lhs[1], ifstmt.Cond) {
+ return nil
+ }
+
+ assertion, ok := assign.Rhs[0].(*ast.TypeAssertExpr)
+ if !ok {
+ return nil
+ }
+ return assertion
+}
+
+func (c *typeAssertChainChecker) checkIfStmt(stmt *ast.IfStmt, assertion *ast.TypeAssertExpr) {
+ if c.countTypeAssertions(stmt, assertion) >= 2 {
+ c.warn()
+ }
+}
+
+func (c *typeAssertChainChecker) countTypeAssertions(stmt *ast.IfStmt, assertion *ast.TypeAssertExpr) int {
+ c.typeSet.Clear()
+
+ count := 1
+ x := assertion.X
+ c.typeSet.Insert(assertion.Type)
+ for {
+ e, ok := stmt.Else.(*ast.IfStmt)
+ if !ok {
+ return count
+ }
+ assertion = c.getTypeAssert(e)
+ if assertion == nil {
+ return count
+ }
+ if !c.typeSet.Insert(assertion.Type) {
+ // Asserted type is duplicated.
+ // Type switch does not permit duplicate cases,
+ // so give up.
+ return 0
+ }
+ if !astequal.Expr(x, assertion.X) {
+ // Mixed type asserting chain.
+ // Can't be easily translated to a type switch.
+ return 0
+ }
+ stmt = e
+ count++
+ c.visited[e] = true
+ }
+}
+
+func (c *typeAssertChainChecker) warn() {
+ c.ctx.Warn(c.cause, "rewrite if-else to type switch statement")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go b/vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go
new file mode 100644
index 000000000..a113597b6
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go
@@ -0,0 +1,88 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "typeSwitchVar"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects type switches that can benefit from type guard clause with variable"
+ info.Before = `
+switch v.(type) {
+case int:
+ return v.(int)
+case point:
+ return v.(point).x + v.(point).y
+default:
+ return 0
+}`
+ info.After = `
+switch v := v.(type) {
+case int:
+ return v
+case point:
+ return v.x + v.y
+default:
+ return 0
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&typeSwitchVarChecker{ctx: ctx})
+ })
+}
+
+type typeSwitchVarChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *typeSwitchVarChecker) VisitStmt(stmt ast.Stmt) {
+ if stmt, ok := stmt.(*ast.TypeSwitchStmt); ok {
+ c.checkTypeSwitch(stmt)
+ }
+}
+
+func (c *typeSwitchVarChecker) checkTypeSwitch(root *ast.TypeSwitchStmt) {
+ if astp.IsAssignStmt(root.Assign) {
+ return // Already with type guard
+ }
+ // Must be a *ast.ExprStmt then.
+ expr := root.Assign.(*ast.ExprStmt).X.(*ast.TypeAssertExpr).X
+ object := c.ctx.TypesInfo.ObjectOf(identOf(expr))
+ if object == nil {
+ return // Give up: can't handle shadowing without object
+ }
+
+ for i, clause := range root.Body.List {
+ clause := clause.(*ast.CaseClause)
+ // Multiple types in a list mean that assert.X will have
+ // a type of interface{} inside clause body.
+ // We are looking for precise type case.
+ if len(clause.List) != 1 {
+ continue
+ }
+ // Create artificial node just for matching.
+ assert1 := ast.TypeAssertExpr{X: expr, Type: clause.List[0]}
+ for _, stmt := range clause.Body {
+ assert2 := lintutil.FindNode(stmt, func(x ast.Node) bool {
+ return astequal.Node(&assert1, x)
+ })
+ if object == c.ctx.TypesInfo.ObjectOf(identOf(assert2)) {
+ c.warn(root, i)
+ break
+ }
+ }
+ }
+}
+
+func (c *typeSwitchVarChecker) warn(n ast.Node, caseIndex int) {
+ c.ctx.Warn(n, "case %d can benefit from type switch with assignment", caseIndex)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go
new file mode 100644
index 000000000..a17c77b49
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go
@@ -0,0 +1,86 @@
+package checkers
+
+import (
+ "go/ast"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astp"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "typeUnparen"
+ info.Tags = []string{"style", "opinionated"}
+ info.Summary = "Detects unneded parenthesis inside type expressions and suggests to remove them"
+ info.Before = `type foo [](func([](func())))`
+ info.After = `type foo []func([]func())`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForTypeExpr(&typeUnparenChecker{ctx: ctx}, ctx.TypesInfo)
+ })
+}
+
+type typeUnparenChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *typeUnparenChecker) VisitTypeExpr(x ast.Expr) {
+ switch x := x.(type) {
+ case *ast.ParenExpr:
+ switch x.X.(type) {
+ case *ast.StructType:
+ c.ctx.Warn(x, "could simplify (struct{...}) to struct{...}")
+ case *ast.InterfaceType:
+ c.ctx.Warn(x, "could simplify (interface{...}) to interface{...}")
+ default:
+ c.warn(x, c.unparenExpr(astcopy.Expr(x)))
+ }
+ default:
+ c.checkTypeExpr(x)
+ }
+}
+
+func (c *typeUnparenChecker) checkTypeExpr(x ast.Expr) {
+ switch x := x.(type) {
+ case *ast.ArrayType:
+ // Arrays require extra care: we don't want to unparen
+ // length expression as they are not type expressions.
+ if !c.hasParens(x.Elt) {
+ return
+ }
+ noParens := astcopy.ArrayType(x)
+ noParens.Elt = c.unparenExpr(noParens.Elt)
+ c.warn(x, noParens)
+ case *ast.StructType, *ast.InterfaceType:
+ // Only nested fields are to be reported.
+ default:
+ if !c.hasParens(x) {
+ return
+ }
+ c.warn(x, c.unparenExpr(astcopy.Expr(x)))
+ }
+}
+
+func (c *typeUnparenChecker) hasParens(x ast.Expr) bool {
+ return lintutil.ContainsNode(x, astp.IsParenExpr)
+}
+
+func (c *typeUnparenChecker) unparenExpr(x ast.Expr) ast.Expr {
+ // Replace every paren expr with expression it encloses.
+ return astutil.Apply(x, nil, func(cur *astutil.Cursor) bool {
+ if paren, ok := cur.Node().(*ast.ParenExpr); ok {
+ cur.Replace(paren.X)
+ }
+ return true
+ }).(ast.Expr)
+}
+
+func (c *typeUnparenChecker) warn(cause, noParens ast.Expr) {
+ c.SkipChilds = true
+ c.ctx.Warn(cause, "could simplify %s to %s", cause, noParens)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/underef_checker.go b/vendor/github.com/go-critic/go-critic/checkers/underef_checker.go
new file mode 100644
index 000000000..dfc6077bb
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/underef_checker.go
@@ -0,0 +1,127 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "underef"
+ info.Tags = []string{"style"}
+ info.Params = lintpack.CheckerParams{
+ "skipRecvDeref": {
+ Value: true,
+ Usage: "whether to skip (*x).method() calls where x is a pointer receiver",
+ },
+ }
+ info.Summary = "Detects dereference expressions that can be omitted"
+ info.Before = `
+(*k).field = 5
+v := (*a)[5] // only if a is array`
+ info.After = `
+k.field = 5
+v := a[5]`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &underefChecker{ctx: ctx}
+ c.skipRecvDeref = info.Params.Bool("skipRecvDeref")
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type underefChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ skipRecvDeref bool
+}
+
+func (c *underefChecker) VisitExpr(expr ast.Expr) {
+ switch n := expr.(type) {
+ case *ast.SelectorExpr:
+ expr := astcast.ToParenExpr(n.X)
+ if c.skipRecvDeref && c.isPtrRecvMethodCall(n.Sel) {
+ return
+ }
+
+ if expr, ok := expr.X.(*ast.StarExpr); ok {
+ if c.checkStarExpr(expr) {
+ c.warnSelect(n)
+ }
+ }
+ case *ast.IndexExpr:
+ expr := astcast.ToParenExpr(n.X)
+ if expr, ok := expr.X.(*ast.StarExpr); ok {
+ if !c.checkStarExpr(expr) {
+ return
+ }
+ if c.checkArray(expr) {
+ c.warnArray(n)
+ }
+ }
+ }
+}
+
+func (c *underefChecker) isPtrRecvMethodCall(fn *ast.Ident) bool {
+ typ, ok := c.ctx.TypesInfo.TypeOf(fn).(*types.Signature)
+ if ok && typ != nil && typ.Recv() != nil {
+ _, ok := typ.Recv().Type().(*types.Pointer)
+ return ok
+ }
+ return false
+}
+
+func (c *underefChecker) underef(x *ast.ParenExpr) ast.Expr {
+ // If there is only 1 deref, can remove parenthesis,
+ // otherwise can remove StarExpr only.
+ dereferenced := x.X.(*ast.StarExpr).X
+ if astp.IsStarExpr(dereferenced) {
+ return &ast.ParenExpr{X: dereferenced}
+ }
+ return dereferenced
+}
+
+func (c *underefChecker) warnSelect(expr *ast.SelectorExpr) {
+ // TODO: add () to function output.
+ c.ctx.Warn(expr, "could simplify %s to %s.%s",
+ expr,
+ c.underef(expr.X.(*ast.ParenExpr)),
+ expr.Sel.Name)
+}
+
+func (c *underefChecker) warnArray(expr *ast.IndexExpr) {
+ c.ctx.Warn(expr, "could simplify %s to %s[%s]",
+ expr,
+ c.underef(expr.X.(*ast.ParenExpr)),
+ expr.Index)
+}
+
+// checkStarExpr checks if ast.StarExpr could be simplified.
+func (c *underefChecker) checkStarExpr(expr *ast.StarExpr) bool {
+ typ, ok := c.ctx.TypesInfo.TypeOf(expr.X).Underlying().(*types.Pointer)
+ if !ok {
+ return false
+ }
+
+ switch typ.Elem().Underlying().(type) {
+ case *types.Pointer, *types.Interface:
+ return false
+ default:
+ return true
+ }
+}
+
+func (c *underefChecker) checkArray(expr *ast.StarExpr) bool {
+ typ, ok := c.ctx.TypesInfo.TypeOf(expr.X).(*types.Pointer)
+ if !ok {
+ return false
+ }
+ _, ok = typ.Elem().(*types.Array)
+ return ok
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go
new file mode 100644
index 000000000..d90c65c2c
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go
@@ -0,0 +1,170 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "unlabelStmt"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects redundant statement labels"
+ info.Before = `
+derp:
+for x := range xs {
+ if x == 0 {
+ break derp
+ }
+}`
+ info.After = `
+for x := range xs {
+ if x == 0 {
+ break
+ }
+}`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmt(&unlabelStmtChecker{ctx: ctx})
+ })
+}
+
+type unlabelStmtChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *unlabelStmtChecker) EnterFunc(fn *ast.FuncDecl) bool {
+ if fn.Body == nil {
+ return false
+ }
+ // TODO(Quasilyte): should not do additional traversal here.
+ // For now, skip all functions that contain goto statement.
+ return !lintutil.ContainsNode(fn.Body, func(n ast.Node) bool {
+ br, ok := n.(*ast.BranchStmt)
+ return ok && br.Tok == token.GOTO
+ })
+}
+
+func (c *unlabelStmtChecker) VisitStmt(stmt ast.Stmt) {
+ labeled, ok := stmt.(*ast.LabeledStmt)
+ if !ok || !c.canBreakFrom(labeled.Stmt) {
+ return
+ }
+
+ // We have a labeled statement from that have labeled continue/break.
+ // This is an invariant, since unused label is a compile-time error
+ // and we're currently skipping functions containing goto.
+ //
+ // Also note that Go labels are function-scoped and there
+ // can be no re-definitions. This means that we don't
+ // need to care about label shadowing or things like that.
+ //
+ // The task is to find cases where labeled branch (continue/break)
+ // is redundant and can be re-written, decreasing the label usages
+ // and potentially leading to its redundancy,
+ // or finding the redundant labels right away.
+
+ name := labeled.Label.Name
+
+ // Simplest case that can prove that label is redundant.
+ //
+ // If labeled branch is somewhere inside the statement block itself
+ // and none of the nested break'able statements refer to that label,
+ // the label can be removed.
+ matchUsage := func(n ast.Node) bool {
+ return c.canBreakFrom(n) && c.usesLabel(c.blockStmtOf(n), name)
+ }
+ if !lintutil.ContainsNode(c.blockStmtOf(labeled.Stmt), matchUsage) {
+ c.warnRedundant(labeled)
+ return
+ }
+
+ // Only for loops: if last stmt in list is a loop
+ // that contains labeled "continue" to the outer loop label,
+ // it can be refactored to use "break" instead.
+ if c.isLoop(labeled.Stmt) {
+ body := c.blockStmtOf(labeled.Stmt)
+ if len(body.List) == 0 {
+ return
+ }
+ last := body.List[len(body.List)-1]
+ if !c.isLoop(last) {
+ return
+ }
+ br := lintutil.FindNode(c.blockStmtOf(last), func(n ast.Node) bool {
+ br, ok := n.(*ast.BranchStmt)
+ return ok && br.Label != nil &&
+ br.Label.Name == name && br.Tok == token.CONTINUE
+ })
+ if br != nil {
+ c.warnLabeledContinue(br, name)
+ }
+ }
+}
+
+// isLoop reports whether n is a loop of some kind.
+// In other words, it tells whether n body can contain "continue"
+// associated with n.
+func (c *unlabelStmtChecker) isLoop(n ast.Node) bool {
+ switch n.(type) {
+ case *ast.ForStmt, *ast.RangeStmt:
+ return true
+ default:
+ return false
+ }
+}
+
+// canBreakFrom reports whether it is possible to "break" or "continue" from n body.
+func (c *unlabelStmtChecker) canBreakFrom(n ast.Node) bool {
+ switch n.(type) {
+ case *ast.RangeStmt, *ast.ForStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
+ return true
+ default:
+ return false
+ }
+}
+
+// blockStmtOf returns body of specified node.
+//
+// TODO(quasilyte): handle other statements and see if it can be useful
+// in other checkers.
+func (c *unlabelStmtChecker) blockStmtOf(n ast.Node) *ast.BlockStmt {
+ switch n := n.(type) {
+ case *ast.RangeStmt:
+ return n.Body
+ case *ast.ForStmt:
+ return n.Body
+ case *ast.SwitchStmt:
+ return n.Body
+ case *ast.TypeSwitchStmt:
+ return n.Body
+ case *ast.SelectStmt:
+ return n.Body
+
+ default:
+ return nil
+ }
+}
+
+// usesLabel reports whether n contains a usage of label.
+func (c *unlabelStmtChecker) usesLabel(n *ast.BlockStmt, label string) bool {
+ return lintutil.ContainsNode(n, func(n ast.Node) bool {
+ branch, ok := n.(*ast.BranchStmt)
+ return ok && branch.Label != nil &&
+ branch.Label.Name == label &&
+ (branch.Tok == token.CONTINUE || branch.Tok == token.BREAK)
+ })
+}
+
+func (c *unlabelStmtChecker) warnRedundant(cause *ast.LabeledStmt) {
+ c.ctx.Warn(cause, "label %s is redundant", cause.Label)
+}
+
+func (c *unlabelStmtChecker) warnLabeledContinue(cause ast.Node, label string) {
+ c.ctx.Warn(cause, "change `continue %s` to `break`", label)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go
new file mode 100644
index 000000000..9e01299bf
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go
@@ -0,0 +1,79 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "unlambda"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects function literals that can be simplified"
+ info.Before = `func(x int) int { return fn(x) }`
+ info.After = `fn`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&unlambdaChecker{ctx: ctx})
+ })
+}
+
+type unlambdaChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *unlambdaChecker) VisitExpr(x ast.Expr) {
+ fn, ok := x.(*ast.FuncLit)
+ if !ok || len(fn.Body.List) != 1 {
+ return
+ }
+
+ ret, ok := fn.Body.List[0].(*ast.ReturnStmt)
+ if !ok || len(ret.Results) != 1 {
+ return
+ }
+
+ result := astcast.ToCallExpr(ret.Results[0])
+ callable := qualifiedName(result.Fun)
+ if callable == "" {
+ return // Skip tricky cases; only handle simple calls
+ }
+ if isBuiltin(callable) {
+ return // See #762
+ }
+ if id, ok := result.Fun.(*ast.Ident); ok {
+ obj := c.ctx.TypesInfo.ObjectOf(id)
+ if _, ok := obj.(*types.Var); ok {
+ return // See #888
+ }
+ }
+ fnType := c.ctx.TypesInfo.TypeOf(fn)
+ resultType := c.ctx.TypesInfo.TypeOf(result.Fun)
+ if !types.Identical(fnType, resultType) {
+ return
+ }
+ // Now check that all arguments match the parameters.
+ n := 0
+ for _, params := range fn.Type.Params.List {
+ for _, id := range params.Names {
+ if !astequal.Expr(id, result.Args[n]) {
+ return
+ }
+ n++
+ }
+ }
+
+ if len(result.Args) == n {
+ c.warn(fn, callable)
+ }
+}
+
+func (c *unlambdaChecker) warn(cause ast.Node, suggestion string) {
+ c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/unnamedResult_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unnamedResult_checker.go
new file mode 100644
index 000000000..09423250a
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/unnamedResult_checker.go
@@ -0,0 +1,103 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "unnamedResult"
+ info.Tags = []string{"style", "opinionated", "experimental"}
+ info.Params = lintpack.CheckerParams{
+ "checkExported": {
+ Value: false,
+ Usage: "whether to check exported functions",
+ },
+ }
+ info.Summary = "Detects unnamed results that may benefit from names"
+ info.Before = `func f() (float64, float64)`
+ info.After = `func f() (x, y float64)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ c := &unnamedResultChecker{ctx: ctx}
+ c.checkExported = info.Params.Bool("checkExported")
+ return astwalk.WalkerForFuncDecl(c)
+ })
+}
+
+type unnamedResultChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ checkExported bool
+}
+
+func (c *unnamedResultChecker) VisitFuncDecl(decl *ast.FuncDecl) {
+ if c.checkExported && !ast.IsExported(decl.Name.Name) {
+ return
+ }
+ results := decl.Type.Results
+ switch {
+ case results == nil:
+ return // Function has no results
+ case len(results.List) != 0 && results.List[0].Names != nil:
+ return // Skip named results
+ }
+
+ typeName := func(x ast.Expr) string { return c.typeName(c.ctx.TypesInfo.TypeOf(x)) }
+ isError := func(x ast.Expr) bool { return qualifiedName(x) == "error" }
+ isBool := func(x ast.Expr) bool { return qualifiedName(x) == "bool" }
+
+ // Main difference with case of len=2 is that we permit any
+ // typ1 as long as second type is either error or bool.
+ if results.NumFields() == 2 {
+ typ1, typ2 := results.List[0].Type, results.List[1].Type
+ name1, name2 := typeName(typ1), typeName(typ2)
+ cond := (name1 != name2 && name2 != "") ||
+ (!isError(typ1) && isError(typ2)) ||
+ (!isBool(typ1) && isBool(typ2))
+ if !cond {
+ c.warn(decl)
+ }
+ return
+ }
+
+ seen := make(map[string]bool, len(results.List))
+ for i := range results.List {
+ typ := results.List[i].Type
+ name := typeName(typ)
+ isLast := i == len(results.List)-1
+
+ cond := !seen[name] ||
+ (isLast && (isError(typ) || isBool(typ)))
+ if !cond {
+ c.warn(decl)
+ return
+ }
+
+ seen[name] = true
+ }
+}
+
+func (c *unnamedResultChecker) typeName(typ types.Type) string {
+ switch typ := typ.(type) {
+ case *types.Array:
+ return c.typeName(typ.Elem())
+ case *types.Pointer:
+ return c.typeName(typ.Elem())
+ case *types.Slice:
+ return c.typeName(typ.Elem())
+ case *types.Named:
+ return typ.Obj().Name()
+ default:
+ return ""
+ }
+}
+
+func (c *unnamedResultChecker) warn(n ast.Node) {
+ c.ctx.Warn(n, "consider giving a name to these results")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go
new file mode 100644
index 000000000..e5dc45f7e
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go
@@ -0,0 +1,69 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "unnecessaryBlock"
+ info.Tags = []string{"style", "opinionated", "experimental"}
+ info.Summary = "Detects unnecessary braced statement blocks"
+ info.Before = `
+x := 1
+{
+ print(x)
+}`
+ info.After = `
+x := 1
+print(x)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmtList(&unnecessaryBlockChecker{ctx: ctx})
+ })
+}
+
+type unnecessaryBlockChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *unnecessaryBlockChecker) VisitStmtList(statements []ast.Stmt) {
+ // Using StmtListVisitor instead of StmtVisitor makes it easier to avoid
+ // false positives on IfStmt, RangeStmt, ForStmt and alike.
+ // We only inspect BlockStmt inside statement lists, so this method is not
+ // called for IfStmt itself, for example.
+
+ for _, stmt := range statements {
+ stmt, ok := stmt.(*ast.BlockStmt)
+ if ok && !c.hasDefinitions(stmt) {
+ c.warn(stmt)
+ }
+ }
+}
+
+func (c *unnecessaryBlockChecker) hasDefinitions(stmt *ast.BlockStmt) bool {
+ for _, bs := range stmt.List {
+ switch stmt := bs.(type) {
+ case *ast.AssignStmt:
+ if stmt.Tok == token.DEFINE {
+ return true
+ }
+ case *ast.DeclStmt:
+ decl := stmt.Decl.(*ast.GenDecl)
+ if len(decl.Specs) != 0 {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func (c *unnecessaryBlockChecker) warn(expr ast.Stmt) {
+ c.ctx.Warn(expr, "block doesn't have definitions, can be simply deleted")
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go
new file mode 100644
index 000000000..06d90819c
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go
@@ -0,0 +1,59 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "unslice"
+ info.Tags = []string{"style"}
+ info.Summary = "Detects slice expressions that can be simplified to sliced expression itself"
+ info.Before = `
+f(s[:]) // s is string
+copy(b[:], values...) // b is []byte`
+ info.After = `
+f(s)
+copy(b, values...)`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&unsliceChecker{ctx: ctx})
+ })
+}
+
+type unsliceChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *unsliceChecker) VisitExpr(expr ast.Expr) {
+ unsliced := c.unslice(expr)
+ if !astequal.Expr(expr, unsliced) {
+ c.warn(expr, unsliced)
+ c.SkipChilds = true
+ }
+}
+
+func (c *unsliceChecker) unslice(expr ast.Expr) ast.Expr {
+ slice, ok := expr.(*ast.SliceExpr)
+ if !ok || slice.Low != nil || slice.High != nil {
+ // No need to worry about 3-index slicing,
+ // because it's only permitted if expr.High is not nil.
+ return expr
+ }
+ switch c.ctx.TypesInfo.TypeOf(slice.X).(type) {
+ case *types.Slice, *types.Basic:
+ // Basic kind catches strings, Slice cathes everything else.
+ return c.unslice(slice.X)
+ }
+ return expr
+}
+
+func (c *unsliceChecker) warn(cause, unsliced ast.Expr) {
+ c.ctx.Warn(cause, "could simplify %s to %s", cause, unsliced)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/utils.go b/vendor/github.com/go-critic/go-critic/checkers/utils.go
new file mode 100644
index 000000000..ba4777dbd
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/utils.go
@@ -0,0 +1,309 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/types"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+)
+
+// goStdlib contains `go list std` command output list.
+// Used to detect packages that belong to standard Go packages distribution.
+var goStdlib = map[string]bool{
+ "archive/tar": true,
+ "archive/zip": true,
+ "bufio": true,
+ "bytes": true,
+ "compress/bzip2": true,
+ "compress/flate": true,
+ "compress/gzip": true,
+ "compress/lzw": true,
+ "compress/zlib": true,
+ "container/heap": true,
+ "container/list": true,
+ "container/ring": true,
+ "context": true,
+ "crypto": true,
+ "crypto/aes": true,
+ "crypto/cipher": true,
+ "crypto/des": true,
+ "crypto/dsa": true,
+ "crypto/ecdsa": true,
+ "crypto/elliptic": true,
+ "crypto/hmac": true,
+ "crypto/internal/randutil": true,
+ "crypto/internal/subtle": true,
+ "crypto/md5": true,
+ "crypto/rand": true,
+ "crypto/rc4": true,
+ "crypto/rsa": true,
+ "crypto/sha1": true,
+ "crypto/sha256": true,
+ "crypto/sha512": true,
+ "crypto/subtle": true,
+ "crypto/tls": true,
+ "crypto/x509": true,
+ "crypto/x509/pkix": true,
+ "database/sql": true,
+ "database/sql/driver": true,
+ "debug/dwarf": true,
+ "debug/elf": true,
+ "debug/gosym": true,
+ "debug/macho": true,
+ "debug/pe": true,
+ "debug/plan9obj": true,
+ "encoding": true,
+ "encoding/ascii85": true,
+ "encoding/asn1": true,
+ "encoding/base32": true,
+ "encoding/base64": true,
+ "encoding/binary": true,
+ "encoding/csv": true,
+ "encoding/gob": true,
+ "encoding/hex": true,
+ "encoding/json": true,
+ "encoding/pem": true,
+ "encoding/xml": true,
+ "errors": true,
+ "expvar": true,
+ "flag": true,
+ "fmt": true,
+ "go/ast": true,
+ "go/build": true,
+ "go/constant": true,
+ "go/doc": true,
+ "go/format": true,
+ "go/importer": true,
+ "go/internal/gccgoimporter": true,
+ "go/internal/gcimporter": true,
+ "go/internal/srcimporter": true,
+ "go/parser": true,
+ "go/printer": true,
+ "go/scanner": true,
+ "go/token": true,
+ "go/types": true,
+ "hash": true,
+ "hash/adler32": true,
+ "hash/crc32": true,
+ "hash/crc64": true,
+ "hash/fnv": true,
+ "html": true,
+ "html/template": true,
+ "image": true,
+ "image/color": true,
+ "image/color/palette": true,
+ "image/draw": true,
+ "image/gif": true,
+ "image/internal/imageutil": true,
+ "image/jpeg": true,
+ "image/png": true,
+ "index/suffixarray": true,
+ "internal/bytealg": true,
+ "internal/cpu": true,
+ "internal/nettrace": true,
+ "internal/poll": true,
+ "internal/race": true,
+ "internal/singleflight": true,
+ "internal/syscall/unix": true,
+ "internal/syscall/windows": true,
+ "internal/syscall/windows/registry": true,
+ "internal/syscall/windows/sysdll": true,
+ "internal/testenv": true,
+ "internal/testlog": true,
+ "internal/trace": true,
+ "io": true,
+ "io/ioutil": true,
+ "log": true,
+ "log/syslog": true,
+ "math": true,
+ "math/big": true,
+ "math/bits": true,
+ "math/cmplx": true,
+ "math/rand": true,
+ "mime": true,
+ "mime/multipart": true,
+ "mime/quotedprintable": true,
+ "net": true,
+ "net/http": true,
+ "net/http/cgi": true,
+ "net/http/cookiejar": true,
+ "net/http/fcgi": true,
+ "net/http/httptest": true,
+ "net/http/httptrace": true,
+ "net/http/httputil": true,
+ "net/http/internal": true,
+ "net/http/pprof": true,
+ "net/internal/socktest": true,
+ "net/mail": true,
+ "net/rpc": true,
+ "net/rpc/jsonrpc": true,
+ "net/smtp": true,
+ "net/textproto": true,
+ "net/url": true,
+ "os": true,
+ "os/exec": true,
+ "os/signal": true,
+ "os/signal/internal/pty": true,
+ "os/user": true,
+ "path": true,
+ "path/filepath": true,
+ "plugin": true,
+ "reflect": true,
+ "regexp": true,
+ "regexp/syntax": true,
+ "runtime": true,
+ "runtime/cgo": true,
+ "runtime/debug": true,
+ "runtime/internal/atomic": true,
+ "runtime/internal/sys": true,
+ "runtime/pprof": true,
+ "runtime/pprof/internal/profile": true,
+ "runtime/race": true,
+ "runtime/trace": true,
+ "sort": true,
+ "strconv": true,
+ "strings": true,
+ "sync": true,
+ "sync/atomic": true,
+ "syscall": true,
+ "testing": true,
+ "testing/internal/testdeps": true,
+ "testing/iotest": true,
+ "testing/quick": true,
+ "text/scanner": true,
+ "text/tabwriter": true,
+ "text/template": true,
+ "text/template/parse": true,
+ "time": true,
+ "unicode": true,
+ "unicode/utf16": true,
+ "unicode/utf8": true,
+ "unsafe": true,
+}
+
+var goBuiltins = map[string]bool{
+ // Types
+ "bool": true,
+ "byte": true,
+ "complex64": true,
+ "complex128": true,
+ "error": true,
+ "float32": true,
+ "float64": true,
+ "int": true,
+ "int8": true,
+ "int16": true,
+ "int32": true,
+ "int64": true,
+ "rune": true,
+ "string": true,
+ "uint": true,
+ "uint8": true,
+ "uint16": true,
+ "uint32": true,
+ "uint64": true,
+ "uintptr": true,
+
+ // Constants
+ "true": true,
+ "false": true,
+ "iota": true,
+
+ // Zero value
+ "nil": true,
+
+ // Functions
+ "append": true,
+ "cap": true,
+ "close": true,
+ "complex": true,
+ "copy": true,
+ "delete": true,
+ "imag": true,
+ "len": true,
+ "make": true,
+ "new": true,
+ "panic": true,
+ "print": true,
+ "println": true,
+ "real": true,
+ "recover": true,
+}
+
+// isBuiltin reports whether sym belongs to a predefined identifier set.
+func isBuiltin(sym string) bool {
+ return goBuiltins[sym]
+}
+
+// isStdlibPkg reports whether pkg is a package from the Go standard library.
+func isStdlibPkg(pkg *types.Package) bool {
+ return pkg != nil && goStdlib[pkg.Path()]
+}
+
+// isExampleTestFunc reports whether FuncDecl looks like a testable example function.
+func isExampleTestFunc(fn *ast.FuncDecl) bool {
+ return len(fn.Type.Params.List) == 0 && strings.HasPrefix(fn.Name.String(), "Example")
+}
+
+// isUnitTestFunc reports whether FuncDecl declares testing function.
+func isUnitTestFunc(ctx *lintpack.CheckerContext, fn *ast.FuncDecl) bool {
+ if !strings.HasPrefix(fn.Name.Name, "Test") {
+ return false
+ }
+ typ := ctx.TypesInfo.TypeOf(fn.Name)
+ if sig, ok := typ.(*types.Signature); ok {
+ return sig.Results().Len() == 0 &&
+ sig.Params().Len() == 1 &&
+ sig.Params().At(0).Type().String() == "*testing.T"
+ }
+ return false
+}
+
+// qualifiedName returns called expr fully-quallified name.
+//
+// It works for simple identifiers like f => "f" and identifiers
+// from other package like pkg.f => "pkg.f".
+//
+// For all unexpected expressions returns empty string.
+func qualifiedName(x ast.Expr) string {
+ switch x := x.(type) {
+ case *ast.SelectorExpr:
+ pkg, ok := x.X.(*ast.Ident)
+ if !ok {
+ return ""
+ }
+ return pkg.Name + "." + x.Sel.Name
+ case *ast.Ident:
+ return x.Name
+ default:
+ return ""
+ }
+}
+
+// identOf returns identifier for x that can be used to obtain associated types.Object.
+// Returns nil for expressions that yield temporary results, like `f().field`.
+func identOf(x ast.Node) *ast.Ident {
+ switch x := x.(type) {
+ case *ast.Ident:
+ return x
+ case *ast.SelectorExpr:
+ return identOf(x.Sel)
+ case *ast.TypeAssertExpr:
+ // x.(type) - x may contain ident.
+ return identOf(x.X)
+ case *ast.IndexExpr:
+ // x[i] - x may contain ident.
+ return identOf(x.X)
+ case *ast.StarExpr:
+ // *x - x may contain ident.
+ return identOf(x.X)
+ case *ast.SliceExpr:
+ // x[:] - x may contain ident.
+ return identOf(x.X)
+
+ default:
+ // Note that this function is not comprehensive.
+ return nil
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go b/vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go
new file mode 100644
index 000000000..ab27f9200
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go
@@ -0,0 +1,64 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "valSwap"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects value swapping code that are not using parallel assignment"
+ info.Before = `
+tmp := *x
+*x = *y
+*y = tmp`
+ info.After = `*x, *y = *y, *x`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForStmtList(&valSwapChecker{ctx: ctx})
+ })
+}
+
+type valSwapChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *valSwapChecker) VisitStmtList(list []ast.Stmt) {
+ for len(list) >= 3 {
+ tmpAssign := astcast.ToAssignStmt(list[0])
+ assignX := astcast.ToAssignStmt(list[1])
+ assignY := astcast.ToAssignStmt(list[2])
+
+ cond := c.isSimpleAssign(tmpAssign) &&
+ c.isSimpleAssign(assignX) &&
+ c.isSimpleAssign(assignY) &&
+ assignX.Tok == token.ASSIGN &&
+ assignY.Tok == token.ASSIGN &&
+ astequal.Expr(assignX.Lhs[0], tmpAssign.Rhs[0]) &&
+ astequal.Expr(assignX.Rhs[0], assignY.Lhs[0]) &&
+ astequal.Expr(assignY.Rhs[0], tmpAssign.Lhs[0])
+ if cond {
+ c.warn(tmpAssign, assignX.Lhs[0], assignY.Lhs[0])
+ list = list[3:]
+ } else {
+ list = list[1:]
+ }
+ }
+}
+
+func (c *valSwapChecker) isSimpleAssign(x *ast.AssignStmt) bool {
+ return len(x.Lhs) == 1 && len(x.Rhs) == 1
+}
+
+func (c *valSwapChecker) warn(cause, x, y ast.Node) {
+ c.ctx.Warn(cause, "can re-write as `%s, %s = %s, %s`",
+ x, y, y, x)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/weakCond_checker.go b/vendor/github.com/go-critic/go-critic/checkers/weakCond_checker.go
new file mode 100644
index 000000000..fcd9aee52
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/weakCond_checker.go
@@ -0,0 +1,77 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-critic/go-critic/checkers/internal/lintutil"
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+ "github.com/go-toolsmith/astequal"
+ "github.com/go-toolsmith/typep"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "weakCond"
+ info.Tags = []string{"diagnostic", "experimental"}
+ info.Summary = "Detects conditions that are unsafe due to not being exhaustive"
+ info.Before = `xs != nil && xs[0] != nil`
+ info.After = `len(xs) != 0 && xs[0] != nil`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForExpr(&weakCondChecker{ctx: ctx})
+ })
+}
+
+type weakCondChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *weakCondChecker) VisitExpr(expr ast.Expr) {
+ // TODO(Quasilyte): more patterns.
+ // TODO(Quasilyte): analyze and fix false positives.
+
+ cond := astcast.ToBinaryExpr(expr)
+ lhs := astcast.ToBinaryExpr(astutil.Unparen(cond.X))
+ rhs := astutil.Unparen(cond.Y)
+
+ // Pattern 1.
+ // `x != nil && usageOf(x[i])`
+ // Pattern 2.
+ // `x == nil || usageOf(x[i])`
+
+ // lhs is `x <op> nil`
+ x := lhs.X
+ if !typep.IsSlice(c.ctx.TypesInfo.TypeOf(x)) {
+ return
+ }
+ if astcast.ToIdent(lhs.Y).Name != "nil" {
+ return
+ }
+
+ pat1prefix := cond.Op == token.LAND && lhs.Op == token.NEQ
+ pat2prefix := cond.Op == token.LOR && lhs.Op == token.EQL
+ if !pat1prefix && !pat2prefix {
+ return
+ }
+
+ if c.isIndexed(rhs, x) {
+ c.warn(expr, "nil check may not be enough, check for len")
+ }
+}
+
+// isIndexed reports whether x is indexed inside given expr tree.
+func (c *weakCondChecker) isIndexed(tree, x ast.Expr) bool {
+ return lintutil.ContainsNode(tree, func(n ast.Node) bool {
+ indexing := astcast.ToIndexExpr(n)
+ return astequal.Expr(x, indexing.X)
+ })
+}
+
+func (c *weakCondChecker) warn(cause ast.Node, suggest string) {
+ c.ctx.Warn(cause, "suspicious `%s`; %s", cause, suggest)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go b/vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go
new file mode 100644
index 000000000..52fefb82c
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go
@@ -0,0 +1,52 @@
+package checkers
+
+import (
+ "go/ast"
+ "regexp"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+)
+
+func init() {
+ info := lintpack.CheckerInfo{
+ Name: "whyNoLint",
+ Tags: []string{"style", "experimental"},
+ Summary: "Ensures that `//nolint` comments include an explanation",
+ Before: `//nolint`,
+ After: `//nolint // reason`,
+ }
+ re := regexp.MustCompile(`^// *nolint(?::[^ ]+)? *(.*)$`)
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForComment(&whyNoLintChecker{
+ ctx: ctx,
+ re: re,
+ })
+ })
+}
+
+type whyNoLintChecker struct {
+ astwalk.WalkHandler
+
+ ctx *lintpack.CheckerContext
+ re *regexp.Regexp
+}
+
+func (c whyNoLintChecker) VisitComment(cg *ast.CommentGroup) {
+ if strings.HasPrefix(cg.List[0].Text, "/*") {
+ return
+ }
+ for _, comment := range cg.List {
+ sl := c.re.FindStringSubmatch(comment.Text)
+ if len(sl) < 2 {
+ continue
+ }
+
+ if s := sl[1]; !strings.HasPrefix(s, "//") || len(strings.TrimPrefix(s, "//")) == 0 {
+ c.ctx.Warn(cg, "include an explanation for nolint directive")
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go b/vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go
new file mode 100644
index 000000000..bba82e5ee
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go
@@ -0,0 +1,224 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcast"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "wrapperFunc"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects function calls that can be replaced with convenience wrappers"
+ info.Before = `wg.Add(-1)`
+ info.After = `wg.Done()`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ type arg struct {
+ index int
+ value string
+ }
+ type pattern struct {
+ pkg string
+ typ string // Only for typ patterns
+ args []arg
+ suggestion string
+ }
+ type matcher struct {
+ pkgPatterns []pattern
+ typPatterns []pattern
+ }
+
+ typPatterns := map[string][]arg{
+ "sync.WaitGroup.Add => WaitGroup.Done": {
+ {0, "-1"},
+ },
+
+ "bytes.Buffer.Truncate => Buffer.Reset": {
+ {0, "0"},
+ },
+ }
+
+ pkgPatterns := map[string][]arg{
+ "http.HandlerFunc => http.NotFoundHandler": {
+ {0, "http.NotFound"},
+ },
+
+ "strings.SplitN => strings.Split": {
+ {2, "-1"},
+ },
+ "strings.Replace => strings.ReplaceAll": {
+ {3, "-1"},
+ },
+ "strings.TrimFunc => strings.TrimSpace": {
+ {1, "unicode.IsSpace"},
+ },
+ "strings.Map => strings.ToTitle": {
+ {0, "unicode.ToTitle"},
+ },
+
+ "bytes.SplitN => bytes.Split": {
+ {2, "-1"},
+ },
+ "bytes.Replace => bytes.ReplaceAll": {
+ {3, "-1"},
+ },
+ "bytes.TrimFunc => bytes.TrimSpace": {
+ {1, "unicode.IsSpace"},
+ },
+ "bytes.Map => bytes.ToUpper": {
+ {0, "unicode.ToUpper"},
+ },
+ "bytes.Map => bytes.ToLower": {
+ {0, "unicode.ToLower"},
+ },
+ "bytes.Map => bytes.ToTitle": {
+ {0, "unicode.ToTitle"},
+ },
+ }
+
+ matchers := make(map[string]*matcher)
+
+ type templateKey struct {
+ from string
+ to string
+ }
+ decodeKey := func(key string) templateKey {
+ parts := strings.Split(key, " => ")
+ return templateKey{from: parts[0], to: parts[1]}
+ }
+
+ // Expand pkg patterns.
+ for key, args := range pkgPatterns {
+ key := decodeKey(key)
+ parts := strings.Split(key.from, ".")
+ fn := parts[1]
+ m := matchers[fn]
+ if m == nil {
+ m = &matcher{}
+ matchers[fn] = m
+ }
+ m.pkgPatterns = append(m.pkgPatterns, pattern{
+ pkg: parts[0],
+ args: args,
+ suggestion: key.to,
+ })
+ }
+ // Expand typ patterns.
+ for key, args := range typPatterns {
+ key := decodeKey(key)
+ parts := strings.Split(key.from, ".")
+ fn := parts[2]
+ m := matchers[fn]
+ if m == nil {
+ m = &matcher{}
+ matchers[fn] = m
+ }
+ m.typPatterns = append(m.typPatterns, pattern{
+ pkg: parts[0],
+ typ: parts[1],
+ args: args,
+ suggestion: key.to,
+ })
+ }
+
+ var valueOf func(x ast.Expr) string
+ valueOf = func(x ast.Expr) string {
+ switch x := x.(type) {
+ case *ast.Ident:
+ return x.Name
+ case *ast.SelectorExpr:
+ id, ok := x.X.(*ast.Ident)
+ if ok {
+ return id.Name + "." + x.Sel.Name
+ }
+ case *ast.BasicLit:
+ return x.Value
+ case *ast.UnaryExpr:
+ switch x.Op {
+ case token.SUB:
+ return "-" + valueOf(x.X)
+ case token.ADD:
+ return valueOf(x.X)
+ }
+ }
+ return ""
+ }
+
+ findSuggestion := func(call *ast.CallExpr, pkg, typ string, patterns []pattern) string {
+ for _, pat := range patterns {
+ if pat.pkg != pkg || pat.typ != typ {
+ continue
+ }
+ for _, arg := range pat.args {
+ if arg.value == valueOf(call.Args[arg.index]) {
+ return pat.suggestion
+ }
+ }
+ }
+ return ""
+ }
+
+ c := &wrapperFuncChecker{ctx: ctx}
+ c.findSuggestion = func(call *ast.CallExpr) string {
+ sel := astcast.ToSelectorExpr(call.Fun).Sel
+ if sel == nil {
+ return ""
+ }
+ x := astcast.ToSelectorExpr(call.Fun).X
+
+ m := matchers[sel.Name]
+ if m == nil {
+ return ""
+ }
+
+ if x, ok := x.(*ast.Ident); ok {
+ obj, ok := c.ctx.TypesInfo.ObjectOf(x).(*types.PkgName)
+ if ok {
+ return findSuggestion(call, obj.Name(), "", m.pkgPatterns)
+ }
+ }
+
+ typ := c.ctx.TypesInfo.TypeOf(x)
+ tn, ok := typ.(*types.Named)
+ if !ok {
+ return ""
+ }
+ return findSuggestion(
+ call,
+ tn.Obj().Pkg().Name(),
+ tn.Obj().Name(),
+ m.typPatterns)
+ }
+
+ return astwalk.WalkerForExpr(c)
+ })
+}
+
+type wrapperFuncChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+
+ findSuggestion func(*ast.CallExpr) string
+}
+
+func (c *wrapperFuncChecker) VisitExpr(expr ast.Expr) {
+ call := astcast.ToCallExpr(expr)
+ if len(call.Args) == 0 {
+ return
+ }
+
+ if suggest := c.findSuggestion(call); suggest != "" {
+ c.warn(call, suggest)
+ }
+}
+
+func (c *wrapperFuncChecker) warn(cause ast.Node, suggest string) {
+ c.ctx.Warn(cause, "use %s method in `%s`", suggest, cause)
+}
diff --git a/vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go b/vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go
new file mode 100644
index 000000000..ddd3099fd
--- /dev/null
+++ b/vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go
@@ -0,0 +1,66 @@
+package checkers
+
+import (
+ "go/ast"
+ "go/token"
+
+ "github.com/go-lintpack/lintpack"
+ "github.com/go-lintpack/lintpack/astwalk"
+ "github.com/go-toolsmith/astcopy"
+ "github.com/go-toolsmith/astp"
+)
+
+func init() {
+ var info lintpack.CheckerInfo
+ info.Name = "yodaStyleExpr"
+ info.Tags = []string{"style", "experimental"}
+ info.Summary = "Detects Yoda style expressions and suggests to replace them"
+ info.Before = `return nil != ptr`
+ info.After = `return ptr != nil`
+
+ collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
+ return astwalk.WalkerForLocalExpr(&yodaStyleExprChecker{ctx: ctx})
+ })
+}
+
+type yodaStyleExprChecker struct {
+ astwalk.WalkHandler
+ ctx *lintpack.CheckerContext
+}
+
+func (c *yodaStyleExprChecker) VisitLocalExpr(expr ast.Expr) {
+ binexpr, ok := expr.(*ast.BinaryExpr)
+ if !ok {
+ return
+ }
+ switch binexpr.Op {
+ case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GEQ, token.GTR:
+ if c.isConstExpr(binexpr.X) && !c.isConstExpr(binexpr.Y) {
+ c.warn(binexpr)
+ }
+ }
+}
+
+func (c *yodaStyleExprChecker) isConstExpr(expr ast.Expr) bool {
+ return qualifiedName(expr) == "nil" || astp.IsBasicLit(expr)
+}
+
+func (c *yodaStyleExprChecker) invert(expr *ast.BinaryExpr) {
+ expr.X, expr.Y = expr.Y, expr.X
+ switch expr.Op {
+ case token.LSS:
+ expr.Op = token.GEQ
+ case token.LEQ:
+ expr.Op = token.GTR
+ case token.GEQ:
+ expr.Op = token.LSS
+ case token.GTR:
+ expr.Op = token.LEQ
+ }
+}
+
+func (c *yodaStyleExprChecker) warn(expr *ast.BinaryExpr) {
+ e := astcopy.BinaryExpr(expr)
+ c.invert(e)
+ c.ctx.Warn(expr, "consider to change order in expression to %s", e)
+}