From b2f2446b46bf02821d90ebedadae2bf7ae0e880e Mon Sep 17 00:00:00 2001 From: Taras Madan Date: Mon, 5 Sep 2022 14:27:54 +0200 Subject: go.mod, vendor: update (#3358) * go.mod, vendor: remove unnecessary dependencies Commands: 1. go mod tidy 2. go mod vendor * go.mod, vendor: update cloud.google.com/go Commands: 1. go get -u cloud.google.com/go 2. go mod tidy 3. go mod vendor * go.mod, vendor: update cloud.google.com/* Commands: 1. go get -u cloud.google.com/storage cloud.google.com/logging 2. go mod tidy 3. go mod vendor * go.mod, .golangci.yml, vendor: update *lint* Commands: 1. go get -u golang.org/x/tools github.com/golangci/golangci-lint@v1.47.0 2. go mod tidy 3. go mod vendor 4. edit .golangci.yml to suppress new errors (resolved in the same PR later) * all: fix lint errors hash.go: copy() recommended by gosimple parse.go: ent is never nil verifier.go: signal.Notify() with unbuffered channel is bad. Have no idea why. * .golangci.yml: adjust godot rules check-all is deprecated, but still work if you're hesitating too - I'll remove this commit --- vendor/github.com/go-critic/go-critic/LICENSE | 3 +- .../go-critic/checkers/appendAssign_checker.go | 2 +- .../go-critic/checkers/appendCombine_checker.go | 2 +- .../go-critic/checkers/argOrder_checker.go | 97 - .../go-critic/checkers/assignOp_checker.go | 102 - .../go-critic/checkers/badCall_checker.go | 63 - .../go-critic/checkers/badLock_checker.go | 116 - .../go-critic/checkers/boolExprSimplify_checker.go | 12 +- .../checkers/commentFormatting_checker.go | 72 +- .../go-critic/checkers/deferInLoop_checker.go | 70 + .../go-critic/checkers/deferUnlambda_checker.go | 94 - .../checkers/deprecatedComment_checker.go | 25 +- .../go-critic/go-critic/checkers/dupArg_checker.go | 133 -- .../go-critic/checkers/dupCase_checker.go | 17 +- .../go-critic/go-critic/checkers/elseif_checker.go | 2 +- .../go-critic/go-critic/checkers/embedded_rules.go | 105 + .../go-critic/checkers/emptyFallthrough_checker.go | 2 +- .../go-critic/checkers/emptyStringTest_checker.go | 58 - .../go-critic/checkers/equalFold_checker.go | 87 - .../go-critic/checkers/flagDeref_checker.go | 65 - .../go-critic/checkers/hugeParam_checker.go | 8 +- .../go-critic/checkers/indexAlloc_checker.go | 50 - .../checkers/internal/astwalk/local_def_visitor.go | 2 +- .../checkers/internal/astwalk/stmt_list_walker.go | 6 +- .../checkers/internal/astwalk/type_expr_walker.go | 2 + .../go-critic/checkers/internal/astwalk/visitor.go | 2 +- .../checkers/internal/lintutil/astfind.go | 32 +- .../go-critic/checkers/nilValReturn_checker.go | 21 +- .../go-critic/checkers/octalLiteral_checker.go | 65 +- .../go-critic/go-critic/checkers/offBy1_checker.go | 66 - .../go-critic/checkers/paramTypeCombine_checker.go | 11 +- .../go-critic/checkers/rangeExprCopy_checker.go | 2 +- .../go-critic/checkers/rangeValCopy_checker.go | 6 +- .../go-critic/checkers/regexpMust_checker.go | 47 - .../go-critic/checkers/regexpSimplify_checker.go | 7 +- .../go-critic/checkers/ruleguard_checker.go | 246 +- .../go-critic/checkers/rulesdata/rulesdata.go | 2367 ++++++++++++++++++++ .../go-critic/checkers/sloppyLen_checker.go | 72 - .../go-critic/checkers/sloppyTypeAssert_checker.go | 26 +- .../go-critic/checkers/stringXbytes_checker.go | 47 - .../go-critic/checkers/switchTrue_checker.go | 49 - .../checkers/todoCommentWithoutDetail_checker.go | 50 + .../go-critic/checkers/tooManyResults_checker.go | 54 + .../go-critic/checkers/truncateCmp_checker.go | 10 +- .../go-critic/checkers/typeDefFirst_checker.go | 5 + .../go-critic/checkers/typeSwitchVar_checker.go | 2 +- .../go-critic/checkers/typeUnparen_checker.go | 87 +- .../go-critic/checkers/unlabelStmt_checker.go | 21 +- .../go-critic/checkers/unnecessaryBlock_checker.go | 10 +- .../go-critic/checkers/unslice_checker.go | 59 - .../go-critic/checkers/valSwap_checker.go | 64 - .../go-critic/checkers/whyNoLint_checker.go | 3 +- .../go-critic/checkers/wrapperFunc_checker.go | 229 -- .../go-critic/checkers/yodaStyleExpr_checker.go | 66 - .../go-critic/framework/linter/go_version.go | 51 + .../go-critic/go-critic/framework/linter/linter.go | 334 +++ .../go-critic/framework/linter/lintpack.go | 269 --- 57 files changed, 3516 insertions(+), 2059 deletions(-) delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/badLock_checker.go create mode 100644 vendor/github.com/go-critic/go-critic/checkers/deferInLoop_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/deferUnlambda_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go create mode 100644 vendor/github.com/go-critic/go-critic/checkers/embedded_rules.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go create mode 100644 vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go create mode 100644 vendor/github.com/go-critic/go-critic/checkers/todoCommentWithoutDetail_checker.go create mode 100644 vendor/github.com/go-critic/go-critic/checkers/tooManyResults_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go delete mode 100644 vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go create mode 100644 vendor/github.com/go-critic/go-critic/framework/linter/go_version.go create mode 100644 vendor/github.com/go-critic/go-critic/framework/linter/linter.go delete mode 100644 vendor/github.com/go-critic/go-critic/framework/linter/lintpack.go (limited to 'vendor/github.com/go-critic') diff --git a/vendor/github.com/go-critic/go-critic/LICENSE b/vendor/github.com/go-critic/go-critic/LICENSE index b944b4bbd..5198a4a94 100644 --- a/vendor/github.com/go-critic/go-critic/LICENSE +++ b/vendor/github.com/go-critic/go-critic/LICENSE @@ -1,7 +1,6 @@ MIT License -Copyright (c) 2018-2019 Alekseev Artem -Copyright (c) 2018-2019 Ravil Bikbulatov +Copyright (c) 2018-2021 go-critic team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 index 42fa97a54..a9324dd02 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go @@ -36,7 +36,7 @@ type appendAssignChecker struct { func (c *appendAssignChecker) VisitStmt(stmt ast.Stmt) { assign, ok := stmt.(*ast.AssignStmt) - if !ok || assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) { + if !ok || (assign.Tok != token.ASSIGN && assign.Tok != token.DEFINE) || len(assign.Lhs) != len(assign.Rhs) { return } for i, rhs := range assign.Rhs { 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 index 03662fc21..3c81449e9 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go @@ -30,7 +30,7 @@ type appendCombineChecker struct { ctx *linter.CheckerContext } -func (c *appendCombineChecker) VisitStmtList(list []ast.Stmt) { +func (c *appendCombineChecker) VisitStmtList(_ ast.Node, 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 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 deleted file mode 100644 index 98cabc54f..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go +++ /dev/null @@ -1,97 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/types" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/astp" - "github.com/go-toolsmith/typep" -) - -func init() { - var info linter.CheckerInfo - info.Name = "argOrder" - info.Tags = []string{"diagnostic"} - info.Summary = "Detects suspicious arguments order" - info.Before = `strings.HasPrefix("#", userpass)` - info.After = `strings.HasPrefix(userpass, "#")` - - collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&argOrderChecker{ctx: ctx}), nil - }) -} - -type argOrderChecker struct { - astwalk.WalkHandler - ctx *linter.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 { - // Also permit byte slices. - switch x := x.(type) { - case *ast.BasicLit: - return true - - case *ast.CallExpr: - // Handle `[]byte("abc")` as well. - if len(x.Args) != 1 || !astp.IsBasicLit(x.Args[0]) { - return false - } - typ, ok := c.ctx.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.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 deleted file mode 100644 index d0bf64417..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go +++ /dev/null @@ -1,102 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/astequal" - "github.com/go-toolsmith/typep" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForStmt(&assignOpChecker{ctx: ctx}), nil - }) -} - -type assignOpChecker struct { - astwalk.WalkHandler - ctx *linter.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 deleted file mode 100644 index 7435ee57b..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go +++ /dev/null @@ -1,63 +0,0 @@ -package checkers - -import ( - "go/ast" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astcopy" -) - -func init() { - var info linter.CheckerInfo - info.Name = "badCall" - info.Tags = []string{"diagnostic"} - 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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&badCallChecker{ctx: ctx}), nil - }) -} - -type badCallChecker struct { - astwalk.WalkHandler - ctx *linter.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/badLock_checker.go b/vendor/github.com/go-critic/go-critic/checkers/badLock_checker.go deleted file mode 100644 index 8628ff2d7..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/badLock_checker.go +++ /dev/null @@ -1,116 +0,0 @@ -package checkers - -import ( - "go/ast" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astequal" -) - -func init() { - var info linter.CheckerInfo - info.Name = "badLock" - info.Tags = []string{"diagnostic", "experimental"} - info.Summary = "Detects suspicious mutex lock/unlock operations" - info.Before = ` -mu.Lock() -mu.Unlock()` - info.After = ` -mu.Lock() -defer mu.Unlock()` - - collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForStmtList(&badLockChecker{ctx: ctx}), nil - }) -} - -type badLockChecker struct { - astwalk.WalkHandler - ctx *linter.CheckerContext -} - -func (c *badLockChecker) VisitStmtList(list []ast.Stmt) { - if len(list) < 2 { - return - } - - for i := 0; i < len(list)-1; i++ { - current, ok := list[i].(*ast.ExprStmt) - if !ok { - continue - } - deferred := false - var next ast.Expr - switch x := list[i+1].(type) { - case *ast.ExprStmt: - next = x.X - case *ast.DeferStmt: - next = x.Call - deferred = true - default: - continue - } - - mutex1, lockFunc, ok := c.asLockedMutex(current.X) - if !ok { - continue - } - mutex2, unlockFunc, ok := c.asUnlockedMutex(next) - if !ok { - continue - } - if !astequal.Expr(mutex1, mutex2) { - continue - } - - switch { - case !deferred: - c.warnImmediateUnlock(mutex2) - case lockFunc == "Lock" && unlockFunc == "RUnlock": - c.warnMismatchingUnlock(mutex2, "Unlock") - case lockFunc == "RLock" && unlockFunc == "Unlock": - c.warnMismatchingUnlock(mutex2, "RUnlock") - } - } -} - -func (c *badLockChecker) asLockedMutex(e ast.Expr) (ast.Expr, string, bool) { - call, ok := e.(*ast.CallExpr) - if !ok || len(call.Args) != 0 { - return nil, "", false - } - switch fn := call.Fun.(type) { - case *ast.SelectorExpr: - if fn.Sel.Name == "Lock" || fn.Sel.Name == "RLock" { - return fn.X, fn.Sel.Name, true - } - return nil, "", false - default: - return nil, "", false - } -} - -func (c *badLockChecker) asUnlockedMutex(e ast.Expr) (ast.Expr, string, bool) { - call, ok := e.(*ast.CallExpr) - if !ok || len(call.Args) != 0 { - return nil, "", false - } - switch fn := call.Fun.(type) { - case *ast.SelectorExpr: - if fn.Sel.Name == "Unlock" || fn.Sel.Name == "RUnlock" { - return fn.X, fn.Sel.Name, true - } - return nil, "", false - default: - return nil, "", false - } -} - -func (c *badLockChecker) warnImmediateUnlock(cause ast.Node) { - c.ctx.Warn(cause, "defer is missing, mutex is unlocked immediately") -} - -func (c *badLockChecker) warnMismatchingUnlock(cause ast.Node, suggestion string) { - c.ctx.Warn(cause, "suspicious unlock, maybe %s was intended?", suggestion) -} 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 index 325fb56a3..b4000a8ce 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go @@ -1,20 +1,20 @@ package checkers import ( - "fmt" "go/ast" "go/token" "strconv" - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/checkers/internal/lintutil" - "github.com/go-critic/go-critic/framework/linter" "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" + + "github.com/go-critic/go-critic/checkers/internal/astwalk" + "github.com/go-critic/go-critic/checkers/internal/lintutil" + "github.com/go-critic/go-critic/framework/linter" ) func init() { @@ -294,7 +294,7 @@ func (c *boolExprSimplifyChecker) foldRanges(cur *astutil.Cursor) bool { if match(&comb) { lhs.Op = token.EQL v := c1 + comb.resDelta - lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v) + lhs.Y.(*ast.BasicLit).Value = strconv.FormatInt(v, 10) cur.Replace(lhs) return true } @@ -316,7 +316,7 @@ func (c *boolExprSimplifyChecker) foldRanges(cur *astutil.Cursor) bool { if match(&comb) { lhs.Op = token.NEQ v := c1 + comb.resDelta - lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v) + lhs.Y.(*ast.BasicLit).Value = strconv.FormatInt(v, 10) cur.Replace(lhs) return true } 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 index 83b6d506e..f330b723a 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go @@ -20,18 +20,30 @@ func init() { info.After = `// This is a comment` collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { + regexpPatterns := []*regexp.Regexp{ + regexp.MustCompile(`^//[\w-]+:.*$`), // e.g.: key: value + } + equalPatterns := []string{ + "//nolint", + } parts := []string{ - `^//go:generate .*$`, // e.g.: go:generate value - `^//\w+:.*$`, // e.g.: key: value - `^//nolint\b`, // e.g.: nolint - `^//line /.*:\d+`, // e.g.: line /path/to/file:123 - `^//export \w+$`, // e.g.: export Foo + "//go:generate ", // e.g.: go:generate value + "//line /", // e.g.: line /path/to/file:123 + "//nolint ", // e.g.: nolint + "//noinspection ", // e.g.: noinspection ALL, some GoLand and friends versions + "//export ", // e.g.: export Foo + "///", // e.g.: vertical breaker ///////////// + "//+", + "//#", + "//-", + "//!", } - pat := "(?m)" + strings.Join(parts, "|") - pragmaRE := regexp.MustCompile(pat) + return astwalk.WalkerForComment(&commentFormattingChecker{ - ctx: ctx, - pragmaRE: pragmaRE, + ctx: ctx, + partPatterns: parts, + equalPatterns: equalPatterns, + regexpPatterns: regexpPatterns, }), nil }) } @@ -40,25 +52,49 @@ type commentFormattingChecker struct { astwalk.WalkHandler ctx *linter.CheckerContext - pragmaRE *regexp.Regexp + partPatterns []string + equalPatterns []string + regexpPatterns []*regexp.Regexp } func (c *commentFormattingChecker) VisitComment(cg *ast.CommentGroup) { if strings.HasPrefix(cg.List[0].Text, "/*") { return } + +outerLoop: for _, comment := range cg.List { - if len(comment.Text) <= len("// ") { + commentLen := len(comment.Text) + if commentLen <= len("// ") { continue } - if c.pragmaRE.MatchString(comment.Text) { - continue + + for _, p := range c.partPatterns { + if commentLen < len(p) { + continue + } + + if strings.EqualFold(comment.Text[:len(p)], p) { + continue outerLoop + } + } + + for _, p := range c.equalPatterns { + if strings.EqualFold(comment.Text, p) { + continue outerLoop + } + } + + for _, p := range c.regexpPatterns { + if p.MatchString(comment.Text) { + continue outerLoop + } } // 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) + c.warn(comment) return } } @@ -74,6 +110,10 @@ func (c *commentFormattingChecker) specialChar(r rune) bool { } } -func (c *commentFormattingChecker) warn(cg *ast.CommentGroup) { - c.ctx.Warn(cg, "put a space between `//` and comment text") +func (c *commentFormattingChecker) warn(comment *ast.Comment) { + c.ctx.WarnFixable(comment, linter.QuickFix{ + From: comment.Pos(), + To: comment.End(), + Replacement: []byte(strings.Replace(comment.Text, "//", "// ", 1)), + }, "put a space between `//` and comment text") } diff --git a/vendor/github.com/go-critic/go-critic/checkers/deferInLoop_checker.go b/vendor/github.com/go-critic/go-critic/checkers/deferInLoop_checker.go new file mode 100644 index 000000000..da90fe67a --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/checkers/deferInLoop_checker.go @@ -0,0 +1,70 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-critic/go-critic/checkers/internal/astwalk" + "github.com/go-critic/go-critic/framework/linter" +) + +func init() { + var info linter.CheckerInfo + info.Name = "deferInLoop" + info.Tags = []string{"diagnostic", "experimental"} + info.Summary = "Detects loops inside functions that use defer" + info.Before = ` +for _, filename := range []string{"foo", "bar"} { + f, err := os.Open(filename) + + defer f.Close() +} +` + info.After = ` +func process(filename string) { + f, err := os.Open(filename) + + defer f.Close() +} +/* ... */ +for _, filename := range []string{"foo", "bar"} { + process(filename) +}` + + collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { + return astwalk.WalkerForFuncDecl(&deferInLoopChecker{ctx: ctx}), nil + }) +} + +type deferInLoopChecker struct { + astwalk.WalkHandler + ctx *linter.CheckerContext + inFor bool +} + +func (c *deferInLoopChecker) VisitFuncDecl(fn *ast.FuncDecl) { + ast.Inspect(fn.Body, c.traversalFunc) +} + +func (c deferInLoopChecker) traversalFunc(cur ast.Node) bool { + switch n := cur.(type) { + case *ast.DeferStmt: + if c.inFor { + c.warn(n) + } + case *ast.RangeStmt, *ast.ForStmt: + if !c.inFor { + ast.Inspect(cur, deferInLoopChecker{ctx: c.ctx, inFor: true}.traversalFunc) + return false + } + case *ast.FuncLit: + ast.Inspect(n.Body, deferInLoopChecker{ctx: c.ctx, inFor: false}.traversalFunc) + return false + case nil: + return false + } + return true +} + +func (c *deferInLoopChecker) warn(cause *ast.DeferStmt) { + c.ctx.Warn(cause, "Possible resource leak, 'defer' is called in the 'for' loop") +} diff --git a/vendor/github.com/go-critic/go-critic/checkers/deferUnlambda_checker.go b/vendor/github.com/go-critic/go-critic/checkers/deferUnlambda_checker.go deleted file mode 100644 index b312bfb68..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/deferUnlambda_checker.go +++ /dev/null @@ -1,94 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/types" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" -) - -func init() { - var info linter.CheckerInfo - info.Name = "deferUnlambda" - info.Tags = []string{"style", "experimental"} - info.Summary = "Detects deferred function literals that can be simplified" - info.Before = `defer func() { f() }()` - info.After = `f()` - - collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForStmt(&deferUnlambdaChecker{ctx: ctx}), nil - }) -} - -type deferUnlambdaChecker struct { - astwalk.WalkHandler - ctx *linter.CheckerContext -} - -func (c *deferUnlambdaChecker) VisitStmt(x ast.Stmt) { - def, ok := x.(*ast.DeferStmt) - if !ok { - return - } - - // We don't analyze deferred function args. - // Most deferred calls don't have them, so it's not a big deal to skip them. - if len(def.Call.Args) != 0 { - return - } - - fn, ok := def.Call.Fun.(*ast.FuncLit) - if !ok { - return - } - - if len(fn.Body.List) != 1 { - return - } - - call, ok := astcast.ToExprStmt(fn.Body.List[0]).X.(*ast.CallExpr) - if !ok || !c.isFunctionCall(call) { - return - } - - // Skip recover() as it can't be moved outside of the lambda. - // Skip panic() to avoid affecting the stack trace. - switch qualifiedName(call.Fun) { - case "recover", "panic": - return - } - - for _, arg := range call.Args { - if !c.isConstExpr(arg) { - return - } - } - - c.warn(def, call) -} - -func (c *deferUnlambdaChecker) isFunctionCall(e *ast.CallExpr) bool { - switch fnExpr := e.Fun.(type) { - case *ast.Ident: - return true - case *ast.SelectorExpr: - x, ok := fnExpr.X.(*ast.Ident) - if !ok { - return false - } - _, ok = c.ctx.TypesInfo.ObjectOf(x).(*types.PkgName) - return ok - default: - return false - } -} - -func (c *deferUnlambdaChecker) isConstExpr(e ast.Expr) bool { - return c.ctx.TypesInfo.Types[e].Value != nil -} - -func (c *deferUnlambdaChecker) warn(cause, suggestion ast.Node) { - c.ctx.Warn(cause, "can rewrite as `defer %s`", suggestion) -} 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 index f60e58b58..0eb507237 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go @@ -2,7 +2,6 @@ package checkers import ( "go/ast" - "regexp" "strings" "github.com/go-critic/go-critic/checkers/internal/astwalk" @@ -24,12 +23,15 @@ func FuncOld() int` collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { 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.*`), + c.commonPatterns = []string{ + "this type is deprecated", + "this function is deprecated", + "[[deprecated]]", + "note: deprecated", + "deprecated in", + "deprecated. use", + "deprecated! use", + "deprecated use", // TODO(quasilyte): more of these? } @@ -41,6 +43,7 @@ func FuncOld() int` "Dprecated: ", "Derecated: ", "Depecated: ", + "Depekated: ", "Deprcated: ", "Depreated: ", "Deprected: ", @@ -63,7 +66,7 @@ type deprecatedCommentChecker struct { astwalk.WalkHandler ctx *linter.CheckerContext - commonPatterns []*regexp.Regexp + commonPatterns []string commonTypos []string } @@ -114,7 +117,11 @@ func (c *deprecatedCommentChecker) VisitDocComment(doc *ast.CommentGroup) { // Check for other commonly used patterns. for _, pat := range c.commonPatterns { - if pat.MatchString(l) { + if len(l) < len(pat) { + continue + } + + if strings.EqualFold(l[:len(pat)], pat) { c.warnPattern(comment) return } 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 deleted file mode 100644 index 9f116d781..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go +++ /dev/null @@ -1,133 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/types" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astequal" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - 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), nil - }) -} - -type dupArgChecker struct { - astwalk.WalkHandler - ctx *linter.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/dupCase_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go index 0c1962682..a56500760 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go @@ -12,7 +12,7 @@ func init() { var info linter.CheckerInfo info.Name = "dupCase" info.Tags = []string{"diagnostic"} - info.Summary = "Detects duplicated case clauses inside switch statements" + info.Summary = "Detects duplicated case clauses inside switch or select statements" info.Before = ` switch x { case ys[0], ys[1], ys[2], ys[0], ys[4]: @@ -35,8 +35,11 @@ type dupCaseChecker struct { } func (c *dupCaseChecker) VisitStmt(stmt ast.Stmt) { - if stmt, ok := stmt.(*ast.SwitchStmt); ok { + switch stmt := stmt.(type) { + case *ast.SwitchStmt: c.checkSwitch(stmt) + case *ast.SelectStmt: + c.checkSelect(stmt) } } @@ -52,6 +55,16 @@ func (c *dupCaseChecker) checkSwitch(stmt *ast.SwitchStmt) { } } +func (c *dupCaseChecker) checkSelect(stmt *ast.SelectStmt) { + c.astSet.Clear() + for i := range stmt.Body.List { + x := stmt.Body.List[i].(*ast.CommClause).Comm + 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/elseif_checker.go b/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go index d017ee6ca..dcc964846 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go @@ -59,7 +59,7 @@ func (c *elseifChecker) VisitStmt(stmt ast.Stmt) { if balanced && c.skipBalanced { return // Configured to skip balanced statements } - if innerIfStmt.Else != nil { + if innerIfStmt.Else != nil || innerIfStmt.Init != nil { return } c.warn(stmt.Else) diff --git a/vendor/github.com/go-critic/go-critic/checkers/embedded_rules.go b/vendor/github.com/go-critic/go-critic/checkers/embedded_rules.go new file mode 100644 index 000000000..8a53ee5e5 --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/checkers/embedded_rules.go @@ -0,0 +1,105 @@ +package checkers + +import ( + "fmt" + "go/ast" + "go/build" + "go/token" + "os" + + "github.com/quasilyte/go-ruleguard/ruleguard" + + "github.com/go-critic/go-critic/checkers/rulesdata" + "github.com/go-critic/go-critic/framework/linter" +) + +//go:generate go run ./rules/precompile.go -rules ./rules/rules.go -o ./rulesdata/rulesdata.go + +func InitEmbeddedRules() { + filename := "rules/rules.go" + + fset := token.NewFileSet() + var groups []ruleguard.GoRuleGroup + + var buildContext *build.Context + + ruleguardDebug := os.Getenv("GOCRITIC_RULEGUARD_DEBUG") != "" + + // First we create an Engine to parse all rules. + // We need it to get the structured info about our rules + // that will be used to generate checkers. + // We introduce an extra scope in hope that rootEngine + // will be garbage-collected after we don't need it. + // LoadedGroups() returns a slice copy and that's all what we need. + { + rootEngine := ruleguard.NewEngine() + rootEngine.InferBuildContext() + buildContext = rootEngine.BuildContext + + loadContext := &ruleguard.LoadContext{ + Fset: fset, + DebugImports: ruleguardDebug, + DebugPrint: func(s string) { + fmt.Println("debug:", s) + }, + } + if err := rootEngine.LoadFromIR(loadContext, filename, rulesdata.PrecompiledRules); err != nil { + panic(fmt.Sprintf("load embedded ruleguard rules: %v", err)) + } + groups = rootEngine.LoadedGroups() + } + + // For every rules group we create a new checker and a separate engine. + // That dedicated ruleguard engine will contain rules only from one group. + for i := range groups { + g := groups[i] + info := &linter.CheckerInfo{ + Name: g.Name, + Summary: g.DocSummary, + Before: g.DocBefore, + After: g.DocAfter, + Note: g.DocNote, + Tags: g.DocTags, + + EmbeddedRuleguard: true, + } + collection.AddChecker(info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { + parseContext := &ruleguard.LoadContext{ + Fset: fset, + GroupFilter: func(gr *ruleguard.GoRuleGroup) bool { + return gr.Name == g.Name + }, + DebugImports: ruleguardDebug, + DebugPrint: func(s string) { + fmt.Println("debug:", s) + }, + } + engine := ruleguard.NewEngine() + engine.BuildContext = buildContext + err := engine.LoadFromIR(parseContext, filename, rulesdata.PrecompiledRules) + if err != nil { + return nil, err + } + c := &embeddedRuleguardChecker{ + ctx: ctx, + engine: engine, + } + return c, nil + }) + } +} + +type embeddedRuleguardChecker struct { + ctx *linter.CheckerContext + engine *ruleguard.Engine +} + +func (c *embeddedRuleguardChecker) WalkFile(f *ast.File) { + runRuleguardEngine(c.ctx, f, c.engine, &ruleguard.RunContext{ + Pkg: c.ctx.Pkg, + Types: c.ctx.TypesInfo, + Sizes: c.ctx.SizesInfo, + Fset: c.ctx.FileSet, + TruncateLen: 100, + }) +} 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 index 2a995b758..ebb8dad45 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go @@ -49,7 +49,7 @@ func (c *emptyFallthroughChecker) VisitStmt(stmt ast.Stmt) { warn = true if prevCaseDefault { c.warnDefault(bs) - } else { + } else if cc.List != nil { c.warn(bs) } } 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 deleted file mode 100644 index 27ccbd2f2..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go +++ /dev/null @@ -1,58 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/typep" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&emptyStringTestChecker{ctx: ctx}), nil - }) -} - -type emptyStringTestChecker struct { - astwalk.WalkHandler - ctx *linter.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.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 deleted file mode 100644 index 13f7fdbe2..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go +++ /dev/null @@ -1,87 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astequal" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&equalFoldChecker{ctx: ctx}), nil - }) -} - -type equalFoldChecker struct { - astwalk.WalkHandler - ctx *linter.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/flagDeref_checker.go b/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go deleted file mode 100644 index 3fe5e52fb..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go +++ /dev/null @@ -1,65 +0,0 @@ -package checkers - -import ( - "go/ast" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - 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), nil - }) -} - -type flagDerefChecker struct { - astwalk.WalkHandler - ctx *linter.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/hugeParam_checker.go b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go index c430431a7..910be180b 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go @@ -5,6 +5,7 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" + "golang.org/x/exp/typeparams" ) func init() { @@ -49,8 +50,11 @@ func (c *hugeParamChecker) checkParams(params []*ast.Field) { for _, p := range params { for _, id := range p.Names { typ := c.ctx.TypeOf(id) - size := c.ctx.SizesInfo.Sizeof(typ) - if size >= c.sizeThreshold { + if _, ok := typ.(*typeparams.TypeParam); ok { + continue + } + size, ok := c.ctx.SizeOf(typ) + if ok && size >= c.sizeThreshold { c.warn(id, size) } } 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 deleted file mode 100644 index 908285c03..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go +++ /dev/null @@ -1,50 +0,0 @@ -package checkers - -import ( - "go/ast" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/typep" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&indexAllocChecker{ctx: ctx}), nil - }) -} - -type indexAllocChecker struct { - astwalk.WalkHandler - ctx *linter.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/internal/astwalk/local_def_visitor.go b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/local_def_visitor.go index bed0f44ab..47de589a4 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/local_def_visitor.go +++ b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/local_def_visitor.go @@ -42,7 +42,7 @@ const ( // Initializing expression is always nil. NameParam NameKind = iota // NameVar is var or ":=" declared name. - // Initizlizing expression may be nil for var-declared names + // Initializing expression may be nil for var-declared names // without explicit initializing expression. NameVar // NameConst is const-declared name. diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/stmt_list_walker.go b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/stmt_list_walker.go index 45c406e7e..403292f66 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/stmt_list_walker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/stmt_list_walker.go @@ -21,11 +21,11 @@ func (w *stmtListWalker) WalkFile(f *ast.File) { ast.Inspect(decl.Body, func(x ast.Node) bool { switch x := x.(type) { case *ast.BlockStmt: - w.visitor.VisitStmtList(x.List) + w.visitor.VisitStmtList(x, x.List) case *ast.CaseClause: - w.visitor.VisitStmtList(x.Body) + w.visitor.VisitStmtList(x, x.Body) case *ast.CommClause: - w.visitor.VisitStmtList(x.Body) + w.visitor.VisitStmtList(x, x.Body) } return !w.visitor.skipChilds() }) diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/type_expr_walker.go b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/type_expr_walker.go index 24c150084..9c198e733 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/type_expr_walker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/type_expr_walker.go @@ -48,6 +48,8 @@ func (w *typeExprWalker) visit(x ast.Expr) bool { func (w *typeExprWalker) walk(x ast.Node) bool { switch x := x.(type) { + case *ast.ChanType: + return w.visit(x) case *ast.ParenExpr: if typep.IsTypeExpr(w.info, x.X) { return w.visit(x) diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/visitor.go b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/visitor.go index 9f973a2b3..e5031a909 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/visitor.go +++ b/vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/visitor.go @@ -36,7 +36,7 @@ type ( // introduced by case clauses and alike. StmtListVisitor interface { walkerEvents - VisitStmtList([]ast.Stmt) + VisitStmtList(ast.Node, []ast.Stmt) } // StmtVisitor visits every statement inside function body. 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 index 3c0a95afc..a6d0ad7c4 100644 --- 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 @@ -7,21 +7,35 @@ import ( ) // FindNode applies pred for root and all it's childs until it returns true. +// If followFunc is defined, it's called before following any node to check whether it needs to be followed. +// followFunc has to return true in order to continuing traversing the node and return false otherwise. // 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 +func FindNode(root ast.Node, followFunc, pred func(ast.Node) bool) ast.Node { + var ( + found ast.Node + preFunc func(*astutil.Cursor) bool + ) + + if followFunc != nil { + preFunc = func(cur *astutil.Cursor) bool { + return followFunc(cur.Node()) } - return true - }) + } + + astutil.Apply(root, + preFunc, + 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 + return FindNode(root, nil, pred) != nil } 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 index e53ca0cc0..0a8e793ee 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go @@ -45,17 +45,24 @@ func (c *nilValReturnChecker) VisitStmt(stmt ast.Stmt) { return } ret, ok := ifStmt.Body.List[0].(*ast.ReturnStmt) - if !ok || len(ret.Results) != 1 { + if !ok { return } expr, ok := ifStmt.Cond.(*ast.BinaryExpr) - cond := ok && - expr.Op == token.EQL && + if !ok { + return + } + xIsNil := 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) + qualifiedName(expr.Y) == "nil" + if !xIsNil { + return + } + for _, res := range ret.Results { + if astequal.Expr(expr.X, res) { + c.warn(ret, expr.X) + break + } } } 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 index 486940452..bed227ac3 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go @@ -3,7 +3,8 @@ package checkers import ( "go/ast" "go/token" - "go/types" + "strings" + "unicode" "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" @@ -13,70 +14,34 @@ import ( func init() { var info linter.CheckerInfo info.Name = "octalLiteral" - info.Tags = []string{"diagnostic", "experimental"} - info.Summary = "Detects octal literals passed to functions" + info.Tags = []string{"style", "experimental", "opinionated"} + info.Summary = "Detects old-style octal literals" info.Before = `foo(02)` - info.After = `foo(2)` + info.After = `foo(0o2)` collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { - c := &octalLiteralChecker{ - ctx: ctx, - octFriendlyPkg: map[string]bool{ - "os": true, - "io/ioutil": true, - }, - } - return astwalk.WalkerForExpr(c), nil + return astwalk.WalkerForExpr(&octalLiteralChecker{ctx: ctx}), nil }) } type octalLiteralChecker struct { astwalk.WalkHandler ctx *linter.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 - } + lit := astcast.ToBasicLit(expr) + if lit.Kind != token.INT { + 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 - } + if !strings.HasPrefix(lit.Value, "0") || len(lit.Value) == 1 { + return } -} - -func (c *octalLiteralChecker) unsign(e ast.Expr) ast.Expr { - u, ok := e.(*ast.UnaryExpr) - if !ok { - return e + if unicode.IsDigit(rune(lit.Value[1])) { + c.warn(lit) } - 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) +func (c *octalLiteralChecker) warn(lit *ast.BasicLit) { + c.ctx.Warn(lit, "use new octal literal style, 0o%s", lit.Value[len("0"):]) } 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 deleted file mode 100644 index ece3fdfdb..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go +++ /dev/null @@ -1,66 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/astequal" - "github.com/go-toolsmith/typep" -) - -func init() { - var info linter.CheckerInfo - info.Name = "offBy1" - info.Tags = []string{"diagnostic"} - 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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&offBy1Checker{ctx: ctx}), nil - }) -} - -type offBy1Checker struct { - astwalk.WalkHandler - ctx *linter.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.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 index 8cdad4eee..c80e6f8bc 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go @@ -5,6 +5,7 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" + "github.com/go-toolsmith/astcopy" "github.com/go-toolsmith/astequal" ) @@ -38,10 +39,12 @@ func (c *paramTypeCombineChecker) VisitFuncDecl(decl *ast.FuncDecl) { } func (c *paramTypeCombineChecker) optimizeFuncType(f *ast.FuncType) *ast.FuncType { - return &ast.FuncType{ - Params: c.optimizeParams(f.Params), - Results: c.optimizeParams(f.Results), - } + optimizedParamFunc := astcopy.FuncType(f) + + optimizedParamFunc.Params = c.optimizeParams(f.Params) + optimizedParamFunc.Results = c.optimizeParams(f.Results) + + return optimizedParamFunc } func (c *paramTypeCombineChecker) optimizeParams(params *ast.FieldList) *ast.FieldList { // To avoid false positives, skip unnamed param lists. 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 index 5615af467..813fff36a 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go @@ -69,7 +69,7 @@ func (c *rangeExprCopyChecker) VisitStmt(stmt ast.Stmt) { if _, ok := tv.Type.(*types.Array); !ok { return } - if size := c.ctx.SizesInfo.Sizeof(tv.Type); size >= c.sizeThreshold { + if size, ok := c.ctx.SizeOf(tv.Type); ok && size >= c.sizeThreshold { c.warn(rng, size) } } 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 index b34aa5c28..eafc549d6 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go @@ -5,6 +5,7 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" + "golang.org/x/exp/typeparams" ) func init() { @@ -65,7 +66,10 @@ func (c *rangeValCopyChecker) VisitStmt(stmt ast.Stmt) { if typ == nil { return } - if size := c.ctx.SizesInfo.Sizeof(typ); size >= c.sizeThreshold { + if _, ok := typ.(*typeparams.TypeParam); ok { + return + } + if size, ok := c.ctx.SizeOf(typ); ok && size >= c.sizeThreshold { c.warn(rng, 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 deleted file mode 100644 index 600aa73d0..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go +++ /dev/null @@ -1,47 +0,0 @@ -package checkers - -import ( - "go/ast" - "strings" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astp" - "golang.org/x/tools/go/ast/astutil" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(®expMustChecker{ctx: ctx}), nil - }) -} - -type regexpMustChecker struct { - astwalk.WalkHandler - ctx *linter.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/regexpSimplify_checker.go b/vendor/github.com/go-critic/go-critic/checkers/regexpSimplify_checker.go index b7dd15948..5b15e05ed 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/regexpSimplify_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/regexpSimplify_checker.go @@ -8,9 +8,10 @@ import ( "strings" "unicode/utf8" + "github.com/quasilyte/regex/syntax" + "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" - "github.com/quasilyte/regex/syntax" ) func init() { @@ -497,9 +498,9 @@ func (c *regexpSimplifyChecker) simplifyCharRange(rng syntax.Expr) string { case 0: return lo case 1: - return fmt.Sprintf("%s%s", lo, hi) + return lo + hi case 2: - return fmt.Sprintf("%s%s%s", lo, string(lo[0]+1), hi) + return lo + string(lo[0]+1) + hi } } diff --git a/vendor/github.com/go-critic/go-critic/checkers/ruleguard_checker.go b/vendor/github.com/go-critic/go-critic/checkers/ruleguard_checker.go index 19e265887..35c6a6449 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/ruleguard_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/ruleguard_checker.go @@ -2,17 +2,19 @@ package checkers import ( "bytes" + "errors" "fmt" "go/ast" "go/token" - "io/ioutil" "log" "os" "path/filepath" + "sort" "strings" - "github.com/go-critic/go-critic/framework/linter" "github.com/quasilyte/go-ruleguard/ruleguard" + + "github.com/go-critic/go-critic/framework/linter" ) func init() { @@ -30,7 +32,23 @@ func init() { }, "failOnError": { Value: false, - Usage: "If true, panic when the gorule files contain a syntax error. If false, log and skip rules that contain an error", + Usage: "deprecated, use failOn param; if set to true, identical to failOn='all', otherwise failOn=''", + }, + "failOn": { + Value: "", + Usage: `Determines the behavior when an error occurs while parsing ruleguard files. +If flag is not set, log error and skip rule files that contain an error. +If flag is set, the value must be a comma-separated list of error conditions. +* 'import': rule refers to a package that cannot be loaded. +* 'dsl': gorule file does not comply with the ruleguard DSL.`, + }, + "enable": { + Value: "", + Usage: "comma-separated list of enabled groups or skip empty to enable everything", + }, + "disable": { + Value: "", + Usage: "comma-separated list of disabled groups or skip empty to enable everything", }, } info.Summary = "Runs user-defined rules using ruleguard linter" @@ -44,6 +62,52 @@ func init() { }) } +// parseErrorHandler is used to determine whether to ignore or fail ruleguard parsing errors. +type parseErrorHandler struct { + // failureConditions is a map of predicates which are evaluated against a ruleguard parsing error. + // If at least one predicate returns true, then an error is returned. + // Otherwise, the ruleguard file is skipped. + failureConditions map[string]func(err error) bool +} + +// failOnParseError returns true if a parseError occurred and that error should be not be ignored. +func (e parseErrorHandler) failOnParseError(parseError error) bool { + for _, p := range e.failureConditions { + if p(parseError) { + return true + } + } + return false +} + +func newErrorHandler(failOnErrorFlag string) (*parseErrorHandler, error) { + h := parseErrorHandler{ + failureConditions: make(map[string]func(err error) bool), + } + var failOnErrorPredicates = map[string]func(error) bool{ + "dsl": func(err error) bool { var e *ruleguard.ImportError; return !errors.As(err, &e) }, + "import": func(err error) bool { var e *ruleguard.ImportError; return errors.As(err, &e) }, + "all": func(err error) bool { return true }, + } + for _, k := range strings.Split(failOnErrorFlag, ",") { + if k == "" { + continue + } + if p, ok := failOnErrorPredicates[k]; ok { + h.failureConditions[k] = p + } else { + // Wrong flag value. + supportedValues := []string{} + for key := range failOnErrorPredicates { + supportedValues = append(supportedValues, key) + } + return nil, fmt.Errorf("ruleguard init error: 'failOnError' flag '%s' is invalid. It must be a comma-separated list and supported values are '%s'", + k, strings.Join(supportedValues, ",")) + } + } + return &h, nil +} + func newRuleguardChecker(info *linter.CheckerInfo, ctx *linter.CheckerContext) (*ruleguardChecker, error) { c := &ruleguardChecker{ ctx: ctx, @@ -53,20 +117,99 @@ func newRuleguardChecker(info *linter.CheckerInfo, ctx *linter.CheckerContext) ( if rulesFlag == "" { return c, nil } - failOnErrorFlag := info.Params.Bool("failOnError") - - // TODO(quasilyte): handle initialization errors better when we make - // a transition to the go/analysis framework. - // - // For now, we log error messages and return a ruleguard checker - // with an empty rules set. + failOn := info.Params.String("failOn") + if failOn == "" { + if info.Params.Bool("failOnError") { + failOn = "all" + } + } + h, err := newErrorHandler(failOn) + if err != nil { + return nil, err + } engine := ruleguard.NewEngine() + engine.InferBuildContext() fset := token.NewFileSet() filePatterns := strings.Split(rulesFlag, ",") - parseContext := &ruleguard.ParseContext{ - Fset: fset, + enabledGroups := make(map[string]bool) + disabledGroups := make(map[string]bool) + enabledTags := make(map[string]bool) + disabledTags := make(map[string]bool) + + for _, g := range strings.Split(info.Params.String("disable"), ",") { + g = strings.TrimSpace(g) + if strings.HasPrefix(g, "#") { + disabledTags[strings.TrimPrefix(g, "#")] = true + continue + } + + disabledGroups[g] = true + } + flagEnable := info.Params.String("enable") + if flagEnable != "" { + for _, g := range strings.Split(flagEnable, ",") { + g = strings.TrimSpace(g) + if strings.HasPrefix(g, "#") { + enabledTags[strings.TrimPrefix(g, "#")] = true + continue + } + + enabledGroups[g] = true + } + } + + if !enabledTags["experimental"] { + disabledTags["experimental"] = true + } + ruleguardDebug := os.Getenv("GOCRITIC_RULEGUARD_DEBUG") != "" + + inEnabledTags := func(g *ruleguard.GoRuleGroup) bool { + for _, t := range g.DocTags { + if enabledTags[t] { + return true + } + } + return false + } + inDisabledTags := func(g *ruleguard.GoRuleGroup) string { + for _, t := range g.DocTags { + if disabledTags[t] { + return t + } + } + return "" + } + + loadContext := &ruleguard.LoadContext{ + Fset: fset, + DebugImports: ruleguardDebug, + DebugPrint: debugPrint, + GroupFilter: func(g *ruleguard.GoRuleGroup) bool { + enabled := flagEnable == "" || enabledGroups[g.Name] || inEnabledTags(g) + whyDisabled := "" + + switch { + case !enabled: + whyDisabled = "not enabled by name or tag (-enable flag)" + case disabledGroups[g.Name]: + whyDisabled = "disabled by name (-disable flag)" + default: + if tag := inDisabledTags(g); tag != "" { + whyDisabled = fmt.Sprintf("disabled by %s tag (-disable flag)", tag) + } + } + + if ruleguardDebug { + if whyDisabled != "" { + debugPrint(fmt.Sprintf("(-) %s is %s", g.Name, whyDisabled)) + } else { + debugPrint(fmt.Sprintf("(+) %s is enabled", g.Name)) + } + } + return whyDisabled == "" + }, } loaded := 0 @@ -77,21 +220,22 @@ func newRuleguardChecker(info *linter.CheckerInfo, ctx *linter.CheckerContext) ( log.Printf("ruleguard init error: %+v", err) continue } + if len(filenames) == 0 { + return nil, fmt.Errorf("ruleguard init error: no file matching '%s'", strings.TrimSpace(filePattern)) + } for _, filename := range filenames { - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { - if failOnErrorFlag { + if h.failOnParseError(err) { return nil, fmt.Errorf("ruleguard init error: %+v", err) } - log.Printf("ruleguard init error: %+v", err) - continue + log.Printf("ruleguard init error, skip %s: %+v", filename, err) } - if err := engine.Load(parseContext, filename, bytes.NewReader(data)); err != nil { - if failOnErrorFlag { + if err := engine.Load(loadContext, filename, bytes.NewReader(data)); err != nil { + if h.failOnParseError(err) { return nil, fmt.Errorf("ruleguard init error: %+v", err) } - log.Printf("ruleguard init error: %+v", err) - continue + log.Printf("ruleguard init error, skip %s: %+v", filename, err) } loaded++ } @@ -115,26 +259,64 @@ func (c *ruleguardChecker) WalkFile(f *ast.File) { return } - ctx := &ruleguard.RunContext{ + runRuleguardEngine(c.ctx, f, c.engine, &ruleguard.RunContext{ Debug: c.debugGroup, DebugPrint: func(s string) { fmt.Fprintln(os.Stderr, s) }, - Pkg: c.ctx.Pkg, - Types: c.ctx.TypesInfo, - Sizes: c.ctx.SizesInfo, - Fset: c.ctx.FileSet, - Report: func(_ ruleguard.GoRuleInfo, n ast.Node, msg string, _ *ruleguard.Suggestion) { - // TODO(quasilyte): investigate whether we should add a rule name as - // a message prefix here. - c.ctx.Warn(n, msg) - }, + Pkg: c.ctx.Pkg, + Types: c.ctx.TypesInfo, + Sizes: c.ctx.SizesInfo, + Fset: c.ctx.FileSet, + TruncateLen: 100, + }) +} + +func runRuleguardEngine(ctx *linter.CheckerContext, f *ast.File, e *ruleguard.Engine, runCtx *ruleguard.RunContext) { + type ruleguardReport struct { + node ast.Node + message string + fix linter.QuickFix + } + var reports []ruleguardReport + + runCtx.Report = func(data *ruleguard.ReportData) { + // TODO(quasilyte): investigate whether we should add a rule name as + // a message prefix here. + r := ruleguardReport{ + node: data.Node, + message: data.Message, + } + fix := data.Suggestion + if fix != nil { + r.fix = linter.QuickFix{ + From: fix.From, + To: fix.To, + Replacement: fix.Replacement, + } + } + reports = append(reports, r) } - if err := c.engine.Run(ctx, f); err != nil { + if err := e.Run(runCtx, f); err != nil { // Normally this should never happen, but since // we don't have a better mechanism to report errors, // emit a warning. - c.ctx.Warn(f, "execution error: %v", err) + ctx.Warn(f, "execution error: %v", err) } + + sort.Slice(reports, func(i, j int) bool { + return reports[i].message < reports[j].message + }) + for _, report := range reports { + if report.fix.Replacement != nil { + ctx.WarnFixable(report.node, report.fix, "%s", report.message) + } else { + ctx.Warn(report.node, "%s", report.message) + } + } +} + +func debugPrint(s string) { + fmt.Println("debug:", s) } diff --git a/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go b/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go new file mode 100644 index 000000000..fd63c9b14 --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go @@ -0,0 +1,2367 @@ +// Code generated by "precompile.go". DO NOT EDIT. + +package rulesdata + +import "github.com/quasilyte/go-ruleguard/ruleguard/ir" + +var PrecompiledRules = &ir.File{ + PkgPath: "gorules", + CustomDecls: []string{}, + BundleImports: []ir.BundleImport{}, + RuleGroups: []ir.RuleGroup{ + { + Line: 11, + Name: "redundantSprint", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects redundant fmt.Sprint calls", + DocBefore: "fmt.Sprint(x)", + DocAfter: "x.String()", + Rules: []ir.Rule{ + { + Line: 12, + SyntaxPatterns: []ir.PatternString{ + {Line: 12, Value: "fmt.Sprint($x)"}, + {Line: 12, Value: "fmt.Sprintf(\"%s\", $x)"}, + {Line: 12, Value: "fmt.Sprintf(\"%v\", $x)"}, + }, + ReportTemplate: "use $x.String() instead", + SuggestTemplate: "$x.String()", + WhereExpr: ir.FilterExpr{ + Line: 13, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"x\"].Type.Implements(`fmt.Stringer`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 13, Op: ir.FilterStringOp, Src: "`fmt.Stringer`", Value: "fmt.Stringer"}}, + }, + }, + { + Line: 17, + SyntaxPatterns: []ir.PatternString{ + {Line: 17, Value: "fmt.Sprint($x)"}, + {Line: 17, Value: "fmt.Sprintf(\"%s\", $x)"}, + {Line: 17, Value: "fmt.Sprintf(\"%v\", $x)"}, + }, + ReportTemplate: "use $x.Error() instead", + SuggestTemplate: "$x.Error()", + WhereExpr: ir.FilterExpr{ + Line: 18, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"x\"].Type.Implements(`error`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 18, Op: ir.FilterStringOp, Src: "`error`", Value: "error"}}, + }, + }, + { + Line: 22, + SyntaxPatterns: []ir.PatternString{ + {Line: 22, Value: "fmt.Sprint($x)"}, + {Line: 22, Value: "fmt.Sprintf(\"%s\", $x)"}, + {Line: 22, Value: "fmt.Sprintf(\"%v\", $x)"}, + }, + ReportTemplate: "$x is already string", + SuggestTemplate: "$x", + WhereExpr: ir.FilterExpr{ + Line: 23, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`string`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 23, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + }, + }, + { + Line: 32, + Name: "deferUnlambda", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects deferred function literals that can be simplified", + DocBefore: "defer func() { f() }()", + DocAfter: "defer f()", + Rules: []ir.Rule{ + { + Line: 33, + SyntaxPatterns: []ir.PatternString{{Line: 33, Value: "defer func() { $f($*args) }()"}}, + ReportTemplate: "can rewrite as `defer $f($args)`", + WhereExpr: ir.FilterExpr{ + Line: 34, + Op: ir.FilterAndOp, + Src: "m[\"f\"].Node.Is(`Ident`) && m[\"f\"].Text != \"panic\" && m[\"f\"].Text != \"recover\" && m[\"args\"].Const", + Args: []ir.FilterExpr{ + { + Line: 34, + Op: ir.FilterAndOp, + Src: "m[\"f\"].Node.Is(`Ident`) && m[\"f\"].Text != \"panic\" && m[\"f\"].Text != \"recover\"", + Args: []ir.FilterExpr{ + { + Line: 34, + Op: ir.FilterAndOp, + Src: "m[\"f\"].Node.Is(`Ident`) && m[\"f\"].Text != \"panic\"", + Args: []ir.FilterExpr{ + { + Line: 34, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"f\"].Node.Is(`Ident`)", + Value: "f", + Args: []ir.FilterExpr{{Line: 34, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, + }, + { + Line: 34, + Op: ir.FilterNeqOp, + Src: "m[\"f\"].Text != \"panic\"", + Args: []ir.FilterExpr{ + {Line: 34, Op: ir.FilterVarTextOp, Src: "m[\"f\"].Text", Value: "f"}, + {Line: 34, Op: ir.FilterStringOp, Src: "\"panic\"", Value: "panic"}, + }, + }, + }, + }, + { + Line: 34, + Op: ir.FilterNeqOp, + Src: "m[\"f\"].Text != \"recover\"", + Args: []ir.FilterExpr{ + {Line: 34, Op: ir.FilterVarTextOp, Src: "m[\"f\"].Text", Value: "f"}, + {Line: 34, Op: ir.FilterStringOp, Src: "\"recover\"", Value: "recover"}, + }, + }, + }, + }, + { + Line: 34, + Op: ir.FilterVarConstOp, + Src: "m[\"args\"].Const", + Value: "args", + }, + }, + }, + }, + { + Line: 37, + SyntaxPatterns: []ir.PatternString{{Line: 37, Value: "defer func() { $pkg.$f($*args) }()"}}, + ReportTemplate: "can rewrite as `defer $pkg.$f($args)`", + WhereExpr: ir.FilterExpr{ + Line: 38, + Op: ir.FilterAndOp, + Src: "m[\"f\"].Node.Is(`Ident`) && m[\"args\"].Const && m[\"pkg\"].Object.Is(`PkgName`)", + Args: []ir.FilterExpr{ + { + Line: 38, + Op: ir.FilterAndOp, + Src: "m[\"f\"].Node.Is(`Ident`) && m[\"args\"].Const", + Args: []ir.FilterExpr{ + { + Line: 38, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"f\"].Node.Is(`Ident`)", + Value: "f", + Args: []ir.FilterExpr{{Line: 38, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, + }, + { + Line: 38, + Op: ir.FilterVarConstOp, + Src: "m[\"args\"].Const", + Value: "args", + }, + }, + }, + { + Line: 38, + Op: ir.FilterVarObjectIsOp, + Src: "m[\"pkg\"].Object.Is(`PkgName`)", + Value: "pkg", + Args: []ir.FilterExpr{{Line: 38, Op: ir.FilterStringOp, Src: "`PkgName`", Value: "PkgName"}}, + }, + }, + }, + }, + }, + }, + { + Line: 46, + Name: "ioutilDeprecated", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects deprecated io/ioutil package usages", + DocBefore: "ioutil.ReadAll(r)", + DocAfter: "io.ReadAll(r)", + Rules: []ir.Rule{ + { + Line: 47, + SyntaxPatterns: []ir.PatternString{{Line: 47, Value: "ioutil.ReadAll($_)"}}, + ReportTemplate: "ioutil.ReadAll is deprecated, use io.ReadAll instead", + WhereExpr: ir.FilterExpr{ + Line: 48, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.16\")", + Value: "1.16", + }, + }, + { + Line: 51, + SyntaxPatterns: []ir.PatternString{{Line: 51, Value: "ioutil.ReadFile($_)"}}, + ReportTemplate: "ioutil.ReadFile is deprecated, use os.ReadFile instead", + WhereExpr: ir.FilterExpr{ + Line: 52, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.16\")", + Value: "1.16", + }, + }, + { + Line: 55, + SyntaxPatterns: []ir.PatternString{{Line: 55, Value: "ioutil.WriteFile($_, $_, $_)"}}, + ReportTemplate: "ioutil.WriteFile is deprecated, use os.WriteFile instead", + WhereExpr: ir.FilterExpr{ + Line: 56, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.16\")", + Value: "1.16", + }, + }, + { + Line: 59, + SyntaxPatterns: []ir.PatternString{{Line: 59, Value: "ioutil.ReadDir($_)"}}, + ReportTemplate: "ioutil.ReadDir is deprecated, use os.ReadDir instead", + WhereExpr: ir.FilterExpr{ + Line: 60, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.16\")", + Value: "1.16", + }, + }, + { + Line: 63, + SyntaxPatterns: []ir.PatternString{{Line: 63, Value: "ioutil.NopCloser($_)"}}, + ReportTemplate: "ioutil.NopCloser is deprecated, use io.NopCloser instead", + WhereExpr: ir.FilterExpr{ + Line: 64, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.16\")", + Value: "1.16", + }, + }, + { + Line: 67, + SyntaxPatterns: []ir.PatternString{{Line: 67, Value: "ioutil.Discard"}}, + ReportTemplate: "ioutil.Discard is deprecated, use io.Discard instead", + WhereExpr: ir.FilterExpr{ + Line: 68, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.16\")", + Value: "1.16", + }, + }, + }, + }, + { + Line: 76, + Name: "badLock", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects suspicious mutex lock/unlock operations", + DocBefore: "mu.Lock(); mu.Unlock()", + DocAfter: "mu.Lock(); defer mu.Unlock()", + Rules: []ir.Rule{ + { + Line: 80, + SyntaxPatterns: []ir.PatternString{{Line: 80, Value: "$mu1.Lock(); $mu2.Unlock()"}}, + ReportTemplate: "defer is missing, mutex is unlocked immediately", + WhereExpr: ir.FilterExpr{ + Line: 81, + Op: ir.FilterEqOp, + Src: "m[\"mu1\"].Text == m[\"mu2\"].Text", + Args: []ir.FilterExpr{ + {Line: 81, Op: ir.FilterVarTextOp, Src: "m[\"mu1\"].Text", Value: "mu1"}, + {Line: 81, Op: ir.FilterVarTextOp, Src: "m[\"mu2\"].Text", Value: "mu2"}, + }, + }, + LocationVar: "mu2", + }, + { + Line: 85, + SyntaxPatterns: []ir.PatternString{{Line: 85, Value: "$mu1.RLock(); $mu2.RUnlock()"}}, + ReportTemplate: "defer is missing, mutex is unlocked immediately", + WhereExpr: ir.FilterExpr{ + Line: 86, + Op: ir.FilterEqOp, + Src: "m[\"mu1\"].Text == m[\"mu2\"].Text", + Args: []ir.FilterExpr{ + {Line: 86, Op: ir.FilterVarTextOp, Src: "m[\"mu1\"].Text", Value: "mu1"}, + {Line: 86, Op: ir.FilterVarTextOp, Src: "m[\"mu2\"].Text", Value: "mu2"}, + }, + }, + LocationVar: "mu2", + }, + { + Line: 91, + SyntaxPatterns: []ir.PatternString{{Line: 91, Value: "$mu1.Lock(); defer $mu2.RUnlock()"}}, + ReportTemplate: "suspicious unlock, maybe Unlock was intended?", + WhereExpr: ir.FilterExpr{ + Line: 92, + Op: ir.FilterEqOp, + Src: "m[\"mu1\"].Text == m[\"mu2\"].Text", + Args: []ir.FilterExpr{ + {Line: 92, Op: ir.FilterVarTextOp, Src: "m[\"mu1\"].Text", Value: "mu1"}, + {Line: 92, Op: ir.FilterVarTextOp, Src: "m[\"mu2\"].Text", Value: "mu2"}, + }, + }, + LocationVar: "mu2", + }, + { + Line: 96, + SyntaxPatterns: []ir.PatternString{{Line: 96, Value: "$mu1.RLock(); defer $mu2.Unlock()"}}, + ReportTemplate: "suspicious unlock, maybe RUnlock was intended?", + WhereExpr: ir.FilterExpr{ + Line: 97, + Op: ir.FilterEqOp, + Src: "m[\"mu1\"].Text == m[\"mu2\"].Text", + Args: []ir.FilterExpr{ + {Line: 97, Op: ir.FilterVarTextOp, Src: "m[\"mu1\"].Text", Value: "mu1"}, + {Line: 97, Op: ir.FilterVarTextOp, Src: "m[\"mu2\"].Text", Value: "mu2"}, + }, + }, + LocationVar: "mu2", + }, + { + Line: 102, + SyntaxPatterns: []ir.PatternString{{Line: 102, Value: "$mu1.Lock(); defer $mu2.Lock()"}}, + ReportTemplate: "maybe defer $mu1.Unlock() was intended?", + WhereExpr: ir.FilterExpr{ + Line: 103, + Op: ir.FilterEqOp, + Src: "m[\"mu1\"].Text == m[\"mu2\"].Text", + Args: []ir.FilterExpr{ + {Line: 103, Op: ir.FilterVarTextOp, Src: "m[\"mu1\"].Text", Value: "mu1"}, + {Line: 103, Op: ir.FilterVarTextOp, Src: "m[\"mu2\"].Text", Value: "mu2"}, + }, + }, + LocationVar: "mu2", + }, + { + Line: 107, + SyntaxPatterns: []ir.PatternString{{Line: 107, Value: "$mu1.RLock(); defer $mu2.RLock()"}}, + ReportTemplate: "maybe defer $mu1.RUnlock() was intended?", + WhereExpr: ir.FilterExpr{ + Line: 108, + Op: ir.FilterEqOp, + Src: "m[\"mu1\"].Text == m[\"mu2\"].Text", + Args: []ir.FilterExpr{ + {Line: 108, Op: ir.FilterVarTextOp, Src: "m[\"mu1\"].Text", Value: "mu1"}, + {Line: 108, Op: ir.FilterVarTextOp, Src: "m[\"mu2\"].Text", Value: "mu2"}, + }, + }, + LocationVar: "mu2", + }, + }, + }, + { + Line: 117, + Name: "httpNoBody", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects nil usages in http.NewRequest calls, suggesting http.NoBody as an alternative", + DocBefore: "http.NewRequest(\"GET\", url, nil)", + DocAfter: "http.NewRequest(\"GET\", url, http.NoBody)", + Rules: []ir.Rule{ + { + Line: 118, + SyntaxPatterns: []ir.PatternString{{Line: 118, Value: "http.NewRequest($method, $url, $nil)"}}, + ReportTemplate: "http.NoBody should be preferred to the nil request body", + SuggestTemplate: "http.NewRequest($method, $url, http.NoBody)", + WhereExpr: ir.FilterExpr{ + Line: 119, + Op: ir.FilterEqOp, + Src: "m[\"nil\"].Text == \"nil\"", + Args: []ir.FilterExpr{ + {Line: 119, Op: ir.FilterVarTextOp, Src: "m[\"nil\"].Text", Value: "nil"}, + {Line: 119, Op: ir.FilterStringOp, Src: "\"nil\"", Value: "nil"}, + }, + }, + LocationVar: "nil", + }, + { + Line: 124, + SyntaxPatterns: []ir.PatternString{{Line: 124, Value: "http.NewRequestWithContext($ctx, $method, $url, $nil)"}}, + ReportTemplate: "http.NoBody should be preferred to the nil request body", + SuggestTemplate: "http.NewRequestWithContext($ctx, $method, $url, http.NoBody)", + WhereExpr: ir.FilterExpr{ + Line: 125, + Op: ir.FilterEqOp, + Src: "m[\"nil\"].Text == \"nil\"", + Args: []ir.FilterExpr{ + {Line: 125, Op: ir.FilterVarTextOp, Src: "m[\"nil\"].Text", Value: "nil"}, + {Line: 125, Op: ir.FilterStringOp, Src: "\"nil\"", Value: "nil"}, + }, + }, + LocationVar: "nil", + }, + }, + }, + { + Line: 136, + Name: "preferDecodeRune", + MatcherName: "m", + DocTags: []string{"performance", "experimental"}, + DocSummary: "Detects expressions like []rune(s)[0] that may cause unwanted rune slice allocation", + DocBefore: "r := []rune(s)[0]", + DocAfter: "r, _ := utf8.DecodeRuneInString(s)", + DocNote: "See Go issue for details: https://github.com/golang/go/issues/45260", + Rules: []ir.Rule{{ + Line: 137, + SyntaxPatterns: []ir.PatternString{{Line: 137, Value: "[]rune($s)[0]"}}, + ReportTemplate: "consider replacing $$ with utf8.DecodeRuneInString($s)", + WhereExpr: ir.FilterExpr{ + Line: 138, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 138, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }}, + }, + { + Line: 146, + Name: "sloppyLen", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects usage of `len` when result is obvious or doesn't make sense", + DocBefore: "len(arr) <= 0", + DocAfter: "len(arr) == 0", + Rules: []ir.Rule{ + { + Line: 147, + SyntaxPatterns: []ir.PatternString{{Line: 147, Value: "len($_) >= 0"}}, + ReportTemplate: "$$ is always true", + }, + { + Line: 148, + SyntaxPatterns: []ir.PatternString{{Line: 148, Value: "len($_) < 0"}}, + ReportTemplate: "$$ is always false", + }, + { + Line: 149, + SyntaxPatterns: []ir.PatternString{{Line: 149, Value: "len($x) <= 0"}}, + ReportTemplate: "$$ can be len($x) == 0", + }, + }, + }, + { + Line: 156, + Name: "valSwap", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects value swapping code that are not using parallel assignment", + DocBefore: "*tmp = *x; *x = *y; *y = *tmp", + DocAfter: "*x, *y = *y, *x", + Rules: []ir.Rule{{ + Line: 157, + SyntaxPatterns: []ir.PatternString{{Line: 157, Value: "$tmp := $y; $y = $x; $x = $tmp"}}, + ReportTemplate: "can re-write as `$y, $x = $x, $y`", + }}, + }, + { + Line: 165, + Name: "switchTrue", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects switch-over-bool statements that use explicit `true` tag value", + DocBefore: "switch true {...}", + DocAfter: "switch {...}", + Rules: []ir.Rule{ + { + Line: 166, + SyntaxPatterns: []ir.PatternString{{Line: 166, Value: "switch true { $*_ }"}}, + ReportTemplate: "replace 'switch true {}' with 'switch {}'", + }, + { + Line: 168, + SyntaxPatterns: []ir.PatternString{{Line: 168, Value: "switch $x; true { $*_ }"}}, + ReportTemplate: "replace 'switch $x; true {}' with 'switch $x; {}'", + }, + }, + }, + { + Line: 176, + Name: "flagDeref", + MatcherName: "m", + DocTags: []string{"diagnostic"}, + DocSummary: "Detects immediate dereferencing of `flag` package pointers", + DocBefore: "b := *flag.Bool(\"b\", false, \"b docs\")", + DocAfter: "var b bool; flag.BoolVar(&b, \"b\", false, \"b docs\")", + Rules: []ir.Rule{ + { + Line: 177, + SyntaxPatterns: []ir.PatternString{{Line: 177, Value: "*flag.Bool($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.BoolVar", + }, + { + Line: 178, + SyntaxPatterns: []ir.PatternString{{Line: 178, Value: "*flag.Duration($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.DurationVar", + }, + { + Line: 179, + SyntaxPatterns: []ir.PatternString{{Line: 179, Value: "*flag.Float64($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.Float64Var", + }, + { + Line: 180, + SyntaxPatterns: []ir.PatternString{{Line: 180, Value: "*flag.Int($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.IntVar", + }, + { + Line: 181, + SyntaxPatterns: []ir.PatternString{{Line: 181, Value: "*flag.Int64($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.Int64Var", + }, + { + Line: 182, + SyntaxPatterns: []ir.PatternString{{Line: 182, Value: "*flag.String($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.StringVar", + }, + { + Line: 183, + SyntaxPatterns: []ir.PatternString{{Line: 183, Value: "*flag.Uint($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.UintVar", + }, + { + Line: 184, + SyntaxPatterns: []ir.PatternString{{Line: 184, Value: "*flag.Uint64($*_)"}}, + ReportTemplate: "immediate deref in $$ is most likely an error; consider using flag.Uint64Var", + }, + }, + }, + { + Line: 191, + Name: "emptyStringTest", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects empty string checks that can be written more idiomatically", + DocBefore: "len(s) == 0", + DocAfter: "s == \"\"", + Rules: []ir.Rule{ + { + Line: 192, + SyntaxPatterns: []ir.PatternString{{Line: 192, Value: "len($s) != 0"}}, + ReportTemplate: "replace `$$` with `$s != \"\"`", + WhereExpr: ir.FilterExpr{ + Line: 193, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 193, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + { + Line: 196, + SyntaxPatterns: []ir.PatternString{{Line: 196, Value: "len($s) == 0"}}, + ReportTemplate: "replace `$$` with `$s == \"\"`", + WhereExpr: ir.FilterExpr{ + Line: 197, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 197, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + }, + }, + { + Line: 205, + Name: "stringXbytes", + MatcherName: "m", + DocTags: []string{"performance"}, + DocSummary: "Detects redundant conversions between string and []byte", + DocBefore: "copy(b, []byte(s))", + DocAfter: "copy(b, s)", + Rules: []ir.Rule{ + { + Line: 206, + SyntaxPatterns: []ir.PatternString{{Line: 206, Value: "copy($_, []byte($s))"}}, + ReportTemplate: "can simplify `[]byte($s)` to `$s`", + }, + { + Line: 208, + SyntaxPatterns: []ir.PatternString{{Line: 208, Value: "string($b) == \"\""}}, + ReportTemplate: "suggestion: len($b) == 0", + SuggestTemplate: "len($b) == 0", + WhereExpr: ir.FilterExpr{ + Line: 208, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"b\"].Type.Is(`[]byte`)", + Value: "b", + Args: []ir.FilterExpr{{Line: 208, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + }, + { + Line: 209, + SyntaxPatterns: []ir.PatternString{{Line: 209, Value: "string($b) != \"\""}}, + ReportTemplate: "suggestion: len($b) != 0", + SuggestTemplate: "len($b) != 0", + WhereExpr: ir.FilterExpr{ + Line: 209, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"b\"].Type.Is(`[]byte`)", + Value: "b", + Args: []ir.FilterExpr{{Line: 209, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + }, + { + Line: 211, + SyntaxPatterns: []ir.PatternString{{Line: 211, Value: "len(string($b))"}}, + ReportTemplate: "suggestion: len($b)", + SuggestTemplate: "len($b)", + WhereExpr: ir.FilterExpr{ + Line: 211, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"b\"].Type.Is(`[]byte`)", + Value: "b", + Args: []ir.FilterExpr{{Line: 211, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + }, + { + Line: 213, + SyntaxPatterns: []ir.PatternString{{Line: 213, Value: "string($x) == string($y)"}}, + ReportTemplate: "suggestion: bytes.Equal($x, $y)", + SuggestTemplate: "bytes.Equal($x, $y)", + WhereExpr: ir.FilterExpr{ + Line: 214, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Type.Is(`[]byte`) && m[\"y\"].Type.Is(`[]byte`)", + Args: []ir.FilterExpr{ + { + Line: 214, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`[]byte`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 214, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + { + Line: 214, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"y\"].Type.Is(`[]byte`)", + Value: "y", + Args: []ir.FilterExpr{{Line: 214, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + }, + }, + }, + { + Line: 217, + SyntaxPatterns: []ir.PatternString{{Line: 217, Value: "string($x) != string($y)"}}, + ReportTemplate: "suggestion: !bytes.Equal($x, $y)", + SuggestTemplate: "!bytes.Equal($x, $y)", + WhereExpr: ir.FilterExpr{ + Line: 218, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Type.Is(`[]byte`) && m[\"y\"].Type.Is(`[]byte`)", + Args: []ir.FilterExpr{ + { + Line: 218, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`[]byte`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 218, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + { + Line: 218, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"y\"].Type.Is(`[]byte`)", + Value: "y", + Args: []ir.FilterExpr{{Line: 218, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + }, + }, + }, + }, + { + Line: 221, + SyntaxPatterns: []ir.PatternString{{Line: 221, Value: "$re.Match([]byte($s))"}}, + ReportTemplate: "suggestion: $re.MatchString($s)", + SuggestTemplate: "$re.MatchString($s)", + WhereExpr: ir.FilterExpr{ + Line: 222, + Op: ir.FilterAndOp, + Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", + Args: []ir.FilterExpr{ + { + Line: 222, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", + Value: "re", + Args: []ir.FilterExpr{{Line: 222, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + }, + { + Line: 222, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 222, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + }, + }, + { + Line: 225, + SyntaxPatterns: []ir.PatternString{{Line: 225, Value: "$re.FindIndex([]byte($s))"}}, + ReportTemplate: "suggestion: $re.FindStringIndex($s)", + SuggestTemplate: "$re.FindStringIndex($s)", + WhereExpr: ir.FilterExpr{ + Line: 226, + Op: ir.FilterAndOp, + Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", + Args: []ir.FilterExpr{ + { + Line: 226, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", + Value: "re", + Args: []ir.FilterExpr{{Line: 226, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + }, + { + Line: 226, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 226, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + }, + }, + { + Line: 229, + SyntaxPatterns: []ir.PatternString{{Line: 229, Value: "$re.FindAllIndex([]byte($s), $n)"}}, + ReportTemplate: "suggestion: $re.FindAllStringIndex($s, $n)", + SuggestTemplate: "$re.FindAllStringIndex($s, $n)", + WhereExpr: ir.FilterExpr{ + Line: 230, + Op: ir.FilterAndOp, + Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", + Args: []ir.FilterExpr{ + { + Line: 230, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", + Value: "re", + Args: []ir.FilterExpr{{Line: 230, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + }, + { + Line: 230, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 230, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + }, + }, + }, + }, + { + Line: 239, + Name: "indexAlloc", + MatcherName: "m", + DocTags: []string{"performance"}, + DocSummary: "Detects strings.Index calls that may cause unwanted allocs", + DocBefore: "strings.Index(string(x), y)", + DocAfter: "bytes.Index(x, []byte(y))", + DocNote: "See Go issue for details: https://github.com/golang/go/issues/25864", + Rules: []ir.Rule{{ + Line: 240, + SyntaxPatterns: []ir.PatternString{{Line: 240, Value: "strings.Index(string($x), $y)"}}, + ReportTemplate: "consider replacing $$ with bytes.Index($x, []byte($y))", + WhereExpr: ir.FilterExpr{ + Line: 241, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure", + Args: []ir.FilterExpr{ + {Line: 241, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 241, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + }, + }, + }}, + }, + { + Line: 249, + Name: "wrapperFunc", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects function calls that can be replaced with convenience wrappers", + DocBefore: "wg.Add(-1)", + DocAfter: "wg.Done()", + Rules: []ir.Rule{ + { + Line: 250, + SyntaxPatterns: []ir.PatternString{{Line: 250, Value: "$wg.Add(-1)"}}, + ReportTemplate: "use WaitGroup.Done method in `$$`", + WhereExpr: ir.FilterExpr{ + Line: 251, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"wg\"].Type.Is(`sync.WaitGroup`)", + Value: "wg", + Args: []ir.FilterExpr{{Line: 251, Op: ir.FilterStringOp, Src: "`sync.WaitGroup`", Value: "sync.WaitGroup"}}, + }, + }, + { + Line: 254, + SyntaxPatterns: []ir.PatternString{{Line: 254, Value: "$buf.Truncate(0)"}}, + ReportTemplate: "use Buffer.Reset method in `$$`", + WhereExpr: ir.FilterExpr{ + Line: 255, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"buf\"].Type.Is(`bytes.Buffer`)", + Value: "buf", + Args: []ir.FilterExpr{{Line: 255, Op: ir.FilterStringOp, Src: "`bytes.Buffer`", Value: "bytes.Buffer"}}, + }, + }, + { + Line: 258, + SyntaxPatterns: []ir.PatternString{{Line: 258, Value: "http.HandlerFunc(http.NotFound)"}}, + ReportTemplate: "use http.NotFoundHandler method in `$$`", + }, + { + Line: 260, + SyntaxPatterns: []ir.PatternString{{Line: 260, Value: "strings.SplitN($_, $_, -1)"}}, + ReportTemplate: "use strings.Split method in `$$`", + }, + { + Line: 261, + SyntaxPatterns: []ir.PatternString{{Line: 261, Value: "strings.Replace($_, $_, $_, -1)"}}, + ReportTemplate: "use strings.ReplaceAll method in `$$`", + }, + { + Line: 262, + SyntaxPatterns: []ir.PatternString{{Line: 262, Value: "strings.Map(unicode.ToTitle, $_)"}}, + ReportTemplate: "use strings.ToTitle method in `$$`", + }, + { + Line: 264, + SyntaxPatterns: []ir.PatternString{{Line: 264, Value: "bytes.SplitN(b, []byte(\".\"), -1)"}}, + ReportTemplate: "use bytes.Split method in `$$`", + }, + { + Line: 265, + SyntaxPatterns: []ir.PatternString{{Line: 265, Value: "bytes.Replace($_, $_, $_, -1)"}}, + ReportTemplate: "use bytes.ReplaceAll method in `$$`", + }, + { + Line: 266, + SyntaxPatterns: []ir.PatternString{{Line: 266, Value: "bytes.Map(unicode.ToUpper, $_)"}}, + ReportTemplate: "use bytes.ToUpper method in `$$`", + }, + { + Line: 267, + SyntaxPatterns: []ir.PatternString{{Line: 267, Value: "bytes.Map(unicode.ToLower, $_)"}}, + ReportTemplate: "use bytes.ToLower method in `$$`", + }, + { + Line: 268, + SyntaxPatterns: []ir.PatternString{{Line: 268, Value: "bytes.Map(unicode.ToTitle, $_)"}}, + ReportTemplate: "use bytes.ToTitle method in `$$`", + }, + { + Line: 270, + SyntaxPatterns: []ir.PatternString{{Line: 270, Value: "draw.DrawMask($_, $_, $_, $_, nil, image.Point{}, $_)"}}, + ReportTemplate: "use draw.Draw method in `$$`", + }, + }, + }, + { + Line: 278, + Name: "regexpMust", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects `regexp.Compile*` that can be replaced with `regexp.MustCompile*`", + DocBefore: "re, _ := regexp.Compile(\"const pattern\")", + DocAfter: "re := regexp.MustCompile(\"const pattern\")", + Rules: []ir.Rule{ + { + Line: 279, + SyntaxPatterns: []ir.PatternString{{Line: 279, Value: "regexp.Compile($pat)"}}, + ReportTemplate: "for const patterns like $pat, use regexp.MustCompile", + WhereExpr: ir.FilterExpr{ + Line: 280, + Op: ir.FilterVarConstOp, + Src: "m[\"pat\"].Const", + Value: "pat", + }, + }, + { + Line: 283, + SyntaxPatterns: []ir.PatternString{{Line: 283, Value: "regexp.CompilePOSIX($pat)"}}, + ReportTemplate: "for const patterns like $pat, use regexp.MustCompilePOSIX", + WhereExpr: ir.FilterExpr{ + Line: 284, + Op: ir.FilterVarConstOp, + Src: "m[\"pat\"].Const", + Value: "pat", + }, + }, + }, + }, + { + Line: 292, + Name: "badCall", + MatcherName: "m", + DocTags: []string{"diagnostic"}, + DocSummary: "Detects suspicious function calls", + DocBefore: "strings.Replace(s, from, to, 0)", + DocAfter: "strings.Replace(s, from, to, -1)", + Rules: []ir.Rule{ + { + Line: 293, + SyntaxPatterns: []ir.PatternString{{Line: 293, Value: "strings.Replace($_, $_, $_, $zero)"}}, + ReportTemplate: "suspicious arg 0, probably meant -1", + WhereExpr: ir.FilterExpr{ + Line: 294, + Op: ir.FilterEqOp, + Src: "m[\"zero\"].Value.Int() == 0", + Args: []ir.FilterExpr{ + { + Line: 294, + Op: ir.FilterVarValueIntOp, + Src: "m[\"zero\"].Value.Int()", + Value: "zero", + }, + { + Line: 294, + Op: ir.FilterIntOp, + Src: "0", + Value: int64(0), + }, + }, + }, + LocationVar: "zero", + }, + { + Line: 296, + SyntaxPatterns: []ir.PatternString{{Line: 296, Value: "bytes.Replace($_, $_, $_, $zero)"}}, + ReportTemplate: "suspicious arg 0, probably meant -1", + WhereExpr: ir.FilterExpr{ + Line: 297, + Op: ir.FilterEqOp, + Src: "m[\"zero\"].Value.Int() == 0", + Args: []ir.FilterExpr{ + { + Line: 297, + Op: ir.FilterVarValueIntOp, + Src: "m[\"zero\"].Value.Int()", + Value: "zero", + }, + { + Line: 297, + Op: ir.FilterIntOp, + Src: "0", + Value: int64(0), + }, + }, + }, + LocationVar: "zero", + }, + { + Line: 300, + SyntaxPatterns: []ir.PatternString{{Line: 300, Value: "strings.SplitN($_, $_, $zero)"}}, + ReportTemplate: "suspicious arg 0, probably meant -1", + WhereExpr: ir.FilterExpr{ + Line: 301, + Op: ir.FilterEqOp, + Src: "m[\"zero\"].Value.Int() == 0", + Args: []ir.FilterExpr{ + { + Line: 301, + Op: ir.FilterVarValueIntOp, + Src: "m[\"zero\"].Value.Int()", + Value: "zero", + }, + { + Line: 301, + Op: ir.FilterIntOp, + Src: "0", + Value: int64(0), + }, + }, + }, + LocationVar: "zero", + }, + { + Line: 303, + SyntaxPatterns: []ir.PatternString{{Line: 303, Value: "bytes.SplitN($_, $_, $zero)"}}, + ReportTemplate: "suspicious arg 0, probably meant -1", + WhereExpr: ir.FilterExpr{ + Line: 304, + Op: ir.FilterEqOp, + Src: "m[\"zero\"].Value.Int() == 0", + Args: []ir.FilterExpr{ + { + Line: 304, + Op: ir.FilterVarValueIntOp, + Src: "m[\"zero\"].Value.Int()", + Value: "zero", + }, + { + Line: 304, + Op: ir.FilterIntOp, + Src: "0", + Value: int64(0), + }, + }, + }, + LocationVar: "zero", + }, + { + Line: 307, + SyntaxPatterns: []ir.PatternString{{Line: 307, Value: "append($_)"}}, + ReportTemplate: "no-op append call, probably missing arguments", + }, + { + Line: 309, + SyntaxPatterns: []ir.PatternString{{Line: 309, Value: "filepath.Join($_)"}}, + ReportTemplate: "suspicious Join on 1 argument", + }, + }, + }, + { + Line: 316, + Name: "assignOp", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects assignments that can be simplified by using assignment operators", + DocBefore: "x = x * 2", + DocAfter: "x *= 2", + Rules: []ir.Rule{ + { + Line: 317, + SyntaxPatterns: []ir.PatternString{{Line: 317, Value: "$x = $x + 1"}}, + ReportTemplate: "replace `$$` with `$x++`", + WhereExpr: ir.FilterExpr{Line: 317, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 318, + SyntaxPatterns: []ir.PatternString{{Line: 318, Value: "$x = $x - 1"}}, + ReportTemplate: "replace `$$` with `$x--`", + WhereExpr: ir.FilterExpr{Line: 318, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 320, + SyntaxPatterns: []ir.PatternString{{Line: 320, Value: "$x = $x + $y"}}, + ReportTemplate: "replace `$$` with `$x += $y`", + WhereExpr: ir.FilterExpr{Line: 320, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 321, + SyntaxPatterns: []ir.PatternString{{Line: 321, Value: "$x = $x - $y"}}, + ReportTemplate: "replace `$$` with `$x -= $y`", + WhereExpr: ir.FilterExpr{Line: 321, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 323, + SyntaxPatterns: []ir.PatternString{{Line: 323, Value: "$x = $x * $y"}}, + ReportTemplate: "replace `$$` with `$x *= $y`", + WhereExpr: ir.FilterExpr{Line: 323, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 324, + SyntaxPatterns: []ir.PatternString{{Line: 324, Value: "$x = $x / $y"}}, + ReportTemplate: "replace `$$` with `$x /= $y`", + WhereExpr: ir.FilterExpr{Line: 324, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 325, + SyntaxPatterns: []ir.PatternString{{Line: 325, Value: "$x = $x % $y"}}, + ReportTemplate: "replace `$$` with `$x %= $y`", + WhereExpr: ir.FilterExpr{Line: 325, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 326, + SyntaxPatterns: []ir.PatternString{{Line: 326, Value: "$x = $x & $y"}}, + ReportTemplate: "replace `$$` with `$x &= $y`", + WhereExpr: ir.FilterExpr{Line: 326, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 327, + SyntaxPatterns: []ir.PatternString{{Line: 327, Value: "$x = $x | $y"}}, + ReportTemplate: "replace `$$` with `$x |= $y`", + WhereExpr: ir.FilterExpr{Line: 327, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 328, + SyntaxPatterns: []ir.PatternString{{Line: 328, Value: "$x = $x ^ $y"}}, + ReportTemplate: "replace `$$` with `$x ^= $y`", + WhereExpr: ir.FilterExpr{Line: 328, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 329, + SyntaxPatterns: []ir.PatternString{{Line: 329, Value: "$x = $x << $y"}}, + ReportTemplate: "replace `$$` with `$x <<= $y`", + WhereExpr: ir.FilterExpr{Line: 329, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 330, + SyntaxPatterns: []ir.PatternString{{Line: 330, Value: "$x = $x >> $y"}}, + ReportTemplate: "replace `$$` with `$x >>= $y`", + WhereExpr: ir.FilterExpr{Line: 330, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 331, + SyntaxPatterns: []ir.PatternString{{Line: 331, Value: "$x = $x &^ $y"}}, + ReportTemplate: "replace `$$` with `$x &^= $y`", + WhereExpr: ir.FilterExpr{Line: 331, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + }, + }, + { + Line: 338, + Name: "preferWriteByte", + MatcherName: "m", + DocTags: []string{"performance", "experimental", "opinionated"}, + DocSummary: "Detects WriteRune calls with rune literal argument that is single byte and reports to use WriteByte instead", + DocBefore: "w.WriteRune('\\n')", + DocAfter: "w.WriteByte('\\n')", + Rules: []ir.Rule{{ + Line: 342, + SyntaxPatterns: []ir.PatternString{{Line: 342, Value: "$w.WriteRune($c)"}}, + ReportTemplate: "consider writing single byte rune $c with $w.WriteByte($c)", + WhereExpr: ir.FilterExpr{ + Line: 343, + Op: ir.FilterAndOp, + Src: "m[\"w\"].Type.Implements(\"io.ByteWriter\") && (m[\"c\"].Const && m[\"c\"].Value.Int() < runeSelf)", + Args: []ir.FilterExpr{ + { + Line: 343, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"w\"].Type.Implements(\"io.ByteWriter\")", + Value: "w", + Args: []ir.FilterExpr{{Line: 343, Op: ir.FilterStringOp, Src: "\"io.ByteWriter\"", Value: "io.ByteWriter"}}, + }, + { + Line: 343, + Op: ir.FilterAndOp, + Src: "(m[\"c\"].Const && m[\"c\"].Value.Int() < runeSelf)", + Args: []ir.FilterExpr{ + { + Line: 343, + Op: ir.FilterVarConstOp, + Src: "m[\"c\"].Const", + Value: "c", + }, + { + Line: 343, + Op: ir.FilterLtOp, + Src: "m[\"c\"].Value.Int() < runeSelf", + Args: []ir.FilterExpr{ + { + Line: 343, + Op: ir.FilterVarValueIntOp, + Src: "m[\"c\"].Value.Int()", + Value: "c", + }, + { + Line: 343, + Op: ir.FilterIntOp, + Src: "runeSelf", + Value: int64(128), + }, + }, + }, + }, + }, + }, + }, + }}, + }, + { + Line: 351, + Name: "preferFprint", + MatcherName: "m", + DocTags: []string{"performance", "experimental"}, + DocSummary: "Detects fmt.Sprint(f/ln) calls which can be replaced with fmt.Fprint(f/ln)", + DocBefore: "w.Write([]byte(fmt.Sprintf(\"%x\", 10)))", + DocAfter: "fmt.Fprintf(w, \"%x\", 10)", + Rules: []ir.Rule{ + { + Line: 352, + SyntaxPatterns: []ir.PatternString{{Line: 352, Value: "$w.Write([]byte(fmt.Sprint($*args)))"}}, + ReportTemplate: "fmt.Fprint($w, $args) should be preferred to the $$", + SuggestTemplate: "fmt.Fprint($w, $args)", + WhereExpr: ir.FilterExpr{ + Line: 353, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"w\"].Type.Implements(\"io.Writer\")", + Value: "w", + Args: []ir.FilterExpr{{Line: 353, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + }, + }, + { + Line: 357, + SyntaxPatterns: []ir.PatternString{{Line: 357, Value: "$w.Write([]byte(fmt.Sprintf($*args)))"}}, + ReportTemplate: "fmt.Fprintf($w, $args) should be preferred to the $$", + SuggestTemplate: "fmt.Fprintf($w, $args)", + WhereExpr: ir.FilterExpr{ + Line: 358, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"w\"].Type.Implements(\"io.Writer\")", + Value: "w", + Args: []ir.FilterExpr{{Line: 358, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + }, + }, + { + Line: 362, + SyntaxPatterns: []ir.PatternString{{Line: 362, Value: "$w.Write([]byte(fmt.Sprintln($*args)))"}}, + ReportTemplate: "fmt.Fprintln($w, $args) should be preferred to the $$", + SuggestTemplate: "fmt.Fprintln($w, $args)", + WhereExpr: ir.FilterExpr{ + Line: 363, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"w\"].Type.Implements(\"io.Writer\")", + Value: "w", + Args: []ir.FilterExpr{{Line: 363, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + }, + }, + { + Line: 367, + SyntaxPatterns: []ir.PatternString{{Line: 367, Value: "io.WriteString($w, fmt.Sprint($*args))"}}, + ReportTemplate: "suggestion: fmt.Fprint($w, $args)", + SuggestTemplate: "fmt.Fprint($w, $args)", + }, + { + Line: 368, + SyntaxPatterns: []ir.PatternString{{Line: 368, Value: "io.WriteString($w, fmt.Sprintf($*args))"}}, + ReportTemplate: "suggestion: fmt.Fprintf($w, $args)", + SuggestTemplate: "fmt.Fprintf($w, $args)", + }, + { + Line: 369, + SyntaxPatterns: []ir.PatternString{{Line: 369, Value: "io.WriteString($w, fmt.Sprintln($*args))"}}, + ReportTemplate: "suggestion: fmt.Fprintln($w, $args)", + SuggestTemplate: "fmt.Fprintln($w, $args)", + }, + }, + }, + { + Line: 376, + Name: "dupArg", + MatcherName: "m", + DocTags: []string{"diagnostic"}, + DocSummary: "Detects suspicious duplicated arguments", + DocBefore: "copy(dst, dst)", + DocAfter: "copy(dst, src)", + Rules: []ir.Rule{ + { + Line: 377, + SyntaxPatterns: []ir.PatternString{ + {Line: 377, Value: "$x.Equal($x)"}, + {Line: 377, Value: "$x.Equals($x)"}, + {Line: 377, Value: "$x.Compare($x)"}, + {Line: 377, Value: "$x.Cmp($x)"}, + }, + ReportTemplate: "suspicious method call with the same argument and receiver", + WhereExpr: ir.FilterExpr{Line: 378, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + { + Line: 381, + SyntaxPatterns: []ir.PatternString{ + {Line: 381, Value: "copy($x, $x)"}, + {Line: 382, Value: "math.Max($x, $x)"}, + {Line: 383, Value: "math.Min($x, $x)"}, + {Line: 384, Value: "reflect.Copy($x, $x)"}, + {Line: 385, Value: "reflect.DeepEqual($x, $x)"}, + {Line: 386, Value: "strings.Contains($x, $x)"}, + {Line: 387, Value: "strings.Compare($x, $x)"}, + {Line: 388, Value: "strings.EqualFold($x, $x)"}, + {Line: 389, Value: "strings.HasPrefix($x, $x)"}, + {Line: 390, Value: "strings.HasSuffix($x, $x)"}, + {Line: 391, Value: "strings.Index($x, $x)"}, + {Line: 392, Value: "strings.LastIndex($x, $x)"}, + {Line: 393, Value: "strings.Split($x, $x)"}, + {Line: 394, Value: "strings.SplitAfter($x, $x)"}, + {Line: 395, Value: "strings.SplitAfterN($x, $x, $_)"}, + {Line: 396, Value: "strings.SplitN($x, $x, $_)"}, + {Line: 397, Value: "strings.Replace($_, $x, $x, $_)"}, + {Line: 398, Value: "strings.ReplaceAll($_, $x, $x)"}, + {Line: 399, Value: "bytes.Contains($x, $x)"}, + {Line: 400, Value: "bytes.Compare($x, $x)"}, + {Line: 401, Value: "bytes.Equal($x, $x)"}, + {Line: 402, Value: "bytes.EqualFold($x, $x)"}, + {Line: 403, Value: "bytes.HasPrefix($x, $x)"}, + {Line: 404, Value: "bytes.HasSuffix($x, $x)"}, + {Line: 405, Value: "bytes.Index($x, $x)"}, + {Line: 406, Value: "bytes.LastIndex($x, $x)"}, + {Line: 407, Value: "bytes.Split($x, $x)"}, + {Line: 408, Value: "bytes.SplitAfter($x, $x)"}, + {Line: 409, Value: "bytes.SplitAfterN($x, $x, $_)"}, + {Line: 410, Value: "bytes.SplitN($x, $x, $_)"}, + {Line: 411, Value: "bytes.Replace($_, $x, $x, $_)"}, + {Line: 412, Value: "bytes.ReplaceAll($_, $x, $x)"}, + {Line: 413, Value: "types.Identical($x, $x)"}, + {Line: 414, Value: "types.IdenticalIgnoreTags($x, $x)"}, + {Line: 415, Value: "draw.Draw($x, $_, $x, $_, $_)"}, + }, + ReportTemplate: "suspicious duplicated args in $$", + WhereExpr: ir.FilterExpr{Line: 416, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + }, + }, + }, + { + Line: 424, + Name: "returnAfterHttpError", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects suspicious http.Error call without following return", + DocBefore: "if err != nil { http.Error(...); }", + DocAfter: "if err != nil { http.Error(...); return; }", + Rules: []ir.Rule{{ + Line: 425, + SyntaxPatterns: []ir.PatternString{{Line: 425, Value: "if $_ { $*_; http.Error($w, $err, $code) }"}}, + ReportTemplate: "Possibly return is missed after the http.Error call", + LocationVar: "w", + }}, + }, + { + Line: 434, + Name: "preferFilepathJoin", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects concatenation with os.PathSeparator which can be replaced with filepath.Join", + DocBefore: "x + string(os.PathSeparator) + y", + DocAfter: "filepath.Join(x, y)", + Rules: []ir.Rule{{ + Line: 435, + SyntaxPatterns: []ir.PatternString{{Line: 435, Value: "$x + string(os.PathSeparator) + $y"}}, + ReportTemplate: "filepath.Join($x, $y) should be preferred to the $$", + SuggestTemplate: "filepath.Join($x, $y)", + WhereExpr: ir.FilterExpr{ + Line: 436, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)", + Args: []ir.FilterExpr{ + { + Line: 436, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`string`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 436, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + { + Line: 436, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"y\"].Type.Is(`string`)", + Value: "y", + Args: []ir.FilterExpr{{Line: 436, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + }, + }}, + }, + { + Line: 445, + Name: "preferStringWriter", + MatcherName: "m", + DocTags: []string{"performance", "experimental"}, + DocSummary: "Detects w.Write or io.WriteString calls which can be replaced with w.WriteString", + DocBefore: "w.Write([]byte(\"foo\"))", + DocAfter: "w.WriteString(\"foo\")", + Rules: []ir.Rule{ + { + Line: 446, + SyntaxPatterns: []ir.PatternString{{Line: 446, Value: "$w.Write([]byte($s))"}}, + ReportTemplate: "$w.WriteString($s) should be preferred to the $$", + SuggestTemplate: "$w.WriteString($s)", + WhereExpr: ir.FilterExpr{ + Line: 447, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", + Value: "w", + Args: []ir.FilterExpr{{Line: 447, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + }, + }, + { + Line: 451, + SyntaxPatterns: []ir.PatternString{{Line: 451, Value: "io.WriteString($w, $s)"}}, + ReportTemplate: "$w.WriteString($s) should be preferred to the $$", + SuggestTemplate: "$w.WriteString($s)", + WhereExpr: ir.FilterExpr{ + Line: 452, + Op: ir.FilterVarTypeImplementsOp, + Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", + Value: "w", + Args: []ir.FilterExpr{{Line: 452, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + }, + }, + }, + }, + { + Line: 461, + Name: "sliceClear", + MatcherName: "m", + DocTags: []string{"performance", "experimental"}, + DocSummary: "Detects slice clear loops, suggests an idiom that is recognized by the Go compiler", + DocBefore: "for i := 0; i < len(buf); i++ { buf[i] = 0 }", + DocAfter: "for i := range buf { buf[i] = 0 }", + Rules: []ir.Rule{{ + Line: 462, + SyntaxPatterns: []ir.PatternString{{Line: 462, Value: "for $i := 0; $i < len($xs); $i++ { $xs[$i] = $zero }"}}, + ReportTemplate: "rewrite as for-range so compiler can recognize this pattern", + WhereExpr: ir.FilterExpr{ + Line: 463, + Op: ir.FilterEqOp, + Src: "m[\"zero\"].Value.Int() == 0", + Args: []ir.FilterExpr{ + { + Line: 463, + Op: ir.FilterVarValueIntOp, + Src: "m[\"zero\"].Value.Int()", + Value: "zero", + }, + { + Line: 463, + Op: ir.FilterIntOp, + Src: "0", + Value: int64(0), + }, + }, + }, + }}, + }, + { + Line: 471, + Name: "syncMapLoadAndDelete", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects sync.Map load+delete operations that can be replaced with LoadAndDelete", + DocBefore: "v, ok := m.Load(k); if ok { m.Delete($k); f(v); }", + DocAfter: "v, deleted := m.LoadAndDelete(k); if deleted { f(v) }", + Rules: []ir.Rule{{ + Line: 472, + SyntaxPatterns: []ir.PatternString{{Line: 472, Value: "$_, $ok := $m.Load($k); if $ok { $m.Delete($k); $*_ }"}}, + ReportTemplate: "use $m.LoadAndDelete to perform load+delete operations atomically", + WhereExpr: ir.FilterExpr{ + Line: 473, + Op: ir.FilterAndOp, + Src: "m.GoVersion().GreaterEqThan(\"1.15\") &&\n\tm[\"m\"].Type.Is(`*sync.Map`)", + Args: []ir.FilterExpr{ + { + Line: 473, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.15\")", + Value: "1.15", + }, + { + Line: 474, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"m\"].Type.Is(`*sync.Map`)", + Value: "m", + Args: []ir.FilterExpr{{Line: 474, Op: ir.FilterStringOp, Src: "`*sync.Map`", Value: "*sync.Map"}}, + }, + }, + }, + }}, + }, + { + Line: 482, + Name: "sprintfQuotedString", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects \"%s\" formatting directives that can be replaced with %q", + DocBefore: "fmt.Sprintf(`\"%s\"`, s)", + DocAfter: "fmt.Sprintf(`%q`, s)", + Rules: []ir.Rule{{ + Line: 483, + SyntaxPatterns: []ir.PatternString{{Line: 483, Value: "fmt.Sprintf($s, $*_)"}}, + ReportTemplate: "use %q instead of \"%s\" for quoted strings", + WhereExpr: ir.FilterExpr{ + Line: 484, + Op: ir.FilterOrOp, + Src: "m[\"s\"].Text.Matches(\"^`.*\\\"%s\\\".*`$\") ||\n\tm[\"s\"].Text.Matches(`^\".*\\\\\"%s\\\\\".*\"$`)", + Args: []ir.FilterExpr{ + { + Line: 484, + Op: ir.FilterVarTextMatchesOp, + Src: "m[\"s\"].Text.Matches(\"^`.*\\\"%s\\\".*`$\")", + Value: "s", + Args: []ir.FilterExpr{{Line: 484, Op: ir.FilterStringOp, Src: "\"^`.*\\\"%s\\\".*`$\"", Value: "^`.*\"%s\".*`$"}}, + }, + { + Line: 485, + Op: ir.FilterVarTextMatchesOp, + Src: "m[\"s\"].Text.Matches(`^\".*\\\\\"%s\\\\\".*\"$`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 485, Op: ir.FilterStringOp, Src: "`^\".*\\\\\"%s\\\\\".*\"$`", Value: "^\".*\\\\\"%s\\\\\".*\"$"}}, + }, + }, + }, + }}, + }, + { + Line: 493, + Name: "offBy1", + MatcherName: "m", + DocTags: []string{"diagnostic"}, + DocSummary: "Detects various off-by-one kind of errors", + DocBefore: "xs[len(xs)]", + DocAfter: "xs[len(xs)-1]", + Rules: []ir.Rule{ + { + Line: 494, + SyntaxPatterns: []ir.PatternString{{Line: 494, Value: "$x[len($x)]"}}, + ReportTemplate: "index expr always panics; maybe you wanted $x[len($x)-1]?", + SuggestTemplate: "$x[len($x)-1]", + WhereExpr: ir.FilterExpr{ + Line: 495, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"x\"].Type.Is(`[]$_`)", + Args: []ir.FilterExpr{ + {Line: 495, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + { + Line: 495, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`[]$_`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 495, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, + }, + }, + }, + }, + { + Line: 502, + SyntaxPatterns: []ir.PatternString{ + {Line: 503, Value: "$i := strings.Index($s, $_); $_ := $slicing[$i:]"}, + {Line: 504, Value: "$i := strings.Index($s, $_); $_ = $slicing[$i:]"}, + {Line: 505, Value: "$i := bytes.Index($s, $_); $_ := $slicing[$i:]"}, + {Line: 506, Value: "$i := bytes.Index($s, $_); $_ = $slicing[$i:]"}, + }, + ReportTemplate: "Index() can return -1; maybe you wanted to do $s[$i+1:]", + WhereExpr: ir.FilterExpr{ + Line: 507, + Op: ir.FilterEqOp, + Src: "m[\"s\"].Text == m[\"slicing\"].Text", + Args: []ir.FilterExpr{ + {Line: 507, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, + {Line: 507, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, + }, + }, + LocationVar: "slicing", + }, + { + Line: 511, + SyntaxPatterns: []ir.PatternString{ + {Line: 512, Value: "$i := strings.Index($s, $_); $_ := $slicing[:$i]"}, + {Line: 513, Value: "$i := strings.Index($s, $_); $_ = $slicing[:$i]"}, + {Line: 514, Value: "$i := bytes.Index($s, $_); $_ := $slicing[:$i]"}, + {Line: 515, Value: "$i := bytes.Index($s, $_); $_ = $slicing[:$i]"}, + }, + ReportTemplate: "Index() can return -1; maybe you wanted to do $s[:$i+1]", + WhereExpr: ir.FilterExpr{ + Line: 516, + Op: ir.FilterEqOp, + Src: "m[\"s\"].Text == m[\"slicing\"].Text", + Args: []ir.FilterExpr{ + {Line: 516, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, + {Line: 516, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, + }, + }, + LocationVar: "slicing", + }, + { + Line: 520, + SyntaxPatterns: []ir.PatternString{ + {Line: 521, Value: "$s[strings.Index($s, $_):]"}, + {Line: 522, Value: "$s[:strings.Index($s, $_)]"}, + {Line: 523, Value: "$s[bytes.Index($s, $_):]"}, + {Line: 524, Value: "$s[:bytes.Index($s, $_)]"}, + }, + ReportTemplate: "Index() can return -1; maybe you wanted to do Index()+1", + }, + }, + }, + { + Line: 532, + Name: "unslice", + MatcherName: "m", + DocTags: []string{"style"}, + DocSummary: "Detects slice expressions that can be simplified to sliced expression itself", + DocBefore: "copy(b[:], values...)", + DocAfter: "copy(b, values...)", + Rules: []ir.Rule{{ + Line: 533, + SyntaxPatterns: []ir.PatternString{{Line: 533, Value: "$s[:]"}}, + ReportTemplate: "could simplify $$ to $s", + SuggestTemplate: "$s", + WhereExpr: ir.FilterExpr{ + Line: 534, + Op: ir.FilterOrOp, + Src: "m[\"s\"].Type.Is(`string`) || m[\"s\"].Type.Is(`[]$_`)", + Args: []ir.FilterExpr{ + { + Line: 534, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 534, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + { + Line: 534, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`[]$_`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 534, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, + }, + }, + }, + }}, + }, + { + Line: 543, + Name: "yodaStyleExpr", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects Yoda style expressions and suggests to replace them", + DocBefore: "return nil != ptr", + DocAfter: "return ptr != nil", + Rules: []ir.Rule{ + { + Line: 544, + SyntaxPatterns: []ir.PatternString{{Line: 544, Value: "$constval != $x"}}, + ReportTemplate: "consider to change order in expression to $x != $constval", + WhereExpr: ir.FilterExpr{ + Line: 544, + Op: ir.FilterAndOp, + Src: "m[\"constval\"].Node.Is(`BasicLit`) && !m[\"x\"].Node.Is(`BasicLit`)", + Args: []ir.FilterExpr{ + { + Line: 544, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"constval\"].Node.Is(`BasicLit`)", + Value: "constval", + Args: []ir.FilterExpr{{Line: 544, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + }, + { + Line: 544, + Op: ir.FilterNotOp, + Src: "!m[\"x\"].Node.Is(`BasicLit`)", + Args: []ir.FilterExpr{{ + Line: 544, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"x\"].Node.Is(`BasicLit`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 544, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + }}, + }, + }, + }, + }, + { + Line: 546, + SyntaxPatterns: []ir.PatternString{{Line: 546, Value: "$constval == $x"}}, + ReportTemplate: "consider to change order in expression to $x == $constval", + WhereExpr: ir.FilterExpr{ + Line: 546, + Op: ir.FilterAndOp, + Src: "m[\"constval\"].Node.Is(`BasicLit`) && !m[\"x\"].Node.Is(`BasicLit`)", + Args: []ir.FilterExpr{ + { + Line: 546, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"constval\"].Node.Is(`BasicLit`)", + Value: "constval", + Args: []ir.FilterExpr{{Line: 546, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + }, + { + Line: 546, + Op: ir.FilterNotOp, + Src: "!m[\"x\"].Node.Is(`BasicLit`)", + Args: []ir.FilterExpr{{ + Line: 546, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"x\"].Node.Is(`BasicLit`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 546, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + }}, + }, + }, + }, + }, + { + Line: 549, + SyntaxPatterns: []ir.PatternString{{Line: 549, Value: "nil != $x"}}, + ReportTemplate: "consider to change order in expression to $x != nil", + WhereExpr: ir.FilterExpr{ + Line: 549, + Op: ir.FilterNotOp, + Src: "!m[\"x\"].Node.Is(`BasicLit`)", + Args: []ir.FilterExpr{{ + Line: 549, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"x\"].Node.Is(`BasicLit`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 549, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + }}, + }, + }, + { + Line: 551, + SyntaxPatterns: []ir.PatternString{{Line: 551, Value: "nil == $x"}}, + ReportTemplate: "consider to change order in expression to $x == nil", + WhereExpr: ir.FilterExpr{ + Line: 551, + Op: ir.FilterNotOp, + Src: "!m[\"x\"].Node.Is(`BasicLit`)", + Args: []ir.FilterExpr{{ + Line: 551, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"x\"].Node.Is(`BasicLit`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 551, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + }}, + }, + }, + }, + }, + { + Line: 559, + Name: "equalFold", + MatcherName: "m", + DocTags: []string{"performance", "experimental"}, + DocSummary: "Detects unoptimal strings/bytes case-insensitive comparison", + DocBefore: "strings.ToLower(x) == strings.ToLower(y)", + DocAfter: "strings.EqualFold(x, y)", + Rules: []ir.Rule{ + { + Line: 568, + SyntaxPatterns: []ir.PatternString{ + {Line: 569, Value: "strings.ToLower($x) == $y"}, + {Line: 570, Value: "strings.ToLower($x) == strings.ToLower($y)"}, + {Line: 571, Value: "$x == strings.ToLower($y)"}, + {Line: 572, Value: "strings.ToUpper($x) == $y"}, + {Line: 573, Value: "strings.ToUpper($x) == strings.ToUpper($y)"}, + {Line: 574, Value: "$x == strings.ToUpper($y)"}, + }, + ReportTemplate: "consider replacing with strings.EqualFold($x, $y)", + SuggestTemplate: "strings.EqualFold($x, $y)", + WhereExpr: ir.FilterExpr{ + Line: 575, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", + Args: []ir.FilterExpr{ + { + Line: 575, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure", + Args: []ir.FilterExpr{ + {Line: 575, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 575, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + }, + }, + { + Line: 575, + Op: ir.FilterNeqOp, + Src: "m[\"x\"].Text != m[\"y\"].Text", + Args: []ir.FilterExpr{ + {Line: 575, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 575, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + }, + }, + }, + }, + }, + { + Line: 580, + SyntaxPatterns: []ir.PatternString{ + {Line: 581, Value: "strings.ToLower($x) != $y"}, + {Line: 582, Value: "strings.ToLower($x) != strings.ToLower($y)"}, + {Line: 583, Value: "$x != strings.ToLower($y)"}, + {Line: 584, Value: "strings.ToUpper($x) != $y"}, + {Line: 585, Value: "strings.ToUpper($x) != strings.ToUpper($y)"}, + {Line: 586, Value: "$x != strings.ToUpper($y)"}, + }, + ReportTemplate: "consider replacing with !strings.EqualFold($x, $y)", + SuggestTemplate: "!strings.EqualFold($x, $y)", + WhereExpr: ir.FilterExpr{ + Line: 587, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", + Args: []ir.FilterExpr{ + { + Line: 587, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure", + Args: []ir.FilterExpr{ + {Line: 587, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 587, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + }, + }, + { + Line: 587, + Op: ir.FilterNeqOp, + Src: "m[\"x\"].Text != m[\"y\"].Text", + Args: []ir.FilterExpr{ + {Line: 587, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 587, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + }, + }, + }, + }, + }, + { + Line: 592, + SyntaxPatterns: []ir.PatternString{ + {Line: 593, Value: "bytes.Equal(bytes.ToLower($x), $y)"}, + {Line: 594, Value: "bytes.Equal(bytes.ToLower($x), bytes.ToLower($y))"}, + {Line: 595, Value: "bytes.Equal($x, bytes.ToLower($y))"}, + {Line: 596, Value: "bytes.Equal(bytes.ToUpper($x), $y)"}, + {Line: 597, Value: "bytes.Equal(bytes.ToUpper($x), bytes.ToUpper($y))"}, + {Line: 598, Value: "bytes.Equal($x, bytes.ToUpper($y))"}, + }, + ReportTemplate: "consider replacing with bytes.EqualFold($x, $y)", + SuggestTemplate: "bytes.EqualFold($x, $y)", + WhereExpr: ir.FilterExpr{ + Line: 599, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", + Args: []ir.FilterExpr{ + { + Line: 599, + Op: ir.FilterAndOp, + Src: "m[\"x\"].Pure && m[\"y\"].Pure", + Args: []ir.FilterExpr{ + {Line: 599, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 599, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + }, + }, + { + Line: 599, + Op: ir.FilterNeqOp, + Src: "m[\"x\"].Text != m[\"y\"].Text", + Args: []ir.FilterExpr{ + {Line: 599, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 599, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + }, + }, + }, + }, + }, + }, + }, + { + Line: 608, + Name: "argOrder", + MatcherName: "m", + DocTags: []string{"diagnostic"}, + DocSummary: "Detects suspicious arguments order", + DocBefore: "strings.HasPrefix(\"#\", userpass)", + DocAfter: "strings.HasPrefix(userpass, \"#\")", + Rules: []ir.Rule{{ + Line: 609, + SyntaxPatterns: []ir.PatternString{ + {Line: 610, Value: "strings.HasPrefix($lit, $s)"}, + {Line: 611, Value: "bytes.HasPrefix($lit, $s)"}, + {Line: 612, Value: "strings.HasSuffix($lit, $s)"}, + {Line: 613, Value: "bytes.HasSuffix($lit, $s)"}, + {Line: 614, Value: "strings.Contains($lit, $s)"}, + {Line: 615, Value: "bytes.Contains($lit, $s)"}, + {Line: 616, Value: "strings.TrimPrefix($lit, $s)"}, + {Line: 617, Value: "bytes.TrimPrefix($lit, $s)"}, + {Line: 618, Value: "strings.TrimSuffix($lit, $s)"}, + {Line: 619, Value: "bytes.TrimSuffix($lit, $s)"}, + {Line: 620, Value: "strings.Split($lit, $s)"}, + {Line: 621, Value: "bytes.Split($lit, $s)"}, + }, + ReportTemplate: "$lit and $s arguments order looks reversed", + WhereExpr: ir.FilterExpr{ + Line: 622, + Op: ir.FilterAndOp, + Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice) &&\n\t!(m[\"s\"].Const || m[\"s\"].ConstSlice) &&\n\t!m[\"lit\"].Node.Is(`Ident`)", + Args: []ir.FilterExpr{ + { + Line: 622, + Op: ir.FilterAndOp, + Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice) &&\n\t!(m[\"s\"].Const || m[\"s\"].ConstSlice)", + Args: []ir.FilterExpr{ + { + Line: 622, + Op: ir.FilterOrOp, + Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice)", + Args: []ir.FilterExpr{ + { + Line: 622, + Op: ir.FilterVarConstOp, + Src: "m[\"lit\"].Const", + Value: "lit", + }, + { + Line: 622, + Op: ir.FilterVarConstSliceOp, + Src: "m[\"lit\"].ConstSlice", + Value: "lit", + }, + }, + }, + { + Line: 623, + Op: ir.FilterNotOp, + Src: "!(m[\"s\"].Const || m[\"s\"].ConstSlice)", + Args: []ir.FilterExpr{{ + Line: 623, + Op: ir.FilterOrOp, + Src: "(m[\"s\"].Const || m[\"s\"].ConstSlice)", + Args: []ir.FilterExpr{ + { + Line: 623, + Op: ir.FilterVarConstOp, + Src: "m[\"s\"].Const", + Value: "s", + }, + { + Line: 623, + Op: ir.FilterVarConstSliceOp, + Src: "m[\"s\"].ConstSlice", + Value: "s", + }, + }, + }}, + }, + }, + }, + { + Line: 624, + Op: ir.FilterNotOp, + Src: "!m[\"lit\"].Node.Is(`Ident`)", + Args: []ir.FilterExpr{{ + Line: 624, + Op: ir.FilterVarNodeIsOp, + Src: "m[\"lit\"].Node.Is(`Ident`)", + Value: "lit", + Args: []ir.FilterExpr{{Line: 624, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, + }}, + }, + }, + }, + }}, + }, + { + Line: 632, + Name: "stringConcatSimplify", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects string concat operations that can be simplified", + DocBefore: "strings.Join([]string{x, y}, \"_\")", + DocAfter: "x + \"_\" + y", + Rules: []ir.Rule{ + { + Line: 633, + SyntaxPatterns: []ir.PatternString{{Line: 633, Value: "strings.Join([]string{$x, $y}, \"\")"}}, + ReportTemplate: "suggestion: $x + $y", + SuggestTemplate: "$x + $y", + }, + { + Line: 634, + SyntaxPatterns: []ir.PatternString{{Line: 634, Value: "strings.Join([]string{$x, $y, $z}, \"\")"}}, + ReportTemplate: "suggestion: $x + $y + $z", + SuggestTemplate: "$x + $y + $z", + }, + { + Line: 635, + SyntaxPatterns: []ir.PatternString{{Line: 635, Value: "strings.Join([]string{$x, $y}, $glue)"}}, + ReportTemplate: "suggestion: $x + $glue + $y", + SuggestTemplate: "$x + $glue + $y", + }, + }, + }, + { + Line: 642, + Name: "timeExprSimplify", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects manual conversion to milli- or microseconds", + DocBefore: "t.Unix() / 1000", + DocAfter: "t.UnixMilli()", + Rules: []ir.Rule{ + { + Line: 647, + SyntaxPatterns: []ir.PatternString{{Line: 647, Value: "$t.Unix() / 1000"}}, + ReportTemplate: "use $t.UnixMilli() instead of $$", + SuggestTemplate: "$t.UnixMilli()", + WhereExpr: ir.FilterExpr{ + Line: 648, + Op: ir.FilterAndOp, + Src: "m.GoVersion().GreaterEqThan(\"1.17\") && isTime(m[\"t\"])", + Args: []ir.FilterExpr{ + { + Line: 648, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.17\")", + Value: "1.17", + }, + { + Line: 648, + Op: ir.FilterOrOp, + Src: "isTime(m[\"t\"])", + Args: []ir.FilterExpr{ + { + Line: 648, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 644, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + }, + { + Line: 648, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`*time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 644, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + }, + }, + }, + }, + }, + }, + { + Line: 652, + SyntaxPatterns: []ir.PatternString{{Line: 652, Value: "$t.UnixNano() * 1000"}}, + ReportTemplate: "use $t.UnixMicro() instead of $$", + SuggestTemplate: "$t.UnixMicro()", + WhereExpr: ir.FilterExpr{ + Line: 653, + Op: ir.FilterAndOp, + Src: "m.GoVersion().GreaterEqThan(\"1.17\") && isTime(m[\"t\"])", + Args: []ir.FilterExpr{ + { + Line: 653, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.17\")", + Value: "1.17", + }, + { + Line: 653, + Op: ir.FilterOrOp, + Src: "isTime(m[\"t\"])", + Args: []ir.FilterExpr{ + { + Line: 653, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 644, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + }, + { + Line: 653, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`*time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 644, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + }, + }, + }, + }, + }, + }, + }, + }, + { + Line: 662, + Name: "timeCmpSimplify", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects Before/After call of time.Time that can be simplified", + DocBefore: "!t.Before(tt)", + DocAfter: "t.After(tt)", + Rules: []ir.Rule{ + { + Line: 667, + SyntaxPatterns: []ir.PatternString{{Line: 667, Value: "!$t.Before($tt)"}}, + ReportTemplate: "suggestion: $t.After($tt)", + SuggestTemplate: "$t.After($tt)", + WhereExpr: ir.FilterExpr{ + Line: 668, + Op: ir.FilterOrOp, + Src: "isTime(m[\"t\"])", + Args: []ir.FilterExpr{ + { + Line: 668, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 664, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + }, + { + Line: 668, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`*time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 664, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + }, + }, + }, + }, + { + Line: 671, + SyntaxPatterns: []ir.PatternString{{Line: 671, Value: "!$t.After($tt)"}}, + ReportTemplate: "suggestion: $t.Before($tt)", + SuggestTemplate: "$t.Before($tt)", + WhereExpr: ir.FilterExpr{ + Line: 672, + Op: ir.FilterOrOp, + Src: "isTime(m[\"t\"])", + Args: []ir.FilterExpr{ + { + Line: 672, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 664, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + }, + { + Line: 672, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"t\"].Type.Is(`*time.Time`)", + Value: "t", + Args: []ir.FilterExpr{{Line: 664, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + }, + }, + }, + }, + }, + }, + { + Line: 680, + Name: "exposedSyncMutex", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects exposed methods from sync.Mutex and sync.RWMutex", + DocBefore: "type Foo struct{ ...; sync.Mutex; ... }", + DocAfter: "type Foo struct{ ...; mu sync.Mutex; ... }", + Rules: []ir.Rule{ + { + Line: 685, + SyntaxPatterns: []ir.PatternString{{Line: 685, Value: "type $x struct { $*_; sync.Mutex; $*_ }"}}, + ReportTemplate: "don't embed sync.Mutex", + WhereExpr: ir.FilterExpr{ + Line: 686, + Op: ir.FilterVarTextMatchesOp, + Src: "isExported(m[\"x\"])", + Value: "x", + Args: []ir.FilterExpr{{Line: 682, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + }, + }, + { + Line: 689, + SyntaxPatterns: []ir.PatternString{{Line: 689, Value: "type $x struct { $*_; *sync.Mutex; $*_ }"}}, + ReportTemplate: "don't embed *sync.Mutex", + WhereExpr: ir.FilterExpr{ + Line: 690, + Op: ir.FilterVarTextMatchesOp, + Src: "isExported(m[\"x\"])", + Value: "x", + Args: []ir.FilterExpr{{Line: 682, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + }, + }, + { + Line: 693, + SyntaxPatterns: []ir.PatternString{{Line: 693, Value: "type $x struct { $*_; sync.RWMutex; $*_ }"}}, + ReportTemplate: "don't embed sync.RWMutex", + WhereExpr: ir.FilterExpr{ + Line: 694, + Op: ir.FilterVarTextMatchesOp, + Src: "isExported(m[\"x\"])", + Value: "x", + Args: []ir.FilterExpr{{Line: 682, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + }, + }, + { + Line: 697, + SyntaxPatterns: []ir.PatternString{{Line: 697, Value: "type $x struct { $*_; *sync.RWMutex; $*_ }"}}, + ReportTemplate: "don't embed *sync.RWMutex", + WhereExpr: ir.FilterExpr{ + Line: 698, + Op: ir.FilterVarTextMatchesOp, + Src: "isExported(m[\"x\"])", + Value: "x", + Args: []ir.FilterExpr{{Line: 682, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + }, + }, + }, + }, + { + Line: 706, + Name: "badSorting", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects bad usage of sort package", + DocBefore: "xs = sort.StringSlice(xs)", + DocAfter: "sort.Strings(xs)", + Rules: []ir.Rule{ + { + Line: 707, + SyntaxPatterns: []ir.PatternString{{Line: 707, Value: "$x = sort.IntSlice($x)"}}, + ReportTemplate: "suspicious sort.IntSlice usage, maybe sort.Ints was intended?", + SuggestTemplate: "sort.Ints($x)", + WhereExpr: ir.FilterExpr{ + Line: 708, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`[]int`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 708, Op: ir.FilterStringOp, Src: "`[]int`", Value: "[]int"}}, + }, + }, + { + Line: 712, + SyntaxPatterns: []ir.PatternString{{Line: 712, Value: "$x = sort.Float64Slice($x)"}}, + ReportTemplate: "suspicious sort.Float64s usage, maybe sort.Float64s was intended?", + SuggestTemplate: "sort.Float64s($x)", + WhereExpr: ir.FilterExpr{ + Line: 713, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`[]float64`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 713, Op: ir.FilterStringOp, Src: "`[]float64`", Value: "[]float64"}}, + }, + }, + { + Line: 717, + SyntaxPatterns: []ir.PatternString{{Line: 717, Value: "$x = sort.StringSlice($x)"}}, + ReportTemplate: "suspicious sort.StringSlice usage, maybe sort.Strings was intended?", + SuggestTemplate: "sort.Strings($x)", + WhereExpr: ir.FilterExpr{ + Line: 718, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"x\"].Type.Is(`[]string`)", + Value: "x", + Args: []ir.FilterExpr{{Line: 718, Op: ir.FilterStringOp, Src: "`[]string`", Value: "[]string"}}, + }, + }, + }, + }, + { + Line: 727, + Name: "externalErrorReassign", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects suspicious reassigment of error from another package", + DocBefore: "io.EOF = nil", + DocAfter: "/* don't do it */", + Rules: []ir.Rule{{ + Line: 728, + SyntaxPatterns: []ir.PatternString{{Line: 728, Value: "$pkg.$err = $x"}}, + ReportTemplate: "suspicious reassigment of error from another package", + WhereExpr: ir.FilterExpr{ + Line: 729, + Op: ir.FilterAndOp, + Src: "m[\"err\"].Type.Is(`error`) && m[\"pkg\"].Object.Is(`PkgName`)", + Args: []ir.FilterExpr{ + { + Line: 729, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"err\"].Type.Is(`error`)", + Value: "err", + Args: []ir.FilterExpr{{Line: 729, Op: ir.FilterStringOp, Src: "`error`", Value: "error"}}, + }, + { + Line: 729, + Op: ir.FilterVarObjectIsOp, + Src: "m[\"pkg\"].Object.Is(`PkgName`)", + Value: "pkg", + Args: []ir.FilterExpr{{Line: 729, Op: ir.FilterStringOp, Src: "`PkgName`", Value: "PkgName"}}, + }, + }, + }, + }}, + }, + { + Line: 737, + Name: "emptyDecl", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects suspicious empty declarations blocks", + DocBefore: "var()", + DocAfter: "/* nothing */", + Rules: []ir.Rule{ + { + Line: 738, + SyntaxPatterns: []ir.PatternString{{Line: 738, Value: "var()"}}, + ReportTemplate: "empty var() block", + }, + { + Line: 739, + SyntaxPatterns: []ir.PatternString{{Line: 739, Value: "const()"}}, + ReportTemplate: "empty const() block", + }, + { + Line: 740, + SyntaxPatterns: []ir.PatternString{{Line: 740, Value: "type()"}}, + ReportTemplate: "empty type() block", + }, + }, + }, + { + Line: 747, + Name: "dynamicFmtString", + MatcherName: "m", + DocTags: []string{"diagnostic", "experimental"}, + DocSummary: "Detects suspicious formatting strings usage", + DocBefore: "fmt.Errorf(msg)", + DocAfter: "fmt.Errorf(\"%s\", msg)", + Rules: []ir.Rule{ + { + Line: 748, + SyntaxPatterns: []ir.PatternString{{Line: 748, Value: "fmt.Errorf($f)"}}, + ReportTemplate: "use errors.New($f) or fmt.Errorf(\"%s\", $f) instead", + SuggestTemplate: "errors.New($f)", + WhereExpr: ir.FilterExpr{ + Line: 749, + Op: ir.FilterNotOp, + Src: "!m[\"f\"].Const", + Args: []ir.FilterExpr{{ + Line: 749, + Op: ir.FilterVarConstOp, + Src: "m[\"f\"].Const", + Value: "f", + }}, + }, + }, + { + Line: 753, + SyntaxPatterns: []ir.PatternString{{Line: 753, Value: "fmt.Errorf($f($*args))"}}, + ReportTemplate: "use errors.New($f($*args)) or fmt.Errorf(\"%s\", $f($*args)) instead", + SuggestTemplate: "errors.New($f($*args))", + }, + }, + }, + { + Line: 762, + Name: "stringsCompare", + MatcherName: "m", + DocTags: []string{"style", "experimental"}, + DocSummary: "Detects strings.Compare usage", + DocBefore: "strings.Compare(x, y)", + DocAfter: "x < y", + Rules: []ir.Rule{ + { + Line: 763, + SyntaxPatterns: []ir.PatternString{{Line: 763, Value: "strings.Compare($s1, $s2) == 0"}}, + ReportTemplate: "suggestion: $s1 == $s2", + SuggestTemplate: "$s1 == $s2", + }, + { + Line: 766, + SyntaxPatterns: []ir.PatternString{ + {Line: 766, Value: "strings.Compare($s1, $s2) == -1"}, + {Line: 767, Value: "strings.Compare($s1, $s2) < 0"}, + }, + ReportTemplate: "suggestion: $s1 < $s2", + SuggestTemplate: "$s1 < $s2", + }, + { + Line: 770, + SyntaxPatterns: []ir.PatternString{ + {Line: 770, Value: "strings.Compare($s1, $s2) == 1"}, + {Line: 771, Value: "strings.Compare($s1, $s2) > 0"}, + }, + ReportTemplate: "suggestion: $s1 > $s2", + SuggestTemplate: "$s1 > $s2", + }, + }, + }, + }, +} + 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 deleted file mode 100644 index a08ef0a5c..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go +++ /dev/null @@ -1,72 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/astfmt" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&sloppyLenChecker{ctx: ctx}), nil - }) -} - -type sloppyLenChecker struct { - astwalk.WalkHandler - ctx *linter.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/sloppyTypeAssert_checker.go b/vendor/github.com/go-critic/go-critic/checkers/sloppyTypeAssert_checker.go index 39b968898..554197768 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/sloppyTypeAssert_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/sloppyTypeAssert_checker.go @@ -12,15 +12,15 @@ import ( func init() { var info linter.CheckerInfo info.Name = "sloppyTypeAssert" - info.Tags = []string{"diagnostic", "experimental"} + info.Tags = []string{"diagnostic"} info.Summary = "Detects redundant type assertions" info.Before = ` -function f(r io.Reader) interface{} { +func f(r io.Reader) interface{} { return r.(interface{}) } ` info.After = ` -function f(r io.Reader) interface{} { +func f(r io.Reader) interface{} { return r } ` @@ -48,28 +48,8 @@ func (c *sloppyTypeAssertChecker) VisitExpr(expr ast.Expr) { c.warnIdentical(expr) return } - - toIface, ok := toType.Underlying().(*types.Interface) - if !ok { - return - } - - switch { - case toIface.Empty(): - c.warnEmpty(expr) - case types.Implements(fromType, toIface): - c.warnImplements(expr, assert.X) - } } func (c *sloppyTypeAssertChecker) warnIdentical(cause ast.Expr) { c.ctx.Warn(cause, "type assertion from/to types are identical") } - -func (c *sloppyTypeAssertChecker) warnEmpty(cause ast.Expr) { - c.ctx.Warn(cause, "type assertion to interface{} may be redundant") -} - -func (c *sloppyTypeAssertChecker) warnImplements(cause, val ast.Expr) { - c.ctx.Warn(cause, "type assertion may be redundant as %s always implements selected interface", val) -} 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 deleted file mode 100644 index bb9f16c07..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go +++ /dev/null @@ -1,47 +0,0 @@ -package checkers - -import ( - "go/ast" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/typep" -) - -func init() { - var info linter.CheckerInfo - info.Name = "stringXbytes" - info.Tags = []string{"style"} - 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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&stringXbytes{ctx: ctx}), nil - }) -} - -type stringXbytes struct { - astwalk.WalkHandler - ctx *linter.CheckerContext -} - -func (c *stringXbytes) VisitExpr(expr ast.Expr) { - x, ok := expr.(*ast.CallExpr) - if !ok || qualifiedName(x.Fun) != "copy" || len(x.Args) != 2 { - return - } - - src := x.Args[1] - - byteCast, ok := src.(*ast.CallExpr) - if ok && typep.IsTypeExpr(c.ctx.TypesInfo, byteCast.Fun) && - typep.HasStringProp(c.ctx.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 deleted file mode 100644 index 0501a0ba1..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go +++ /dev/null @@ -1,49 +0,0 @@ -package checkers - -import ( - "go/ast" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForStmt(&switchTrueChecker{ctx: ctx}), nil - }) -} - -type switchTrueChecker struct { - astwalk.WalkHandler - ctx *linter.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/todoCommentWithoutDetail_checker.go b/vendor/github.com/go-critic/go-critic/checkers/todoCommentWithoutDetail_checker.go new file mode 100644 index 000000000..5ec2881b4 --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/checkers/todoCommentWithoutDetail_checker.go @@ -0,0 +1,50 @@ +package checkers + +import ( + "go/ast" + "regexp" + + "github.com/go-critic/go-critic/checkers/internal/astwalk" + "github.com/go-critic/go-critic/framework/linter" +) + +func init() { + var info linter.CheckerInfo + info.Name = "todoCommentWithoutDetail" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Summary = "Detects TODO comments without detail/assignee" + info.Before = ` +// TODO +fiiWithCtx(nil, a, b) +` + info.After = ` +// TODO(admin): pass context.TODO() instead of nil +fiiWithCtx(nil, a, b) +` + collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { + visitor := &todoCommentWithoutCodeChecker{ + ctx: ctx, + regex: regexp.MustCompile(`^(//|/\*)?\s*(TODO|FIX|FIXME|BUG)\s*(\*/)?$`), + } + return astwalk.WalkerForComment(visitor), nil + }) +} + +type todoCommentWithoutCodeChecker struct { + astwalk.WalkHandler + ctx *linter.CheckerContext + regex *regexp.Regexp +} + +func (c *todoCommentWithoutCodeChecker) VisitComment(cg *ast.CommentGroup) { + for _, comment := range cg.List { + if c.regex.MatchString(comment.Text) { + c.warn(cg) + break + } + } +} + +func (c *todoCommentWithoutCodeChecker) warn(cause ast.Node) { + c.ctx.Warn(cause, "may want to add detail/assignee to this TODO/FIXME/BUG comment") +} diff --git a/vendor/github.com/go-critic/go-critic/checkers/tooManyResults_checker.go b/vendor/github.com/go-critic/go-critic/checkers/tooManyResults_checker.go new file mode 100644 index 000000000..4d4dcc26e --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/checkers/tooManyResults_checker.go @@ -0,0 +1,54 @@ +package checkers + +import ( + "go/ast" + "go/types" + + "github.com/go-critic/go-critic/checkers/internal/astwalk" + "github.com/go-critic/go-critic/framework/linter" +) + +func init() { + var info linter.CheckerInfo + info.Name = "tooManyResultsChecker" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Params = linter.CheckerParams{ + "maxResults": { + Value: 5, + Usage: "maximum number of results", + }, + } + info.Summary = "Detects function with too many results" + info.Before = `func fn() (a, b, c, d float32, _ int, _ bool)` + info.After = `func fn() (resultStruct, bool)` + + collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { + c := astwalk.WalkerForFuncDecl(&tooManyResultsChecker{ + ctx: ctx, + maxParams: info.Params.Int("maxResults"), + }) + return c, nil + }) +} + +type tooManyResultsChecker struct { + astwalk.WalkHandler + ctx *linter.CheckerContext + maxParams int +} + +func (c *tooManyResultsChecker) VisitFuncDecl(decl *ast.FuncDecl) { + typ := c.ctx.TypeOf(decl.Name) + sig, ok := typ.(*types.Signature) + if !ok { + return + } + + if count := sig.Results().Len(); count > c.maxParams { + c.warn(decl) + } +} + +func (c *tooManyResultsChecker) warn(n ast.Node) { + c.ctx.Warn(n, "function has more than %d results, consider to simplify the function", c.maxParams) +} 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 index cd2346c78..9d40c2b63 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go @@ -96,8 +96,14 @@ func (c *truncateCmpChecker) checkCmp(cmpX, cmpY ast.Expr) { return } - xsize := c.ctx.SizesInfo.Sizeof(xtyp) - ysize := c.ctx.SizesInfo.Sizeof(ytyp) + xsize, ok := c.ctx.SizeOf(xtyp) + if !ok { + return + } + ysize, ok := c.ctx.SizeOf(ytyp) + if !ok { + return + } if xsize <= ysize { return } diff --git a/vendor/github.com/go-critic/go-critic/checkers/typeDefFirst_checker.go b/vendor/github.com/go-critic/go-critic/checkers/typeDefFirst_checker.go index 491e71dfd..bc59eef1c 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/typeDefFirst_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/typeDefFirst_checker.go @@ -6,6 +6,7 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" + "golang.org/x/exp/typeparams" ) func init() { @@ -78,6 +79,10 @@ func (c *typeDefFirstChecker) receiverType(e ast.Expr) string { return c.receiverType(e.X) case *ast.Ident: return e.Name + case *ast.IndexExpr: + return c.receiverType(e.X) + case *typeparams.IndexListExpr: + return c.receiverType(e.X) default: panic("unreachable") } 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 index 6bbec5037..1e11e4937 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go @@ -74,7 +74,7 @@ func (c *typeSwitchVarChecker) checkTypeSwitch(root *ast.TypeSwitchStmt) { // 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 { + assert2 := lintutil.FindNode(stmt, nil, func(x ast.Node) bool { return astequal.Node(&assert1, x) }) if object == c.ctx.TypesInfo.ObjectOf(identOf(assert2)) { 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 index a3f02e14c..cd8e04337 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go @@ -4,11 +4,9 @@ import ( "go/ast" "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/checkers/internal/lintutil" "github.com/go-critic/go-critic/framework/linter" "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/astp" - "golang.org/x/tools/go/ast/astutil" + "github.com/go-toolsmith/astequal" ) func init() { @@ -29,58 +27,69 @@ type typeUnparenChecker struct { ctx *linter.CheckerContext } -func (c *typeUnparenChecker) VisitTypeExpr(x ast.Expr) { - switch x := x.(type) { +func (c *typeUnparenChecker) VisitTypeExpr(e ast.Expr) { + switch e := e.(type) { case *ast.ParenExpr: - switch x.X.(type) { + switch e.X.(type) { case *ast.StructType: - c.ctx.Warn(x, "could simplify (struct{...}) to struct{...}") + c.ctx.Warn(e, "could simplify (struct{...}) to struct{...}") case *ast.InterfaceType: - c.ctx.Warn(x, "could simplify (interface{...}) to interface{...}") + c.ctx.Warn(e, "could simplify (interface{...}) to interface{...}") default: - c.warn(x, c.unparenExpr(astcopy.Expr(x))) + c.checkType(e) } - 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))) + c.checkType(e) } } -func (c *typeUnparenChecker) hasParens(x ast.Expr) bool { - return lintutil.ContainsNode(x, astp.IsParenExpr) +func (c *typeUnparenChecker) checkType(e ast.Expr) { + noParens := c.removeRedundantParens(astcopy.Expr(e)) + if !astequal.Expr(e, noParens) { + c.warn(e, noParens) + } + c.SkipChilds = true } -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) +func (c *typeUnparenChecker) removeRedundantParens(e ast.Expr) ast.Expr { + switch e := e.(type) { + case *ast.ParenExpr: + return c.removeRedundantParens(e.X) + case *ast.ArrayType: + e.Elt = c.removeRedundantParens(e.Elt) + case *ast.StarExpr: + e.X = c.removeRedundantParens(e.X) + case *ast.TypeAssertExpr: + e.Type = c.removeRedundantParens(e.Type) + case *ast.FuncType: + for _, field := range e.Params.List { + field.Type = c.removeRedundantParens(field.Type) + } + if e.Results != nil { + for _, field := range e.Results.List { + field.Type = c.removeRedundantParens(field.Type) + } } - return true - }).(ast.Expr) + case *ast.MapType: + e.Key = c.removeRedundantParens(e.Key) + e.Value = c.removeRedundantParens(e.Value) + case *ast.ChanType: + if valueWithParens, ok := e.Value.(*ast.ParenExpr); ok { + if nestedChan, ok := valueWithParens.X.(*ast.ChanType); ok { + const anyDir = ast.SEND | ast.RECV + if nestedChan.Dir != anyDir || e.Dir != anyDir { + valueWithParens.X = c.removeRedundantParens(valueWithParens.X) + return e + } + } + } + e.Value = c.removeRedundantParens(e.Value) + } + return e } 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/unlabelStmt_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go index fab864ec5..bcca24d2a 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go @@ -87,6 +87,7 @@ func (c *unlabelStmtChecker) VisitStmt(stmt ast.Stmt) { // 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. + // Exceptions: select statements with a labeled "continue" are ignored. if c.isLoop(labeled.Stmt) { body := c.blockStmtOf(labeled.Stmt) if len(body.List) == 0 { @@ -96,11 +97,21 @@ func (c *unlabelStmtChecker) VisitStmt(stmt ast.Stmt) { 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 - }) + br := lintutil.FindNode(c.blockStmtOf(last), + func(n ast.Node) bool { + switch n.(type) { + case *ast.SelectStmt: + return false + default: + return true + } + }, + 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) } 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 index 72807ddbf..6cbdfdfd0 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go @@ -6,6 +6,7 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/framework/linter" + "github.com/go-toolsmith/astp" ) func init() { @@ -32,12 +33,19 @@ type unnecessaryBlockChecker struct { ctx *linter.CheckerContext } -func (c *unnecessaryBlockChecker) VisitStmtList(statements []ast.Stmt) { +func (c *unnecessaryBlockChecker) VisitStmtList(x ast.Node, 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. + if (astp.IsCaseClause(x) || astp.IsCommClause(x)) && len(statements) == 1 { + if _, ok := statements[0].(*ast.BlockStmt); ok { + c.ctx.Warn(statements[0], "case statement doesn't require a block statement") + return + } + } + for _, stmt := range statements { stmt, ok := stmt.(*ast.BlockStmt) if ok && !c.hasDefinitions(stmt) { 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 deleted file mode 100644 index 26a4de061..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go +++ /dev/null @@ -1,59 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/types" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astequal" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForExpr(&unsliceChecker{ctx: ctx}), nil - }) -} - -type unsliceChecker struct { - astwalk.WalkHandler - ctx *linter.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.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/valSwap_checker.go b/vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go deleted file mode 100644 index d03e11223..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go +++ /dev/null @@ -1,64 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" - "github.com/go-toolsmith/astequal" -) - -func init() { - var info linter.CheckerInfo - info.Name = "valSwap" - info.Tags = []string{"style"} - 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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForStmtList(&valSwapChecker{ctx: ctx}), nil - }) -} - -type valSwapChecker struct { - astwalk.WalkHandler - ctx *linter.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/whyNoLint_checker.go b/vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go index 260039f2b..6829433ea 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go @@ -17,12 +17,11 @@ func init() { Before: `//nolint`, After: `//nolint // reason`, } - re := regexp.MustCompile(`^// *nolint(?::[^ ]+)? *(.*)$`) collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { return astwalk.WalkerForComment(&whyNoLintChecker{ ctx: ctx, - re: re, + re: regexp.MustCompile(`^// *nolint(?::[^ ]+)? *(.*)$`), }), nil }) } 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 deleted file mode 100644 index d474989d0..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go +++ /dev/null @@ -1,229 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - "go/types" - "strings" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcast" -) - -func init() { - var info linter.CheckerInfo - info.Name = "wrapperFunc" - info.Tags = []string{"style"} - 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 *linter.CheckerContext) (linter.FileWalker, error) { - 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"}, - }, - - "draw.DrawMask => draw.Draw": { - {4, "nil"}, - {5, "image.Point{}"}, - }, - } - - 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.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), nil - }) -} - -type wrapperFuncChecker struct { - astwalk.WalkHandler - ctx *linter.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 deleted file mode 100644 index c533d143b..000000000 --- a/vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go +++ /dev/null @@ -1,66 +0,0 @@ -package checkers - -import ( - "go/ast" - "go/token" - - "github.com/go-critic/go-critic/checkers/internal/astwalk" - "github.com/go-critic/go-critic/framework/linter" - "github.com/go-toolsmith/astcopy" - "github.com/go-toolsmith/astp" -) - -func init() { - var info linter.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 *linter.CheckerContext) (linter.FileWalker, error) { - return astwalk.WalkerForLocalExpr(&yodaStyleExprChecker{ctx: ctx}), nil - }) -} - -type yodaStyleExprChecker struct { - astwalk.WalkHandler - ctx *linter.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) -} diff --git a/vendor/github.com/go-critic/go-critic/framework/linter/go_version.go b/vendor/github.com/go-critic/go-critic/framework/linter/go_version.go new file mode 100644 index 000000000..d8091d453 --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/framework/linter/go_version.go @@ -0,0 +1,51 @@ +package linter + +import ( + "fmt" + "strconv" + "strings" +) + +type GoVersion struct { + Major int + Minor int +} + +// GreaterOrEqual performs $v >= $other operation. +// +// In other words, it reports whether $v version constraint can use +// a feature from the $other Go version. +// +// As a special case, Major=0 covers all versions. +func (v GoVersion) GreaterOrEqual(other GoVersion) bool { + if v.Major == 0 { + return true + } + if v.Major == other.Major { + return v.Minor >= other.Minor + } + return v.Major >= other.Major +} + +func ParseGoVersion(version string) (GoVersion, error) { + var result GoVersion + version = strings.TrimPrefix(version, "go") + if version == "" { + return result, nil + } + parts := strings.Split(version, ".") + if len(parts) != 2 { + return result, fmt.Errorf("invalid Go version format: %s", version) + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return result, fmt.Errorf("invalid major version part: %s: %w", parts[0], err) + } + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return result, fmt.Errorf("invalid minor version part: %s: %w", parts[1], err) + } + result.Major = major + result.Minor = minor + return result, nil +} diff --git a/vendor/github.com/go-critic/go-critic/framework/linter/linter.go b/vendor/github.com/go-critic/go-critic/framework/linter/linter.go new file mode 100644 index 000000000..750ff7cd9 --- /dev/null +++ b/vendor/github.com/go-critic/go-critic/framework/linter/linter.go @@ -0,0 +1,334 @@ +package linter + +import ( + "go/ast" + "go/token" + "go/types" + + "github.com/go-toolsmith/astfmt" + "golang.org/x/exp/typeparams" +) + +// CheckerCollection provides additional information for a group of checkers. +type CheckerCollection struct { + // URL is a link for a main source of information on the collection. + URL string +} + +// AddChecker registers a new checker into a checkers pool. +// Constructor is used to create a new checker instance. +// Checker name (defined in CheckerInfo.Name) must be unique. +// +// CheckerInfo.Collection is automatically set to the coll (the receiver). +// +// If checker is never needed, for example if it is disabled, +// constructor will not be called. +func (coll *CheckerCollection) AddChecker(info *CheckerInfo, constructor func(*CheckerContext) (FileWalker, error)) { + if coll == nil { + panic("adding checker to a nil collection") + } + info.Collection = coll + addChecker(info, constructor) +} + +// CheckerParam describes a single checker customizable parameter. +type CheckerParam struct { + // Value holds parameter bound value. + // It might be overwritten by the integrating linter. + // + // Permitted types include: + // - int + // - bool + // - string + Value interface{} + + // Usage gives an overview about what parameter does. + Usage string +} + +// CheckerParams holds all checker-specific parameters. +// +// Provides convenient access to the loosely typed underlying map. +type CheckerParams map[string]*CheckerParam + +// Int lookups pname key in underlying map and type-asserts it to int. +func (params CheckerParams) Int(pname string) int { return params[pname].Value.(int) } + +// Bool lookups pname key in underlying map and type-asserts it to bool. +func (params CheckerParams) Bool(pname string) bool { return params[pname].Value.(bool) } + +// String lookups pname key in underlying map and type-asserts it to string. +func (params CheckerParams) String(pname string) string { return params[pname].Value.(string) } + +// CheckerInfo holds checker metadata and structured documentation. +type CheckerInfo struct { + // Name is a checker name. + Name string + + // Tags is a list of labels that can be used to enable or disable checker. + // Common tags are "experimental" and "performance". + Tags []string + + // Params declares checker-specific parameters. Optional. + Params CheckerParams + + // Summary is a short one sentence description. + // Should not end with a period. + Summary string + + // Details extends summary with additional info. Optional. + Details string + + // Before is a code snippet of code that will violate rule. + Before string + + // After is a code snippet of fixed code that complies to the rule. + After string + + // Note is an optional caution message or advice. + Note string + + // EmbeddedRuleguard tells whether this checker is auto-generated + // from the embedded ruleguard rules. + EmbeddedRuleguard bool + + // Collection establishes a checker-to-collection relationship. + Collection *CheckerCollection +} + +// GetCheckersInfo returns a checkers info list for all registered checkers. +// The slice is sorted by a checker name. +// +// Info objects can be used to instantiate checkers with NewChecker function. +func GetCheckersInfo() []*CheckerInfo { + return getCheckersInfo() +} + +// HasTag reports whether checker described by the info has specified tag. +func (info *CheckerInfo) HasTag(tag string) bool { + for i := range info.Tags { + if info.Tags[i] == tag { + return true + } + } + return false +} + +// Checker is an implementation of a check that is described by the associated info. +type Checker struct { + // Info is an info object that was used to instantiate this checker. + Info *CheckerInfo + + ctx CheckerContext + + fileWalker FileWalker +} + +// Check runs rule checker over file f. +func (c *Checker) Check(f *ast.File) []Warning { + c.ctx.warnings = c.ctx.warnings[:0] + c.fileWalker.WalkFile(f) + return c.ctx.warnings +} + +// QuickFix is our analysis.TextEdit; we're using it here to avoid +// direct analysis package dependency for now. +type QuickFix struct { + From token.Pos + To token.Pos + Replacement []byte +} + +// Warning represents issue that is found by checker. +type Warning struct { + // Node is an AST node that caused warning to trigger. + // Can be used to obtain proper error location. + Node ast.Node + + // Text is warning message without source location info. + Text string + + // Suggestion is a quick fix for a given problem. + // QuickFix is analysis.TextEdit and can be used to + // construct an analysis.SuggestedFix object. + // + // For convenience, there is Warning.HasQuickFix() method + // that reports whether Suggestion has something meaningful. + Suggestion QuickFix +} + +// HasQuickFix reports whether this warning has a suggested fix. +func (warn Warning) HasQuickFix() bool { + return warn.Suggestion.Replacement != nil +} + +// NewChecker returns initialized checker identified by an info. +// info must be non-nil. +// Returns an error if info describes a checker that was not properly registered, +// or if checker fails to initialize. +func NewChecker(ctx *Context, info *CheckerInfo) (*Checker, error) { + return newChecker(ctx, info) +} + +// Context is a readonly state shared among every checker. +type Context struct { + // TypesInfo carries parsed packages types information. + TypesInfo *types.Info + + // SizesInfo carries alignment and type size information. + // Arch-dependent. + SizesInfo types.Sizes + + // GoVersion is a target Go version. + GoVersion GoVersion + + // FileSet is a file set that was used during the program loading. + FileSet *token.FileSet + + // Pkg describes package that is being checked. + Pkg *types.Package + + // Filename is a currently checked file name. + Filename string + + // Require records what optional resources are required + // by the checkers set that use this context. + // + // Every require fields makes associated context field + // to be properly initialized. + // For example, Context.require.PkgObjects => Context.PkgObjects. + Require struct { + PkgObjects bool + PkgRenames bool + } + + // PkgObjects stores all imported packages and their local names. + PkgObjects map[*types.PkgName]string + + // PkgRenames maps package path to its local renaming. + // Contains no entries for packages that were imported without + // explicit local names. + PkgRenames map[string]string +} + +// NewContext returns new shared context to be used by every checker. +// +// All data carried by the context is readonly for checkers, +// but can be modified by the integrating application. +func NewContext(fset *token.FileSet, sizes types.Sizes) *Context { + return &Context{ + FileSet: fset, + SizesInfo: sizes, + TypesInfo: &types.Info{}, + } +} + +// SetGoVersion adjust the target Go language version. +// +// The format is like "1.5", "1.8", etc. +// It's permitted to have "go" prefix (e.g. "go1.5"). +// +// Empty string (the default) means that we make no +// Go version assumptions and (like gocritic does) behave +// like all features are available. To make gocritic +// more conservative, the upper Go version level should be adjusted. +func (c *Context) SetGoVersion(version string) { + v, err := ParseGoVersion(version) + if err != nil { + panic(err) + } + c.GoVersion = v +} + +// SetPackageInfo sets package-related metadata. +// +// Must be called for every package being checked. +func (c *Context) SetPackageInfo(info *types.Info, pkg *types.Package) { + if info != nil { + // We do this kind of assignment to avoid + // changing c.typesInfo field address after + // every re-assignment. + *c.TypesInfo = *info + } + c.Pkg = pkg +} + +// SetFileInfo sets file-related metadata. +// +// Must be called for every source code file being checked. +func (c *Context) SetFileInfo(name string, f *ast.File) { + c.Filename = name + if c.Require.PkgObjects { + resolvePkgObjects(c, f) + } + if c.Require.PkgRenames { + resolvePkgRenames(c, f) + } +} + +// CheckerContext is checker-local context copy. +// Fields that are not from Context itself are writeable. +type CheckerContext struct { + *Context + + // printer used to format warning text. + printer *astfmt.Printer + + warnings []Warning +} + +// Warn adds a Warning to checker output. +func (ctx *CheckerContext) Warn(node ast.Node, format string, args ...interface{}) { + ctx.warnings = append(ctx.warnings, Warning{ + Text: ctx.printer.Sprintf(format, args...), + Node: node, + }) +} + +// WarnFixable emits a warning with a fix suggestion provided by the caller. +func (ctx *CheckerContext) WarnFixable(node ast.Node, fix QuickFix, format string, args ...interface{}) { + ctx.warnings = append(ctx.warnings, Warning{ + Text: ctx.printer.Sprintf(format, args...), + Node: node, + Suggestion: fix, + }) +} + +// UnknownType is a special sentinel value that is returned from the CheckerContext.TypeOf +// method instead of the nil type. +var UnknownType types.Type = types.Typ[types.Invalid] + +// TypeOf returns the type of expression x. +// +// Unlike TypesInfo.TypeOf, it never returns nil. +// Instead, it returns the Invalid type as a sentinel UnknownType value. +func (ctx *CheckerContext) TypeOf(x ast.Expr) types.Type { + typ := ctx.TypesInfo.TypeOf(x) + if typ != nil { + return typ + } + // Usually it means that some incorrect type info was loaded + // or the analyzed package was only partially (?) correct. + // To avoid nil pointer panics we can return a sentinel value + // that will fail most type assertions as well as kind checks + // (if the call side expects a *types.Basic). + return UnknownType +} + +// SizeOf returns the size of the typ in bytes. +// +// Unlike SizesInfo.SizeOf, it will not panic on generic types. +func (ctx *CheckerContext) SizeOf(typ types.Type) (int64, bool) { + if _, ok := typ.(*typeparams.TypeParam); ok { + return 0, false + } + return ctx.SizesInfo.Sizeof(typ), true +} + +// FileWalker is an interface every checker should implement. +// +// The WalkFile method is executed for every Go file inside the +// package that is being checked. +type FileWalker interface { + WalkFile(*ast.File) +} diff --git a/vendor/github.com/go-critic/go-critic/framework/linter/lintpack.go b/vendor/github.com/go-critic/go-critic/framework/linter/lintpack.go deleted file mode 100644 index 5c8662c69..000000000 --- a/vendor/github.com/go-critic/go-critic/framework/linter/lintpack.go +++ /dev/null @@ -1,269 +0,0 @@ -package linter - -import ( - "go/ast" - "go/token" - "go/types" - - "github.com/go-toolsmith/astfmt" -) - -// CheckerCollection provides additional information for a group of checkers. -type CheckerCollection struct { - // URL is a link for a main source of information on the collection. - URL string -} - -// AddChecker registers a new checker into a checkers pool. -// Constructor is used to create a new checker instance. -// Checker name (defined in CheckerInfo.Name) must be unique. -// -// CheckerInfo.Collection is automatically set to the coll (the receiver). -// -// If checker is never needed, for example if it is disabled, -// constructor will not be called. -func (coll *CheckerCollection) AddChecker(info *CheckerInfo, constructor func(*CheckerContext) (FileWalker, error)) { - if coll == nil { - panic("adding checker to a nil collection") - } - info.Collection = coll - addChecker(info, constructor) -} - -// CheckerParam describes a single checker customizable parameter. -type CheckerParam struct { - // Value holds parameter bound value. - // It might be overwritten by the integrating linter. - // - // Permitted types include: - // - int - // - bool - // - string - Value interface{} - - // Usage gives an overview about what parameter does. - Usage string -} - -// CheckerParams holds all checker-specific parameters. -// -// Provides convenient access to the loosely typed underlying map. -type CheckerParams map[string]*CheckerParam - -// Int lookups pname key in underlying map and type-asserts it to int. -func (params CheckerParams) Int(pname string) int { return params[pname].Value.(int) } - -// Bool lookups pname key in underlying map and type-asserts it to bool. -func (params CheckerParams) Bool(pname string) bool { return params[pname].Value.(bool) } - -// String lookups pname key in underlying map and type-asserts it to string. -func (params CheckerParams) String(pname string) string { return params[pname].Value.(string) } - -// CheckerInfo holds checker metadata and structured documentation. -type CheckerInfo struct { - // Name is a checker name. - Name string - - // Tags is a list of labels that can be used to enable or disable checker. - // Common tags are "experimental" and "performance". - Tags []string - - // Params declares checker-specific parameters. Optional. - Params CheckerParams - - // Summary is a short one sentence description. - // Should not end with a period. - Summary string - - // Details extends summary with additional info. Optional. - Details string - - // Before is a code snippet of code that will violate rule. - Before string - - // After is a code snippet of fixed code that complies to the rule. - After string - - // Note is an optional caution message or advice. - Note string - - // Collection establishes a checker-to-collection relationship. - Collection *CheckerCollection -} - -// GetCheckersInfo returns a checkers info list for all registered checkers. -// The slice is sorted by a checker name. -// -// Info objects can be used to instantiate checkers with NewChecker function. -func GetCheckersInfo() []*CheckerInfo { - return getCheckersInfo() -} - -// HasTag reports whether checker described by the info has specified tag. -func (info *CheckerInfo) HasTag(tag string) bool { - for i := range info.Tags { - if info.Tags[i] == tag { - return true - } - } - return false -} - -// Checker is an implementation of a check that is described by the associated info. -type Checker struct { - // Info is an info object that was used to instantiate this checker. - Info *CheckerInfo - - ctx CheckerContext - - fileWalker FileWalker -} - -// Check runs rule checker over file f. -func (c *Checker) Check(f *ast.File) []Warning { - c.ctx.warnings = c.ctx.warnings[:0] - c.fileWalker.WalkFile(f) - return c.ctx.warnings -} - -// Warning represents issue that is found by checker. -type Warning struct { - // Node is an AST node that caused warning to trigger. - // Can be used to obtain proper error location. - Node ast.Node - - // Text is warning message without source location info. - Text string -} - -// NewChecker returns initialized checker identified by an info. -// info must be non-nil. -// Returns an error if info describes a checker that was not properly registered, -// or if checker fails to initialize. -func NewChecker(ctx *Context, info *CheckerInfo) (*Checker, error) { - return newChecker(ctx, info) -} - -// Context is a readonly state shared among every checker. -type Context struct { - // TypesInfo carries parsed packages types information. - TypesInfo *types.Info - - // SizesInfo carries alignment and type size information. - // Arch-dependent. - SizesInfo types.Sizes - - // FileSet is a file set that was used during the program loading. - FileSet *token.FileSet - - // Pkg describes package that is being checked. - Pkg *types.Package - - // Filename is a currently checked file name. - Filename string - - // Require records what optional resources are required - // by the checkers set that use this context. - // - // Every require fields makes associated context field - // to be properly initialized. - // For example, Context.require.PkgObjects => Context.PkgObjects. - Require struct { - PkgObjects bool - PkgRenames bool - } - - // PkgObjects stores all imported packages and their local names. - PkgObjects map[*types.PkgName]string - - // PkgRenames maps package path to its local renaming. - // Contains no entries for packages that were imported without - // explicit local names. - PkgRenames map[string]string -} - -// NewContext returns new shared context to be used by every checker. -// -// All data carried by the context is readonly for checkers, -// but can be modified by the integrating application. -func NewContext(fset *token.FileSet, sizes types.Sizes) *Context { - return &Context{ - FileSet: fset, - SizesInfo: sizes, - TypesInfo: &types.Info{}, - } -} - -// SetPackageInfo sets package-related metadata. -// -// Must be called for every package being checked. -func (c *Context) SetPackageInfo(info *types.Info, pkg *types.Package) { - if info != nil { - // We do this kind of assignment to avoid - // changing c.typesInfo field address after - // every re-assignment. - *c.TypesInfo = *info - } - c.Pkg = pkg -} - -// SetFileInfo sets file-related metadata. -// -// Must be called for every source code file being checked. -func (c *Context) SetFileInfo(name string, f *ast.File) { - c.Filename = name - if c.Require.PkgObjects { - resolvePkgObjects(c, f) - } - if c.Require.PkgRenames { - resolvePkgRenames(c, f) - } -} - -// CheckerContext is checker-local context copy. -// Fields that are not from Context itself are writeable. -type CheckerContext struct { - *Context - - // printer used to format warning text. - printer *astfmt.Printer - - warnings []Warning -} - -// Warn adds a Warning to checker output. -func (ctx *CheckerContext) Warn(node ast.Node, format string, args ...interface{}) { - ctx.warnings = append(ctx.warnings, Warning{ - Text: ctx.printer.Sprintf(format, args...), - Node: node, - }) -} - -// UnknownType is a special sentinel value that is returned from the CheckerContext.TypeOf -// method instead of the nil type. -var UnknownType types.Type = types.Typ[types.Invalid] - -// TypeOf returns the type of expression x. -// -// Unlike TypesInfo.TypeOf, it never returns nil. -// Instead, it returns the Invalid type as a sentinel UnknownType value. -func (ctx *CheckerContext) TypeOf(x ast.Expr) types.Type { - typ := ctx.TypesInfo.TypeOf(x) - if typ != nil { - return typ - } - // Usually it means that some incorrect type info was loaded - // or the analyzed package was only partially (?) correct. - // To avoid nil pointer panics we can return a sentinel value - // that will fail most type assertions as well as kind checks - // (if the call side expects a *types.Basic). - return UnknownType -} - -// FileWalker is an interface every checker should implement. -// -// The WalkFile method is executed for every Go file inside the -// package that is being checked. -type FileWalker interface { - WalkFile(*ast.File) -} -- cgit mrf-deployment