aboutsummaryrefslogtreecommitdiff
path: root/vendor/honnef.co/go/tools/simple
diff options
context:
space:
mode:
authorJoseph Richey <joerichey94@gmail.com>2018-02-11 20:34:07 -0800
committerJoseph Richey <joerichey94@gmail.com>2018-02-11 20:34:07 -0800
commit23b8c7b4eab0375b3d59cf4b2a1f3d7356515f95 (patch)
tree6ec67f8d6b15420c3870d2b7c43b2b636fbb8349 /vendor/honnef.co/go/tools/simple
parentfff13ea9041a3945e36d5f002c3c0a1e0e93c825 (diff)
vendor: include source for tools
This change vendors the source for all our build, formatting, and linting tools. Generated by running "dep ensure".
Diffstat (limited to 'vendor/honnef.co/go/tools/simple')
-rw-r--r--vendor/honnef.co/go/tools/simple/lint.go1771
-rw-r--r--vendor/honnef.co/go/tools/simple/lint17.go7
-rw-r--r--vendor/honnef.co/go/tools/simple/lint18.go7
3 files changed, 1785 insertions, 0 deletions
diff --git a/vendor/honnef.co/go/tools/simple/lint.go b/vendor/honnef.co/go/tools/simple/lint.go
new file mode 100644
index 0000000..47ee690
--- /dev/null
+++ b/vendor/honnef.co/go/tools/simple/lint.go
@@ -0,0 +1,1771 @@
+// Package simple contains a linter for Go source code.
+package simple // import "honnef.co/go/tools/simple"
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/token"
+ "go/types"
+ "reflect"
+ "strings"
+
+ "honnef.co/go/tools/internal/sharedcheck"
+ "honnef.co/go/tools/lint"
+ "honnef.co/go/tools/ssa"
+
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+type Checker struct {
+ CheckGenerated bool
+ MS *typeutil.MethodSetCache
+
+ nodeFns map[ast.Node]*ssa.Function
+}
+
+func NewChecker() *Checker {
+ return &Checker{
+ MS: &typeutil.MethodSetCache{},
+ }
+}
+
+func (*Checker) Name() string { return "gosimple" }
+func (*Checker) Prefix() string { return "S" }
+
+func (c *Checker) Init(prog *lint.Program) {
+ c.nodeFns = lint.NodeFns(prog.Packages)
+}
+
+func (c *Checker) Funcs() map[string]lint.Func {
+ return map[string]lint.Func{
+ "S1000": c.LintSingleCaseSelect,
+ "S1001": c.LintLoopCopy,
+ "S1002": c.LintIfBoolCmp,
+ "S1003": c.LintStringsContains,
+ "S1004": c.LintBytesCompare,
+ "S1005": c.LintUnnecessaryBlank,
+ "S1006": c.LintForTrue,
+ "S1007": c.LintRegexpRaw,
+ "S1008": c.LintIfReturn,
+ "S1009": c.LintRedundantNilCheckWithLen,
+ "S1010": c.LintSlicing,
+ "S1011": c.LintLoopAppend,
+ "S1012": c.LintTimeSince,
+ "S1013": c.LintSimplerReturn,
+ "S1014": nil,
+ "S1015": nil,
+ "S1016": c.LintSimplerStructConversion,
+ "S1017": c.LintTrim,
+ "S1018": c.LintLoopSlide,
+ "S1019": c.LintMakeLenCap,
+ "S1020": c.LintAssertNotNil,
+ "S1021": c.LintDeclareAssign,
+ "S1022": nil,
+ "S1023": c.LintRedundantBreak,
+ "S1024": c.LintTimeUntil,
+ "S1025": c.LintRedundantSprintf,
+ "S1026": nil,
+ "S1027": nil,
+ "S1028": c.LintErrorsNewSprintf,
+ "S1029": c.LintRangeStringRunes,
+ "S1030": c.LintBytesBufferConversions,
+ "S1031": c.LintNilCheckAroundRange,
+ }
+}
+
+func (c *Checker) filterGenerated(files []*ast.File) []*ast.File {
+ if c.CheckGenerated {
+ return files
+ }
+ var out []*ast.File
+ for _, f := range files {
+ if !lint.IsGenerated(f) {
+ out = append(out, f)
+ }
+ }
+ return out
+}
+
+func (c *Checker) LintSingleCaseSelect(j *lint.Job) {
+ isSingleSelect := func(node ast.Node) bool {
+ v, ok := node.(*ast.SelectStmt)
+ if !ok {
+ return false
+ }
+ return len(v.Body.List) == 1
+ }
+
+ seen := map[ast.Node]struct{}{}
+ fn := func(node ast.Node) bool {
+ switch v := node.(type) {
+ case *ast.ForStmt:
+ if len(v.Body.List) != 1 {
+ return true
+ }
+ if !isSingleSelect(v.Body.List[0]) {
+ return true
+ }
+ if _, ok := v.Body.List[0].(*ast.SelectStmt).Body.List[0].(*ast.CommClause).Comm.(*ast.SendStmt); ok {
+ // Don't suggest using range for channel sends
+ return true
+ }
+ seen[v.Body.List[0]] = struct{}{}
+ j.Errorf(node, "should use for range instead of for { select {} }")
+ case *ast.SelectStmt:
+ if _, ok := seen[v]; ok {
+ return true
+ }
+ if !isSingleSelect(v) {
+ return true
+ }
+ j.Errorf(node, "should use a simple channel send/receive instead of select with a single case")
+ return true
+ }
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintLoopCopy(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ loop, ok := node.(*ast.RangeStmt)
+ if !ok {
+ return true
+ }
+
+ if loop.Key == nil {
+ return true
+ }
+ if len(loop.Body.List) != 1 {
+ return true
+ }
+ stmt, ok := loop.Body.List[0].(*ast.AssignStmt)
+ if !ok {
+ return true
+ }
+ if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 {
+ return true
+ }
+ lhs, ok := stmt.Lhs[0].(*ast.IndexExpr)
+ if !ok {
+ return true
+ }
+ if _, ok := j.Program.Info.TypeOf(lhs.X).(*types.Slice); !ok {
+ return true
+ }
+ lidx, ok := lhs.Index.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ key, ok := loop.Key.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if j.Program.Info.TypeOf(lhs) == nil || j.Program.Info.TypeOf(stmt.Rhs[0]) == nil {
+ return true
+ }
+ if j.Program.Info.ObjectOf(lidx) != j.Program.Info.ObjectOf(key) {
+ return true
+ }
+ if !types.Identical(j.Program.Info.TypeOf(lhs), j.Program.Info.TypeOf(stmt.Rhs[0])) {
+ return true
+ }
+ if _, ok := j.Program.Info.TypeOf(loop.X).(*types.Slice); !ok {
+ return true
+ }
+
+ if rhs, ok := stmt.Rhs[0].(*ast.IndexExpr); ok {
+ rx, ok := rhs.X.(*ast.Ident)
+ _ = rx
+ if !ok {
+ return true
+ }
+ ridx, ok := rhs.Index.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if j.Program.Info.ObjectOf(ridx) != j.Program.Info.ObjectOf(key) {
+ return true
+ }
+ } else if rhs, ok := stmt.Rhs[0].(*ast.Ident); ok {
+ value, ok := loop.Value.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if j.Program.Info.ObjectOf(rhs) != j.Program.Info.ObjectOf(value) {
+ return true
+ }
+ } else {
+ return true
+ }
+ j.Errorf(loop, "should use copy() instead of a loop")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintIfBoolCmp(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ expr, ok := node.(*ast.BinaryExpr)
+ if !ok || (expr.Op != token.EQL && expr.Op != token.NEQ) {
+ return true
+ }
+ x := j.IsBoolConst(expr.X)
+ y := j.IsBoolConst(expr.Y)
+ if !x && !y {
+ return true
+ }
+ var other ast.Expr
+ var val bool
+ if x {
+ val = j.BoolConst(expr.X)
+ other = expr.Y
+ } else {
+ val = j.BoolConst(expr.Y)
+ other = expr.X
+ }
+ basic, ok := j.Program.Info.TypeOf(other).Underlying().(*types.Basic)
+ if !ok || basic.Kind() != types.Bool {
+ return true
+ }
+ op := ""
+ if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) {
+ op = "!"
+ }
+ r := op + j.Render(other)
+ l1 := len(r)
+ r = strings.TrimLeft(r, "!")
+ if (l1-len(r))%2 == 1 {
+ r = "!" + r
+ }
+ j.Errorf(expr, "should omit comparison to bool constant, can be simplified to %s", r)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintBytesBufferConversions(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ call, ok := node.(*ast.CallExpr)
+ if !ok || len(call.Args) != 1 {
+ return true
+ }
+
+ argCall, ok := call.Args[0].(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ sel, ok := argCall.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+
+ typ := j.Program.Info.TypeOf(call.Fun)
+ if typ == types.Universe.Lookup("string").Type() && j.IsCallToAST(call.Args[0], "(*bytes.Buffer).Bytes") {
+ j.Errorf(call, "should use %v.String() instead of %v", j.Render(sel.X), j.Render(call))
+ } else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && j.IsCallToAST(call.Args[0], "(*bytes.Buffer).String") {
+ j.Errorf(call, "should use %v.Bytes() instead of %v", j.Render(sel.X), j.Render(call))
+ }
+
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintStringsContains(j *lint.Job) {
+ // map of value to token to bool value
+ allowed := map[int64]map[token.Token]bool{
+ -1: {token.GTR: true, token.NEQ: true, token.EQL: false},
+ 0: {token.GEQ: true, token.LSS: false},
+ }
+ fn := func(node ast.Node) bool {
+ expr, ok := node.(*ast.BinaryExpr)
+ if !ok {
+ return true
+ }
+ switch expr.Op {
+ case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL:
+ default:
+ return true
+ }
+
+ value, ok := j.ExprToInt(expr.Y)
+ if !ok {
+ return true
+ }
+
+ allowedOps, ok := allowed[value]
+ if !ok {
+ return true
+ }
+ b, ok := allowedOps[expr.Op]
+ if !ok {
+ return true
+ }
+
+ call, ok := expr.X.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ pkgIdent, ok := sel.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ funIdent := sel.Sel
+ if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" {
+ return true
+ }
+ newFunc := ""
+ switch funIdent.Name {
+ case "IndexRune":
+ newFunc = "ContainsRune"
+ case "IndexAny":
+ newFunc = "ContainsAny"
+ case "Index":
+ newFunc = "Contains"
+ default:
+ return true
+ }
+
+ prefix := ""
+ if !b {
+ prefix = "!"
+ }
+ j.Errorf(node, "should use %s%s.%s(%s) instead", prefix, pkgIdent.Name, newFunc, j.RenderArgs(call.Args))
+
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintBytesCompare(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ expr, ok := node.(*ast.BinaryExpr)
+ if !ok {
+ return true
+ }
+ if expr.Op != token.NEQ && expr.Op != token.EQL {
+ return true
+ }
+ call, ok := expr.X.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if !j.IsCallToAST(call, "bytes.Compare") {
+ return true
+ }
+ value, ok := j.ExprToInt(expr.Y)
+ if !ok || value != 0 {
+ return true
+ }
+ args := j.RenderArgs(call.Args)
+ prefix := ""
+ if expr.Op == token.NEQ {
+ prefix = "!"
+ }
+ j.Errorf(node, "should use %sbytes.Equal(%s) instead", prefix, args)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintForTrue(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ loop, ok := node.(*ast.ForStmt)
+ if !ok {
+ return true
+ }
+ if loop.Init != nil || loop.Post != nil {
+ return true
+ }
+ if !j.IsBoolConst(loop.Cond) || !j.BoolConst(loop.Cond) {
+ return true
+ }
+ j.Errorf(loop, "should use for {} instead of for true {}")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintRegexpRaw(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ call, ok := node.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if !j.IsCallToAST(call, "regexp.MustCompile") &&
+ !j.IsCallToAST(call, "regexp.Compile") {
+ return true
+ }
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ if len(call.Args) != 1 {
+ // invalid function call
+ return true
+ }
+ lit, ok := call.Args[0].(*ast.BasicLit)
+ if !ok {
+ // TODO(dominikh): support string concat, maybe support constants
+ return true
+ }
+ if lit.Kind != token.STRING {
+ // invalid function call
+ return true
+ }
+ if lit.Value[0] != '"' {
+ // already a raw string
+ return true
+ }
+ val := lit.Value
+ if !strings.Contains(val, `\\`) {
+ return true
+ }
+
+ bs := false
+ for _, c := range val {
+ if !bs && c == '\\' {
+ bs = true
+ continue
+ }
+ if bs && c == '\\' {
+ bs = false
+ continue
+ }
+ if bs {
+ // backslash followed by non-backslash -> escape sequence
+ return true
+ }
+ }
+
+ j.Errorf(call, "should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintIfReturn(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ block, ok := node.(*ast.BlockStmt)
+ if !ok {
+ return true
+ }
+ l := len(block.List)
+ if l < 2 {
+ return true
+ }
+ n1, n2 := block.List[l-2], block.List[l-1]
+
+ if len(block.List) >= 3 {
+ if _, ok := block.List[l-3].(*ast.IfStmt); ok {
+ // Do not flag a series of if statements
+ return true
+ }
+ }
+ // if statement with no init, no else, a single condition
+ // checking an identifier or function call and just a return
+ // statement in the body, that returns a boolean constant
+ ifs, ok := n1.(*ast.IfStmt)
+ if !ok {
+ return true
+ }
+ if ifs.Else != nil || ifs.Init != nil {
+ return true
+ }
+ if len(ifs.Body.List) != 1 {
+ return true
+ }
+ if op, ok := ifs.Cond.(*ast.BinaryExpr); ok {
+ switch op.Op {
+ case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
+ default:
+ return true
+ }
+ }
+ ret1, ok := ifs.Body.List[0].(*ast.ReturnStmt)
+ if !ok {
+ return true
+ }
+ if len(ret1.Results) != 1 {
+ return true
+ }
+ if !j.IsBoolConst(ret1.Results[0]) {
+ return true
+ }
+
+ ret2, ok := n2.(*ast.ReturnStmt)
+ if !ok {
+ return true
+ }
+ if len(ret2.Results) != 1 {
+ return true
+ }
+ if !j.IsBoolConst(ret2.Results[0]) {
+ return true
+ }
+ j.Errorf(n1, "should use 'return <expr>' instead of 'if <expr> { return <bool> }; return <bool>'")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+// LintRedundantNilCheckWithLen checks for the following reduntant nil-checks:
+//
+// if x == nil || len(x) == 0 {}
+// if x != nil && len(x) != 0 {}
+// if x != nil && len(x) == N {} (where N != 0)
+// if x != nil && len(x) > N {}
+// if x != nil && len(x) >= N {} (where N != 0)
+//
+func (c *Checker) LintRedundantNilCheckWithLen(j *lint.Job) {
+ isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) {
+ _, ok := expr.(*ast.BasicLit)
+ if ok {
+ return true, lint.IsZero(expr)
+ }
+ id, ok := expr.(*ast.Ident)
+ if !ok {
+ return false, false
+ }
+ c, ok := j.Program.Info.ObjectOf(id).(*types.Const)
+ if !ok {
+ return false, false
+ }
+ return true, c.Val().Kind() == constant.Int && c.Val().String() == "0"
+ }
+
+ fn := func(node ast.Node) bool {
+ // check that expr is "x || y" or "x && y"
+ expr, ok := node.(*ast.BinaryExpr)
+ if !ok {
+ return true
+ }
+ if expr.Op != token.LOR && expr.Op != token.LAND {
+ return true
+ }
+ eqNil := expr.Op == token.LOR
+
+ // check that x is "xx == nil" or "xx != nil"
+ x, ok := expr.X.(*ast.BinaryExpr)
+ if !ok {
+ return true
+ }
+ if eqNil && x.Op != token.EQL {
+ return true
+ }
+ if !eqNil && x.Op != token.NEQ {
+ return true
+ }
+ xx, ok := x.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if !j.IsNil(x.Y) {
+ return true
+ }
+
+ // check that y is "len(xx) == 0" or "len(xx) ... "
+ y, ok := expr.Y.(*ast.BinaryExpr)
+ if !ok {
+ return true
+ }
+ if eqNil && y.Op != token.EQL { // must be len(xx) *==* 0
+ return false
+ }
+ yx, ok := y.X.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ yxFun, ok := yx.Fun.(*ast.Ident)
+ if !ok || yxFun.Name != "len" || len(yx.Args) != 1 {
+ return true
+ }
+ yxArg, ok := yx.Args[0].(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if yxArg.Name != xx.Name {
+ return true
+ }
+
+ if eqNil && !lint.IsZero(y.Y) { // must be len(x) == *0*
+ return true
+ }
+
+ if !eqNil {
+ isConst, isZero := isConstZero(y.Y)
+ if !isConst {
+ return true
+ }
+ switch y.Op {
+ case token.EQL:
+ // avoid false positive for "xx != nil && len(xx) == 0"
+ if isZero {
+ return true
+ }
+ case token.GEQ:
+ // avoid false positive for "xx != nil && len(xx) >= 0"
+ if isZero {
+ return true
+ }
+ case token.NEQ:
+ // avoid false positive for "xx != nil && len(xx) != <non-zero>"
+ if !isZero {
+ return true
+ }
+ case token.GTR:
+ // ok
+ default:
+ return true
+ }
+ }
+
+ // finally check that xx type is one of array, slice, map or chan
+ // this is to prevent false positive in case if xx is a pointer to an array
+ var nilType string
+ switch j.Program.Info.TypeOf(xx).(type) {
+ case *types.Slice:
+ nilType = "nil slices"
+ case *types.Map:
+ nilType = "nil maps"
+ case *types.Chan:
+ nilType = "nil channels"
+ default:
+ return true
+ }
+ j.Errorf(expr, "should omit nil check; len() for %s is defined as zero", nilType)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintSlicing(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ n, ok := node.(*ast.SliceExpr)
+ if !ok {
+ return true
+ }
+ if n.Max != nil {
+ return true
+ }
+ s, ok := n.X.(*ast.Ident)
+ if !ok || s.Obj == nil {
+ return true
+ }
+ call, ok := n.High.(*ast.CallExpr)
+ if !ok || len(call.Args) != 1 || call.Ellipsis.IsValid() {
+ return true
+ }
+ fun, ok := call.Fun.(*ast.Ident)
+ if !ok || fun.Name != "len" {
+ return true
+ }
+ if _, ok := j.Program.Info.ObjectOf(fun).(*types.Builtin); !ok {
+ return true
+ }
+ arg, ok := call.Args[0].(*ast.Ident)
+ if !ok || arg.Obj != s.Obj {
+ return true
+ }
+ j.Errorf(n, "should omit second index in slice, s[a:len(s)] is identical to s[a:]")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func refersTo(info *types.Info, expr ast.Expr, ident *ast.Ident) bool {
+ found := false
+ fn := func(node ast.Node) bool {
+ ident2, ok := node.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if info.ObjectOf(ident) == info.ObjectOf(ident2) {
+ found = true
+ return false
+ }
+ return true
+ }
+ ast.Inspect(expr, fn)
+ return found
+}
+
+func (c *Checker) LintLoopAppend(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ loop, ok := node.(*ast.RangeStmt)
+ if !ok {
+ return true
+ }
+ if !lint.IsBlank(loop.Key) {
+ return true
+ }
+ val, ok := loop.Value.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if len(loop.Body.List) != 1 {
+ return true
+ }
+ stmt, ok := loop.Body.List[0].(*ast.AssignStmt)
+ if !ok {
+ return true
+ }
+ if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 {
+ return true
+ }
+ if refersTo(j.Program.Info, stmt.Lhs[0], val) {
+ return true
+ }
+ call, ok := stmt.Rhs[0].(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if len(call.Args) != 2 || call.Ellipsis.IsValid() {
+ return true
+ }
+ fun, ok := call.Fun.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ obj := j.Program.Info.ObjectOf(fun)
+ fn, ok := obj.(*types.Builtin)
+ if !ok || fn.Name() != "append" {
+ return true
+ }
+
+ src := j.Program.Info.TypeOf(loop.X)
+ dst := j.Program.Info.TypeOf(call.Args[0])
+ // TODO(dominikh) remove nil check once Go issue #15173 has
+ // been fixed
+ if src == nil {
+ return true
+ }
+ if !types.Identical(src, dst) {
+ return true
+ }
+
+ if j.Render(stmt.Lhs[0]) != j.Render(call.Args[0]) {
+ return true
+ }
+
+ el, ok := call.Args[1].(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if j.Program.Info.ObjectOf(val) != j.Program.Info.ObjectOf(el) {
+ return true
+ }
+ j.Errorf(loop, "should replace loop with %s = append(%s, %s...)",
+ j.Render(stmt.Lhs[0]), j.Render(call.Args[0]), j.Render(loop.X))
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintTimeSince(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ call, ok := node.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ if !j.IsCallToAST(sel.X, "time.Now") {
+ return true
+ }
+ if sel.Sel.Name != "Sub" {
+ return true
+ }
+ j.Errorf(call, "should use time.Since instead of time.Now().Sub")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintTimeUntil(j *lint.Job) {
+ if !j.IsGoVersion(8) {
+ return
+ }
+ fn := func(node ast.Node) bool {
+ call, ok := node.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if !j.IsCallToAST(call, "(time.Time).Sub") {
+ return true
+ }
+ if !j.IsCallToAST(call.Args[0], "time.Now") {
+ return true
+ }
+ j.Errorf(call, "should use time.Until instead of t.Sub(time.Now())")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintSimplerReturn(j *lint.Job) {
+ fn1 := func(node ast.Node) bool {
+ var ret *ast.FieldList
+ switch x := node.(type) {
+ case *ast.FuncDecl:
+ ret = x.Type.Results
+ case *ast.FuncLit:
+ ret = x.Type.Results
+ default:
+ return true
+ }
+ if ret == nil {
+ return true
+ }
+
+ fn2 := func(node ast.Node) bool {
+ block, ok := node.(*ast.BlockStmt)
+ if !ok {
+ return true
+ }
+ if len(block.List) < 2 {
+ return true
+ }
+
+ outer:
+ for i, stmt := range block.List {
+ if i == len(block.List)-1 {
+ break
+ }
+ if i > 0 {
+ // don't flag an if in a series of ifs
+ if _, ok := block.List[i-1].(*ast.IfStmt); ok {
+ continue
+ }
+ }
+
+ // if <id1> != nil
+ ifs, ok := stmt.(*ast.IfStmt)
+ if !ok || len(ifs.Body.List) != 1 || ifs.Else != nil {
+ continue
+ }
+ expr, ok := ifs.Cond.(*ast.BinaryExpr)
+ if !ok || expr.Op != token.NEQ || !j.IsNil(expr.Y) {
+ continue
+ }
+ id1, ok := expr.X.(*ast.Ident)
+ if !ok {
+ continue
+ }
+
+ // return ..., <id1>
+ ret1, ok := ifs.Body.List[0].(*ast.ReturnStmt)
+ if !ok || len(ret1.Results) == 0 {
+ continue
+ }
+ var results1 []types.Object
+ for _, res := range ret1.Results {
+ ident, ok := res.(*ast.Ident)
+ if !ok {
+ continue outer
+ }
+ results1 = append(results1, j.Program.Info.ObjectOf(ident))
+ }
+ if results1[len(results1)-1] != j.Program.Info.ObjectOf(id1) {
+ continue
+ }
+
+ // return ..., [<id1> | nil]
+ ret2, ok := block.List[i+1].(*ast.ReturnStmt)
+ if !ok || len(ret2.Results) == 0 {
+ continue
+ }
+ var results2 []types.Object
+ for _, res := range ret2.Results {
+ ident, ok := res.(*ast.Ident)
+ if !ok {
+ continue outer
+ }
+ results2 = append(results2, j.Program.Info.ObjectOf(ident))
+ }
+ _, isNil := results2[len(results2)-1].(*types.Nil)
+ if results2[len(results2)-1] != j.Program.Info.ObjectOf(id1) &&
+ !isNil {
+ continue
+ }
+ for i, v := range results1[:len(results1)-1] {
+ if v != results2[i] {
+ continue outer
+ }
+ }
+
+ id1Obj := j.Program.Info.ObjectOf(id1)
+ if id1Obj == nil {
+ continue
+ }
+ _, idIface := id1Obj.Type().Underlying().(*types.Interface)
+ _, retIface := j.Program.Info.TypeOf(ret.List[len(ret.List)-1].Type).Underlying().(*types.Interface)
+
+ if retIface && !idIface {
+ // When the return value is an interface, but the
+ // identifier is not, an explicit check for nil is
+ // required to return an untyped nil.
+ continue
+ }
+
+ j.Errorf(ifs, "'if %s != nil { return %s }; return %s' can be simplified to 'return %s'",
+ j.Render(expr.X), j.RenderArgs(ret1.Results),
+ j.RenderArgs(ret2.Results), j.RenderArgs(ret1.Results))
+ }
+ return true
+ }
+ ast.Inspect(node, fn2)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn1)
+ }
+}
+
+func (c *Checker) LintUnnecessaryBlank(j *lint.Job) {
+ fn1 := func(node ast.Node) {
+ assign, ok := node.(*ast.AssignStmt)
+ if !ok {
+ return
+ }
+ if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 {
+ return
+ }
+ if !lint.IsBlank(assign.Lhs[1]) {
+ return
+ }
+ switch rhs := assign.Rhs[0].(type) {
+ case *ast.IndexExpr:
+ // The type-checker should make sure that it's a map, but
+ // let's be safe.
+ if _, ok := j.Program.Info.TypeOf(rhs.X).Underlying().(*types.Map); !ok {
+ return
+ }
+ case *ast.UnaryExpr:
+ if rhs.Op != token.ARROW {
+ return
+ }
+ default:
+ return
+ }
+ cp := *assign
+ cp.Lhs = cp.Lhs[0:1]
+ j.Errorf(assign, "should write %s instead of %s", j.Render(&cp), j.Render(assign))
+ }
+
+ fn2 := func(node ast.Node) {
+ stmt, ok := node.(*ast.AssignStmt)
+ if !ok {
+ return
+ }
+ if len(stmt.Lhs) != len(stmt.Rhs) {
+ return
+ }
+ for i, lh := range stmt.Lhs {
+ rh := stmt.Rhs[i]
+ if !lint.IsBlank(lh) {
+ continue
+ }
+ expr, ok := rh.(*ast.UnaryExpr)
+ if !ok {
+ continue
+ }
+ if expr.Op != token.ARROW {
+ continue
+ }
+ j.Errorf(lh, "'_ = <-ch' can be simplified to '<-ch'")
+ }
+ }
+
+ fn3 := func(node ast.Node) {
+ rs, ok := node.(*ast.RangeStmt)
+ if !ok {
+ return
+ }
+ if lint.IsBlank(rs.Key) && (rs.Value == nil || lint.IsBlank(rs.Value)) {
+ j.Errorf(rs.Key, "should omit values from range; this loop is equivalent to `for range ...`")
+ }
+ }
+
+ fn := func(node ast.Node) bool {
+ fn1(node)
+ fn2(node)
+ if j.IsGoVersion(4) {
+ fn3(node)
+ }
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintSimplerStructConversion(j *lint.Job) {
+ var skip ast.Node
+ fn := func(node ast.Node) bool {
+ // Do not suggest type conversion between pointers
+ if unary, ok := node.(*ast.UnaryExpr); ok && unary.Op == token.AND {
+ if lit, ok := unary.X.(*ast.CompositeLit); ok {
+ skip = lit
+ }
+ return true
+ }
+
+ if node == skip {
+ return true
+ }
+
+ lit, ok := node.(*ast.CompositeLit)
+ if !ok {
+ return true
+ }
+ typ1, _ := j.Program.Info.TypeOf(lit.Type).(*types.Named)
+ if typ1 == nil {
+ return true
+ }
+ s1, ok := typ1.Underlying().(*types.Struct)
+ if !ok {
+ return true
+ }
+
+ var typ2 *types.Named
+ var ident *ast.Ident
+ getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) {
+ sel, ok := expr.(*ast.SelectorExpr)
+ if !ok {
+ return nil, nil, false
+ }
+ ident, ok := sel.X.(*ast.Ident)
+ if !ok {
+ return nil, nil, false
+ }
+ typ := j.Program.Info.TypeOf(sel.X)
+ return typ, ident, typ != nil
+ }
+ if len(lit.Elts) == 0 {
+ return true
+ }
+ if s1.NumFields() != len(lit.Elts) {
+ return true
+ }
+ for i, elt := range lit.Elts {
+ var t types.Type
+ var id *ast.Ident
+ var ok bool
+ switch elt := elt.(type) {
+ case *ast.SelectorExpr:
+ t, id, ok = getSelType(elt)
+ if !ok {
+ return true
+ }
+ if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name {
+ return true
+ }
+ case *ast.KeyValueExpr:
+ var sel *ast.SelectorExpr
+ sel, ok = elt.Value.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+
+ if elt.Key.(*ast.Ident).Name != sel.Sel.Name {
+ return true
+ }
+ t, id, ok = getSelType(elt.Value)
+ }
+ if !ok {
+ return true
+ }
+ // All fields must be initialized from the same object
+ if ident != nil && ident.Obj != id.Obj {
+ return true
+ }
+ typ2, _ = t.(*types.Named)
+ if typ2 == nil {
+ return true
+ }
+ ident = id
+ }
+
+ if typ2 == nil {
+ return true
+ }
+
+ if typ1.Obj().Pkg() != typ2.Obj().Pkg() {
+ // Do not suggest type conversions between different
+ // packages. Types in different packages might only match
+ // by coincidence. Furthermore, if the dependency ever
+ // adds more fields to its type, it could break the code
+ // that relies on the type conversion to work.
+ return true
+ }
+
+ s2, ok := typ2.Underlying().(*types.Struct)
+ if !ok {
+ return true
+ }
+ if typ1 == typ2 {
+ return true
+ }
+ if !structsIdentical(s1, s2) {
+ return true
+ }
+ j.Errorf(node, "should convert %s (type %s) to %s instead of using struct literal",
+ ident.Name, typ2.Obj().Name(), typ1.Obj().Name())
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintTrim(j *lint.Job) {
+ sameNonDynamic := func(node1, node2 ast.Node) bool {
+ if reflect.TypeOf(node1) != reflect.TypeOf(node2) {
+ return false
+ }
+
+ switch node1 := node1.(type) {
+ case *ast.Ident:
+ return node1.Obj == node2.(*ast.Ident).Obj
+ case *ast.SelectorExpr:
+ return j.Render(node1) == j.Render(node2)
+ case *ast.IndexExpr:
+ return j.Render(node1) == j.Render(node2)
+ }
+ return false
+ }
+
+ isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool {
+ call, ok := fn.(*ast.CallExpr)
+ if !ok {
+ return false
+ }
+ if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "len" {
+ return false
+ }
+ if len(call.Args) != 1 {
+ return false
+ }
+ return sameNonDynamic(call.Args[0], ident)
+ }
+
+ fn := func(node ast.Node) bool {
+ var pkg string
+ var fun string
+
+ ifstmt, ok := node.(*ast.IfStmt)
+ if !ok {
+ return true
+ }
+ if ifstmt.Init != nil {
+ return true
+ }
+ if ifstmt.Else != nil {
+ return true
+ }
+ if len(ifstmt.Body.List) != 1 {
+ return true
+ }
+ condCall, ok := ifstmt.Cond.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ call, ok := condCall.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ if lint.IsIdent(call.X, "strings") {
+ pkg = "strings"
+ } else if lint.IsIdent(call.X, "bytes") {
+ pkg = "bytes"
+ } else {
+ return true
+ }
+ if lint.IsIdent(call.Sel, "HasPrefix") {
+ fun = "HasPrefix"
+ } else if lint.IsIdent(call.Sel, "HasSuffix") {
+ fun = "HasSuffix"
+ } else {
+ return true
+ }
+
+ assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt)
+ if !ok {
+ return true
+ }
+ if assign.Tok != token.ASSIGN {
+ return true
+ }
+ if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
+ return true
+ }
+ if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
+ return true
+ }
+ slice, ok := assign.Rhs[0].(*ast.SliceExpr)
+ if !ok {
+ return true
+ }
+ if slice.Slice3 {
+ return true
+ }
+ if !sameNonDynamic(slice.X, condCall.Args[0]) {
+ return true
+ }
+ var index ast.Expr
+ switch fun {
+ case "HasPrefix":
+ // TODO(dh) We could detect a High that is len(s), but another
+ // rule will already flag that, anyway.
+ if slice.High != nil {
+ return true
+ }
+ index = slice.Low
+ case "HasSuffix":
+ if slice.Low != nil {
+ n, ok := j.ExprToInt(slice.Low)
+ if !ok || n != 0 {
+ return true
+ }
+ }
+ index = slice.High
+ }
+
+ switch index := index.(type) {
+ case *ast.CallExpr:
+ if fun != "HasPrefix" {
+ return true
+ }
+ if fn, ok := index.Fun.(*ast.Ident); !ok || fn.Name != "len" {
+ return true
+ }
+ if len(index.Args) != 1 {
+ return true
+ }
+ id3 := index.Args[0]
+ switch oid3 := condCall.Args[1].(type) {
+ case *ast.BasicLit:
+ if pkg != "strings" {
+ return false
+ }
+ lit, ok := id3.(*ast.BasicLit)
+ if !ok {
+ return true
+ }
+ s1, ok1 := j.ExprToString(lit)
+ s2, ok2 := j.ExprToString(condCall.Args[1])
+ if !ok1 || !ok2 || s1 != s2 {
+ return true
+ }
+ default:
+ if !sameNonDynamic(id3, oid3) {
+ return true
+ }
+ }
+ case *ast.BasicLit, *ast.Ident:
+ if fun != "HasPrefix" {
+ return true
+ }
+ if pkg != "strings" {
+ return true
+ }
+ string, ok1 := j.ExprToString(condCall.Args[1])
+ int, ok2 := j.ExprToInt(slice.Low)
+ if !ok1 || !ok2 || int != int64(len(string)) {
+ return true
+ }
+ case *ast.BinaryExpr:
+ if fun != "HasSuffix" {
+ return true
+ }
+ if index.Op != token.SUB {
+ return true
+ }
+ if !isLenOnIdent(index.X, condCall.Args[0]) ||
+ !isLenOnIdent(index.Y, condCall.Args[1]) {
+ return true
+ }
+ default:
+ return true
+ }
+
+ var replacement string
+ switch fun {
+ case "HasPrefix":
+ replacement = "TrimPrefix"
+ case "HasSuffix":
+ replacement = "TrimSuffix"
+ }
+ j.Errorf(ifstmt, "should replace this if statement with an unconditional %s.%s", pkg, replacement)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintLoopSlide(j *lint.Job) {
+ // TODO(dh): detect bs[i+offset] in addition to bs[offset+i]
+ // TODO(dh): consider merging this function with LintLoopCopy
+ // TODO(dh): detect length that is an expression, not a variable name
+ // TODO(dh): support sliding to a different offset than the beginning of the slice
+
+ fn := func(node ast.Node) bool {
+ /*
+ for i := 0; i < n; i++ {
+ bs[i] = bs[offset+i]
+ }
+
+ ↓
+
+ copy(bs[:n], bs[offset:offset+n])
+ */
+
+ loop, ok := node.(*ast.ForStmt)
+ if !ok || len(loop.Body.List) != 1 || loop.Init == nil || loop.Cond == nil || loop.Post == nil {
+ return true
+ }
+ assign, ok := loop.Init.(*ast.AssignStmt)
+ if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 || !lint.IsZero(assign.Rhs[0]) {
+ return true
+ }
+ initvar, ok := assign.Lhs[0].(*ast.Ident)
+ if !ok {
+ return true
+ }
+ post, ok := loop.Post.(*ast.IncDecStmt)
+ if !ok || post.Tok != token.INC {
+ return true
+ }
+ postvar, ok := post.X.(*ast.Ident)
+ if !ok || j.Program.Info.ObjectOf(postvar) != j.Program.Info.ObjectOf(initvar) {
+ return true
+ }
+ bin, ok := loop.Cond.(*ast.BinaryExpr)
+ if !ok || bin.Op != token.LSS {
+ return true
+ }
+ binx, ok := bin.X.(*ast.Ident)
+ if !ok || j.Program.Info.ObjectOf(binx) != j.Program.Info.ObjectOf(initvar) {
+ return true
+ }
+ biny, ok := bin.Y.(*ast.Ident)
+ if !ok {
+ return true
+ }
+
+ assign, ok = loop.Body.List[0].(*ast.AssignStmt)
+ if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 || assign.Tok != token.ASSIGN {
+ return true
+ }
+ lhs, ok := assign.Lhs[0].(*ast.IndexExpr)
+ if !ok {
+ return true
+ }
+ rhs, ok := assign.Rhs[0].(*ast.IndexExpr)
+ if !ok {
+ return true
+ }
+
+ bs1, ok := lhs.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ bs2, ok := rhs.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ obj1 := j.Program.Info.ObjectOf(bs1)
+ obj2 := j.Program.Info.ObjectOf(bs2)
+ if obj1 != obj2 {
+ return true
+ }
+ if _, ok := obj1.Type().Underlying().(*types.Slice); !ok {
+ return true
+ }
+
+ index1, ok := lhs.Index.(*ast.Ident)
+ if !ok || j.Program.Info.ObjectOf(index1) != j.Program.Info.ObjectOf(initvar) {
+ return true
+ }
+ index2, ok := rhs.Index.(*ast.BinaryExpr)
+ if !ok || index2.Op != token.ADD {
+ return true
+ }
+ add1, ok := index2.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ add2, ok := index2.Y.(*ast.Ident)
+ if !ok || j.Program.Info.ObjectOf(add2) != j.Program.Info.ObjectOf(initvar) {
+ return true
+ }
+
+ j.Errorf(loop, "should use copy(%s[:%s], %s[%s:]) instead", j.Render(bs1), j.Render(biny), j.Render(bs1), j.Render(add1))
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintMakeLenCap(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ call, ok := node.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "make" {
+ // FIXME check whether make is indeed the built-in function
+ return true
+ }
+ switch len(call.Args) {
+ case 2:
+ // make(T, len)
+ if _, ok := j.Program.Info.TypeOf(call.Args[0]).Underlying().(*types.Slice); ok {
+ break
+ }
+ if lint.IsZero(call.Args[1]) {
+ j.Errorf(call.Args[1], "should use make(%s) instead", j.Render(call.Args[0]))
+ }
+ case 3:
+ // make(T, len, cap)
+ if j.Render(call.Args[1]) == j.Render(call.Args[2]) {
+ j.Errorf(call.Args[1], "should use make(%s, %s) instead", j.Render(call.Args[0]), j.Render(call.Args[1]))
+ }
+ }
+ return false
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintAssertNotNil(j *lint.Job) {
+ isNilCheck := func(ident *ast.Ident, expr ast.Expr) bool {
+ xbinop, ok := expr.(*ast.BinaryExpr)
+ if !ok || xbinop.Op != token.NEQ {
+ return false
+ }
+ xident, ok := xbinop.X.(*ast.Ident)
+ if !ok || xident.Obj != ident.Obj {
+ return false
+ }
+ if !j.IsNil(xbinop.Y) {
+ return false
+ }
+ return true
+ }
+ isOKCheck := func(ident *ast.Ident, expr ast.Expr) bool {
+ yident, ok := expr.(*ast.Ident)
+ if !ok || yident.Obj != ident.Obj {
+ return false
+ }
+ return true
+ }
+ fn := func(node ast.Node) bool {
+ ifstmt, ok := node.(*ast.IfStmt)
+ if !ok {
+ return true
+ }
+ assign, ok := ifstmt.Init.(*ast.AssignStmt)
+ if !ok || len(assign.Lhs) != 2 || len(assign.Rhs) != 1 || !lint.IsBlank(assign.Lhs[0]) {
+ return true
+ }
+ assert, ok := assign.Rhs[0].(*ast.TypeAssertExpr)
+ if !ok {
+ return true
+ }
+ binop, ok := ifstmt.Cond.(*ast.BinaryExpr)
+ if !ok || binop.Op != token.LAND {
+ return true
+ }
+ assertIdent, ok := assert.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ assignIdent, ok := assign.Lhs[1].(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if !(isNilCheck(assertIdent, binop.X) && isOKCheck(assignIdent, binop.Y)) &&
+ !(isNilCheck(assertIdent, binop.Y) && isOKCheck(assignIdent, binop.X)) {
+ return true
+ }
+ j.Errorf(ifstmt, "when %s is true, %s can't be nil", j.Render(assignIdent), j.Render(assertIdent))
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintDeclareAssign(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ block, ok := node.(*ast.BlockStmt)
+ if !ok {
+ return true
+ }
+ if len(block.List) < 2 {
+ return true
+ }
+ for i, stmt := range block.List[:len(block.List)-1] {
+ _ = i
+ decl, ok := stmt.(*ast.DeclStmt)
+ if !ok {
+ continue
+ }
+ gdecl, ok := decl.Decl.(*ast.GenDecl)
+ if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 {
+ continue
+ }
+ vspec, ok := gdecl.Specs[0].(*ast.ValueSpec)
+ if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 {
+ continue
+ }
+
+ assign, ok := block.List[i+1].(*ast.AssignStmt)
+ if !ok || assign.Tok != token.ASSIGN {
+ continue
+ }
+ if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
+ continue
+ }
+ ident, ok := assign.Lhs[0].(*ast.Ident)
+ if !ok {
+ continue
+ }
+ if vspec.Names[0].Obj != ident.Obj {
+ continue
+ }
+
+ if refersTo(j.Program.Info, assign.Rhs[0], ident) {
+ continue
+ }
+ j.Errorf(decl, "should merge variable declaration with assignment on next line")
+ }
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintRedundantBreak(j *lint.Job) {
+ fn1 := func(node ast.Node) {
+ clause, ok := node.(*ast.CaseClause)
+ if !ok {
+ return
+ }
+ if len(clause.Body) < 2 {
+ return
+ }
+ branch, ok := clause.Body[len(clause.Body)-1].(*ast.BranchStmt)
+ if !ok || branch.Tok != token.BREAK || branch.Label != nil {
+ return
+ }
+ j.Errorf(branch, "redundant break statement")
+ return
+ }
+ fn2 := func(node ast.Node) {
+ var ret *ast.FieldList
+ var body *ast.BlockStmt
+ switch x := node.(type) {
+ case *ast.FuncDecl:
+ ret = x.Type.Results
+ body = x.Body
+ case *ast.FuncLit:
+ ret = x.Type.Results
+ body = x.Body
+ default:
+ return
+ }
+ // if the func has results, a return can't be redundant.
+ // similarly, if there are no statements, there can be
+ // no return.
+ if ret != nil || body == nil || len(body.List) < 1 {
+ return
+ }
+ rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt)
+ if !ok {
+ return
+ }
+ // we don't need to check rst.Results as we already
+ // checked x.Type.Results to be nil.
+ j.Errorf(rst, "redundant return statement")
+ }
+ fn := func(node ast.Node) bool {
+ fn1(node)
+ fn2(node)
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) Implements(j *lint.Job, typ types.Type, iface string) bool {
+ // OPT(dh): we can cache the type lookup
+ idx := strings.IndexRune(iface, '.')
+ var scope *types.Scope
+ var ifaceName string
+ if idx == -1 {
+ scope = types.Universe
+ ifaceName = iface
+ } else {
+ pkgName := iface[:idx]
+ pkg := j.Program.Prog.Package(pkgName)
+ if pkg == nil {
+ return false
+ }
+ scope = pkg.Pkg.Scope()
+ ifaceName = iface[idx+1:]
+ }
+
+ obj := scope.Lookup(ifaceName)
+ if obj == nil {
+ return false
+ }
+ i, ok := obj.Type().Underlying().(*types.Interface)
+ if !ok {
+ return false
+ }
+ return types.Implements(typ, i)
+}
+
+func (c *Checker) LintRedundantSprintf(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ call, ok := node.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ if !j.IsCallToAST(call, "fmt.Sprintf") {
+ return true
+ }
+ if len(call.Args) != 2 {
+ return true
+ }
+ if s, ok := j.ExprToString(call.Args[0]); !ok || s != "%s" {
+ return true
+ }
+ pkg := j.NodePackage(call)
+ arg := call.Args[1]
+ typ := pkg.Info.TypeOf(arg)
+
+ if c.Implements(j, typ, "fmt.Stringer") {
+ j.Errorf(call, "should use String() instead of fmt.Sprintf")
+ return true
+ }
+
+ if typ.Underlying() == types.Universe.Lookup("string").Type() {
+ if typ == types.Universe.Lookup("string").Type() {
+ j.Errorf(call, "the argument is already a string, there's no need to use fmt.Sprintf")
+ } else {
+ j.Errorf(call, "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf")
+ }
+ }
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintErrorsNewSprintf(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ if !j.IsCallToAST(node, "errors.New") {
+ return true
+ }
+ call := node.(*ast.CallExpr)
+ if !j.IsCallToAST(call.Args[0], "fmt.Sprintf") {
+ return true
+ }
+ j.Errorf(node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))")
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
+
+func (c *Checker) LintRangeStringRunes(j *lint.Job) {
+ sharedcheck.CheckRangeStringRunes(c.nodeFns, j)
+}
+
+func (c *Checker) LintNilCheckAroundRange(j *lint.Job) {
+ fn := func(node ast.Node) bool {
+ ifstmt, ok := node.(*ast.IfStmt)
+ if !ok {
+ return true
+ }
+
+ cond, ok := ifstmt.Cond.(*ast.BinaryExpr)
+ if !ok {
+ return true
+ }
+
+ if cond.Op != token.NEQ || !j.IsNil(cond.Y) || len(ifstmt.Body.List) != 1 {
+ return true
+ }
+
+ loop, ok := ifstmt.Body.List[0].(*ast.RangeStmt)
+ if !ok {
+ return true
+ }
+ ifXIdent, ok := cond.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ rangeXIdent, ok := loop.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if ifXIdent.Obj != rangeXIdent.Obj {
+ return true
+ }
+ switch j.Program.Info.TypeOf(rangeXIdent).(type) {
+ case *types.Slice, *types.Map:
+ j.Errorf(node, "unnecessary nil check around range")
+ }
+ return true
+ }
+ for _, f := range c.filterGenerated(j.Program.Files) {
+ ast.Inspect(f, fn)
+ }
+}
diff --git a/vendor/honnef.co/go/tools/simple/lint17.go b/vendor/honnef.co/go/tools/simple/lint17.go
new file mode 100644
index 0000000..53f529c
--- /dev/null
+++ b/vendor/honnef.co/go/tools/simple/lint17.go
@@ -0,0 +1,7 @@
+// +build !go1.8
+
+package simple
+
+import "go/types"
+
+var structsIdentical = types.Identical
diff --git a/vendor/honnef.co/go/tools/simple/lint18.go b/vendor/honnef.co/go/tools/simple/lint18.go
new file mode 100644
index 0000000..ab9ea72
--- /dev/null
+++ b/vendor/honnef.co/go/tools/simple/lint18.go
@@ -0,0 +1,7 @@
+// +build go1.8
+
+package simple
+
+import "go/types"
+
+var structsIdentical = types.IdenticalIgnoreTags