// 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)
	}
}
