diff --git a/go.mod b/go.mod index 5c8e3472a79d..4589957cf20e 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,12 @@ require ( github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/davecgh/go-spew v1.1.0 // indirect github.com/fatih/color v1.6.0 - github.com/go-critic/checkers v0.0.0-20181031185637-879460b6c936 - github.com/go-lintpack/lintpack v0.0.0-20181105152233-7ff0297828fc + github.com/go-critic/checkers v0.0.0-20181204210945-97246d3b3c67 + github.com/go-lintpack/lintpack v0.5.1 github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-toolsmith/astcast v0.0.0-20181028201508-b7a89ed70af1 // indirect github.com/go-toolsmith/astcopy v0.0.0-20180903214859-79b422d080c4 // indirect - github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6 // indirect - github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086 // indirect - github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30 // indirect + github.com/go-toolsmith/pkgload v0.0.0-20181120203407-5122569a890b // indirect github.com/go-toolsmith/strparse v0.0.0-20180903215201-830b6daa1241 // indirect github.com/go-toolsmith/typep v0.0.0-20181030061450-d63dc7650676 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -66,7 +64,7 @@ require ( github.com/spf13/viper v1.0.2 github.com/stretchr/testify v1.2.1 golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect - golang.org/x/tools v0.0.0-20180831211245-6c7e314b6563 + golang.org/x/tools v0.0.0-20181220024903-92cdcd90bf52 gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect gopkg.in/yaml.v2 v2.2.1 diff --git a/go.sum b/go.sum index 44eee35d8581..34eceba2d2ef 100644 --- a/go.sum +++ b/go.sum @@ -10,10 +10,10 @@ github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-critic/checkers v0.0.0-20181031185637-879460b6c936 h1:z3p5plDo+NvU4XrN1qn3V7t46REsIchDEb2Czj0/FyA= -github.com/go-critic/checkers v0.0.0-20181031185637-879460b6c936/go.mod h1:Cg5JCP9M6m93z6fecpRcVgD2lZf2RvPtb85ldjiShZc= -github.com/go-lintpack/lintpack v0.0.0-20181105152233-7ff0297828fc h1:1WRIbHnQimY5g784lRFOa93tWZdssXVtOp/E2use7as= -github.com/go-lintpack/lintpack v0.0.0-20181105152233-7ff0297828fc/go.mod h1:1zNnkB6R9YD0l/ZxcCk0oOFbL9aEs6UtHeOyL+cpoHk= +github.com/go-critic/checkers v0.0.0-20181204210945-97246d3b3c67 h1:AhL5n4pH/qzefJ64+0RbymXZSBsvgbBaVJQCcjFaJPw= +github.com/go-critic/checkers v0.0.0-20181204210945-97246d3b3c67/go.mod h1:Cg5JCP9M6m93z6fecpRcVgD2lZf2RvPtb85ldjiShZc= +github.com/go-lintpack/lintpack v0.5.1 h1:v5D/csM90cu5PANqkj1JcNZGX/mrr3Z2Wu7Q8KuFd9M= +github.com/go-lintpack/lintpack v0.5.1/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-toolsmith/astcast v0.0.0-20181028201508-b7a89ed70af1 h1:h+1eMw+tZAlgTVclcVN0/rdPaBI/RUzG0peblT6df+Q= @@ -26,6 +26,9 @@ github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086 h1:EIMuvbE9fbt github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30 h1:zRJPftZJNLPDiOtvYbFRwjSbaJAcVOf80TeEmWGe2kQ= github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v0.0.0-20181120203407-5122569a890b h1:M3arPs33ilAwsEZcEzBcZGj5XjNLo1Qohmg0IkSmrnA= +github.com/go-toolsmith/pkgload v0.0.0-20181120203407-5122569a890b/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= github.com/go-toolsmith/strparse v0.0.0-20180903215201-830b6daa1241 h1:ZRDeQioMGTBLeJxcPxXfFifEUgYxzR7fXw7w2WR+1bo= github.com/go-toolsmith/strparse v0.0.0-20180903215201-830b6daa1241/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/typep v0.0.0-20181030061450-d63dc7650676 h1:6Qrsp0+25KEkaS2bB26UE0giFgRrIc8mYXboDL5OVMA= @@ -81,6 +84,8 @@ github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSW github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/golangci/unparam v0.0.0-20180902112548-7ad9dbcccc16 h1:QURX/XMP2uJUzzEvfJ291v1snmbJuyznAJLSQVnPyko= github.com/golangci/unparam v0.0.0-20180902112548-7ad9dbcccc16/go.mod h1:KW2L33j82vo0S0U6RP6uUQSuat+0Q457Yf+1mXC98/M= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -89,6 +94,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= @@ -149,8 +155,9 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180826000951-f6ba57429505/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180831211245-6c7e314b6563 h1:O7esB7nCcy+y8pwGEyEYSegt07MF0357oRStgxd2TDo= -golang.org/x/tools v0.0.0-20180831211245-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181220024903-92cdcd90bf52 h1:oOIe9Zzq27JsS/3ACpGF1HwWnWNflZWT/3EvM7mtcEk= +golang.org/x/tools v0.0.0-20181220024903-92cdcd90bf52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/pkg/golinters/gocritic.go b/pkg/golinters/gocritic.go index 4865e9254ad7..eb409dec32aa 100644 --- a/pkg/golinters/gocritic.go +++ b/pkg/golinters/gocritic.go @@ -8,7 +8,6 @@ import ( "path/filepath" "runtime" "runtime/debug" - "strings" "sync" _ "github.com/go-critic/checkers" // this import register checkers @@ -41,8 +40,7 @@ func (lint Gocritic) Run(ctx context.Context, lintCtx *linter.Context) ([]result continue } - params := s.SettingsPerCheck[strings.ToLower(info.Name)] - c := lintpack.NewChecker(lintpackCtx, info, params) + c := lintpack.NewChecker(lintpackCtx, info) enabledCheckers = append(enabledCheckers, c) } diff --git a/test/linters_test.go b/test/linters_test.go index 3f81c82515f9..e8afc0205271 100644 --- a/test/linters_test.go +++ b/test/linters_test.go @@ -57,8 +57,6 @@ func TestTypecheck(t *testing.T) { } func TestGoimportsLocal(t *testing.T) { - t.Skipf("strange travis and go bug, enable it when it will be fixed: https://travis-ci.com/golangci/golangci-lint/jobs/157695743") - sourcePath := filepath.Join(testdataDir, "goimports", "goimports.go") args := []string{ "--disable-all", "--print-issued-lines=false", "--print-linter-name=false", "--out-format=line-number", diff --git a/vendor/github.com/go-critic/checkers/appendAssign_checker.go b/vendor/github.com/go-critic/checkers/appendAssign_checker.go index 2c7d29827a58..47d12f014269 100644 --- a/vendor/github.com/go-critic/checkers/appendAssign_checker.go +++ b/vendor/github.com/go-critic/checkers/appendAssign_checker.go @@ -24,7 +24,7 @@ p.negatives = append(p.negatives, y)` p.positives = append(p.positives, x) p.negatives = append(p.negatives, y)` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&appendAssignChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/appendCombine_checker.go b/vendor/github.com/go-critic/checkers/appendCombine_checker.go index 3d64ab7aa950..9cf91eae14b3 100644 --- a/vendor/github.com/go-critic/checkers/appendCombine_checker.go +++ b/vendor/github.com/go-critic/checkers/appendCombine_checker.go @@ -20,7 +20,7 @@ xs = append(xs, 1) xs = append(xs, 2)` info.After = `xs = append(xs, 1, 2)` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmtList(&appendCombineChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/assignOp_checker.go b/vendor/github.com/go-critic/checkers/assignOp_checker.go index 8c312e1be39d..e4e82c98b1a3 100644 --- a/vendor/github.com/go-critic/checkers/assignOp_checker.go +++ b/vendor/github.com/go-critic/checkers/assignOp_checker.go @@ -8,6 +8,7 @@ import ( "github.com/go-lintpack/lintpack/astwalk" "github.com/go-toolsmith/astcopy" "github.com/go-toolsmith/astequal" + "github.com/go-toolsmith/typep" ) func init() { @@ -18,7 +19,7 @@ func init() { info.Before = `x = x * 2` info.After = `x *= 2` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&assignOpChecker{ctx: ctx}) }) } @@ -34,7 +35,7 @@ func (c *assignOpChecker) VisitStmt(stmt ast.Stmt) { assign.Tok == token.ASSIGN && len(assign.Lhs) == 1 && len(assign.Rhs) == 1 && - isSafeExpr(c.ctx.TypesInfo, assign.Lhs[0]) + typep.SideEffectFree(c.ctx.TypesInfo, assign.Lhs[0]) if !cond { return } diff --git a/vendor/github.com/go-critic/checkers/boolExprSimplify_checker.go b/vendor/github.com/go-critic/checkers/boolExprSimplify_checker.go index a3f9bd590540..7eaa52c40345 100644 --- a/vendor/github.com/go-critic/checkers/boolExprSimplify_checker.go +++ b/vendor/github.com/go-critic/checkers/boolExprSimplify_checker.go @@ -9,6 +9,7 @@ import ( "github.com/go-lintpack/lintpack/astwalk" "github.com/go-toolsmith/astcopy" "github.com/go-toolsmith/astequal" + "github.com/go-toolsmith/typep" "golang.org/x/tools/go/ast/astutil" ) @@ -24,7 +25,7 @@ b := !(x) == !(y)` a := elapsed < expectElapsedMin b := (x) == (y)` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(&boolExprSimplifyChecker{ctx: ctx}) }) } @@ -115,7 +116,9 @@ func (c *boolExprSimplifyChecker) combineChecks(cur *astutil.Cursor) bool { if !astequal.Expr(lhs.X, rhs.X) || !astequal.Expr(lhs.Y, rhs.Y) { return false } - if !isSafeExpr(c.ctx.TypesInfo, lhs.X) || !isSafeExpr(c.ctx.TypesInfo, lhs.Y) { + safe := typep.SideEffectFree(c.ctx.TypesInfo, lhs.X) && + typep.SideEffectFree(c.ctx.TypesInfo, lhs.Y) + if !safe { return false } diff --git a/vendor/github.com/go-critic/checkers/builtinShadow_checker.go b/vendor/github.com/go-critic/checkers/builtinShadow_checker.go new file mode 100644 index 000000000000..f7928f8ff7d4 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/builtinShadow_checker.go @@ -0,0 +1,87 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "builtinShadow" + info.Tags = []string{"style", "opinionated"} + info.Summary = "Detects when predeclared identifiers shadowed in assignments" + info.Before = `len := 10` + info.After = `length := 10` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + builtins := map[string]bool{ + // Types + "bool": true, + "byte": true, + "complex64": true, + "complex128": true, + "error": true, + "float32": true, + "float64": true, + "int": true, + "int8": true, + "int16": true, + "int32": true, + "int64": true, + "rune": true, + "string": true, + "uint": true, + "uint8": true, + "uint16": true, + "uint32": true, + "uint64": true, + "uintptr": true, + + // Constants + "true": true, + "false": true, + "iota": true, + + // Zero value + "nil": true, + + // Functions + "append": true, + "cap": true, + "close": true, + "complex": true, + "copy": true, + "delete": true, + "imag": true, + "len": true, + "make": true, + "new": true, + "panic": true, + "print": true, + "println": true, + "real": true, + "recover": true, + } + c := &builtinShadowChecker{ctx: ctx, builtins: builtins} + return astwalk.WalkerForLocalDef(c, ctx.TypesInfo) + }) +} + +type builtinShadowChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + builtins map[string]bool +} + +func (c *builtinShadowChecker) VisitLocalDef(name astwalk.Name, _ ast.Expr) { + if _, isBuiltin := c.builtins[name.ID.String()]; isBuiltin { + c.warn(name.ID) + } +} + +func (c *builtinShadowChecker) warn(ident *ast.Ident) { + c.ctx.Warn(ident, "shadowing of predeclared identifier: %s", ident) +} diff --git a/vendor/github.com/go-critic/checkers/captLocal_checker.go b/vendor/github.com/go-critic/checkers/captLocal_checker.go new file mode 100644 index 000000000000..b21467ec4b73 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/captLocal_checker.go @@ -0,0 +1,50 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "captLocal" + info.Tags = []string{"style"} + info.Params = lintpack.CheckerParams{ + "paramsOnly": { + Value: true, + Usage: "whether to restrict checker to params only", + }, + } + info.Summary = "Detects capitalized names for local variables" + info.Before = `func f(IN int, OUT *int) (ERR error) {}` + info.After = `func f(in int, out *int) (err error) {}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + c := &captLocalChecker{ctx: ctx} + c.paramsOnly = info.Params.Bool("paramsOnly") + return astwalk.WalkerForLocalDef(c, ctx.TypesInfo) + }) +} + +type captLocalChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + upcaseNames map[string]bool + paramsOnly bool +} + +func (c *captLocalChecker) VisitLocalDef(def astwalk.Name, _ ast.Expr) { + if c.paramsOnly && def.Kind != astwalk.NameParam { + return + } + if ast.IsExported(def.ID.Name) { + c.warn(def.ID) + } +} + +func (c *captLocalChecker) warn(id ast.Node) { + c.ctx.Warn(id, "`%s' should not be capitalized", id) +} diff --git a/vendor/github.com/go-critic/checkers/caseOrder_checker.go b/vendor/github.com/go-critic/checkers/caseOrder_checker.go index c3a861ab4a7a..3da6f5c87d11 100644 --- a/vendor/github.com/go-critic/checkers/caseOrder_checker.go +++ b/vendor/github.com/go-critic/checkers/caseOrder_checker.go @@ -28,7 +28,7 @@ case ast.Expr: fmt.Println("expr") }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&caseOrderChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/checkers.go b/vendor/github.com/go-critic/checkers/checkers.go new file mode 100644 index 000000000000..7cf972944ac3 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/checkers.go @@ -0,0 +1,10 @@ +// Package checkers is a gocritic linter main checkers collection. +package checkers + +import ( + "github.com/go-lintpack/lintpack" +) + +var collection = &lintpack.CheckerCollection{ + URL: "https://github.com/go-critic/checkers", +} diff --git a/vendor/github.com/go-critic/checkers/commentedOutCode_checker.go b/vendor/github.com/go-critic/checkers/commentedOutCode_checker.go new file mode 100644 index 000000000000..77b36ef33bab --- /dev/null +++ b/vendor/github.com/go-critic/checkers/commentedOutCode_checker.go @@ -0,0 +1,119 @@ +package checkers + +import ( + "go/ast" + "go/token" + "regexp" + "strings" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/strparse" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "commentedOutCode" + info.Tags = []string{"diagnostic", "experimental"} + info.Summary = "Detects commented-out code inside function bodies" + info.Before = ` +// fmt.Println("Debugging hard") +foo(1, 2)` + info.After = `foo(1, 2)` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForLocalComment(&commentedOutCodeChecker{ + ctx: ctx, + notQuiteFuncCall: regexp.MustCompile(`\w+\s+\([^)]*\)\s*$`), + }) + }) +} + +type commentedOutCodeChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + notQuiteFuncCall *regexp.Regexp +} + +func (c *commentedOutCodeChecker) VisitLocalComment(cg *ast.CommentGroup) { + s := cg.Text() // Collect text once + + // We do multiple heuristics to avoid false positives. + // Many things can be improved here. + + markers := []string{ + "TODO", // TODO comments with code are permitted. + + // "http://" is interpreted as a label with comment. + // There are other protocols we might want to include. + "http://", + "https://", + + "e.g. ", // Clearly not a "selector expr" (mostly due to extra space) + } + for _, m := range markers { + if strings.Contains(s, m) { + return + } + } + + // Some very short comment that can be skipped. + // Usually triggering on these results in false positive. + // Unless there is a very popular call like print/println. + cond := len(s) < len("quite too short") && + !strings.Contains(s, "print") && + !strings.Contains(s, "fmt.") && + !strings.Contains(s, "log.") + if cond { + return + } + + // Almost looks like a commented-out function call, + // but there is a whitespace between function name and + // parameters list. Skip these to avoid false positives. + if c.notQuiteFuncCall.MatchString(s) { + return + } + + stmt := strparse.Stmt(s) + if stmt == strparse.BadStmt { + return // Most likely not a code + } + + if !c.isPermittedStmt(stmt) { + c.warn(cg) + } +} + +func (c *commentedOutCodeChecker) isPermittedStmt(stmt ast.Stmt) bool { + switch stmt := stmt.(type) { + case *ast.ExprStmt: + return c.isPermittedExpr(stmt.X) + case *ast.LabeledStmt: + return c.isPermittedStmt(stmt.Stmt) + case *ast.DeclStmt: + decl := stmt.Decl.(*ast.GenDecl) + return decl.Tok == token.TYPE + default: + return false + } +} + +func (c *commentedOutCodeChecker) isPermittedExpr(x ast.Expr) bool { + // Permit anything except expressions that can be used + // with complete result discarding. + switch x := x.(type) { + case *ast.CallExpr: + return false + case *ast.UnaryExpr: + // "<-" channel receive is not permitted. + return x.Op != token.ARROW + default: + return true + } +} + +func (c *commentedOutCodeChecker) warn(cause ast.Node) { + c.ctx.Warn(cause, "may want to remove commented-out code") +} diff --git a/vendor/github.com/go-critic/checkers/commentedOutImport_checker.go b/vendor/github.com/go-critic/checkers/commentedOutImport_checker.go new file mode 100644 index 000000000000..5aeb86c07656 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/commentedOutImport_checker.go @@ -0,0 +1,76 @@ +package checkers + +import ( + "go/ast" + "go/token" + "regexp" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "commentedOutImport" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects commented-out imports" + info.Before = ` +import ( + "fmt" + //"os" +)` + info.After = ` +import ( + "fmt" +)` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + const pattern = `(?m)^(?://|/\*)?\s*"([a-zA-Z0-9_/]+)"\s*(?:\*/)?$` + return &commentedOutImportChecker{ + ctx: ctx, + importStringRE: regexp.MustCompile(pattern), + } + }) +} + +type commentedOutImportChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + importStringRE *regexp.Regexp +} + +func (c *commentedOutImportChecker) WalkFile(f *ast.File) { + // TODO(Quasilyte): handle commented-out import spec, + // for example: // import "errors". + + for _, decl := range f.Decls { + decl, ok := decl.(*ast.GenDecl) + if !ok || decl.Tok != token.IMPORT { + // Import decls can only be in the beginning of the file. + // If we've met some other decl, there will be no more + // import decls. + break + } + + // Find comments inside this import decl span. + for _, cg := range f.Comments { + if cg.Pos() > decl.Rparen { + break // Below the decl, stop. + } + if cg.Pos() < decl.Lparen { + continue // Before the decl, skip. + } + + for _, comment := range cg.List { + for _, m := range c.importStringRE.FindAllStringSubmatch(comment.Text, -1) { + c.warn(comment, m[1]) + } + } + } + } +} + +func (c *commentedOutImportChecker) warn(cause ast.Node, path string) { + c.ctx.Warn(cause, "remove commented-out %q import", path) +} diff --git a/vendor/github.com/go-critic/checkers/defaultCaseOrder_checker.go b/vendor/github.com/go-critic/checkers/defaultCaseOrder_checker.go index f0096d78e320..caa0de657201 100644 --- a/vendor/github.com/go-critic/checkers/defaultCaseOrder_checker.go +++ b/vendor/github.com/go-critic/checkers/defaultCaseOrder_checker.go @@ -31,7 +31,7 @@ default: // <- last case (could also be the first one) // ... }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&defaultCaseOrderChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/deprecatedComment_checker.go b/vendor/github.com/go-critic/checkers/deprecatedComment_checker.go index a390deaee922..f8b3e5625c9a 100644 --- a/vendor/github.com/go-critic/checkers/deprecatedComment_checker.go +++ b/vendor/github.com/go-critic/checkers/deprecatedComment_checker.go @@ -21,7 +21,7 @@ func FuncOld() int` // Deprecated: use FuncNew instead func FuncOld() int` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { c := &deprecatedCommentChecker{ctx: ctx} c.commonPatterns = []*regexp.Regexp{ @@ -84,28 +84,34 @@ func (c *deprecatedCommentChecker) VisitDocComment(doc *ast.CommentGroup) { // // TODO(quasilyte): there are also multi-line deprecation comments. - for _, l := range strings.Split(doc.Text(), "\n") { + for _, comment := range doc.List { + if strings.HasPrefix(comment.Text, "/*") { + // TODO(quasilyte): handle multi-line doc comments. + continue + } + l := comment.Text[len("//"):] if len(l) < len("Deprecated: ") { continue } + l = strings.TrimSpace(l) // Check whether someone messed up with a prefix casing. upcase := strings.ToUpper(l) if strings.HasPrefix(upcase, "DEPRECATED: ") && !strings.HasPrefix(l, "Deprecated: ") { - c.warnCasing(doc, l) + c.warnCasing(comment, l) return } // Check is someone used comma instead of a colon. if strings.HasPrefix(l, "Deprecated, ") { - c.warnComma(doc) + c.warnComma(comment) return } // Check for other commonly used patterns. for _, pat := range c.commonPatterns { if pat.MatchString(l) { - c.warnPattern(doc) + c.warnPattern(comment) return } } @@ -113,7 +119,7 @@ func (c *deprecatedCommentChecker) VisitDocComment(doc *ast.CommentGroup) { // Detect some simple typos. for _, prefixWithTypo := range c.commonTypos { if strings.HasPrefix(upcase, prefixWithTypo) { - c.warnTypo(doc, l) + c.warnTypo(comment, l) return } } diff --git a/vendor/github.com/go-critic/checkers/docStub_checker.go b/vendor/github.com/go-critic/checkers/docStub_checker.go new file mode 100644 index 000000000000..1098ecd99695 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/docStub_checker.go @@ -0,0 +1,49 @@ +package checkers + +import ( + "go/ast" + "regexp" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "docStub" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects comments that silence go lint complaints about doc-comment" + info.Before = ` +// Foo ... +func Foo() { +}` + info.After = ` +// (A) - remove the doc-comment stub +func Foo() {} +// (B) - replace it with meaningful comment +// Foo is a demonstration-only function. +func Foo() {}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + c := &docStubChecker{ctx: ctx} + c.badCommentRE = regexp.MustCompile(`//\s?\w+([^a-zA-Z]+|( XXX.?))$`) + return astwalk.WalkerForFuncDecl(c) + }) +} + +type docStubChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + badCommentRE *regexp.Regexp +} + +func (c *docStubChecker) VisitFuncDecl(decl *ast.FuncDecl) { + if decl.Name.IsExported() && decl.Doc != nil && c.badCommentRE.MatchString(decl.Doc.List[0].Text) { + c.warn(decl) + } +} + +func (c *docStubChecker) warn(decl *ast.FuncDecl) { + c.ctx.Warn(decl, "silencing go lint doc-comment warnings is unadvised") +} diff --git a/vendor/github.com/go-critic/checkers/dupArg_checker.go b/vendor/github.com/go-critic/checkers/dupArg_checker.go index 4fba221bdbad..3eb885aa4ed3 100644 --- a/vendor/github.com/go-critic/checkers/dupArg_checker.go +++ b/vendor/github.com/go-critic/checkers/dupArg_checker.go @@ -16,7 +16,7 @@ func init() { info.Before = `copy(dst, dst)` info.After = `copy(dst, src)` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { c := &dupArgChecker{ctx: ctx} // newMatcherFunc returns a function that matches a call if // args[xIndex] and args[yIndex] are equal. @@ -33,6 +33,7 @@ func init() { 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. @@ -61,6 +62,8 @@ func init() { "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, ...)"], @@ -74,6 +77,8 @@ func init() { "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, ...)"], diff --git a/vendor/github.com/go-critic/checkers/dupBranchBody_checker.go b/vendor/github.com/go-critic/checkers/dupBranchBody_checker.go index f6e859d37aed..a13884873c1b 100644 --- a/vendor/github.com/go-critic/checkers/dupBranchBody_checker.go +++ b/vendor/github.com/go-critic/checkers/dupBranchBody_checker.go @@ -26,7 +26,7 @@ if cond { println("cond=false") }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&dupBranchBodyChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/dupCase_checker.go b/vendor/github.com/go-critic/checkers/dupCase_checker.go index 42284210297a..ceffb7535f2a 100644 --- a/vendor/github.com/go-critic/checkers/dupCase_checker.go +++ b/vendor/github.com/go-critic/checkers/dupCase_checker.go @@ -22,7 +22,7 @@ switch x { case ys[0], ys[1], ys[2], ys[3], ys[4]: }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&dupCaseChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/dupSubExpr_checker.go b/vendor/github.com/go-critic/checkers/dupSubExpr_checker.go new file mode 100644 index 000000000000..24bb52434f36 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/dupSubExpr_checker.go @@ -0,0 +1,102 @@ +package checkers + +import ( + "go/ast" + "go/token" + "go/types" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astequal" + "github.com/go-toolsmith/typep" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "dupSubExpr" + info.Tags = []string{"diagnostic"} + info.Summary = "Detects suspicious duplicated sub-expressions" + info.Before = ` +sort.Slice(xs, func(i, j int) bool { + return xs[i].v < xs[i].v // Duplicated index +})` + info.After = ` +sort.Slice(xs, func(i, j int) bool { + return xs[i].v < xs[j].v +})` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + c := &dupSubExprChecker{ctx: ctx} + + ops := []struct { + op token.Token + float bool // Whether float args require special care + }{ + {op: token.LOR}, // x || x + {op: token.LAND}, // x && x + {op: token.OR}, // x | x + {op: token.AND}, // x & x + {op: token.XOR}, // x ^ x + {op: token.LSS}, // x < x + {op: token.GTR}, // x > x + {op: token.AND_NOT}, // x &^ x + {op: token.REM}, // x % x + + {op: token.EQL, float: true}, // x == x + {op: token.NEQ, float: true}, // x != x + {op: token.LEQ, float: true}, // x <= x + {op: token.GEQ, float: true}, // x >= x + {op: token.QUO, float: true}, // x / x + {op: token.SUB, float: true}, // x - x + } + + c.opSet = make(map[token.Token]bool) + c.floatOpsSet = make(map[token.Token]bool) + for _, opInfo := range ops { + c.opSet[opInfo.op] = true + if opInfo.float { + c.floatOpsSet[opInfo.op] = true + } + } + + return astwalk.WalkerForExpr(c) + }) +} + +type dupSubExprChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + // opSet is a set of binary operations that do not make + // sense with duplicated (same) RHS and LHS. + opSet map[token.Token]bool + + floatOpsSet map[token.Token]bool +} + +func (c *dupSubExprChecker) VisitExpr(expr ast.Expr) { + if expr, ok := expr.(*ast.BinaryExpr); ok { + c.checkBinaryExpr(expr) + } +} + +func (c *dupSubExprChecker) checkBinaryExpr(expr *ast.BinaryExpr) { + if !c.opSet[expr.Op] { + return + } + if c.resultIsFloat(expr.X) && c.floatOpsSet[expr.Op] { + return + } + if typep.SideEffectFree(c.ctx.TypesInfo, expr) && c.opSet[expr.Op] && astequal.Expr(expr.X, expr.Y) { + c.warn(expr) + } +} + +func (c *dupSubExprChecker) resultIsFloat(expr ast.Expr) bool { + typ, ok := c.ctx.TypesInfo.TypeOf(expr).(*types.Basic) + return ok && typ.Info()&types.IsFloat != 0 +} + +func (c *dupSubExprChecker) warn(cause *ast.BinaryExpr) { + c.ctx.Warn(cause, "suspicious identical LHS and RHS for `%s` operator", cause.Op) +} diff --git a/vendor/github.com/go-critic/checkers/elseif_checker.go b/vendor/github.com/go-critic/checkers/elseif_checker.go new file mode 100644 index 000000000000..c3a9546bf07f --- /dev/null +++ b/vendor/github.com/go-critic/checkers/elseif_checker.go @@ -0,0 +1,71 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astp" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "elseif" + info.Tags = []string{"style"} + info.Params = lintpack.CheckerParams{ + "skipBalanced": { + Value: true, + Usage: "whether to skip balanced if-else pairs", + }, + } + info.Summary = "Detects else with nested if statement that can be replaced with else-if" + info.Before = ` +if cond1 { +} else { + if x := cond2; x { + } +}` + info.After = ` +if cond1 { +} else if x := cond2; x { +}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + c := &elseifChecker{ctx: ctx} + c.skipBalanced = info.Params.Bool("skipBalanced") + return astwalk.WalkerForStmt(c) + }) +} + +type elseifChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + skipBalanced bool +} + +func (c *elseifChecker) VisitStmt(stmt ast.Stmt) { + if stmt, ok := stmt.(*ast.IfStmt); ok { + elseBody, ok := stmt.Else.(*ast.BlockStmt) + if !ok || len(elseBody.List) != 1 { + return + } + innerIfStmt, ok := elseBody.List[0].(*ast.IfStmt) + if !ok { + return + } + balanced := len(stmt.Body.List) == 1 && + astp.IsIfStmt(stmt.Body.List[0]) + if balanced && c.skipBalanced { + return // Configured to skip balanced statements + } + if innerIfStmt.Else != nil { + return + } + c.warn(stmt.Else) + } +} + +func (c *elseifChecker) warn(cause ast.Node) { + c.ctx.Warn(cause, "can replace 'else {if cond {}}' with 'else if cond {}'") +} diff --git a/vendor/github.com/go-critic/checkers/emptyFallthrough_checker.go b/vendor/github.com/go-critic/checkers/emptyFallthrough_checker.go new file mode 100644 index 000000000000..5908dfa31672 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/emptyFallthrough_checker.go @@ -0,0 +1,70 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "emptyFallthrough" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects fallthrough that can be avoided by using multi case values" + info.Before = `switch kind { +case reflect.Int: + fallthrough +case reflect.Int32: + return Int +}` + info.After = `switch kind { +case reflect.Int, reflect.Int32: + return Int +}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForStmt(&emptyFallthroughChecker{ctx: ctx}) + }) +} + +type emptyFallthroughChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *emptyFallthroughChecker) VisitStmt(stmt ast.Stmt) { + ss, ok := stmt.(*ast.SwitchStmt) + if !ok { + return + } + + prevCaseDefault := false + for i := len(ss.Body.List) - 1; i >= 0; i-- { + if cc, ok := ss.Body.List[i].(*ast.CaseClause); ok { + warn := false + if len(cc.Body) == 1 { + if bs, ok := cc.Body[0].(*ast.BranchStmt); ok && bs.Tok == token.FALLTHROUGH { + warn = true + if prevCaseDefault { + c.warnDefault(bs) + } else { + c.warn(bs) + } + } + } + if !warn { + prevCaseDefault = cc.List == nil + } + } + } +} + +func (c *emptyFallthroughChecker) warnDefault(cause ast.Node) { + c.ctx.Warn(cause, "remove empty case containing only fallthrough to default case") +} + +func (c *emptyFallthroughChecker) warn(cause ast.Node) { + c.ctx.Warn(cause, "replace empty case containing only fallthrough with expression list") +} diff --git a/vendor/github.com/go-critic/checkers/emptyStringTest_checker.go b/vendor/github.com/go-critic/checkers/emptyStringTest_checker.go new file mode 100644 index 000000000000..a7be906ed08d --- /dev/null +++ b/vendor/github.com/go-critic/checkers/emptyStringTest_checker.go @@ -0,0 +1,58 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcast" + "github.com/go-toolsmith/astcopy" + "github.com/go-toolsmith/typep" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "emptyStringTest" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects empty string checks that can be written more idiomatically" + info.Before = `len(s) == 0` + info.After = `s == ""` + info.Note = "See https://dmitri.shuralyov.com/idiomatic-go#empty-string-check." + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForExpr(&emptyStringTestChecker{ctx: ctx}) + }) +} + +type emptyStringTestChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *emptyStringTestChecker) VisitExpr(e ast.Expr) { + cmp := astcast.ToBinaryExpr(e) + if cmp.Op != token.EQL && cmp.Op != token.NEQ { + return + } + lenCall := astcast.ToCallExpr(cmp.X) + if astcast.ToIdent(lenCall.Fun).Name != "len" { + return + } + s := lenCall.Args[0] + if !typep.HasStringProp(c.ctx.TypesInfo.TypeOf(s)) { + return + } + zero := astcast.ToBasicLit(cmp.Y) + if zero.Value != "0" { + return + } + c.warn(cmp, s) +} + +func (c *emptyStringTestChecker) warn(cmp *ast.BinaryExpr, s ast.Expr) { + suggest := astcopy.BinaryExpr(cmp) + suggest.X = s + suggest.Y = &ast.BasicLit{Value: `""`} + c.ctx.Warn(cmp, "replace `%s` with `%s`", cmp, suggest) +} diff --git a/vendor/github.com/go-critic/checkers/flagDeref_checker.go b/vendor/github.com/go-critic/checkers/flagDeref_checker.go index a78ce575c735..cb9faee716d5 100644 --- a/vendor/github.com/go-critic/checkers/flagDeref_checker.go +++ b/vendor/github.com/go-critic/checkers/flagDeref_checker.go @@ -21,7 +21,7 @@ flag.BoolVar(&b, "b", false, "b docs")` Dereferencing returned pointers will lead to hard to find errors where flag values are not updated after flag.Parse().` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { c := &flagDerefChecker{ ctx: ctx, flagPtrFuncs: map[string]bool{ diff --git a/vendor/github.com/go-critic/checkers/flagName_checker.go b/vendor/github.com/go-critic/checkers/flagName_checker.go new file mode 100644 index 000000000000..9709cb775ce5 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/flagName_checker.go @@ -0,0 +1,56 @@ +package checkers + +import ( + "go/ast" + "go/constant" + "strings" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcast" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "flagName" + info.Tags = []string{"diagnostic", "experimental"} + info.Summary = "Detects flag names with whitespace" + info.Before = `b := flag.Bool(" foo ", false, "description")` + info.After = `b := flag.Bool("foo", false, "description")` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForExpr(&flagNameChecker{ctx: ctx}) + }) +} + +type flagNameChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *flagNameChecker) VisitExpr(expr ast.Expr) { + call := astcast.ToCallExpr(expr) + switch qualifiedName(call.Fun) { + case "flag.Bool", "flag.Duration", "flag.Float64", "flag.String", + "flag.Int", "flag.Int64", "flag.Uint", "flag.Uint64": + c.checkFlagName(call, call.Args[0]) + case "flag.BoolVar", "flag.DurationVar", "flag.Float64Var", "flag.StringVar", + "flag.IntVar", "flag.Int64Var", "flag.UintVar", "flag.Uint64Var": + c.checkFlagName(call, call.Args[1]) + } +} + +func (c *flagNameChecker) checkFlagName(call *ast.CallExpr, arg ast.Expr) { + cv := c.ctx.TypesInfo.Types[arg].Value + if cv == nil { + return // Non-constant name + } + name := constant.StringVal(cv) + if strings.Contains(name, " ") { + c.warnWhitespace(call, name) + } +} + +func (c *flagNameChecker) warnWhitespace(cause ast.Node, name string) { + c.ctx.Warn(cause, "flag name %q contains whitespace", name) +} diff --git a/vendor/github.com/go-critic/checkers/hugeParam_checker.go b/vendor/github.com/go-critic/checkers/hugeParam_checker.go new file mode 100644 index 000000000000..656b4cc2d79a --- /dev/null +++ b/vendor/github.com/go-critic/checkers/hugeParam_checker.go @@ -0,0 +1,63 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "hugeParam" + info.Tags = []string{"performance"} + info.Params = lintpack.CheckerParams{ + "sizeThreshold": { + Value: 80, + Usage: "size in bytes that makes the warning trigger", + }, + } + info.Summary = "Detects params that incur excessive amount of copying" + info.Before = `func f(x [1024]int) {}` + info.After = `func f(x *[1024]int) {}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForFuncDecl(&hugeParamChecker{ + ctx: ctx, + sizeThreshold: int64(info.Params.Int("sizeThreshold")), + }) + }) +} + +type hugeParamChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + sizeThreshold int64 +} + +func (c *hugeParamChecker) VisitFuncDecl(decl *ast.FuncDecl) { + // TODO(quasilyte): maybe it's worthwhile to permit skipping + // test files for this checker? + if decl.Recv != nil { + c.checkParams(decl.Recv.List) + } + c.checkParams(decl.Type.Params.List) +} + +func (c *hugeParamChecker) checkParams(params []*ast.Field) { + for _, p := range params { + for _, id := range p.Names { + typ := c.ctx.TypesInfo.TypeOf(id) + size := c.ctx.SizesInfo.Sizeof(typ) + if size >= c.sizeThreshold { + c.warn(id, size) + } + } + } +} + +func (c *hugeParamChecker) warn(cause *ast.Ident, size int64) { + c.ctx.Warn(cause, "%s is heavy (%d bytes); consider passing it by pointer", + cause, size) +} diff --git a/vendor/github.com/go-critic/checkers/ifElseChain_checker.go b/vendor/github.com/go-critic/checkers/ifElseChain_checker.go index 7d9ee35fca90..c0a456afd214 100644 --- a/vendor/github.com/go-critic/checkers/ifElseChain_checker.go +++ b/vendor/github.com/go-critic/checkers/ifElseChain_checker.go @@ -34,7 +34,7 @@ Permits single else or else-if; repeated else-if or else + else-if will trigger suggestion to use switch statement. See [EffectiveGo#switch](https://golang.org/doc/effective_go.html#switch).` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&ifElseChainChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/importShadow_checker.go b/vendor/github.com/go-critic/checkers/importShadow_checker.go new file mode 100644 index 000000000000..452841883062 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/importShadow_checker.go @@ -0,0 +1,48 @@ +package checkers + +import ( + "go/ast" + "go/types" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "importShadow" + info.Tags = []string{"style", "opinionated"} + info.Summary = "Detects when imported package names shadowed in the assignments" + info.Before = ` +// "path/filepath" is imported. +filepath := "foo.txt"` + info.After = ` +filename := "foo.txt"` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + ctx.Require.PkgObjects = true + return astwalk.WalkerForLocalDef(&importShadowChecker{ctx: ctx}, ctx.TypesInfo) + }) +} + +type importShadowChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *importShadowChecker) VisitLocalDef(def astwalk.Name, _ ast.Expr) { + for pkgObj, name := range c.ctx.PkgObjects { + if name == def.ID.Name && name != "_" { + c.warn(def.ID, name, pkgObj.Imported()) + } + } +} + +func (c *importShadowChecker) warn(id ast.Node, importedName string, pkg *types.Package) { + if pkg.Path() == pkg.Name() { + // Сheck for standart library packages. + c.ctx.Warn(id, "shadow of imported package '%s'", importedName) + } else { + c.ctx.Warn(id, "shadow of imported from '%s' package '%s'", pkg.Path(), importedName) + } +} diff --git a/vendor/github.com/go-critic/checkers/indexAlloc_checker.go b/vendor/github.com/go-critic/checkers/indexAlloc_checker.go index 69b62bfb1edb..8fbe98c9d2b1 100644 --- a/vendor/github.com/go-critic/checkers/indexAlloc_checker.go +++ b/vendor/github.com/go-critic/checkers/indexAlloc_checker.go @@ -18,7 +18,7 @@ func init() { info.After = `bytes.Index(x, []byte(y))` info.Note = `See Go issue for details: https://github.com/golang/go/issues/25864` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(&indexAllocChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/initClause_checker.go b/vendor/github.com/go-critic/checkers/initClause_checker.go new file mode 100644 index 000000000000..bfbd661b245e --- /dev/null +++ b/vendor/github.com/go-critic/checkers/initClause_checker.go @@ -0,0 +1,56 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astp" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "initClause" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Summary = "Detects non-assignment statements inside if/switch init clause" + info.Before = `if sideEffect(); cond { +}` + info.After = `sideEffect() +if cond { +}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForStmt(&initClauseChecker{ctx: ctx}) + }) +} + +type initClauseChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *initClauseChecker) VisitStmt(stmt ast.Stmt) { + initClause := c.getInitClause(stmt) + if initClause != nil && !astp.IsAssignStmt(initClause) { + c.warn(stmt, initClause) + } +} + +func (c *initClauseChecker) getInitClause(x ast.Stmt) ast.Stmt { + switch x := x.(type) { + case *ast.IfStmt: + return x.Init + case *ast.SwitchStmt: + return x.Init + default: + return nil + } +} + +func (c *initClauseChecker) warn(stmt, clause ast.Stmt) { + name := "if" + if astp.IsSwitchStmt(stmt) { + name = "switch" + } + c.ctx.Warn(stmt, "consider to move `%s` before %s", clause, name) +} diff --git a/vendor/github.com/go-critic/checkers/methodExprCall_checker.go b/vendor/github.com/go-critic/checkers/methodExprCall_checker.go index 870384d9d190..9db052534598 100644 --- a/vendor/github.com/go-critic/checkers/methodExprCall_checker.go +++ b/vendor/github.com/go-critic/checkers/methodExprCall_checker.go @@ -20,7 +20,7 @@ foo.bar(f)` info.After = `f := foo{} f.bar()` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(&methodExprCallChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/nestingReduce_checker.go b/vendor/github.com/go-critic/checkers/nestingReduce_checker.go new file mode 100644 index 000000000000..4a0331d5c722 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/nestingReduce_checker.go @@ -0,0 +1,73 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "nestingReduce" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Params = lintpack.CheckerParams{ + "bodyWidth": { + Value: 5, + Usage: "min number of statements inside a branch to trigger a warning", + }, + } + info.Summary = "Finds where nesting level could be reduced" + info.Before = ` +for _, v := range a { + if v.Bool { + body() + } +}` + info.After = ` +for _, v := range a { + if !v.Bool { + continue + } + body() +}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + c := &nestingReduceChecker{ctx: ctx} + c.bodyWidth = info.Params.Int("bodyWidth") + return astwalk.WalkerForStmt(c) + }) +} + +type nestingReduceChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + bodyWidth int +} + +func (c *nestingReduceChecker) VisitStmt(stmt ast.Stmt) { + switch stmt := stmt.(type) { + case *ast.ForStmt: + c.checkLoopBody(stmt.Body.List) + case *ast.RangeStmt: + c.checkLoopBody(stmt.Body.List) + } +} + +func (c *nestingReduceChecker) checkLoopBody(body []ast.Stmt) { + if len(body) != 1 { + return + } + stmt, ok := body[0].(*ast.IfStmt) + if !ok { + return + } + if len(stmt.Body.List) >= c.bodyWidth && stmt.Else == nil { + c.warnLoop(stmt) + } +} + +func (c *nestingReduceChecker) warnLoop(cause ast.Node) { + c.ctx.Warn(cause, "invert if cond, replace body with `continue`, move old body after the statement") +} diff --git a/vendor/github.com/go-critic/checkers/nilValReturn_checker.go b/vendor/github.com/go-critic/checkers/nilValReturn_checker.go new file mode 100644 index 000000000000..231e25800fa0 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/nilValReturn_checker.go @@ -0,0 +1,64 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astequal" + "github.com/go-toolsmith/typep" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "nilValReturn" + info.Tags = []string{"diagnostic", "experimental"} + info.Summary = "Detects return statements those results evaluate to nil" + info.Before = ` +if err == nil { + return err +}` + info.After = ` +// (A) - return nil explicitly +if err == nil { + return nil +} +// (B) - typo in "==", change to "!=" +if err != nil { + return err +}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForStmt(&nilValReturnChecker{ctx: ctx}) + }) +} + +type nilValReturnChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *nilValReturnChecker) VisitStmt(stmt ast.Stmt) { + ifStmt, ok := stmt.(*ast.IfStmt) + if !ok || len(ifStmt.Body.List) != 1 { + return + } + ret, ok := ifStmt.Body.List[0].(*ast.ReturnStmt) + if !ok || len(ret.Results) != 1 { + return + } + expr, ok := ifStmt.Cond.(*ast.BinaryExpr) + cond := ok && + expr.Op == token.EQL && + typep.SideEffectFree(c.ctx.TypesInfo, expr.X) && + qualifiedName(expr.Y) == "nil" && + astequal.Expr(expr.X, ret.Results[0]) + if cond { + c.warn(ret, expr.X) + } +} + +func (c *nilValReturnChecker) warn(cause, val ast.Node) { + c.ctx.Warn(cause, "returned expr is always nil; replace %s with nil", val) +} diff --git a/vendor/github.com/go-critic/checkers/offBy1_checker.go b/vendor/github.com/go-critic/checkers/offBy1_checker.go new file mode 100644 index 000000000000..d5c8de0b7969 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/offBy1_checker.go @@ -0,0 +1,66 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcast" + "github.com/go-toolsmith/astcopy" + "github.com/go-toolsmith/astequal" + "github.com/go-toolsmith/typep" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "offBy1" + info.Tags = []string{"diagnostic", "experimental"} + info.Summary = "Detects various off-by-one kind of errors" + info.Before = `xs[len(xs)]` + info.After = `xs[len(xs)-1]` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForExpr(&offBy1Checker{ctx: ctx}) + }) +} + +type offBy1Checker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *offBy1Checker) VisitExpr(e ast.Expr) { + // TODO(Quasilyte): handle more off-by-1 patterns. + // TODO(Quasilyte): check whether go/analysis can help here. + + // Detect s[len(s)] expressions that always panic. + // The correct form is s[len(s)-1]. + + indexExpr := astcast.ToIndexExpr(e) + indexed := indexExpr.X + if !typep.IsSlice(c.ctx.TypesInfo.TypeOf(indexed)) { + return + } + if !typep.SideEffectFree(c.ctx.TypesInfo, indexed) { + return + } + call := astcast.ToCallExpr(indexExpr.Index) + if astcast.ToIdent(call.Fun).Name != "len" { + return + } + if len(call.Args) != 1 || !astequal.Expr(call.Args[0], indexed) { + return + } + c.warnLenIndex(indexExpr) +} + +func (c *offBy1Checker) warnLenIndex(cause *ast.IndexExpr) { + suggest := astcopy.IndexExpr(cause) + suggest.Index = &ast.BinaryExpr{ + Op: token.SUB, + X: cause.Index, + Y: &ast.BasicLit{Value: "1"}, + } + c.ctx.Warn(cause, "index expr always panics; maybe you wanted %s?", suggest) +} diff --git a/vendor/github.com/go-critic/checkers/paramTypeCombine_checker.go b/vendor/github.com/go-critic/checkers/paramTypeCombine_checker.go new file mode 100644 index 000000000000..ffa74061e671 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/paramTypeCombine_checker.go @@ -0,0 +1,86 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astequal" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "paramTypeCombine" + info.Tags = []string{"style", "opinionated"} + info.Summary = "Detects if function parameters could be combined by type and suggest the way to do it" + info.Before = `func foo(a, b int, c, d int, e, f int, g int) {}` + info.After = `func foo(a, b, c, d, e, f, g int) {}` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForFuncDecl(¶mTypeCombineChecker{ctx: ctx}) + }) +} + +type paramTypeCombineChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *paramTypeCombineChecker) EnterFunc(*ast.FuncDecl) bool { + return true +} + +func (c *paramTypeCombineChecker) VisitFuncDecl(decl *ast.FuncDecl) { + typ := c.optimizeFuncType(decl.Type) + if !astequal.Expr(typ, decl.Type) { + c.warn(decl.Type, typ) + } +} + +func (c *paramTypeCombineChecker) optimizeFuncType(f *ast.FuncType) *ast.FuncType { + return &ast.FuncType{ + Params: c.optimizeParams(f.Params), + Results: c.optimizeParams(f.Results), + } +} +func (c *paramTypeCombineChecker) optimizeParams(params *ast.FieldList) *ast.FieldList { + // To avoid false positives, skip unnamed param lists. + // + // We're using a property that Go only permits unnamed params + // for the whole list, so it's enough to check whether any of + // ast.Field have empty name list. + skip := params == nil || + len(params.List) < 2 || + len(params.List[0].Names) == 0 + if skip { + return params + } + + list := []*ast.Field{} + names := make([]*ast.Ident, len(params.List[0].Names)) + copy(names, params.List[0].Names) + list = append(list, &ast.Field{ + Names: names, + Type: params.List[0].Type, + }) + for i, p := range params.List[1:] { + names = make([]*ast.Ident, len(p.Names)) + copy(names, p.Names) + if astequal.Expr(p.Type, params.List[i].Type) { + list[len(list)-1].Names = + append(list[len(list)-1].Names, names...) + } else { + list = append(list, &ast.Field{ + Names: names, + Type: params.List[i+1].Type, + }) + } + } + return &ast.FieldList{ + List: list, + } +} + +func (c *paramTypeCombineChecker) warn(f1, f2 *ast.FuncType) { + c.ctx.Warn(f1, "%s could be replaced with %s", f1, f2) +} diff --git a/vendor/github.com/go-critic/checkers/ptrToRefParam_checker.go b/vendor/github.com/go-critic/checkers/ptrToRefParam_checker.go new file mode 100644 index 000000000000..dacffc85ab42 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/ptrToRefParam_checker.go @@ -0,0 +1,70 @@ +package checkers + +import ( + "go/ast" + "go/types" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "ptrToRefParam" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Summary = "Detects input and output parameters that have a type of pointer to referential type" + info.Before = `func f(m *map[string]int) (*chan *int)` + info.After = `func f(m map[string]int) (chan *int)` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForFuncDecl(&ptrToRefParamChecker{ctx: ctx}) + }) +} + +type ptrToRefParamChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *ptrToRefParamChecker) VisitFuncDecl(fn *ast.FuncDecl) { + c.checkParams(fn.Type.Params.List) + if fn.Type.Results != nil { + c.checkParams(fn.Type.Results.List) + } +} + +func (c *ptrToRefParamChecker) checkParams(params []*ast.Field) { + for _, param := range params { + ptr, ok := c.ctx.TypesInfo.TypeOf(param.Type).(*types.Pointer) + if !ok { + continue + } + + if c.isRefType(ptr.Elem()) { + if len(param.Names) == 0 { + c.ctx.Warn(param, "consider to make non-pointer type for `%s`", param.Type) + } else { + for i := range param.Names { + c.warn(param.Names[i]) + } + } + } + } +} + +func (c *ptrToRefParamChecker) isRefType(x types.Type) bool { + switch typ := x.(type) { + case *types.Map, *types.Chan, *types.Interface: + return true + case *types.Named: + // Handle underlying type only for interfaces. + if _, ok := typ.Underlying().(*types.Interface); ok { + return true + } + } + return false +} + +func (c *ptrToRefParamChecker) warn(id *ast.Ident) { + c.ctx.Warn(id, "consider `%s' to be of non-pointer type", id) +} diff --git a/vendor/github.com/go-critic/checkers/rangeExprCopy_checker.go b/vendor/github.com/go-critic/checkers/rangeExprCopy_checker.go index db206d30e567..387d1bbbcc07 100644 --- a/vendor/github.com/go-critic/checkers/rangeExprCopy_checker.go +++ b/vendor/github.com/go-critic/checkers/rangeExprCopy_checker.go @@ -12,6 +12,16 @@ func init() { var info lintpack.CheckerInfo info.Name = "rangeExprCopy" info.Tags = []string{"performance"} + info.Params = lintpack.CheckerParams{ + "sizeThreshold": { + Value: 512, + Usage: "size in bytes that makes the warning trigger", + }, + "skipTestFuncs": { + Value: true, + Usage: "whether to check test functions", + }, + } info.Summary = "Detects expensive copies of `for` loop range expressions" info.Details = "Suggests to use pointer to array to avoid the copy using `&` on range expression." info.Before = ` @@ -26,10 +36,10 @@ for _, x := range &xs { // No copy }` info.Note = "See Go issue for details: https://github.com/golang/go/issues/15812." - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { c := &rangeExprCopyChecker{ctx: ctx} - c.sizeThreshold = int64(c.ctx.Params.Int("sizeThreshold", 512)) - c.skipTestFuncs = c.ctx.Params.Bool("skipTestFuncs", true) + c.sizeThreshold = int64(info.Params.Int("sizeThreshold")) + c.skipTestFuncs = info.Params.Bool("skipTestFuncs") return astwalk.WalkerForStmt(c) }) } diff --git a/vendor/github.com/go-critic/checkers/rangeValCopy_checker.go b/vendor/github.com/go-critic/checkers/rangeValCopy_checker.go index 88f220676634..0e24369225c7 100644 --- a/vendor/github.com/go-critic/checkers/rangeValCopy_checker.go +++ b/vendor/github.com/go-critic/checkers/rangeValCopy_checker.go @@ -11,6 +11,16 @@ func init() { var info lintpack.CheckerInfo info.Name = "rangeValCopy" info.Tags = []string{"performance"} + info.Params = lintpack.CheckerParams{ + "sizeThreshold": { + Value: 128, + Usage: "size in bytes that makes the warning trigger", + }, + "skipTestFuncs": { + Value: true, + Usage: "whether to check test functions", + }, + } info.Summary = "Detects loops that copy big objects during each iteration" info.Details = "Suggests to use index access or take address and make use pointer instead." info.Before = ` @@ -25,10 +35,10 @@ for i := range xs { // Loop body. }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { c := &rangeValCopyChecker{ctx: ctx} - c.sizeThreshold = int64(c.ctx.Params.Int("sizeThreshold", 128)) - c.skipTestFuncs = c.ctx.Params.Bool("skipTestFuncs", true) + c.sizeThreshold = int64(info.Params.Int("sizeThreshold")) + c.skipTestFuncs = info.Params.Bool("skipTestFuncs") return astwalk.WalkerForStmt(c) }) } diff --git a/vendor/github.com/go-critic/checkers/regexpMust_checker.go b/vendor/github.com/go-critic/checkers/regexpMust_checker.go index a4a78ba45fd3..ef7a397877c5 100644 --- a/vendor/github.com/go-critic/checkers/regexpMust_checker.go +++ b/vendor/github.com/go-critic/checkers/regexpMust_checker.go @@ -18,7 +18,7 @@ func init() { info.Before = `re, _ := regexp.Compile("const pattern")` info.After = `re := regexp.MustCompile("const pattern")` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(®expMustChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/singleCaseSwitch_checker.go b/vendor/github.com/go-critic/checkers/singleCaseSwitch_checker.go index 89b1754e8e8b..6cdb06aefc48 100644 --- a/vendor/github.com/go-critic/checkers/singleCaseSwitch_checker.go +++ b/vendor/github.com/go-critic/checkers/singleCaseSwitch_checker.go @@ -22,7 +22,7 @@ if x, ok := x.(int); ok { body() }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&singleCaseSwitchChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/sloppyLen_checker.go b/vendor/github.com/go-critic/checkers/sloppyLen_checker.go index b5c414f4b2a7..45123ec6bdc6 100644 --- a/vendor/github.com/go-critic/checkers/sloppyLen_checker.go +++ b/vendor/github.com/go-critic/checkers/sloppyLen_checker.go @@ -23,7 +23,7 @@ len(arr) < 0 // Doesn't make sense at all` len(arr) > 0 len(arr) == 0` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(&sloppyLenChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/sloppyReassign_checker.go b/vendor/github.com/go-critic/checkers/sloppyReassign_checker.go new file mode 100644 index 000000000000..1a7c198774e9 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/sloppyReassign_checker.go @@ -0,0 +1,80 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcast" + "github.com/go-toolsmith/astcopy" + "github.com/go-toolsmith/astequal" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "sloppyReassign" + info.Tags = []string{"diagnostic", "experimental"} + info.Summary = "Detects suspicious/confusing re-assignments" + info.Before = `if err = f(); err != nil { return err }` + info.After = `if err := f(); err != nil { return err }` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForStmt(&sloppyReassignChecker{ctx: ctx}) + }) +} + +type sloppyReassignChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *sloppyReassignChecker) VisitStmt(stmt ast.Stmt) { + // Right now only check assignments in if statements init. + ifStmt := astcast.ToIfStmt(stmt) + assign := astcast.ToAssignStmt(ifStmt.Init) + if assign.Tok != token.ASSIGN { + return + } + + // TODO(Quasilyte): is handling of multi-value assignments worthwhile? + if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { + return + } + + // TODO(Quasilyte): handle not only the simplest, return-only case. + body := ifStmt.Body.List + if len(body) != 1 { + return + } + + // Variable that is being re-assigned. + reAssigned := astcast.ToIdent(assign.Lhs[0]) + if reAssigned.Name == "" { + return + } + + // TODO(Quasilyte): handle not only nil comparisons. + eqToNil := &ast.BinaryExpr{ + Op: token.NEQ, + X: reAssigned, + Y: &ast.Ident{Name: "nil"}, + } + if !astequal.Expr(ifStmt.Cond, eqToNil) { + return + } + + results := astcast.ToReturnStmt(body[0]).Results + for _, res := range results { + if astequal.Expr(reAssigned, res) { + c.warnAssignToDefine(assign, reAssigned.Name) + break + } + } +} + +func (c *sloppyReassignChecker) warnAssignToDefine(assign *ast.AssignStmt, name string) { + suggest := astcopy.AssignStmt(assign) + suggest.Tok = token.DEFINE + c.ctx.Warn(assign, "re-assignment to `%s` can be replaced with `%s`", name, suggest) +} diff --git a/vendor/github.com/go-critic/checkers/switchTrue_checker.go b/vendor/github.com/go-critic/checkers/switchTrue_checker.go index 1840f1d88f1a..3b2766276970 100644 --- a/vendor/github.com/go-critic/checkers/switchTrue_checker.go +++ b/vendor/github.com/go-critic/checkers/switchTrue_checker.go @@ -21,7 +21,7 @@ switch { case x > y: }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&switchTrueChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/typeSwitchVar_checker.go b/vendor/github.com/go-critic/checkers/typeSwitchVar_checker.go index ac30fe9c5618..613e82869edb 100644 --- a/vendor/github.com/go-critic/checkers/typeSwitchVar_checker.go +++ b/vendor/github.com/go-critic/checkers/typeSwitchVar_checker.go @@ -33,7 +33,7 @@ default: return 0 }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&typeSwitchVarChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/typeUnparen_checker.go b/vendor/github.com/go-critic/checkers/typeUnparen_checker.go new file mode 100644 index 000000000000..0286171dc70c --- /dev/null +++ b/vendor/github.com/go-critic/checkers/typeUnparen_checker.go @@ -0,0 +1,85 @@ +package checkers + +import ( + "go/ast" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcopy" + "github.com/go-toolsmith/astp" + "golang.org/x/tools/go/ast/astutil" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "typeUnparen" + info.Tags = []string{"style", "opinionated"} + info.Summary = "Detects unneded parenthesis inside type expressions and suggests to remove them" + info.Before = `type foo [](func([](func())))` + info.After = `type foo []func([]func())` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForTypeExpr(&typeUnparenChecker{ctx: ctx}, ctx.TypesInfo) + }) +} + +type typeUnparenChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *typeUnparenChecker) VisitTypeExpr(x ast.Expr) { + switch x := x.(type) { + case *ast.ParenExpr: + switch x.X.(type) { + case *ast.StructType: + c.ctx.Warn(x, "could simplify (struct{...}) to struct{...}") + case *ast.InterfaceType: + c.ctx.Warn(x, "could simplify (interface{...}) to interface{...}") + default: + c.warn(x, c.unparenExpr(astcopy.Expr(x))) + } + default: + c.checkTypeExpr(x) + } +} + +func (c *typeUnparenChecker) checkTypeExpr(x ast.Expr) { + switch x := x.(type) { + case *ast.ArrayType: + // Arrays require extra care: we don't want to unparen + // length expression as they are not type expressions. + if !c.hasParens(x.Elt) { + return + } + noParens := astcopy.ArrayType(x) + noParens.Elt = c.unparenExpr(noParens.Elt) + c.warn(x, noParens) + case *ast.StructType, *ast.InterfaceType: + // Only nested fields are to be reported. + default: + if !c.hasParens(x) { + return + } + c.warn(x, c.unparenExpr(astcopy.Expr(x))) + } +} + +func (c *typeUnparenChecker) hasParens(x ast.Expr) bool { + return containsNode(x, astp.IsParenExpr) +} + +func (c *typeUnparenChecker) unparenExpr(x ast.Expr) ast.Expr { + // Replace every paren expr with expression it encloses. + return astutil.Apply(x, nil, func(cur *astutil.Cursor) bool { + if paren, ok := cur.Node().(*ast.ParenExpr); ok { + cur.Replace(paren.X) + } + return true + }).(ast.Expr) +} + +func (c *typeUnparenChecker) warn(cause, noParens ast.Expr) { + c.SkipChilds = true + c.ctx.Warn(cause, "could simplify %s to %s", cause, noParens) +} diff --git a/vendor/github.com/go-critic/checkers/underef_checker.go b/vendor/github.com/go-critic/checkers/underef_checker.go index 328b5b36d52d..64def25fb942 100644 --- a/vendor/github.com/go-critic/checkers/underef_checker.go +++ b/vendor/github.com/go-critic/checkers/underef_checker.go @@ -14,6 +14,12 @@ func init() { var info lintpack.CheckerInfo info.Name = "underef" info.Tags = []string{"style"} + info.Params = lintpack.CheckerParams{ + "skipRecvDeref": { + Value: true, + Usage: "whether to skip (*x).method() calls where x is a pointer receiver", + }, + } info.Summary = "Detects dereference expressions that can be omitted" info.Before = ` (*k).field = 5 @@ -22,9 +28,9 @@ v := (*a)[5] // only if a is array` k.field = 5 v := a[5]` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { c := &underefChecker{ctx: ctx} - c.skipRecvCopy = ctx.Params.Bool("skipRecvCopy", true) + c.skipRecvDeref = info.Params.Bool("skipRecvDeref") return astwalk.WalkerForExpr(c) }) } @@ -33,14 +39,14 @@ type underefChecker struct { astwalk.WalkHandler ctx *lintpack.CheckerContext - skipRecvCopy bool + skipRecvDeref bool } func (c *underefChecker) VisitExpr(expr ast.Expr) { switch n := expr.(type) { case *ast.SelectorExpr: expr := lintutil.AsParenExpr(n.X) - if c.skipRecvCopy && c.isPtrRecvMethodCall(n.Sel) { + if c.skipRecvDeref && c.isPtrRecvMethodCall(n.Sel) { return } @@ -66,7 +72,7 @@ func (c *underefChecker) isPtrRecvMethodCall(fn *ast.Ident) bool { typ, ok := c.ctx.TypesInfo.TypeOf(fn).(*types.Signature) if ok && typ != nil && typ.Recv() != nil { _, ok := typ.Recv().Type().(*types.Pointer) - return ok && c.skipRecvCopy + return ok } return false } diff --git a/vendor/github.com/go-critic/checkers/unlabelStmt_checker.go b/vendor/github.com/go-critic/checkers/unlabelStmt_checker.go index 2fbdbf83225d..9d767ae65b31 100644 --- a/vendor/github.com/go-critic/checkers/unlabelStmt_checker.go +++ b/vendor/github.com/go-critic/checkers/unlabelStmt_checker.go @@ -27,7 +27,7 @@ for x := range xs { } }` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForStmt(&unlabelStmtChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/unlambda_checker.go b/vendor/github.com/go-critic/checkers/unlambda_checker.go index 3b75fc3b08f5..833a1e8a9b72 100644 --- a/vendor/github.com/go-critic/checkers/unlambda_checker.go +++ b/vendor/github.com/go-critic/checkers/unlambda_checker.go @@ -18,7 +18,7 @@ func init() { info.Before = `func(x int) int { return fn(x) }` info.After = `fn` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(&unlambdaChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/unnamedResult_checker.go b/vendor/github.com/go-critic/checkers/unnamedResult_checker.go new file mode 100644 index 000000000000..0a575e818f12 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/unnamedResult_checker.go @@ -0,0 +1,103 @@ +package checkers + +import ( + "go/ast" + "go/types" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "unnamedResult" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Params = lintpack.CheckerParams{ + "checkExported": { + Value: false, + Usage: "whether to check exported functions", + }, + } + info.Summary = "Detects unnamed results that may benefit from names" + info.Before = `func f() (float64, float64)` + info.After = `func f() (x, y float64)` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + c := &unnamedResultChecker{ctx: ctx} + c.checkExported = info.Params.Bool("checkExported") + return astwalk.WalkerForFuncDecl(c) + }) +} + +type unnamedResultChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + checkExported bool +} + +func (c *unnamedResultChecker) VisitFuncDecl(decl *ast.FuncDecl) { + if c.checkExported && !ast.IsExported(decl.Name.Name) { + return + } + results := decl.Type.Results + switch { + case results == nil: + return // Function has no results + case len(results.List) > 0 && results.List[0].Names != nil: + return // Skip named results + } + + typeName := func(x ast.Expr) string { return c.typeName(c.ctx.TypesInfo.TypeOf(x)) } + isError := func(x ast.Expr) bool { return qualifiedName(x) == "error" } + isBool := func(x ast.Expr) bool { return qualifiedName(x) == "bool" } + + // Main difference with case of len=2 is that we permit any + // typ1 as long as second type is either error or bool. + if results.NumFields() == 2 { + typ1, typ2 := results.List[0].Type, results.List[1].Type + name1, name2 := typeName(typ1), typeName(typ2) + cond := (name1 != name2 && name2 != "") || + (!isError(typ1) && isError(typ2)) || + (!isBool(typ1) && isBool(typ2)) + if !cond { + c.warn(decl) + } + return + } + + seen := make(map[string]bool, len(results.List)) + for i := range results.List { + typ := results.List[i].Type + name := typeName(typ) + isLast := i == len(results.List)-1 + + cond := !seen[name] || + (isLast && (isError(typ) || isBool(typ))) + if !cond { + c.warn(decl) + return + } + + seen[name] = true + } +} + +func (c *unnamedResultChecker) typeName(typ types.Type) string { + switch typ := typ.(type) { + case *types.Array: + return c.typeName(typ.Elem()) + case *types.Pointer: + return c.typeName(typ.Elem()) + case *types.Slice: + return c.typeName(typ.Elem()) + case *types.Named: + return typ.Obj().Name() + default: + return "" + } +} + +func (c *unnamedResultChecker) warn(n ast.Node) { + c.ctx.Warn(n, "consider giving a name to these results") +} diff --git a/vendor/github.com/go-critic/checkers/unnecessaryBlock_checker.go b/vendor/github.com/go-critic/checkers/unnecessaryBlock_checker.go new file mode 100644 index 000000000000..e5dc45f7ef05 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/unnecessaryBlock_checker.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "unnecessaryBlock" + info.Tags = []string{"style", "opinionated", "experimental"} + info.Summary = "Detects unnecessary braced statement blocks" + info.Before = ` +x := 1 +{ + print(x) +}` + info.After = ` +x := 1 +print(x)` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForStmtList(&unnecessaryBlockChecker{ctx: ctx}) + }) +} + +type unnecessaryBlockChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *unnecessaryBlockChecker) VisitStmtList(statements []ast.Stmt) { + // Using StmtListVisitor instead of StmtVisitor makes it easier to avoid + // false positives on IfStmt, RangeStmt, ForStmt and alike. + // We only inspect BlockStmt inside statement lists, so this method is not + // called for IfStmt itself, for example. + + for _, stmt := range statements { + stmt, ok := stmt.(*ast.BlockStmt) + if ok && !c.hasDefinitions(stmt) { + c.warn(stmt) + } + } +} + +func (c *unnecessaryBlockChecker) hasDefinitions(stmt *ast.BlockStmt) bool { + for _, bs := range stmt.List { + switch stmt := bs.(type) { + case *ast.AssignStmt: + if stmt.Tok == token.DEFINE { + return true + } + case *ast.DeclStmt: + decl := stmt.Decl.(*ast.GenDecl) + if len(decl.Specs) != 0 { + return true + } + } + } + + return false +} + +func (c *unnecessaryBlockChecker) warn(expr ast.Stmt) { + c.ctx.Warn(expr, "block doesn't have definitions, can be simply deleted") +} diff --git a/vendor/github.com/go-critic/checkers/unslice_checker.go b/vendor/github.com/go-critic/checkers/unslice_checker.go index ce6ffd574062..bf9e2846c002 100644 --- a/vendor/github.com/go-critic/checkers/unslice_checker.go +++ b/vendor/github.com/go-critic/checkers/unslice_checker.go @@ -21,7 +21,7 @@ copy(b[:], values...) // b is []byte` f(s) copy(b, values...)` - lintpack.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { return astwalk.WalkerForExpr(&unsliceChecker{ctx: ctx}) }) } diff --git a/vendor/github.com/go-critic/checkers/utils.go b/vendor/github.com/go-critic/checkers/utils.go index 090bf5a01420..0ef6556a2a42 100644 --- a/vendor/github.com/go-critic/checkers/utils.go +++ b/vendor/github.com/go-critic/checkers/utils.go @@ -2,11 +2,9 @@ package checkers import ( "go/ast" - "go/token" "go/types" "strings" - "github.com/go-critic/checkers/internal/lintutil" "github.com/go-lintpack/lintpack" "golang.org/x/tools/go/ast/astutil" ) @@ -92,55 +90,3 @@ func findNode(root ast.Node, pred func(ast.Node) bool) ast.Node { func containsNode(root ast.Node, pred func(ast.Node) bool) bool { return findNode(root, pred) != nil } - -// isSafeExpr reports whether expr is softly safe expression and contains -// no significant side-effects. As opposed to strictly safe expressions, -// soft safe expressions permit some forms of side-effects, like -// panic possibility during indexing or nil pointer dereference. -// -// Uses types info to determine type conversion expressions that -// are the only permitted kinds of call expressions. -func isSafeExpr(info *types.Info, expr ast.Expr) bool { - // This list switch is not comprehensive and uses - // whitelist to be on the conservative side. - // Can be extended as needed. - // - // Note that it is not very strict "safe" as - // index expressions are permitted even though they - // may cause panics. - switch expr := expr.(type) { - case *ast.StarExpr: - return isSafeExpr(info, expr.X) - case *ast.BinaryExpr: - return isSafeExpr(info, expr.X) && isSafeExpr(info, expr.Y) - case *ast.UnaryExpr: - return expr.Op != token.ARROW && isSafeExpr(info, expr.X) - case *ast.BasicLit, *ast.Ident: - return true - case *ast.IndexExpr: - return isSafeExpr(info, expr.X) && isSafeExpr(info, expr.Index) - case *ast.SelectorExpr: - return isSafeExpr(info, expr.X) - case *ast.ParenExpr: - return isSafeExpr(info, expr.X) - case *ast.CompositeLit: - return isSafeExprList(info, expr.Elts) - case *ast.CallExpr: - return lintutil.IsTypeExpr(info, expr.Fun) && - isSafeExprList(info, expr.Args) - - default: - return false - } -} - -// isSafeExprList reports whether every expr in list is safe. -// See isSafeExpr. -func isSafeExprList(info *types.Info, list []ast.Expr) bool { - for _, expr := range list { - if !isSafeExpr(info, expr) { - return false - } - } - return true -} diff --git a/vendor/github.com/go-critic/checkers/valSwap_checker.go b/vendor/github.com/go-critic/checkers/valSwap_checker.go new file mode 100644 index 000000000000..ab27f920079a --- /dev/null +++ b/vendor/github.com/go-critic/checkers/valSwap_checker.go @@ -0,0 +1,64 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcast" + "github.com/go-toolsmith/astequal" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "valSwap" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects value swapping code that are not using parallel assignment" + info.Before = ` +tmp := *x +*x = *y +*y = tmp` + info.After = `*x, *y = *y, *x` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForStmtList(&valSwapChecker{ctx: ctx}) + }) +} + +type valSwapChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *valSwapChecker) VisitStmtList(list []ast.Stmt) { + for len(list) >= 3 { + tmpAssign := astcast.ToAssignStmt(list[0]) + assignX := astcast.ToAssignStmt(list[1]) + assignY := astcast.ToAssignStmt(list[2]) + + cond := c.isSimpleAssign(tmpAssign) && + c.isSimpleAssign(assignX) && + c.isSimpleAssign(assignY) && + assignX.Tok == token.ASSIGN && + assignY.Tok == token.ASSIGN && + astequal.Expr(assignX.Lhs[0], tmpAssign.Rhs[0]) && + astequal.Expr(assignX.Rhs[0], assignY.Lhs[0]) && + astequal.Expr(assignY.Rhs[0], tmpAssign.Lhs[0]) + if cond { + c.warn(tmpAssign, assignX.Lhs[0], assignY.Lhs[0]) + list = list[3:] + } else { + list = list[1:] + } + } +} + +func (c *valSwapChecker) isSimpleAssign(x *ast.AssignStmt) bool { + return len(x.Lhs) == 1 && len(x.Rhs) == 1 +} + +func (c *valSwapChecker) warn(cause, x, y ast.Node) { + c.ctx.Warn(cause, "can re-write as `%s, %s = %s, %s`", + x, y, y, x) +} diff --git a/vendor/github.com/go-critic/checkers/wrapperFunc_checker.go b/vendor/github.com/go-critic/checkers/wrapperFunc_checker.go new file mode 100644 index 000000000000..e2308ff4b675 --- /dev/null +++ b/vendor/github.com/go-critic/checkers/wrapperFunc_checker.go @@ -0,0 +1,214 @@ +package checkers + +import ( + "go/ast" + "go/token" + "go/types" + "strings" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcast" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "wrapperFunc" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects function calls that can be replaced with convenience wrappers" + info.Before = `wg.Add(-1)` + info.After = `wg.Done()` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + type arg struct { + index int + value string + } + type pattern struct { + pkg string + typ string // Only for typ patterns + args []arg + suggestion string + } + type matcher struct { + pkgPatterns []pattern + typPatterns []pattern + } + + typPatterns := map[string][]arg{ + "sync.WaitGroup.Add => WaitGroup.Done": { + {0, "-1"}, + }, + } + + pkgPatterns := map[string][]arg{ + "http.HandlerFunc => http.NotFoundHandler": { + {0, "http.NotFound"}, + }, + + "strings.Replace => strings.ReplaceAll": { + {3, "-1"}, + }, + "strings.TrimFunc => strings.TrimSpace": { + {1, "unicode.IsSpace"}, + }, + "strings.Map => strings.ToTitle": { + {0, "unicode.ToTitle"}, + }, + + "bytes.Replace => bytes.ReplaceAll": { + {3, "-1"}, + }, + "bytes.TrimFunc => bytes.TrimSpace": { + {1, "unicode.IsSpace"}, + }, + "bytes.Map => bytes.ToUpper": { + {0, "unicode.ToUpper"}, + }, + "bytes.Map => bytes.ToLower": { + {0, "unicode.ToLower"}, + }, + "bytes.Map => bytes.ToTitle": { + {0, "unicode.ToTitle"}, + }, + } + + matchers := make(map[string]*matcher) + + type templateKey struct { + from string + to string + } + decodeKey := func(key string) templateKey { + parts := strings.Split(key, " => ") + return templateKey{from: parts[0], to: parts[1]} + } + + // Expand pkg patterns. + for key, args := range pkgPatterns { + key := decodeKey(key) + parts := strings.Split(key.from, ".") + fn := parts[1] + m := matchers[fn] + if m == nil { + m = &matcher{} + matchers[fn] = m + } + m.pkgPatterns = append(m.pkgPatterns, pattern{ + pkg: parts[0], + args: args, + suggestion: key.to, + }) + } + // Expand typ patterns. + for key, args := range typPatterns { + key := decodeKey(key) + parts := strings.Split(key.from, ".") + fn := parts[2] + m := matchers[fn] + if m == nil { + m = &matcher{} + matchers[fn] = m + } + m.typPatterns = append(m.typPatterns, pattern{ + pkg: parts[0], + typ: parts[1], + args: args, + suggestion: key.to, + }) + } + + var valueOf func(x ast.Expr) string + valueOf = func(x ast.Expr) string { + switch x := x.(type) { + case *ast.Ident: + return x.Name + case *ast.SelectorExpr: + id, ok := x.X.(*ast.Ident) + if ok { + return id.Name + "." + x.Sel.Name + } + case *ast.BasicLit: + return x.Value + case *ast.UnaryExpr: + switch x.Op { + case token.SUB: + return "-" + valueOf(x.X) + case token.ADD: + return valueOf(x.X) + } + } + return "" + } + + findSuggestion := func(call *ast.CallExpr, pkg, typ string, patterns []pattern) string { + for _, pat := range patterns { + if pat.pkg != pkg || pat.typ != typ { + continue + } + for _, arg := range pat.args { + if arg.value == valueOf(call.Args[arg.index]) { + return pat.suggestion + } + } + } + return "" + } + + c := &wrapperFuncChecker{ctx: ctx} + c.findSuggestion = func(call *ast.CallExpr) string { + sel := astcast.ToSelectorExpr(call.Fun).Sel + if sel == nil { + return "" + } + x := astcast.ToSelectorExpr(call.Fun).X + + m := matchers[sel.Name] + if m == nil { + return "" + } + + if x, ok := x.(*ast.Ident); ok { + obj, ok := c.ctx.TypesInfo.ObjectOf(x).(*types.PkgName) + if ok { + return findSuggestion(call, obj.Name(), "", m.pkgPatterns) + } + } + + typ := c.ctx.TypesInfo.TypeOf(x) + tn, ok := typ.(*types.Named) + if !ok { + return "" + } + return findSuggestion( + call, + tn.Obj().Pkg().Name(), + tn.Obj().Name(), + m.typPatterns) + } + + return astwalk.WalkerForExpr(c) + }) +} + +type wrapperFuncChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext + + findSuggestion func(*ast.CallExpr) string +} + +func (c *wrapperFuncChecker) VisitExpr(expr ast.Expr) { + call := astcast.ToCallExpr(expr) + if len(call.Args) == 0 { + return + } + + if suggest := c.findSuggestion(call); suggest != "" { + c.warn(call, suggest) + } +} + +func (c *wrapperFuncChecker) warn(cause ast.Node, suggest string) { + c.ctx.Warn(cause, "use %s method in `%s`", suggest, cause) +} diff --git a/vendor/github.com/go-critic/checkers/yodaStyleExpr_checker.go b/vendor/github.com/go-critic/checkers/yodaStyleExpr_checker.go new file mode 100644 index 000000000000..06577788e0ca --- /dev/null +++ b/vendor/github.com/go-critic/checkers/yodaStyleExpr_checker.go @@ -0,0 +1,51 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "github.com/go-lintpack/lintpack" + "github.com/go-lintpack/lintpack/astwalk" + "github.com/go-toolsmith/astcopy" + "github.com/go-toolsmith/astp" +) + +func init() { + var info lintpack.CheckerInfo + info.Name = "yodaStyleExpr" + info.Tags = []string{"style", "experimental"} + info.Summary = "Detects Yoda style expressions and suggests to replace them" + info.Before = `return nil != ptr` + info.After = `return ptr != nil` + + collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker { + return astwalk.WalkerForLocalExpr(&yodaStyleExprChecker{ctx: ctx}) + }) +} + +type yodaStyleExprChecker struct { + astwalk.WalkHandler + ctx *lintpack.CheckerContext +} + +func (c *yodaStyleExprChecker) VisitLocalExpr(expr ast.Expr) { + binexpr, ok := expr.(*ast.BinaryExpr) + if !ok { + return + } + if binexpr.Op == token.EQL || binexpr.Op == token.NEQ { + 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) warn(expr *ast.BinaryExpr) { + e := astcopy.BinaryExpr(expr) + e.X, e.Y = e.Y, e.X + c.ctx.Warn(expr, "consider to change order in expression to %s", e) +} diff --git a/vendor/github.com/go-lintpack/lintpack/.travis.yml b/vendor/github.com/go-lintpack/lintpack/.travis.yml new file mode 100644 index 000000000000..41a0cbac510b --- /dev/null +++ b/vendor/github.com/go-lintpack/lintpack/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: + - 1.x +install: + - # Prevent default install action "go get -t -v ./...". +script: + - make ci diff --git a/vendor/github.com/go-lintpack/lintpack/Makefile b/vendor/github.com/go-lintpack/lintpack/Makefile new file mode 100644 index 000000000000..63f21d2f93d1 --- /dev/null +++ b/vendor/github.com/go-lintpack/lintpack/Makefile @@ -0,0 +1,14 @@ +.PHONY: test ci + +%: # stubs to get makefile param for `test-checker` command + @: # see: https://stackoverflow.com/a/6273809/433041 + +build: + go build cmd/lintpack/build.go cmd/lintpack/main.go + +test: + go test -v -count=1 ./... + +ci: + go get -t -v ./... + go test -v -count=1 ./... diff --git a/vendor/github.com/go-lintpack/lintpack/README.md b/vendor/github.com/go-lintpack/lintpack/README.md index 57eb07fc4cf8..5702228ebce4 100644 --- a/vendor/github.com/go-lintpack/lintpack/README.md +++ b/vendor/github.com/go-lintpack/lintpack/README.md @@ -1,9 +1,17 @@ +[![Build Status][travis-image]][travis-url] +[![Go Report Card][go-report-image]][go-report-url] + +[travis-image]: https://travis-ci.org/go-critic/go-critic.svg?branch=master +[travis-url]: https://travis-ci.org/go-critic/go-critic +[go-report-image]: https://goreportcard.com/badge/github.com/go-critic/go-critic +[go-report-url]: https://goreportcard.com/report/github.com/go-critic/go-critic + ## Quick start / Installation / Usage Install `lintpack`: ```bash -go get -v -u github.com/lintpack/lintpack/... +go get -v -u github.com/go-lintpack/lintpack/... ``` Install checkers from [go-critic/checkers](https://github.com/go-critic/checkers): diff --git a/vendor/github.com/go-lintpack/lintpack/astwalk/local_comment_walker.go b/vendor/github.com/go-lintpack/lintpack/astwalk/local_comment_walker.go index a6c2b5a8f83b..34d0df4990a2 100644 --- a/vendor/github.com/go-lintpack/lintpack/astwalk/local_comment_walker.go +++ b/vendor/github.com/go-lintpack/lintpack/astwalk/local_comment_walker.go @@ -1,6 +1,9 @@ package astwalk -import "go/ast" +import ( + "go/ast" + "strings" +) type localCommentWalker struct { visitor LocalCommentVisitor @@ -16,13 +19,32 @@ func (w *localCommentWalker) WalkFile(f *ast.File) { if !ok || !w.visitor.EnterFunc(decl) { continue } - for _, c := range f.Comments { + + for _, cg := range f.Comments { // Not sure that decls/comments are sorted // by positions, so do a naive full scan for now. - if c.Pos() < decl.Pos() || c.Pos() > decl.End() { + if cg.Pos() < decl.Pos() || cg.Pos() > decl.End() { continue } - w.visitor.VisitLocalComment(c) + + var group []*ast.Comment + visitGroup := func(list []*ast.Comment) { + if len(list) == 0 { + return + } + cg := &ast.CommentGroup{List: list} + w.visitor.VisitLocalComment(cg) + } + for _, comment := range cg.List { + if strings.HasPrefix(comment.Text, "/*") { + visitGroup(group) + group = group[:0] + visitGroup([]*ast.Comment{comment}) + } else { + group = append(group, comment) + } + } + visitGroup(group) } } } diff --git a/vendor/github.com/go-lintpack/lintpack/checker.go b/vendor/github.com/go-lintpack/lintpack/checker.go deleted file mode 100644 index 161eef2cf451..000000000000 --- a/vendor/github.com/go-lintpack/lintpack/checker.go +++ /dev/null @@ -1,27 +0,0 @@ -package lintpack - -import ( - "go/ast" -) - -type checkerProto struct { - info *CheckerInfo - constructor func(*Context, parameters) *Checker -} - -type Checker struct { - Info *CheckerInfo - - ctx CheckerContext - - fileWalker FileWalker - - Init func(ctx *Context) -} - -// Check runs rule checker over file f. -func (c *Checker) Check(f *ast.File) []Warning { - c.ctx.warnings = c.ctx.warnings[:0] - c.fileWalker.WalkFile(f) - return c.ctx.warnings -} diff --git a/vendor/github.com/go-lintpack/lintpack/checkers_db.go b/vendor/github.com/go-lintpack/lintpack/checkers_db.go index 813a77067456..83d41b4e4c04 100644 --- a/vendor/github.com/go-lintpack/lintpack/checkers_db.go +++ b/vendor/github.com/go-lintpack/lintpack/checkers_db.go @@ -2,23 +2,24 @@ package lintpack import ( "fmt" - "go/ast" + "regexp" "sort" "strings" "github.com/go-toolsmith/astfmt" ) +type checkerProto struct { + info *CheckerInfo + constructor func(*Context) *Checker +} + // prototypes is a set of registered checkers that are not yet instantiated. // Registration should be done with AddChecker function. // Initialized checkers can be obtained with NewChecker function. var prototypes = make(map[string]checkerProto) -// GetCheckersInfo returns a checkers info list for all registered checkers. -// The slice is sorted by a checker name. -// -// Info objects can be used to instantiate checkers with NewChecker function. -func GetCheckersInfo() []*CheckerInfo { +func getCheckersInfo() []*CheckerInfo { infoList := make([]*CheckerInfo, 0, len(prototypes)) for _, proto := range prototypes { infoCopy := *proto.info @@ -30,65 +31,48 @@ func GetCheckersInfo() []*CheckerInfo { return infoList } -// NewChecker returns initialized checker identified by an info. -// info must be non-nil. -// Panics if info describes a checker that was not properly registered. -// -// params argument specifies per-checker options.NewChecker. Can be nil. -func NewChecker(ctx *Context, info *CheckerInfo, params map[string]interface{}) *Checker { - proto, ok := prototypes[info.Name] - if !ok { - panic(fmt.Sprintf("checker with name %q not registered", info.Name)) - } - return proto.constructor(ctx, params) -} - -// FileWalker is an interface every checker should implement. -// -// The WalkFile method is executed for every Go file inside the -// package that is being checked. -type FileWalker interface { - WalkFile(*ast.File) -} - -// AddChecker registers a new checker into a checkers pool. -// Constructor is used to create a new checker instance. -// Checker name (defined in CheckerInfo.Name) must be unique. -// -// If checker is never needed, for example if it is disabled, -// constructor will not be called. -func AddChecker(info *CheckerInfo, constructor func(*CheckerContext) FileWalker) { +func addChecker(info *CheckerInfo, constructor func(*CheckerContext) FileWalker) { if _, ok := prototypes[info.Name]; ok { panic(fmt.Sprintf("checker with name %q already registered", info.Name)) } - trimDocumentation := func(d *CheckerInfo) { + // Validate param value type. + for pname, param := range info.Params { + switch param.Value.(type) { + case string, int, bool: + // OK. + default: + panic(fmt.Sprintf("unsupported %q param type value: %T", + pname, param.Value)) + } + } + + trimDocumentation := func(info *CheckerInfo) { fields := []*string{ - &d.Summary, - &d.Details, - &d.Before, - &d.After, - &d.Note, + &info.Summary, + &info.Details, + &info.Before, + &info.After, + &info.Note, } for _, f := range fields { *f = strings.TrimSpace(*f) } } - validateDocumentation := func(d *CheckerInfo) { - // TODO(Quasilyte): validate documentation. - } trimDocumentation(info) - validateDocumentation(info) + + if err := validateCheckerInfo(info); err != nil { + panic(err) + } proto := checkerProto{ info: info, - constructor: func(ctx *Context, params parameters) *Checker { + constructor: func(ctx *Context) *Checker { var c Checker c.Info = info c.ctx = CheckerContext{ Context: ctx, - Params: params, printer: astfmt.NewPrinter(ctx.FileSet), } c.fileWalker = constructor(&c.ctx) @@ -98,3 +82,54 @@ func AddChecker(info *CheckerInfo, constructor func(*CheckerContext) FileWalker) prototypes[info.Name] = proto } + +func newChecker(ctx *Context, info *CheckerInfo) *Checker { + proto, ok := prototypes[info.Name] + if !ok { + panic(fmt.Sprintf("checker with name %q not registered", info.Name)) + } + return proto.constructor(ctx) +} + +func validateCheckerInfo(info *CheckerInfo) error { + steps := []func(*CheckerInfo) error{ + validateCheckerName, + validateCheckerDocumentation, + validateCheckerTags, + } + + for _, step := range steps { + if err := step(info); err != nil { + return fmt.Errorf("%q validation error: %v", info.Name, err) + } + } + return nil +} + +var validIdentRE = regexp.MustCompile(`^\w+$`) + +func validateCheckerName(info *CheckerInfo) error { + if !validIdentRE.MatchString(info.Name) { + return fmt.Errorf("checker name contains illegal chars") + } + return nil +} + +func validateCheckerDocumentation(info *CheckerInfo) error { + // TODO(Quasilyte): validate documentation. + return nil +} + +func validateCheckerTags(info *CheckerInfo) error { + tagSet := make(map[string]bool) + for _, tag := range info.Tags { + if tagSet[tag] { + return fmt.Errorf("duplicated tag %q", tag) + } + if !validIdentRE.MatchString(tag) { + return fmt.Errorf("checker tag %q contains illegal chars", tag) + } + tagSet[tag] = true + } + return nil +} diff --git a/vendor/github.com/go-lintpack/lintpack/context.go b/vendor/github.com/go-lintpack/lintpack/context.go index b6a0fcfbeb2e..8671e175ca3b 100644 --- a/vendor/github.com/go-lintpack/lintpack/context.go +++ b/vendor/github.com/go-lintpack/lintpack/context.go @@ -2,110 +2,10 @@ package lintpack import ( "go/ast" - "go/token" "go/types" "strconv" - - "github.com/go-toolsmith/astfmt" ) -// Context is a readonly state shared among every checker. -type Context struct { - // TypesInfo carries parsed packages types information. - TypesInfo *types.Info - - // SizesInfo carries alignment and type size information. - // Arch-dependent. - SizesInfo types.Sizes - - // FileSet is a file set that was used during the program loading. - FileSet *token.FileSet - - // Pkg describes package that is being checked. - Pkg *types.Package - - // Filename is a currently checked file name. - Filename string - - // Require records what optional resources are required - // by the checkers set that use this context. - // - // Every require fields makes associated context field - // to be properly initialized. - // For example, Context.require.PkgObjects => Context.PkgObjects. - Require struct { - PkgObjects bool - PkgRenames bool - } - - // PkgObjects stores all imported packages and their local names. - PkgObjects map[*types.PkgName]string - - // PkgRenames maps package path to its local renaming. - // Contains no entries for packages that were imported without - // explicit local names. - PkgRenames map[string]string -} - -// NewContext returns new shared context to be used by every checker. -// -// All data carried by the context is readonly for checkers, -// but can be modified by the integrating application. -func NewContext(fset *token.FileSet, sizes types.Sizes) *Context { - return &Context{ - FileSet: fset, - SizesInfo: sizes, - TypesInfo: &types.Info{}, - } -} - -// SetPackageInfo sets package-related metadata. -// -// Must be called for every package being checked. -func (c *Context) SetPackageInfo(info *types.Info, pkg *types.Package) { - if info != nil { - // We do this kind of assignment to avoid - // changing c.typesInfo field address after - // every re-assignment. - *c.TypesInfo = *info - } - c.Pkg = pkg -} - -// SetFileInfo sets file-related metadata. -// -// Must be called for every source code file being checked. -func (c *Context) SetFileInfo(name string, f *ast.File) { - c.Filename = name - if c.Require.PkgObjects { - resolvePkgObjects(c, f) - } - if c.Require.PkgRenames { - resolvePkgRenames(c, f) - } -} - -// CheckerContext is checker-local context copy. -// Fields that are not from Context itself are writeable. -type CheckerContext struct { - *Context - - // printer used to format warning text. - printer *astfmt.Printer - - Params parameters - - warnings []Warning -} - -// Warn adds a Warning to checker output. -func (ctx *CheckerContext) Warn(node ast.Node, format string, args ...interface{}) { - ctx.warnings = append(ctx.warnings, Warning{ - Text: ctx.printer.Sprintf(format, args...), - Node: node, - }) -} - func resolvePkgObjects(ctx *Context, f *ast.File) { ctx.PkgObjects = make(map[*types.PkgName]string, len(f.Imports)) diff --git a/vendor/github.com/go-lintpack/lintpack/doc.go b/vendor/github.com/go-lintpack/lintpack/doc.go new file mode 100644 index 000000000000..4aba342f536e --- /dev/null +++ b/vendor/github.com/go-lintpack/lintpack/doc.go @@ -0,0 +1,5 @@ +// Package lintpack provides shared API between the linter and its checkers. +// +// Linter is usually implemented by creating instances of registered checkers. +// Checkers are registered by the AddChecker call. +package lintpack diff --git a/vendor/github.com/go-lintpack/lintpack/go.mod b/vendor/github.com/go-lintpack/lintpack/go.mod new file mode 100644 index 000000000000..b2e4cd984d7b --- /dev/null +++ b/vendor/github.com/go-lintpack/lintpack/go.mod @@ -0,0 +1,11 @@ +module github.com/go-lintpack/lintpack + +require ( + github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6 + github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086 + github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30 + github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8 + github.com/google/go-cmp v0.2.0 + github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e + golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09 +) diff --git a/vendor/github.com/go-lintpack/lintpack/go.sum b/vendor/github.com/go-lintpack/lintpack/go.sum new file mode 100644 index 000000000000..bd9f5dcb95e7 --- /dev/null +++ b/vendor/github.com/go-lintpack/lintpack/go.sum @@ -0,0 +1,14 @@ +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6 h1:aTBUNRTatDDU24gbOEKEoLiDwxtc98ga6K/iMTm6fvs= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086 h1:EIMuvbE9fbtQtimdLe5yeXjuC5CeKbQt8zH6GwtIrhM= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30 h1:zRJPftZJNLPDiOtvYbFRwjSbaJAcVOf80TeEmWGe2kQ= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8 h1:vVouagbdmqTVlCIAxpyYsNNTbkKZ3V66VpKOLU/s6W4= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09 h1:QJFxMApN9XdBRwtqXfOidB2azUCA4ziuiMTrQ1uBGxw= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/vendor/github.com/go-lintpack/lintpack/lintpack.go b/vendor/github.com/go-lintpack/lintpack/lintpack.go index d31594499ab0..28c3a6354eb1 100644 --- a/vendor/github.com/go-lintpack/lintpack/lintpack.go +++ b/vendor/github.com/go-lintpack/lintpack/lintpack.go @@ -1,9 +1,65 @@ package lintpack import ( + "fmt" "go/ast" + "go/token" + "go/types" + + "github.com/go-toolsmith/astfmt" ) +// CheckerCollection provides additional information for a group of checkers. +type CheckerCollection struct { + // URL is a link for a main source of information on the collection. + URL string +} + +// AddChecker registers a new checker into a checkers pool. +// Constructor is used to create a new checker instance. +// Checker name (defined in CheckerInfo.Name) must be unique. +// +// CheckerInfo.Collection is automatically set to the coll (the receiver). +// +// If checker is never needed, for example if it is disabled, +// constructor will not be called. +func (coll *CheckerCollection) AddChecker(info *CheckerInfo, constructor func(*CheckerContext) FileWalker) { + if coll == nil { + panic(fmt.Sprintf("adding checker to a nil collection")) + } + info.Collection = coll + addChecker(info, constructor) +} + +// CheckerParam describes a single checker customizable parameter. +type CheckerParam struct { + // Value holds parameter bound value. + // It might be overwritten by the integrating linter. + // + // Permitted types include: + // - int + // - bool + // - string + Value interface{} + + // Usage gives an overview about what parameter does. + Usage string +} + +// CheckerParams holds all checker-specific parameters. +// +// Provides convenient access to the loosely typed underlying map. +type CheckerParams map[string]*CheckerParam + +// Int lookups pname key in underlying map and type-asserts it to int. +func (params CheckerParams) Int(pname string) int { return params[pname].Value.(int) } + +// Bool lookups pname key in underlying map and type-asserts it to bool. +func (params CheckerParams) Bool(pname string) bool { return params[pname].Value.(bool) } + +// String lookups pname key in underlying map and type-asserts it to string. +func (params CheckerParams) String(pname string) string { return params[pname].Value.(string) } + // CheckerInfo holds checker metadata and structured documentation. type CheckerInfo struct { // Name is a checker name. @@ -13,6 +69,9 @@ type CheckerInfo struct { // Common tags are "experimental" and "performance". Tags []string + // Params declares checker-specific parameters. Optional. + Params CheckerParams + // Summary is a short one sentence description. // Should not end with a period. Summary string @@ -28,6 +87,44 @@ type CheckerInfo struct { // Note is an optional caution message or advice. Note string + + // Collection establishes a checker-to-collection relationship. + Collection *CheckerCollection +} + +// GetCheckersInfo returns a checkers info list for all registered checkers. +// The slice is sorted by a checker name. +// +// Info objects can be used to instantiate checkers with NewChecker function. +func GetCheckersInfo() []*CheckerInfo { + return getCheckersInfo() +} + +// HasTag reports whether checker described by the info has specified tag. +func (info *CheckerInfo) HasTag(tag string) bool { + for i := range info.Tags { + if info.Tags[i] == tag { + return true + } + } + return false +} + +// Checker is an implementation of a check that is described by the associated info. +type Checker struct { + // Info is an info object that was used to instantiate this checker. + Info *CheckerInfo + + ctx CheckerContext + + fileWalker FileWalker +} + +// Check runs rule checker over file f. +func (c *Checker) Check(f *ast.File) []Warning { + c.ctx.warnings = c.ctx.warnings[:0] + c.fileWalker.WalkFile(f) + return c.ctx.warnings } // Warning represents issue that is found by checker. @@ -39,3 +136,113 @@ type Warning struct { // Text is warning message without source location info. Text string } + +// NewChecker returns initialized checker identified by an info. +// info must be non-nil. +// Panics if info describes a checker that was not properly registered. +func NewChecker(ctx *Context, info *CheckerInfo) *Checker { + return newChecker(ctx, info) +} + +// Context is a readonly state shared among every checker. +type Context struct { + // TypesInfo carries parsed packages types information. + TypesInfo *types.Info + + // SizesInfo carries alignment and type size information. + // Arch-dependent. + SizesInfo types.Sizes + + // FileSet is a file set that was used during the program loading. + FileSet *token.FileSet + + // Pkg describes package that is being checked. + Pkg *types.Package + + // Filename is a currently checked file name. + Filename string + + // Require records what optional resources are required + // by the checkers set that use this context. + // + // Every require fields makes associated context field + // to be properly initialized. + // For example, Context.require.PkgObjects => Context.PkgObjects. + Require struct { + PkgObjects bool + PkgRenames bool + } + + // PkgObjects stores all imported packages and their local names. + PkgObjects map[*types.PkgName]string + + // PkgRenames maps package path to its local renaming. + // Contains no entries for packages that were imported without + // explicit local names. + PkgRenames map[string]string +} + +// NewContext returns new shared context to be used by every checker. +// +// All data carried by the context is readonly for checkers, +// but can be modified by the integrating application. +func NewContext(fset *token.FileSet, sizes types.Sizes) *Context { + return &Context{ + FileSet: fset, + SizesInfo: sizes, + TypesInfo: &types.Info{}, + } +} + +// SetPackageInfo sets package-related metadata. +// +// Must be called for every package being checked. +func (c *Context) SetPackageInfo(info *types.Info, pkg *types.Package) { + if info != nil { + // We do this kind of assignment to avoid + // changing c.typesInfo field address after + // every re-assignment. + *c.TypesInfo = *info + } + c.Pkg = pkg +} + +// SetFileInfo sets file-related metadata. +// +// Must be called for every source code file being checked. +func (c *Context) SetFileInfo(name string, f *ast.File) { + c.Filename = name + if c.Require.PkgObjects { + resolvePkgObjects(c, f) + } + if c.Require.PkgRenames { + resolvePkgRenames(c, f) + } +} + +// CheckerContext is checker-local context copy. +// Fields that are not from Context itself are writeable. +type CheckerContext struct { + *Context + + // printer used to format warning text. + printer *astfmt.Printer + + warnings []Warning +} + +// Warn adds a Warning to checker output. +func (ctx *CheckerContext) Warn(node ast.Node, format string, args ...interface{}) { + ctx.warnings = append(ctx.warnings, Warning{ + Text: ctx.printer.Sprintf(format, args...), + Node: node, + }) +} + +// FileWalker is an interface every checker should implement. +// +// The WalkFile method is executed for every Go file inside the +// package that is being checked. +type FileWalker interface { + WalkFile(*ast.File) +} diff --git a/vendor/github.com/go-lintpack/lintpack/parameters.go b/vendor/github.com/go-lintpack/lintpack/parameters.go deleted file mode 100644 index 3d728bd15fcd..000000000000 --- a/vendor/github.com/go-lintpack/lintpack/parameters.go +++ /dev/null @@ -1,41 +0,0 @@ -package lintpack - -import ( - "log" - "strings" -) - -type parameters map[string]interface{} - -func (p parameters) Int(key string, defaultValue int) int { - k := strings.ToLower(key) - if value, ok := p[k]; ok { - if value, ok := value.(int); ok { - return value - } - log.Printf("incorrect value for `%s`, want int", key) - } - return defaultValue -} - -func (p parameters) String(key, defaultValue string) string { - k := strings.ToLower(key) - if value, ok := p[k]; ok { - if value, ok := value.(string); ok { - return value - } - log.Printf("incorrect value for `%s`, want int", key) - } - return defaultValue -} - -func (p parameters) Bool(key string, defaultValue bool) bool { - k := strings.ToLower(key) - if value, ok := p[k]; ok { - if value, ok := value.(bool); ok { - return value - } - log.Printf("incorrect value for `%s`, want bool", key) - } - return defaultValue -} diff --git a/vendor/github.com/go-toolsmith/strparse/.travis.yml b/vendor/github.com/go-toolsmith/strparse/.travis.yml new file mode 100644 index 000000000000..8994d395c610 --- /dev/null +++ b/vendor/github.com/go-toolsmith/strparse/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.x +install: + - # Prevent default install action "go get -t -v ./...". +script: + - go get -t -v ./... + - go tool vet . + - go test -v -race ./... \ No newline at end of file diff --git a/vendor/github.com/go-toolsmith/strparse/LICENSE b/vendor/github.com/go-toolsmith/strparse/LICENSE new file mode 100644 index 000000000000..eef17180f89f --- /dev/null +++ b/vendor/github.com/go-toolsmith/strparse/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 go-toolsmith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-toolsmith/strparse/README.md b/vendor/github.com/go-toolsmith/strparse/README.md new file mode 100644 index 000000000000..ae80a5398a97 --- /dev/null +++ b/vendor/github.com/go-toolsmith/strparse/README.md @@ -0,0 +1,34 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/go-toolsmith/strparse)](https://goreportcard.com/report/github.com/go-toolsmith/strparse) +[![GoDoc](https://godoc.org/github.com/go-toolsmith/strparse?status.svg)](https://godoc.org/github.com/go-toolsmith/strparse) +[![Build Status](https://travis-ci.org/go-toolsmith/strparse.svg?branch=master)](https://travis-ci.org/go-toolsmith/strparse) + + +# strparse + +Package strparse provides convenience wrappers around `go/parser` for simple +expression, statement and declaretion parsing from string. + +## Installation + +```bash +go get github.com/go-toolsmith/strparse +``` + +## Example + +```go +package main + +import ( + "go-toolsmith/astequal" + "go-toolsmith/strparse" +) + +func main() { + // Comparing AST strings for equallity (note different spacing): + x := strparse.Expr(`1 + f(v[0].X)`) + y := strparse.Expr(` 1+f( v[0].X ) `) + fmt.Println(astequal.Expr(x, y)) // => true +} + +``` diff --git a/vendor/github.com/go-toolsmith/strparse/go.mod b/vendor/github.com/go-toolsmith/strparse/go.mod new file mode 100644 index 000000000000..ed9d88136661 --- /dev/null +++ b/vendor/github.com/go-toolsmith/strparse/go.mod @@ -0,0 +1 @@ +module github.com/go-toolsmith/strparse diff --git a/vendor/github.com/go-toolsmith/strparse/strparse.go b/vendor/github.com/go-toolsmith/strparse/strparse.go new file mode 100644 index 000000000000..894c7ebac348 --- /dev/null +++ b/vendor/github.com/go-toolsmith/strparse/strparse.go @@ -0,0 +1,59 @@ +// Package strparse provides convenience wrappers around `go/parser` for simple +// expression, statement and declaration parsing from string. +// +// Can be used to construct AST nodes using source syntax. +package strparse + +import ( + "go/ast" + "go/parser" + "go/token" +) + +var ( + // BadExpr is returned as a parse result for malformed expressions. + // Should be treated as constant or readonly variable. + BadExpr = &ast.BadExpr{} + + // BadStmt is returned as a parse result for malformed statmenents. + // Should be treated as constant or readonly variable. + BadStmt = &ast.BadStmt{} + + // BadDecl is returned as a parse result for malformed declarations. + // Should be treated as constant or readonly variable. + BadDecl = &ast.BadDecl{} +) + +// Expr parses single expression node from s. +// In case of parse error, BadExpr is returned. +func Expr(s string) ast.Expr { + node, err := parser.ParseExpr(s) + if err != nil { + return BadExpr + } + return node +} + +// Stmt parses single statement node from s. +// In case of parse error, BadStmt is returned. +func Stmt(s string) ast.Stmt { + node, err := parser.ParseFile(token.NewFileSet(), "", "package main;func main() {"+s+"}", 0) + if err != nil { + return BadStmt + } + fn := node.Decls[0].(*ast.FuncDecl) + if len(fn.Body.List) != 1 { + return BadStmt + } + return fn.Body.List[0] +} + +// Decl parses single declaration node from s. +// In case of parse error, BadDecl is returned. +func Decl(s string) ast.Decl { + node, err := parser.ParseFile(token.NewFileSet(), "", "package main;"+s, 0) + if err != nil || len(node.Decls) != 1 { + return BadDecl + } + return node.Decls[0] +} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/vendor/golang.org/x/tools/go/ast/astutil/imports.go index 04ad6795d921..3e4b195368b3 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/imports.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/imports.go @@ -14,26 +14,26 @@ import ( ) // AddImport adds the import path to the file f, if absent. -func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) { - return AddNamedImport(fset, f, "", ipath) +func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) { + return AddNamedImport(fset, f, "", path) } -// AddNamedImport adds the import path to the file f, if absent. +// AddNamedImport adds the import with the given name and path to the file f, if absent. // If name is not empty, it is used to rename the import. // // For example, calling // AddNamedImport(fset, f, "pathpkg", "path") // adds // import pathpkg "path" -func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) { - if imports(f, ipath) { +func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) { + if imports(f, name, path) { return false } newImport := &ast.ImportSpec{ Path: &ast.BasicLit{ Kind: token.STRING, - Value: strconv.Quote(ipath), + Value: strconv.Quote(path), }, } if name != "" { @@ -43,14 +43,14 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added // Find an import decl to add to. // The goal is to find an existing import // whose import path has the longest shared - // prefix with ipath. + // prefix with path. var ( bestMatch = -1 // length of longest shared prefix lastImport = -1 // index in f.Decls of the file's final import decl impDecl *ast.GenDecl // import decl containing the best match impIndex = -1 // spec index in impDecl containing the best match - isThirdPartyPath = isThirdParty(ipath) + isThirdPartyPath = isThirdParty(path) ) for i, decl := range f.Decls { gen, ok := decl.(*ast.GenDecl) @@ -81,7 +81,7 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added for j, spec := range gen.Specs { impspec := spec.(*ast.ImportSpec) p := importPath(impspec) - n := matchLen(p, ipath) + n := matchLen(p, path) if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { bestMatch = n impDecl = gen @@ -197,11 +197,13 @@ func isThirdParty(importPath string) bool { } // DeleteImport deletes the import path from the file f, if present. +// If there are duplicate import declarations, all matching ones are deleted. func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { return DeleteNamedImport(fset, f, "", path) } // DeleteNamedImport deletes the import with the given name and path from the file f, if present. +// If there are duplicate import declarations, all matching ones are deleted. func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { var delspecs []*ast.ImportSpec var delcomments []*ast.CommentGroup @@ -216,13 +218,7 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del for j := 0; j < len(gen.Specs); j++ { spec := gen.Specs[j] impspec := spec.(*ast.ImportSpec) - if impspec.Name == nil && name != "" { - continue - } - if impspec.Name != nil && impspec.Name.Name != name { - continue - } - if importPath(impspec) != path { + if importName(impspec) != name || importPath(impspec) != path { continue } @@ -383,9 +379,14 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor { return fn } -// imports returns true if f imports path. -func imports(f *ast.File, path string) bool { - return importSpec(f, path) != nil +// imports reports whether f has an import with the specified name and path. +func imports(f *ast.File, name, path string) bool { + for _, s := range f.Imports { + if importName(s) == name && importPath(s) == path { + return true + } + } + return false } // importSpec returns the import spec if f imports path, @@ -399,14 +400,23 @@ func importSpec(f *ast.File, path string) *ast.ImportSpec { return nil } +// importName returns the name of s, +// or "" if the import is not named. +func importName(s *ast.ImportSpec) string { + if s.Name == nil { + return "" + } + return s.Name.Name +} + // importPath returns the unquoted import path of s, // or "" if the path is not properly quoted. func importPath(s *ast.ImportSpec) string { t, err := strconv.Unquote(s.Path.Value) - if err == nil { - return t + if err != nil { + return "" } - return "" + return t } // declImports reports whether gen contains an import of path. diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index 4c238d10131c..98b3987b9746 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -16,7 +16,7 @@ // time before the Go 1.8 release and rebuild and redeploy their // developer tools, which will then be able to consume both Go 1.7 and // Go 1.8 export data files, so they will work before and after the -// Go update. (See discussion at https://github.com/golang/go/issues/15651.) +// Go update. (See discussion at https://golang.org/issue/15651.) // package gcexportdata // import "golang.org/x/tools/go/gcexportdata" diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go new file mode 100644 index 000000000000..eecf07fee96a --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -0,0 +1,157 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package packagesdriver fetches type sizes for go/packages and go/analysis. +package packagesdriver + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "go/types" + "log" + "os" + "os/exec" + "strings" + "time" +) + +var debug = false + +// GetSizes returns the sizes used by the underlying driver with the given parameters. +func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { + // TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. + const toolPrefix = "GOPACKAGESDRIVER=" + tool := "" + for _, env := range env { + if val := strings.TrimPrefix(env, toolPrefix); val != env { + tool = val + } + } + + if tool == "" { + var err error + tool, err = exec.LookPath("gopackagesdriver") + if err != nil { + // We did not find the driver, so use "go list". + tool = "off" + } + } + + if tool == "off" { + return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) + } + + req, err := json.Marshal(struct { + Command string `json:"command"` + Env []string `json:"env"` + BuildFlags []string `json:"build_flags"` + }{ + Command: "sizes", + Env: env, + BuildFlags: buildFlags, + }) + if err != nil { + return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) + } + + buf := new(bytes.Buffer) + cmd := exec.CommandContext(ctx, tool) + cmd.Dir = dir + cmd.Env = env + cmd.Stdin = bytes.NewReader(req) + cmd.Stdout = buf + cmd.Stderr = new(bytes.Buffer) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) + } + var response struct { + // Sizes, if not nil, is the types.Sizes to use when type checking. + Sizes *types.StdSizes + } + if err := json.Unmarshal(buf.Bytes(), &response); err != nil { + return nil, err + } + return response.Sizes, nil +} + +func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { + args := []string{"list", "-f", "{{context.GOARCH}} {{context.Compiler}}"} + args = append(args, buildFlags...) + args = append(args, "--", "unsafe") + stdout, err := InvokeGo(ctx, env, dir, usesExportData, args...) + if err != nil { + return nil, err + } + fields := strings.Fields(stdout.String()) + goarch := fields[0] + compiler := fields[1] + return types.SizesFor(compiler, goarch), nil +} + +// InvokeGo returns the stdout of a go command invocation. +func InvokeGo(ctx context.Context, env []string, dir string, usesExportData bool, args ...string) (*bytes.Buffer, error) { + if debug { + defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(env, args...)) }(time.Now()) + } + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.CommandContext(ctx, "go", args...) + // On darwin the cwd gets resolved to the real path, which breaks anything that + // expects the working directory to keep the original path, including the + // go command when dealing with modules. + // The Go stdlib has a special feature where if the cwd and the PWD are the + // same node then it trusts the PWD, so by setting it in the env for the child + // process we fix up all the paths returned by the go command. + cmd.Env = append(append([]string{}, env...), "PWD="+dir) + cmd.Dir = dir + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + exitErr, ok := err.(*exec.ExitError) + if !ok { + // Catastrophic error: + // - executable not found + // - context cancellation + return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) + } + + // Export mode entails a build. + // If that build fails, errors appear on stderr + // (despite the -e flag) and the Export field is blank. + // Do not fail in that case. + if !usesExportData { + return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) + } + } + + // As of writing, go list -export prints some non-fatal compilation + // errors to stderr, even with -e set. We would prefer that it put + // them in the Package.Error JSON (see https://golang.org/issue/26319). + // In the meantime, there's nowhere good to put them, but they can + // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS + // is set. + if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(env, args...), stderr) + } + + // debugging + if false { + fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(env, args...), stdout) + } + + return stdout, nil +} + +func cmdDebugStr(envlist []string, args ...string) string { + env := make(map[string]string) + for _, kv := range envlist { + split := strings.Split(kv, "=") + k, v := split[0], split[1] + env[k] = v + } + + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args) +} diff --git a/vendor/golang.org/x/tools/go/loader/loader.go b/vendor/golang.org/x/tools/go/loader/loader.go index c45666118953..de34b809c478 100644 --- a/vendor/golang.org/x/tools/go/loader/loader.go +++ b/vendor/golang.org/x/tools/go/loader/loader.go @@ -780,7 +780,7 @@ func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, err if to == "C" { // This should be unreachable, but ad hoc packages are // not currently subject to cgo preprocessing. - // See https://github.com/golang/go/issues/11627. + // See https://golang.org/issue/11627. return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`, from.Pkg.Path()) } diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go index f9dd1b023791..e645651f1b8c 100644 --- a/vendor/golang.org/x/tools/go/packages/doc.go +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -180,22 +180,13 @@ Instead, ssadump no longer requests the runtime package, but seeks it among the dependencies of the user-specified packages, and emits an error if it is not found. -Overlays: the ParseFile hook in the API permits clients to vary the way -in which ASTs are obtained from filenames; the default implementation is -based on parser.ParseFile. This features enables editor-integrated tools -that analyze the contents of modified but unsaved buffers: rather than -read from the file system, a tool can read from an archive of modified -buffers provided by the editor. -This approach has its limits. Because package metadata is obtained by -fork/execing an external query command for each build system, we can -fake only the file contents seen by the parser, type-checker, and -application, but not by the metadata query, so, for example: -- additional imports in the fake file will not be described by the - metadata, so the type checker will fail to load imports that create - new dependencies. -- in TypeCheck mode, because export data is produced by the query - command, it will not reflect the fake file contents. -- this mechanism cannot add files to a package without first saving them. +Overlays: The Overlay field in the Config allows providing alternate contents +for Go source files, by providing a mapping from file path to contents. +go/packages will pull in new imports added in overlay files when go/packages +is run in LoadImports mode or greater. +Overlay support for the go list driver isn't complete yet: if the file doesn't +exist on disk, it will only be recognized in an overlay if it is a non-test file +and the package would be reported even without the overlay. Questions & Tasks diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go index 53cc080d9d7b..860c3ec156d6 100644 --- a/vendor/golang.org/x/tools/go/packages/external.go +++ b/vendor/golang.org/x/tools/go/packages/external.go @@ -16,7 +16,17 @@ import ( "strings" ) -// findExternalTool returns the file path of a tool that supplies +// Driver +type driverRequest struct { + Command string `json "command"` + Mode LoadMode `json:"mode"` + Env []string `json:"env"` + BuildFlags []string `json:"build_flags"` + Tests bool `json:"tests"` + Overlay map[string][]byte `json:"overlay"` +} + +// findExternalDriver returns the file path of a tool that supplies // the build system package structure, or "" if not found." // If GOPACKAGESDRIVER is set in the environment findExternalTool returns its // value, otherwise it searches for a binary named gopackagesdriver on the PATH. @@ -39,21 +49,22 @@ func findExternalDriver(cfg *Config) driver { } } return func(cfg *Config, words ...string) (*driverResponse, error) { - buf := new(bytes.Buffer) - fullargs := []string{ - "list", - fmt.Sprintf("-test=%t", cfg.Tests), - fmt.Sprintf("-export=%t", usesExportData(cfg)), - fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports), - } - for _, f := range cfg.BuildFlags { - fullargs = append(fullargs, fmt.Sprintf("-buildflag=%v", f)) + req, err := json.Marshal(driverRequest{ + Mode: cfg.Mode, + Env: cfg.Env, + BuildFlags: cfg.BuildFlags, + Tests: cfg.Tests, + Overlay: cfg.Overlay, + }) + if err != nil { + return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) } - fullargs = append(fullargs, "--") - fullargs = append(fullargs, words...) - cmd := exec.CommandContext(cfg.Context, tool, fullargs...) - cmd.Env = cfg.Env + + buf := new(bytes.Buffer) + cmd := exec.CommandContext(cfg.Context, tool, words...) cmd.Dir = cfg.Dir + cmd.Env = cfg.Env + cmd.Stdin = bytes.NewReader(req) cmd.Stdout = buf cmd.Stderr = new(bytes.Buffer) if err := cmd.Run(); err != nil { diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 6f336c864f55..e5551905b113 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -8,19 +8,26 @@ import ( "bytes" "encoding/json" "fmt" + "go/types" "io/ioutil" "log" "os" "os/exec" "path/filepath" + "reflect" "regexp" "strings" "sync" + "time" + "golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/internal/gopathwalk" "golang.org/x/tools/internal/semver" ) +// debug controls verbose logging. +const debug = false + // A goTooOldError reports that the go command // found by exec.LookPath is too old to use the new go list behavior. type goTooOldError struct { @@ -31,6 +38,17 @@ type goTooOldError struct { // the build system package structure. // See driver for more details. func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { + var sizes types.Sizes + var sizeserr error + var sizeswg sync.WaitGroup + if cfg.Mode >= LoadTypes { + sizeswg.Add(1) + go func() { + sizes, sizeserr = getSizes(cfg) + sizeswg.Done() + }() + } + // Determine files requested in contains patterns var containFiles []string var packagesNamed []string @@ -66,24 +84,14 @@ extractQueries: } } patterns = restPatterns - // Look for the deprecated contains: syntax. - // TODO(matloob): delete this around mid-October 2018. - restPatterns = restPatterns[:0] - for _, pattern := range patterns { - if strings.HasPrefix(pattern, "contains:") { - containFile := strings.TrimPrefix(pattern, "contains:") - containFiles = append(containFiles, containFile) - } else { - restPatterns = append(restPatterns, pattern) - } - } - containFiles = absJoin(cfg.Dir, containFiles) // TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released. var listfunc driver + var isFallback bool listfunc = func(cfg *Config, words ...string) (*driverResponse, error) { response, err := golistDriverCurrent(cfg, words...) if _, ok := err.(goTooOldError); ok { + isFallback = true listfunc = golistDriverFallback return listfunc(cfg, words...) } @@ -104,9 +112,12 @@ extractQueries: response = &driverResponse{} } - if len(containFiles) == 0 && len(packagesNamed) == 0 { - return response, nil + sizeswg.Wait() + if sizeserr != nil { + return nil, sizeserr } + // types.SizesFor always returns nil or a *types.StdSizes + response.Sizes, _ = sizes.(*types.StdSizes) seenPkgs := make(map[string]*Package) // for deduplication. different containing queries could produce same packages for _, pkg := range response.Packages { @@ -120,27 +131,83 @@ extractQueries: response.Packages = append(response.Packages, p) } - containsResults, err := runContainsQueries(cfg, listfunc, addPkg, containFiles) - if err != nil { - return nil, err + var containsCandidates []string + + if len(containFiles) != 0 { + containsCandidates, err = runContainsQueries(cfg, listfunc, isFallback, addPkg, containFiles) + if err != nil { + return nil, err + } + } + + if len(packagesNamed) != 0 { + namedResults, err := runNamedQueries(cfg, listfunc, addPkg, packagesNamed) + if err != nil { + return nil, err + } + response.Roots = append(response.Roots, namedResults...) } - response.Roots = append(response.Roots, containsResults...) - namedResults, err := runNamedQueries(cfg, listfunc, addPkg, packagesNamed) + modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response) if err != nil { return nil, err } - response.Roots = append(response.Roots, namedResults...) + if len(containFiles) > 0 { + containsCandidates = append(containsCandidates, modifiedPkgs...) + containsCandidates = append(containsCandidates, needPkgs...) + } + + if len(needPkgs) > 0 { + addNeededOverlayPackages(cfg, listfunc, addPkg, needPkgs) + if err != nil { + return nil, err + } + } + // Check candidate packages for containFiles. + if len(containFiles) > 0 { + for _, id := range containsCandidates { + pkg := seenPkgs[id] + for _, f := range containFiles { + for _, g := range pkg.GoFiles { + if sameFile(f, g) { + response.Roots = append(response.Roots, id) + } + } + } + } + } + return response, nil } -func runContainsQueries(cfg *Config, driver driver, addPkg func(*Package), queries []string) ([]string, error) { +func addNeededOverlayPackages(cfg *Config, driver driver, addPkg func(*Package), pkgs []string) error { + response, err := driver(cfg, pkgs...) + if err != nil { + return err + } + for _, pkg := range response.Packages { + addPkg(pkg) + } + return nil +} + +func runContainsQueries(cfg *Config, driver driver, isFallback bool, addPkg func(*Package), queries []string) ([]string, error) { var results []string for _, query := range queries { // TODO(matloob): Do only one query per directory. fdir := filepath.Dir(query) - cfg.Dir = fdir - dirResponse, err := driver(cfg, ".") + // Pass absolute path of directory to go list so that it knows to treat it as a directory, + // not a package path. + pattern, err := filepath.Abs(fdir) + if err != nil { + return nil, fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) + } + if isFallback { + pattern = "." + cfg.Dir = fdir + } + + dirResponse, err := driver(cfg, pattern) if err != nil { return nil, err } @@ -173,6 +240,10 @@ func runContainsQueries(cfg *Config, driver driver, addPkg func(*Package), queri var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) func runNamedQueries(cfg *Config, driver driver, addPkg func(*Package), queries []string) ([]string, error) { + // calling `go env` isn't free; bail out if there's nothing to do. + if len(queries) == 0 { + return nil, nil + } // Determine which directories are relevant to scan. roots, modRoot, err := roots(cfg) if err != nil { @@ -209,7 +280,12 @@ func runNamedQueries(cfg *Config, driver driver, addPkg func(*Package), queries } } } - gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != ""}) + + startWalk := time.Now() + gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug}) + if debug { + log.Printf("%v for walk", time.Since(startWalk)) + } // Weird special case: the top-level package in a module will be in // whatever directory the user checked the repository out into. It's @@ -286,6 +362,12 @@ func runNamedQueries(cfg *Config, driver driver, addPkg func(*Package), queries gomod.WriteString(")\n") tmpCfg := *cfg + + // We're only trying to look at stuff in the module cache, so + // disable the network. This should speed things up, and has + // prevented errors in at least one case, #28518. + tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...)) + var err error tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") if err != nil { @@ -308,6 +390,10 @@ func runNamedQueries(cfg *Config, driver driver, addPkg func(*Package), queries return results, nil } +func getSizes(cfg *Config) (types.Sizes, error) { + return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) +} + // roots selects the appropriate paths to walk based on the passed-in configuration, // particularly the environment and the presence of a go.mod in cfg.Dir's parents. func roots(cfg *Config) ([]gopathwalk.Root, string, error) { @@ -469,6 +555,7 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) if err != nil { return nil, err } + seen := make(map[string]*jsonPackage) // Decode the JSON and convert it to Package form. var response driverResponse for dec := json.NewDecoder(buf); dec.More(); { @@ -491,6 +578,15 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) return nil, fmt.Errorf("package missing import path: %+v", p) } + if old, found := seen[p.ImportPath]; found { + if !reflect.DeepEqual(p, old) { + return nil, fmt.Errorf("go list repeated package %v with different values", p.ImportPath) + } + // skip the duplicate + continue + } + seen[p.ImportPath] = p + pkg := &Package{ Name: p.Name, ID: p.ImportPath, @@ -499,6 +595,17 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) OtherFiles: absJoin(p.Dir, otherFiles(p)...), } + // Workaround for https://golang.org/issue/28749. + // TODO(adonovan): delete before go1.12 release. + out := pkg.CompiledGoFiles[:0] + for _, f := range pkg.CompiledGoFiles { + if strings.HasSuffix(f, ".s") { + continue + } + out = append(out, f) + } + pkg.CompiledGoFiles = out + // Extract the PkgPath from the package's ID. if i := strings.IndexByte(pkg.ID, ' '); i >= 0 { pkg.PkgPath = pkg.ID[:i] @@ -545,7 +652,9 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) response.Roots = append(response.Roots, pkg.ID) } - // TODO(matloob): Temporary hack since CompiledGoFiles isn't always set. + // Work around for pre-go.1.11 versions of go list. + // TODO(matloob): they should be handled by the fallback. + // Can we delete this? if len(pkg.CompiledGoFiles) == 0 { pkg.CompiledGoFiles = pkg.GoFiles } @@ -591,6 +700,9 @@ func golistargs(cfg *Config, words []string) []string { // invokeGo returns the stdout of a go command invocation. func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { + if debug { + defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cfg, args...)) }(time.Now()) + } stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) cmd := exec.CommandContext(cfg.Context, "go", args...) @@ -622,25 +734,48 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { // If that build fails, errors appear on stderr // (despite the -e flag) and the Export field is blank. // Do not fail in that case. - if !usesExportData(cfg) { + // The same is true if an ad-hoc package given to go list doesn't exist. + // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when + // packages don't exist or a build fails. + if !usesExportData(cfg) && !containsGoFile(args) { return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) } } // As of writing, go list -export prints some non-fatal compilation // errors to stderr, even with -e set. We would prefer that it put - // them in the Package.Error JSON (see http://golang.org/issue/26319). + // them in the Package.Error JSON (see https://golang.org/issue/26319). // In the meantime, there's nowhere good to put them, but they can // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS // is set. if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { - fmt.Fprintf(os.Stderr, "go %v stderr: <<%s>>\n", args, stderr) + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cfg, args...), stderr) } // debugging if false { - fmt.Fprintf(os.Stderr, "go %v stdout: <<%s>>\n", args, stdout) + fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cfg, args...), stdout) } return stdout, nil } + +func containsGoFile(s []string) bool { + for _, f := range s { + if strings.HasSuffix(f, ".go") { + return true + } + } + return false +} + +func cmdDebugStr(cfg *Config, args ...string) string { + env := make(map[string]string) + for _, kv := range cfg.Env { + split := strings.Split(kv, "=") + k, v := split[0], split[1] + env[k] = v + } + + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args) +} diff --git a/vendor/golang.org/x/tools/go/packages/golist_fallback.go b/vendor/golang.org/x/tools/go/packages/golist_fallback.go index 09894016a7af..8e88607a0ec8 100644 --- a/vendor/golang.org/x/tools/go/packages/golist_fallback.go +++ b/vendor/golang.org/x/tools/go/packages/golist_fallback.go @@ -46,7 +46,7 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) var response driverResponse allPkgs := make(map[string]bool) - addPackage := func(p *jsonPackage) { + addPackage := func(p *jsonPackage, isRoot bool) { id := p.ImportPath if allPkgs[id] { @@ -54,7 +54,6 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) } allPkgs[id] = true - isRoot := original[id] != nil pkgpath := id if pkgpath == "unsafe" { @@ -221,7 +220,7 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) } for _, pkg := range original { - addPackage(pkg) + addPackage(pkg, true) } if cfg.Mode < LoadImports || len(deps) == 0 { return &response, nil @@ -239,7 +238,7 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) return nil, fmt.Errorf("JSON decoding failed: %v", err) } - addPackage(p) + addPackage(p, false) } for _, v := range needsTestVariant { @@ -356,14 +355,13 @@ func vendorlessPath(ipath string) string { } // getDeps runs an initial go list to determine all the dependency packages. -func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, deps []string, err error) { +func getDeps(cfg *Config, words ...string) (initial []*jsonPackage, deps []string, err error) { buf, err := invokeGo(cfg, golistArgsFallback(cfg, words)...) if err != nil { return nil, nil, err } depsSet := make(map[string]bool) - originalSet = make(map[string]*jsonPackage) var testImports []string // Extract deps from the JSON. @@ -373,7 +371,7 @@ func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) } - originalSet[p.ImportPath] = p + initial = append(initial, p) for _, dep := range p.Deps { depsSet[dep] = true } @@ -407,8 +405,8 @@ func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, } } - for orig := range originalSet { - delete(depsSet, orig) + for _, orig := range initial { + delete(depsSet, orig.ImportPath) } deps = make([]string, 0, len(depsSet)) @@ -416,7 +414,7 @@ func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, deps = append(deps, dep) } sort.Strings(deps) // ensure output is deterministic - return originalSet, deps, nil + return initial, deps, nil } func golistArgsFallback(cfg *Config, words []string) []string { diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go new file mode 100644 index 000000000000..71ffcd9d55bb --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go @@ -0,0 +1,104 @@ +package packages + +import ( + "go/parser" + "go/token" + "path/filepath" + "strconv" + "strings" +) + +// processGolistOverlay provides rudimentary support for adding +// files that don't exist on disk to an overlay. The results can be +// sometimes incorrect. +// TODO(matloob): Handle unsupported cases, including the following: +// - test files +// - adding test and non-test files to test variants of packages +// - determining the correct package to add given a new import path +// - creating packages that don't exist +func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs, needPkgs []string, err error) { + havePkgs := make(map[string]string) // importPath -> non-test package ID + needPkgsSet := make(map[string]bool) + modifiedPkgsSet := make(map[string]bool) + + for _, pkg := range response.Packages { + // This is an approximation of import path to id. This can be + // wrong for tests, vendored packages, and a number of other cases. + havePkgs[pkg.PkgPath] = pkg.ID + } + +outer: + for path, contents := range cfg.Overlay { + base := filepath.Base(path) + if strings.HasSuffix(path, "_test.go") { + // Overlays don't support adding new test files yet. + // TODO(matloob): support adding new test files. + continue + } + dir := filepath.Dir(path) + for _, pkg := range response.Packages { + var dirContains, fileExists bool + for _, f := range pkg.GoFiles { + if sameFile(filepath.Dir(f), dir) { + dirContains = true + } + if filepath.Base(f) == base { + fileExists = true + } + } + if dirContains { + if !fileExists { + pkg.GoFiles = append(pkg.GoFiles, path) // TODO(matloob): should the file just be added to GoFiles? + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, path) + modifiedPkgsSet[pkg.ID] = true + } + imports, err := extractImports(path, contents) + if err != nil { + // Let the parser or type checker report errors later. + continue outer + } + for _, imp := range imports { + _, found := pkg.Imports[imp] + if !found { + needPkgsSet[imp] = true + // TODO(matloob): Handle cases when the following block isn't correct. + // These include imports of test variants, imports of vendored packages, etc. + id, ok := havePkgs[imp] + if !ok { + id = imp + } + pkg.Imports[imp] = &Package{ID: id} + } + } + continue outer + } + } + } + + needPkgs = make([]string, 0, len(needPkgsSet)) + for pkg := range needPkgsSet { + needPkgs = append(needPkgs, pkg) + } + modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) + for pkg := range modifiedPkgsSet { + modifiedPkgs = append(modifiedPkgs, pkg) + } + return modifiedPkgs, needPkgs, err +} + +func extractImports(filename string, contents []byte) ([]string, error) { + f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? + if err != nil { + return nil, err + } + var res []string + for _, imp := range f.Imports { + quotedPath := imp.Path.Value + path, err := strconv.Unquote(quotedPath) + if err != nil { + return nil, err + } + res = append(res, path) + } + return res, nil +} diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index ae70c22b8186..15927f1d3804 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -19,9 +19,6 @@ import ( "log" "os" "path/filepath" - "runtime" - "sort" - "strings" "sync" "golang.org/x/tools/go/gcexportdata" @@ -60,7 +57,7 @@ const ( LoadAllSyntax ) -// An Config specifies details about how packages should be loaded. +// A Config specifies details about how packages should be loaded. // The zero value is a valid configuration. // Calls to Load do not modify this struct. type Config struct { @@ -128,9 +125,8 @@ type Config struct { // If the file with the given path already exists, the parser will use the // alternative file contents provided by the map. // - // The Package.Imports map may not include packages that are imported only - // by the alternative file contents provided by Overlay. This may cause - // type-checking to fail. + // Overlays provide incomplete support for when a given file doesn't + // already exist on disk. See the package doc above for more details. Overlay map[string][]byte } @@ -140,6 +136,9 @@ type driver func(cfg *Config, patterns ...string) (*driverResponse, error) // driverResponse contains the results for a driver query. type driverResponse struct { + // Sizes, if not nil, is the types.Sizes to use when type checking. + Sizes *types.StdSizes + // Roots is the set of package IDs that make up the root packages. // We have to encode this separately because when we encode a single package // we cannot know if it is one of the roots as that requires knowledge of the @@ -174,7 +173,7 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { if err != nil { return nil, err } - sort.Strings(response.Roots) // make all driver responses deterministic + l.sizes = response.Sizes return l.refine(response.Roots, response.Packages...) } @@ -369,6 +368,7 @@ type loaderPackage struct { type loader struct { pkgs map[string]*loaderPackage Config + sizes types.Sizes exportMu sync.Mutex // enforces mutual exclusion of exportdata operations } @@ -413,28 +413,38 @@ func newLoader(cfg *Config) *loader { // refine connects the supplied packages into a graph and then adds type and // and syntax information as requested by the LoadMode. func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { - isRoot := make(map[string]bool, len(roots)) - for _, root := range roots { - isRoot[root] = true + rootMap := make(map[string]int, len(roots)) + for i, root := range roots { + rootMap[root] = i } ld.pkgs = make(map[string]*loaderPackage) // first pass, fixup and build the map and roots - var initial []*loaderPackage + var initial = make([]*loaderPackage, len(roots)) for _, pkg := range list { + rootIndex := -1 + if i, found := rootMap[pkg.ID]; found { + rootIndex = i + } lpkg := &loaderPackage{ Package: pkg, needtypes: ld.Mode >= LoadAllSyntax || - ld.Mode >= LoadTypes && isRoot[pkg.ID], + ld.Mode >= LoadTypes && rootIndex >= 0, needsrc: ld.Mode >= LoadAllSyntax || - ld.Mode >= LoadSyntax && isRoot[pkg.ID] || + ld.Mode >= LoadSyntax && rootIndex >= 0 || + len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files pkg.ExportFile == "" && pkg.PkgPath != "unsafe", } ld.pkgs[lpkg.ID] = lpkg - if isRoot[lpkg.ID] { - initial = append(initial, lpkg) + if rootIndex >= 0 { + initial[rootIndex] = lpkg lpkg.initial = true } } + for i, root := range roots { + if initial[i] == nil { + return nil, fmt.Errorf("root package %v is missing", root) + } + } // Materialize the import graph. @@ -685,17 +695,6 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { panic("unreachable") }) - // This is only an approximation. - // TODO(adonovan): derive Sizes from the underlying build system. - goarch := runtime.GOARCH - const goarchPrefix = "GOARCH=" - for _, e := range ld.Config.Env { - if strings.HasPrefix(e, goarchPrefix) { - goarch = e[len(goarchPrefix):] - } - } - sizes := types.SizesFor("gc", goarch) - // type-check tc := &types.Config{ Importer: importer, @@ -706,7 +705,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { IgnoreFuncBodies: ld.Mode < LoadAllSyntax && !lpkg.initial, Error: appendError, - Sizes: sizes, + Sizes: ld.sizes, } types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) @@ -820,6 +819,15 @@ func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) { // the same file. // func sameFile(x, y string) bool { + if x == y { + // It could be the case that y doesn't exist. + // For instance, it may be an overlay file that + // hasn't been written to disk. To handle that case + // let x == y through. (We added the exact absolute path + // string to the CompiledGoFiles list, so the unwritten + // overlay case implies x==y.) + return true + } if filepath.Base(x) == filepath.Base(y) { // (optimisation) if xi, err := os.Stat(x); err == nil { if yi, err := os.Stat(y); err == nil { diff --git a/vendor/golang.org/x/tools/go/packages/visit.go b/vendor/golang.org/x/tools/go/packages/visit.go index c1a4b28ca03d..b13cb081fcbe 100644 --- a/vendor/golang.org/x/tools/go/packages/visit.go +++ b/vendor/golang.org/x/tools/go/packages/visit.go @@ -24,7 +24,7 @@ func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) { for path := range pkg.Imports { paths = append(paths, path) } - sort.Strings(paths) // for determinism + sort.Strings(paths) // Imports is a map, this makes visit stable for _, path := range paths { visit(pkg.Imports[path]) } diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go index 61896ffe7eac..e880d358b138 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go @@ -16,7 +16,12 @@ import ( func direntNamlen(dirent *syscall.Dirent) uint64 { const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name)) nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) - nameLen := bytes.IndexByte(nameBuf[:dirent.Reclen-fixedHdr], 0) + const nameBufLen = uint16(len(nameBuf)) + limit := dirent.Reclen - fixedHdr + if limit > nameBufLen { + limit = nameBufLen + } + nameLen := bytes.IndexByte(nameBuf[:limit], 0) if nameLen < 0 { panic("failed to find terminating 0 byte in dirent") } diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go index dc085fc16084..a561f9f4148e 100644 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -11,12 +11,13 @@ import ( "bytes" "fmt" "go/build" - "golang.org/x/tools/internal/fastwalk" "io/ioutil" "log" "os" "path/filepath" "strings" + + "golang.org/x/tools/internal/fastwalk" ) // Options controls the behavior of a Walk call. @@ -176,7 +177,9 @@ func (w *walker) walk(path string, typ os.FileMode) error { if typ == os.ModeDir { base := filepath.Base(path) if base == "" || base[0] == '.' || base[0] == '_' || - base == "testdata" || (!w.opts.ModulesEnabled && base == "node_modules") { + base == "testdata" || + (w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") || + (!w.opts.ModulesEnabled && base == "node_modules") { return filepath.SkipDir } fi, err := os.Lstat(path) diff --git a/vendor/modules.txt b/vendor/modules.txt index 63b187bb230d..a06a942fe542 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,10 +8,10 @@ github.com/davecgh/go-spew/spew github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify -# github.com/go-critic/checkers v0.0.0-20181031185637-879460b6c936 +# github.com/go-critic/checkers v0.0.0-20181204210945-97246d3b3c67 github.com/go-critic/checkers github.com/go-critic/checkers/internal/lintutil -# github.com/go-lintpack/lintpack v0.0.0-20181105152233-7ff0297828fc +# github.com/go-lintpack/lintpack v0.5.1 github.com/go-lintpack/lintpack github.com/go-lintpack/lintpack/astwalk github.com/go-lintpack/lintpack/internal/lintutil @@ -28,6 +28,8 @@ github.com/go-toolsmith/astequal github.com/go-toolsmith/astfmt # github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30 github.com/go-toolsmith/astp +# github.com/go-toolsmith/strparse v0.0.0-20180903215201-830b6daa1241 +github.com/go-toolsmith/strparse # github.com/go-toolsmith/typep v0.0.0-20181030061450-d63dc7650676 github.com/go-toolsmith/typep # github.com/gobwas/glob v0.2.3 @@ -195,7 +197,7 @@ golang.org/x/sys/windows golang.org/x/text/width golang.org/x/text/transform golang.org/x/text/unicode/norm -# golang.org/x/tools v0.0.0-20180831211245-6c7e314b6563 +# golang.org/x/tools v0.0.0-20181220024903-92cdcd90bf52 golang.org/x/tools/go/loader golang.org/x/tools/go/packages golang.org/x/tools/go/ast/astutil @@ -203,6 +205,7 @@ golang.org/x/tools/go/types/typeutil golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/buildutil golang.org/x/tools/go/internal/cgo +golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/semver golang.org/x/tools/go/internal/gcimporter