aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/go-critic
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2022-09-05 14:27:54 +0200
committerGitHub <noreply@github.com>2022-09-05 12:27:54 +0000
commitb2f2446b46bf02821d90ebedadae2bf7ae0e880e (patch)
tree923cf42842918d6bebca1d6bbdc08abed54d274d /vendor/github.com/go-critic
parente6654faff4bcca4be92e9a8596fd4b77f747c39e (diff)
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
Diffstat (limited to 'vendor/github.com/go-critic')
-rw-r--r--vendor/github.com/go-critic/go-critic/LICENSE3
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go97
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go102
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go63
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/badLock_checker.go116
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go12
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go72
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/deferInLoop_checker.go70
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/deferUnlambda_checker.go94
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go25
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go133
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go17
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/embedded_rules.go105
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go58
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go87
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go65
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go8
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go50
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/local_def_visitor.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/stmt_list_walker.go6
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/type_expr_walker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/astwalk/visitor.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go32
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go21
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go65
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/offBy1_checker.go66
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/paramTypeCombine_checker.go11
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/rangeExprCopy_checker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/rangeValCopy_checker.go6
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/regexpMust_checker.go47
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/regexpSimplify_checker.go7
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/ruleguard_checker.go246
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go2367
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/sloppyLen_checker.go72
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/sloppyTypeAssert_checker.go26
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/stringXbytes_checker.go47
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/switchTrue_checker.go49
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/todoCommentWithoutDetail_checker.go50
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/tooManyResults_checker.go54
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/truncateCmp_checker.go10
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/typeDefFirst_checker.go5
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/typeSwitchVar_checker.go2
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go87
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unlabelStmt_checker.go21
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unnecessaryBlock_checker.go10
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/unslice_checker.go59
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/valSwap_checker.go64
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/whyNoLint_checker.go3
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/wrapperFunc_checker.go229
-rw-r--r--vendor/github.com/go-critic/go-critic/checkers/yodaStyleExpr_checker.go66
-rw-r--r--vendor/github.com/go-critic/go-critic/framework/linter/go_version.go51
-rw-r--r--vendor/github.com/go-critic/go-critic/framework/linter/linter.go (renamed from vendor/github.com/go-critic/go-critic/framework/linter/lintpack.go)65
56 files changed, 3247 insertions, 1790 deletions
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(&regexpMustChecker{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: "<all>",
+ 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 != "<all>" {
+ 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 == "<all>" || 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/lintpack.go b/vendor/github.com/go-critic/go-critic/framework/linter/linter.go
index 5c8662c69..750ff7cd9 100644
--- a/vendor/github.com/go-critic/go-critic/framework/linter/lintpack.go
+++ b/vendor/github.com/go-critic/go-critic/framework/linter/linter.go
@@ -6,6 +6,7 @@ import (
"go/types"
"github.com/go-toolsmith/astfmt"
+ "golang.org/x/exp/typeparams"
)
// CheckerCollection provides additional information for a group of checkers.
@@ -87,6 +88,10 @@ type CheckerInfo struct {
// 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
}
@@ -126,6 +131,14 @@ func (c *Checker) Check(f *ast.File) []Warning {
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.
@@ -134,6 +147,19 @@ type Warning struct {
// 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.
@@ -153,6 +179,9 @@ type Context struct {
// 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
@@ -194,6 +223,23 @@ func NewContext(fset *token.FileSet, sizes types.Sizes) *Context {
}
}
+// 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.
@@ -239,6 +285,15 @@ func (ctx *CheckerContext) Warn(node ast.Node, format string, args ...interface{
})
}
+// 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]
@@ -260,6 +315,16 @@ func (ctx *CheckerContext) TypeOf(x ast.Expr) types.Type {
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