1 // Package simple contains a linter for Go source code.
15 "honnef.co/go/tools/analysis/code"
16 "honnef.co/go/tools/analysis/edit"
17 "honnef.co/go/tools/analysis/lint"
18 "honnef.co/go/tools/analysis/report"
19 "honnef.co/go/tools/go/ast/astutil"
20 "honnef.co/go/tools/go/types/typeutil"
21 "honnef.co/go/tools/internal/passes/buildir"
22 "honnef.co/go/tools/internal/sharedcheck"
23 "honnef.co/go/tools/knowledge"
24 "honnef.co/go/tools/pattern"
26 "golang.org/x/tools/go/analysis"
27 gotypeutil "golang.org/x/tools/go/types/typeutil"
31 checkSingleCaseSelectQ1 = pattern.MustParse(`
38 (AssignStmt _ _ (UnaryExpr "<-" _)))
40 checkSingleCaseSelectQ2 = pattern.MustParse(`(SelectStmt (CommClause _ _))`)
43 func CheckSingleCaseSelect(pass *analysis.Pass) (interface{}, error) {
44 seen := map[ast.Node]struct{}{}
45 fn := func(node ast.Node) {
46 if m, ok := code.Match(pass, checkSingleCaseSelectQ1, node); ok {
47 seen[m.State["select"].(ast.Node)] = struct{}{}
48 report.Report(pass, node, "should use for range instead of for { select {} }", report.FilterGenerated())
49 } else if _, ok := code.Match(pass, checkSingleCaseSelectQ2, node); ok {
50 if _, ok := seen[node]; !ok {
51 report.Report(pass, node, "should use a simple channel send/receive instead of select with a single case",
53 report.FilterGenerated())
57 code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.SelectStmt)(nil))
62 checkLoopCopyQ = pattern.MustParse(`
65 key value ":=" src@(Ident _)
67 (IndexExpr dst@(Ident _) key)
71 key nil ":=" src@(Ident _)
73 (IndexExpr dst@(Ident _) key)
75 (IndexExpr src key))]))`)
76 checkLoopCopyR = pattern.MustParse(`(CallExpr (Ident "copy") [dst src])`)
79 func CheckLoopCopy(pass *analysis.Pass) (interface{}, error) {
80 fn := func(node ast.Node) {
81 m, edits, ok := code.MatchAndEdit(pass, checkLoopCopyQ, checkLoopCopyR, node)
85 t1 := pass.TypesInfo.TypeOf(m.State["src"].(*ast.Ident))
86 t2 := pass.TypesInfo.TypeOf(m.State["dst"].(*ast.Ident))
87 if _, ok := t1.Underlying().(*types.Slice); !ok {
90 if !types.Identical(t1, t2) {
94 tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy")
95 if err == nil && tv.IsBuiltin() {
96 report.Report(pass, node,
97 "should use copy() instead of a loop",
99 report.FilterGenerated(),
100 report.Fixes(edit.Fix("replace loop with call to copy()", edits...)))
102 report.Report(pass, node, "should use copy() instead of a loop", report.FilterGenerated())
105 code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
109 func CheckIfBoolCmp(pass *analysis.Pass) (interface{}, error) {
110 fn := func(node ast.Node) {
111 if code.IsInTest(pass, node) {
115 expr := node.(*ast.BinaryExpr)
116 if expr.Op != token.EQL && expr.Op != token.NEQ {
119 x := code.IsBoolConst(pass, expr.X)
120 y := code.IsBoolConst(pass, expr.Y)
127 val = code.BoolConst(pass, expr.X)
130 val = code.BoolConst(pass, expr.Y)
133 basic, ok := pass.TypesInfo.TypeOf(other).Underlying().(*types.Basic)
134 if !ok || basic.Kind() != types.Bool {
138 if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) {
141 r := op + report.Render(pass, other)
143 r = strings.TrimLeft(r, "!")
144 if (l1-len(r))%2 == 1 {
147 report.Report(pass, expr, fmt.Sprintf("should omit comparison to bool constant, can be simplified to %s", r),
148 report.FilterGenerated(),
149 report.Fixes(edit.Fix("simplify bool comparison", edit.ReplaceWithString(pass.Fset, expr, r))))
151 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
156 checkBytesBufferConversionsQ = pattern.MustParse(`(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])`)
157 checkBytesBufferConversionsRs = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "String")) [])`)
158 checkBytesBufferConversionsRb = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "Bytes")) [])`)
161 func CheckBytesBufferConversions(pass *analysis.Pass) (interface{}, error) {
162 if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
163 // The bytes package can use itself however it wants
166 fn := func(node ast.Node, stack []ast.Node) {
167 m, ok := code.Match(pass, checkBytesBufferConversionsQ, node)
171 call := node.(*ast.CallExpr)
172 sel := m.State["sel"].(*ast.SelectorExpr)
174 typ := pass.TypesInfo.TypeOf(call.Fun)
175 if typ == types.Universe.Lookup("string").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).Bytes") {
176 if _, ok := stack[len(stack)-2].(*ast.IndexExpr); ok {
177 // Don't flag m[string(buf.Bytes())] – thanks to a
178 // compiler optimization, this is actually faster than
183 report.Report(pass, call, fmt.Sprintf("should use %v.String() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
184 report.FilterGenerated(),
185 report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass, checkBytesBufferConversionsRs, m.State, node))))
186 } else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).String") {
187 report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
188 report.FilterGenerated(),
189 report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass, checkBytesBufferConversionsRb, m.State, node))))
193 code.PreorderStack(pass, fn, (*ast.CallExpr)(nil))
197 func CheckStringsContains(pass *analysis.Pass) (interface{}, error) {
198 // map of value to token to bool value
199 allowed := map[int64]map[token.Token]bool{
200 -1: {token.GTR: true, token.NEQ: true, token.EQL: false},
201 0: {token.GEQ: true, token.LSS: false},
203 fn := func(node ast.Node) {
204 expr := node.(*ast.BinaryExpr)
206 case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL:
211 value, ok := code.ExprToInt(pass, expr.Y)
216 allowedOps, ok := allowed[value]
220 b, ok := allowedOps[expr.Op]
225 call, ok := expr.X.(*ast.CallExpr)
229 sel, ok := call.Fun.(*ast.SelectorExpr)
233 pkgIdent, ok := sel.X.(*ast.Ident)
238 if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" {
243 switch funIdent.Name {
245 r = &ast.SelectorExpr{
247 Sel: &ast.Ident{Name: "ContainsRune"},
250 r = &ast.SelectorExpr{
252 Sel: &ast.Ident{Name: "ContainsAny"},
255 r = &ast.SelectorExpr{
257 Sel: &ast.Ident{Name: "Contains"},
274 report.Report(pass, node, fmt.Sprintf("should use %s instead", report.Render(pass, r)),
275 report.FilterGenerated(),
276 report.Fixes(edit.Fix(fmt.Sprintf("simplify use of %s", report.Render(pass, call.Fun)), edit.ReplaceWithNode(pass.Fset, node, r))))
278 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
283 checkBytesCompareQ = pattern.MustParse(`(BinaryExpr (CallExpr (Function "bytes.Compare") args) op@(Or "==" "!=") (BasicLit "INT" "0"))`)
284 checkBytesCompareRe = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args)`)
285 checkBytesCompareRn = pattern.MustParse(`(UnaryExpr "!" (CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args))`)
288 func CheckBytesCompare(pass *analysis.Pass) (interface{}, error) {
289 if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
290 // the bytes package is free to use bytes.Compare as it sees fit
293 fn := func(node ast.Node) {
294 m, ok := code.Match(pass, checkBytesCompareQ, node)
299 args := report.RenderArgs(pass, m.State["args"].([]ast.Expr))
301 if m.State["op"].(token.Token) == token.NEQ {
305 var fix analysis.SuggestedFix
306 switch tok := m.State["op"].(token.Token); tok {
308 fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass, checkBytesCompareRe, m.State, node))
310 fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass, checkBytesCompareRn, m.State, node))
312 panic(fmt.Sprintf("unexpected token %v", tok))
314 report.Report(pass, node, fmt.Sprintf("should use %sbytes.Equal(%s) instead", prefix, args), report.FilterGenerated(), report.Fixes(fix))
316 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
320 func CheckForTrue(pass *analysis.Pass) (interface{}, error) {
321 fn := func(node ast.Node) {
322 loop := node.(*ast.ForStmt)
323 if loop.Init != nil || loop.Post != nil {
326 if !code.IsBoolConst(pass, loop.Cond) || !code.BoolConst(pass, loop.Cond) {
329 report.Report(pass, loop, "should use for {} instead of for true {}",
331 report.FilterGenerated())
333 code.Preorder(pass, fn, (*ast.ForStmt)(nil))
337 func CheckRegexpRaw(pass *analysis.Pass) (interface{}, error) {
338 fn := func(node ast.Node) {
339 call := node.(*ast.CallExpr)
340 if !code.IsCallToAny(pass, call, "regexp.MustCompile", "regexp.Compile") {
343 sel, ok := call.Fun.(*ast.SelectorExpr)
347 lit, ok := call.Args[knowledge.Arg("regexp.Compile.expr")].(*ast.BasicLit)
349 // TODO(dominikh): support string concat, maybe support constants
352 if lit.Kind != token.STRING {
353 // invalid function call
356 if lit.Value[0] != '"' {
357 // already a raw string
361 if !strings.Contains(val, `\\`) {
364 if strings.Contains(val, "`") {
369 for _, c := range val {
370 if !bs && c == '\\' {
379 // backslash followed by non-backslash -> escape sequence
384 report.Report(pass, call, fmt.Sprintf("should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name), report.FilterGenerated())
386 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
391 checkIfReturnQIf = pattern.MustParse(`(IfStmt nil cond [(ReturnStmt [ret@(Ident _)])] nil)`)
392 checkIfReturnQRet = pattern.MustParse(`(ReturnStmt [ret@(Ident _)])`)
395 func CheckIfReturn(pass *analysis.Pass) (interface{}, error) {
396 fn := func(node ast.Node) {
397 block := node.(*ast.BlockStmt)
402 n1, n2 := block.List[l-2], block.List[l-1]
404 if len(block.List) >= 3 {
405 if _, ok := block.List[l-3].(*ast.IfStmt); ok {
406 // Do not flag a series of if statements
410 m1, ok := code.Match(pass, checkIfReturnQIf, n1)
414 m2, ok := code.Match(pass, checkIfReturnQRet, n2)
419 if op, ok := m1.State["cond"].(*ast.BinaryExpr); ok {
421 case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
427 ret1 := m1.State["ret"].(*ast.Ident)
428 if !code.IsBoolConst(pass, ret1) {
431 ret2 := m2.State["ret"].(*ast.Ident)
432 if !code.IsBoolConst(pass, ret2) {
436 if ret1.Name == ret2.Name {
437 // we want the function to return true and false, not the
438 // same value both times.
442 cond := m1.State["cond"].(ast.Expr)
444 if ret1.Name == "false" {
447 report.Report(pass, n1,
448 fmt.Sprintf("should use 'return %s' instead of 'if %s { return %s }; return %s'",
449 report.Render(pass, cond),
450 report.Render(pass, origCond), report.Render(pass, ret1), report.Render(pass, ret2)),
451 report.FilterGenerated())
453 code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
457 func negate(expr ast.Expr) ast.Expr {
458 switch expr := expr.(type) {
459 case *ast.BinaryExpr:
476 case *ast.Ident, *ast.CallExpr, *ast.IndexExpr:
477 return &ast.UnaryExpr{
482 return &ast.UnaryExpr{
491 // CheckRedundantNilCheckWithLen checks for the following redundant nil-checks:
493 // if x == nil || len(x) == 0 {}
494 // if x != nil && len(x) != 0 {}
495 // if x != nil && len(x) == N {} (where N != 0)
496 // if x != nil && len(x) > N {}
497 // if x != nil && len(x) >= N {} (where N != 0)
499 func CheckRedundantNilCheckWithLen(pass *analysis.Pass) (interface{}, error) {
500 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) {
501 _, ok := expr.(*ast.BasicLit)
503 return true, astutil.IsIntLiteral(expr, "0")
505 id, ok := expr.(*ast.Ident)
509 c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const)
513 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0"
516 fn := func(node ast.Node) {
517 // check that expr is "x || y" or "x && y"
518 expr := node.(*ast.BinaryExpr)
519 if expr.Op != token.LOR && expr.Op != token.LAND {
522 eqNil := expr.Op == token.LOR
524 // check that x is "xx == nil" or "xx != nil"
525 x, ok := expr.X.(*ast.BinaryExpr)
529 if eqNil && x.Op != token.EQL {
532 if !eqNil && x.Op != token.NEQ {
535 xx, ok := x.X.(*ast.Ident)
539 if !code.IsNil(pass, x.Y) {
543 // check that y is "len(xx) == 0" or "len(xx) ... "
544 y, ok := expr.Y.(*ast.BinaryExpr)
548 if eqNil && y.Op != token.EQL { // must be len(xx) *==* 0
551 yx, ok := y.X.(*ast.CallExpr)
555 yxFun, ok := yx.Fun.(*ast.Ident)
556 if !ok || yxFun.Name != "len" || len(yx.Args) != 1 {
559 yxArg, ok := yx.Args[knowledge.Arg("len.v")].(*ast.Ident)
563 if yxArg.Name != xx.Name {
567 if eqNil && !astutil.IsIntLiteral(y.Y, "0") { // must be len(x) == *0*
572 isConst, isZero := isConstZero(y.Y)
578 // avoid false positive for "xx != nil && len(xx) == 0"
583 // avoid false positive for "xx != nil && len(xx) >= 0"
588 // avoid false positive for "xx != nil && len(xx) != <non-zero>"
599 // finally check that xx type is one of array, slice, map or chan
600 // this is to prevent false positive in case if xx is a pointer to an array
602 switch pass.TypesInfo.TypeOf(xx).(type) {
604 nilType = "nil slices"
608 nilType = "nil channels"
612 report.Report(pass, expr, fmt.Sprintf("should omit nil check; len() for %s is defined as zero", nilType), report.FilterGenerated())
614 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
618 var checkSlicingQ = pattern.MustParse(`(SliceExpr x@(Object _) low (CallExpr (Builtin "len") [x]) nil)`)
620 func CheckSlicing(pass *analysis.Pass) (interface{}, error) {
621 fn := func(node ast.Node) {
622 if _, ok := code.Match(pass, checkSlicingQ, node); ok {
623 expr := node.(*ast.SliceExpr)
624 report.Report(pass, expr.High,
625 "should omit second index in slice, s[a:len(s)] is identical to s[a:]",
626 report.FilterGenerated(),
627 report.Fixes(edit.Fix("simplify slice expression", edit.Delete(expr.High))))
630 code.Preorder(pass, fn, (*ast.SliceExpr)(nil))
634 func refersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
636 fn := func(node ast.Node) bool {
637 ident2, ok := node.(*ast.Ident)
641 if ident == pass.TypesInfo.ObjectOf(ident2) {
647 ast.Inspect(expr, fn)
651 var checkLoopAppendQ = pattern.MustParse(`
657 [(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]) `)
659 func CheckLoopAppend(pass *analysis.Pass) (interface{}, error) {
660 fn := func(node ast.Node) {
661 m, ok := code.Match(pass, checkLoopAppendQ, node)
666 val := m.State["val"].(types.Object)
667 if refersTo(pass, m.State["lhs"].(ast.Expr), val) {
671 src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr))
672 dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr))
673 if !types.Identical(src, dst) {
677 r := &ast.AssignStmt{
678 Lhs: []ast.Expr{m.State["lhs"].(ast.Expr)},
682 Fun: &ast.Ident{Name: "append"},
684 m.State["lhs"].(ast.Expr),
685 m.State["x"].(ast.Expr),
692 report.Report(pass, node, fmt.Sprintf("should replace loop with %s", report.Render(pass, r)),
694 report.FilterGenerated(),
695 report.Fixes(edit.Fix("replace loop with call to append", edit.ReplaceWithNode(pass.Fset, node, r))))
697 code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
702 checkTimeSinceQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (Function "time.Now") []) (Function "(time.Time).Sub")) [arg])`)
703 checkTimeSinceR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Since")) [arg])`)
706 func CheckTimeSince(pass *analysis.Pass) (interface{}, error) {
707 fn := func(node ast.Node) {
708 if _, edits, ok := code.MatchAndEdit(pass, checkTimeSinceQ, checkTimeSinceR, node); ok {
709 report.Report(pass, node, "should use time.Since instead of time.Now().Sub",
710 report.FilterGenerated(),
711 report.Fixes(edit.Fix("replace with call to time.Since", edits...)))
714 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
719 checkTimeUntilQ = pattern.MustParse(`(CallExpr (Function "(time.Time).Sub") [(CallExpr (Function "time.Now") [])])`)
720 checkTimeUntilR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Until")) [arg])`)
723 func CheckTimeUntil(pass *analysis.Pass) (interface{}, error) {
724 if !code.IsGoVersion(pass, 8) {
727 fn := func(node ast.Node) {
728 if _, ok := code.Match(pass, checkTimeUntilQ, node); ok {
729 if sel, ok := node.(*ast.CallExpr).Fun.(*ast.SelectorExpr); ok {
730 r := pattern.NodeToAST(checkTimeUntilR.Root, map[string]interface{}{"arg": sel.X}).(ast.Node)
731 report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())",
732 report.FilterGenerated(),
733 report.Fixes(edit.Fix("replace with call to time.Until", edit.ReplaceWithNode(pass.Fset, node, r))))
735 report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())", report.FilterGenerated())
739 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
744 checkUnnecessaryBlankQ1 = pattern.MustParse(`
750 (UnaryExpr "<-" _))) `)
751 checkUnnecessaryBlankQ2 = pattern.MustParse(`
753 (Ident "_") _ recv@(UnaryExpr "<-" _))`)
756 func CheckUnnecessaryBlank(pass *analysis.Pass) (interface{}, error) {
757 fn1 := func(node ast.Node) {
758 if _, ok := code.Match(pass, checkUnnecessaryBlankQ1, node); ok {
759 r := *node.(*ast.AssignStmt)
761 report.Report(pass, node, "unnecessary assignment to the blank identifier",
762 report.FilterGenerated(),
763 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.ReplaceWithNode(pass.Fset, node, &r))))
764 } else if m, ok := code.Match(pass, checkUnnecessaryBlankQ2, node); ok {
765 report.Report(pass, node, "unnecessary assignment to the blank identifier",
766 report.FilterGenerated(),
767 report.Fixes(edit.Fix("simplify channel receive operation", edit.ReplaceWithNode(pass.Fset, node, m.State["recv"].(ast.Node)))))
771 fn3 := func(node ast.Node) {
772 rs := node.(*ast.RangeStmt)
775 if rs.Value == nil && astutil.IsBlank(rs.Key) {
776 report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier",
777 report.FilterGenerated(),
778 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1}))))
782 if astutil.IsBlank(rs.Key) && astutil.IsBlank(rs.Value) {
783 // FIXME we should mark both key and value
784 report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier",
785 report.FilterGenerated(),
786 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1}))))
790 if !astutil.IsBlank(rs.Key) && astutil.IsBlank(rs.Value) {
791 report.Report(pass, rs.Value, "unnecessary assignment to the blank identifier",
792 report.FilterGenerated(),
793 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.End(), rs.Value.End()}))))
797 code.Preorder(pass, fn1, (*ast.AssignStmt)(nil))
798 if code.IsGoVersion(pass, 4) {
799 code.Preorder(pass, fn3, (*ast.RangeStmt)(nil))
804 func CheckSimplerStructConversion(pass *analysis.Pass) (interface{}, error) {
805 fn := func(node ast.Node, stack []ast.Node) {
806 if unary, ok := stack[len(stack)-2].(*ast.UnaryExpr); ok && unary.Op == token.AND {
807 // Do not suggest type conversion between pointers
811 lit := node.(*ast.CompositeLit)
812 typ1, _ := pass.TypesInfo.TypeOf(lit.Type).(*types.Named)
816 s1, ok := typ1.Underlying().(*types.Struct)
821 var typ2 *types.Named
823 getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) {
824 sel, ok := expr.(*ast.SelectorExpr)
826 return nil, nil, false
828 ident, ok := sel.X.(*ast.Ident)
830 return nil, nil, false
832 typ := pass.TypesInfo.TypeOf(sel.X)
833 return typ, ident, typ != nil
835 if len(lit.Elts) == 0 {
838 if s1.NumFields() != len(lit.Elts) {
841 for i, elt := range lit.Elts {
845 switch elt := elt.(type) {
846 case *ast.SelectorExpr:
847 t, id, ok = getSelType(elt)
851 if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name {
854 case *ast.KeyValueExpr:
855 var sel *ast.SelectorExpr
856 sel, ok = elt.Value.(*ast.SelectorExpr)
861 if elt.Key.(*ast.Ident).Name != sel.Sel.Name {
864 t, id, ok = getSelType(elt.Value)
869 // All fields must be initialized from the same object
870 if ident != nil && ident.Obj != id.Obj {
873 typ2, _ = t.(*types.Named)
884 if typ1.Obj().Pkg() != typ2.Obj().Pkg() {
885 // Do not suggest type conversions between different
886 // packages. Types in different packages might only match
887 // by coincidence. Furthermore, if the dependency ever
888 // adds more fields to its type, it could break the code
889 // that relies on the type conversion to work.
893 s2, ok := typ2.Underlying().(*types.Struct)
900 if code.IsGoVersion(pass, 8) {
901 if !types.IdenticalIgnoreTags(s1, s2) {
905 if !types.Identical(s1, s2) {
912 Args: []ast.Expr{ident},
914 report.Report(pass, node,
915 fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, typ2.Obj().Name(), typ1.Obj().Name()),
916 report.FilterGenerated(),
917 report.Fixes(edit.Fix("use type conversion", edit.ReplaceWithNode(pass.Fset, node, r))))
919 code.PreorderStack(pass, fn, (*ast.CompositeLit)(nil))
923 func CheckTrim(pass *analysis.Pass) (interface{}, error) {
924 sameNonDynamic := func(node1, node2 ast.Node) bool {
925 if reflect.TypeOf(node1) != reflect.TypeOf(node2) {
929 switch node1 := node1.(type) {
931 return node1.Obj == node2.(*ast.Ident).Obj
932 case *ast.SelectorExpr:
933 return report.Render(pass, node1) == report.Render(pass, node2)
935 return report.Render(pass, node1) == report.Render(pass, node2)
940 isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool {
941 call, ok := fn.(*ast.CallExpr)
945 if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "len" {
948 if len(call.Args) != 1 {
951 return sameNonDynamic(call.Args[knowledge.Arg("len.v")], ident)
954 fn := func(node ast.Node) {
958 ifstmt := node.(*ast.IfStmt)
959 if ifstmt.Init != nil {
962 if ifstmt.Else != nil {
965 if len(ifstmt.Body.List) != 1 {
968 condCall, ok := ifstmt.Cond.(*ast.CallExpr)
973 condCallName := code.CallName(pass, condCall)
974 switch condCallName {
975 case "strings.HasPrefix":
978 case "strings.HasSuffix":
981 case "strings.Contains":
984 case "bytes.HasPrefix":
987 case "bytes.HasSuffix":
990 case "bytes.Contains":
997 assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt)
1001 if assign.Tok != token.ASSIGN {
1004 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
1007 if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
1011 switch rhs := assign.Rhs[0].(type) {
1013 if len(rhs.Args) < 2 || !sameNonDynamic(condCall.Args[0], rhs.Args[0]) || !sameNonDynamic(condCall.Args[1], rhs.Args[1]) {
1017 rhsName := code.CallName(pass, rhs)
1018 if condCallName == "strings.HasPrefix" && rhsName == "strings.TrimPrefix" ||
1019 condCallName == "strings.HasSuffix" && rhsName == "strings.TrimSuffix" ||
1020 condCallName == "strings.Contains" && rhsName == "strings.Replace" ||
1021 condCallName == "bytes.HasPrefix" && rhsName == "bytes.TrimPrefix" ||
1022 condCallName == "bytes.HasSuffix" && rhsName == "bytes.TrimSuffix" ||
1023 condCallName == "bytes.Contains" && rhsName == "bytes.Replace" {
1024 report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s", rhsName), report.FilterGenerated())
1027 case *ast.SliceExpr:
1035 if !sameNonDynamic(slice.X, condCall.Args[0]) {
1041 // TODO(dh) We could detect a High that is len(s), but another
1042 // rule will already flag that, anyway.
1043 if slice.High != nil {
1048 if slice.Low != nil {
1049 n, ok := code.ExprToInt(pass, slice.Low)
1057 switch index := index.(type) {
1059 if fun != "HasPrefix" {
1062 if fn, ok := index.Fun.(*ast.Ident); !ok || fn.Name != "len" {
1065 if len(index.Args) != 1 {
1068 id3 := index.Args[knowledge.Arg("len.v")]
1069 switch oid3 := condCall.Args[1].(type) {
1071 if pkg != "strings" {
1074 lit, ok := id3.(*ast.BasicLit)
1078 s1, ok1 := code.ExprToString(pass, lit)
1079 s2, ok2 := code.ExprToString(pass, condCall.Args[1])
1080 if !ok1 || !ok2 || s1 != s2 {
1084 if !sameNonDynamic(id3, oid3) {
1088 case *ast.BasicLit, *ast.Ident:
1089 if fun != "HasPrefix" {
1092 if pkg != "strings" {
1095 string, ok1 := code.ExprToString(pass, condCall.Args[1])
1096 int, ok2 := code.ExprToInt(pass, slice.Low)
1097 if !ok1 || !ok2 || int != int64(len(string)) {
1100 case *ast.BinaryExpr:
1101 if fun != "HasSuffix" {
1104 if index.Op != token.SUB {
1107 if !isLenOnIdent(index.X, condCall.Args[0]) ||
1108 !isLenOnIdent(index.Y, condCall.Args[1]) {
1115 var replacement string
1118 replacement = "TrimPrefix"
1120 replacement = "TrimSuffix"
1122 report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s.%s", pkg, replacement),
1123 report.ShortRange(),
1124 report.FilterGenerated())
1127 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1132 checkLoopSlideQ = pattern.MustParse(`
1134 (AssignStmt initvar@(Ident _) _ (BasicLit "INT" "0"))
1135 (BinaryExpr initvar "<" limit@(Ident _))
1136 (IncDecStmt initvar "++")
1138 (IndexExpr slice@(Ident _) initvar)
1140 (IndexExpr slice (BinaryExpr offset@(Ident _) "+" initvar)))])`)
1141 checkLoopSlideR = pattern.MustParse(`
1144 [(SliceExpr slice nil limit nil)
1145 (SliceExpr slice offset nil nil)])`)
1148 func CheckLoopSlide(pass *analysis.Pass) (interface{}, error) {
1149 // TODO(dh): detect bs[i+offset] in addition to bs[offset+i]
1150 // TODO(dh): consider merging this function with LintLoopCopy
1151 // TODO(dh): detect length that is an expression, not a variable name
1152 // TODO(dh): support sliding to a different offset than the beginning of the slice
1154 fn := func(node ast.Node) {
1155 loop := node.(*ast.ForStmt)
1156 m, edits, ok := code.MatchAndEdit(pass, checkLoopSlideQ, checkLoopSlideR, loop)
1160 if _, ok := pass.TypesInfo.TypeOf(m.State["slice"].(*ast.Ident)).Underlying().(*types.Slice); !ok {
1164 report.Report(pass, loop, "should use copy() instead of loop for sliding slice elements",
1165 report.ShortRange(),
1166 report.FilterGenerated(),
1167 report.Fixes(edit.Fix("use copy() instead of loop", edits...)))
1169 code.Preorder(pass, fn, (*ast.ForStmt)(nil))
1174 checkMakeLenCapQ1 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size@(BasicLit "INT" "0")])`)
1175 checkMakeLenCapQ2 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size size])`)
1178 func CheckMakeLenCap(pass *analysis.Pass) (interface{}, error) {
1179 fn := func(node ast.Node) {
1180 if pass.Pkg.Path() == "runtime_test" && filepath.Base(pass.Fset.Position(node.Pos()).Filename) == "map_test.go" {
1181 // special case of runtime tests testing map creation
1184 if m, ok := code.Match(pass, checkMakeLenCapQ1, node); ok {
1185 T := m.State["typ"].(ast.Expr)
1186 size := m.State["size"].(ast.Node)
1187 if _, ok := pass.TypesInfo.TypeOf(T).Underlying().(*types.Slice); ok {
1190 report.Report(pass, size, fmt.Sprintf("should use make(%s) instead", report.Render(pass, T)), report.FilterGenerated())
1191 } else if m, ok := code.Match(pass, checkMakeLenCapQ2, node); ok {
1192 // TODO(dh): don't consider sizes identical if they're
1193 // dynamic. for example: make(T, <-ch, <-ch).
1194 T := m.State["typ"].(ast.Expr)
1195 size := m.State["size"].(ast.Node)
1196 report.Report(pass, size,
1197 fmt.Sprintf("should use make(%s, %s) instead", report.Render(pass, T), report.Render(pass, size)),
1198 report.FilterGenerated())
1201 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1206 checkAssertNotNilFn1Q = pattern.MustParse(`
1208 (AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr assert@(Object _) _)])
1210 (BinaryExpr ok "&&" (BinaryExpr assert "!=" (Builtin "nil")))
1211 (BinaryExpr (BinaryExpr assert "!=" (Builtin "nil")) "&&" ok))
1214 checkAssertNotNilFn2Q = pattern.MustParse(`
1217 (BinaryExpr lhs@(Object _) "!=" (Builtin "nil"))
1220 (AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr lhs _)])
1228 func CheckAssertNotNil(pass *analysis.Pass) (interface{}, error) {
1229 fn1 := func(node ast.Node) {
1230 m, ok := code.Match(pass, checkAssertNotNilFn1Q, node)
1234 assert := m.State["assert"].(types.Object)
1235 assign := m.State["ok"].(types.Object)
1236 report.Report(pass, node, fmt.Sprintf("when %s is true, %s can't be nil", assign.Name(), assert.Name()),
1237 report.ShortRange(),
1238 report.FilterGenerated())
1240 fn2 := func(node ast.Node) {
1241 m, ok := code.Match(pass, checkAssertNotNilFn2Q, node)
1245 ifstmt := m.State["ifstmt"].(*ast.IfStmt)
1246 lhs := m.State["lhs"].(types.Object)
1247 assignIdent := m.State["ok"].(types.Object)
1248 report.Report(pass, ifstmt, fmt.Sprintf("when %s is true, %s can't be nil", assignIdent.Name(), lhs.Name()),
1249 report.ShortRange(),
1250 report.FilterGenerated())
1252 // OPT(dh): merge fn1 and fn2
1253 code.Preorder(pass, fn1, (*ast.IfStmt)(nil))
1254 code.Preorder(pass, fn2, (*ast.IfStmt)(nil))
1258 func CheckDeclareAssign(pass *analysis.Pass) (interface{}, error) {
1259 hasMultipleAssignments := func(root ast.Node, ident *ast.Ident) bool {
1261 ast.Inspect(root, func(node ast.Node) bool {
1265 assign, ok := node.(*ast.AssignStmt)
1269 for _, lhs := range assign.Lhs {
1270 if oident, ok := lhs.(*ast.Ident); ok {
1271 if oident.Obj == ident.Obj {
1281 fn := func(node ast.Node) {
1282 block := node.(*ast.BlockStmt)
1283 if len(block.List) < 2 {
1286 for i, stmt := range block.List[:len(block.List)-1] {
1288 decl, ok := stmt.(*ast.DeclStmt)
1292 gdecl, ok := decl.Decl.(*ast.GenDecl)
1293 if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 {
1296 vspec, ok := gdecl.Specs[0].(*ast.ValueSpec)
1297 if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 {
1301 assign, ok := block.List[i+1].(*ast.AssignStmt)
1302 if !ok || assign.Tok != token.ASSIGN {
1305 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
1308 ident, ok := assign.Lhs[0].(*ast.Ident)
1312 if vspec.Names[0].Obj != ident.Obj {
1316 if refersTo(pass, assign.Rhs[0], pass.TypesInfo.ObjectOf(ident)) {
1319 if hasMultipleAssignments(block, ident) {
1327 Values: []ast.Expr{assign.Rhs[0]},
1333 report.Report(pass, decl, "should merge variable declaration with assignment on next line",
1334 report.FilterGenerated(),
1335 report.Fixes(edit.Fix("merge declaration with assignment", edit.ReplaceWithNode(pass.Fset, edit.Range{decl.Pos(), assign.End()}, r))))
1338 code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
1342 func CheckRedundantBreak(pass *analysis.Pass) (interface{}, error) {
1343 fn1 := func(node ast.Node) {
1344 clause := node.(*ast.CaseClause)
1345 if len(clause.Body) < 2 {
1348 branch, ok := clause.Body[len(clause.Body)-1].(*ast.BranchStmt)
1349 if !ok || branch.Tok != token.BREAK || branch.Label != nil {
1352 report.Report(pass, branch, "redundant break statement", report.FilterGenerated())
1354 fn2 := func(node ast.Node) {
1355 var ret *ast.FieldList
1356 var body *ast.BlockStmt
1357 switch x := node.(type) {
1359 ret = x.Type.Results
1362 ret = x.Type.Results
1365 lint.ExhaustiveTypeSwitch(node)
1367 // if the func has results, a return can't be redundant.
1368 // similarly, if there are no statements, there can be
1370 if ret != nil || body == nil || len(body.List) < 1 {
1373 rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt)
1377 // we don't need to check rst.Results as we already
1378 // checked x.Type.Results to be nil.
1379 report.Report(pass, rst, "redundant return statement", report.FilterGenerated())
1381 code.Preorder(pass, fn1, (*ast.CaseClause)(nil))
1382 code.Preorder(pass, fn2, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
1386 func isStringer(T types.Type, msCache *gotypeutil.MethodSetCache) bool {
1387 ms := msCache.MethodSet(T)
1388 sel := ms.Lookup(nil, "String")
1392 fn, ok := sel.Obj().(*types.Func)
1394 // should be unreachable
1397 sig := fn.Type().(*types.Signature)
1398 if sig.Params().Len() != 0 {
1401 if sig.Results().Len() != 1 {
1404 if !typeutil.IsType(sig.Results().At(0).Type(), "string") {
1410 func isFormatter(T types.Type, msCache *gotypeutil.MethodSetCache) bool {
1411 // TODO(dh): this function also exists in staticcheck/lint.go – deduplicate.
1413 ms := msCache.MethodSet(T)
1414 sel := ms.Lookup(nil, "Format")
1418 fn, ok := sel.Obj().(*types.Func)
1420 // should be unreachable
1423 sig := fn.Type().(*types.Signature)
1424 if sig.Params().Len() != 2 {
1427 // TODO(dh): check the types of the arguments for more
1429 if sig.Results().Len() != 0 {
1435 var checkRedundantSprintfQ = pattern.MustParse(`(CallExpr (Function "fmt.Sprintf") [format arg])`)
1437 func CheckRedundantSprintf(pass *analysis.Pass) (interface{}, error) {
1438 fn := func(node ast.Node) {
1439 m, ok := code.Match(pass, checkRedundantSprintfQ, node)
1444 format := m.State["format"].(ast.Expr)
1445 arg := m.State["arg"].(ast.Expr)
1446 if s, ok := code.ExprToString(pass, format); !ok || s != "%s" {
1449 typ := pass.TypesInfo.TypeOf(arg)
1450 irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
1452 if types.TypeString(typ, nil) == "reflect.Value" {
1453 // printing with %s produces output different from using
1454 // the String method
1458 if isFormatter(typ, &irpkg.Prog.MethodSets) {
1459 // the type may choose to handle %s in arbitrary ways
1463 if isStringer(typ, &irpkg.Prog.MethodSets) {
1464 replacement := &ast.CallExpr{
1465 Fun: &ast.SelectorExpr{
1467 Sel: &ast.Ident{Name: "String"},
1470 report.Report(pass, node, "should use String() instead of fmt.Sprintf",
1471 report.Fixes(edit.Fix("replace with call to String method", edit.ReplaceWithNode(pass.Fset, node, replacement))))
1475 if typ.Underlying() == types.Universe.Lookup("string").Type() {
1476 if typ == types.Universe.Lookup("string").Type() {
1477 report.Report(pass, node, "the argument is already a string, there's no need to use fmt.Sprintf",
1478 report.FilterGenerated(),
1479 report.Fixes(edit.Fix("remove unnecessary call to fmt.Sprintf", edit.ReplaceWithNode(pass.Fset, node, arg))))
1481 replacement := &ast.CallExpr{
1482 Fun: &ast.Ident{Name: "string"},
1483 Args: []ast.Expr{arg},
1485 report.Report(pass, node, "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf",
1486 report.FilterGenerated(),
1487 report.Fixes(edit.Fix("replace with conversion to string", edit.ReplaceWithNode(pass.Fset, node, replacement))))
1491 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1496 checkErrorsNewSprintfQ = pattern.MustParse(`(CallExpr (Function "errors.New") [(CallExpr (Function "fmt.Sprintf") args)])`)
1497 checkErrorsNewSprintfR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "fmt") (Ident "Errorf")) args)`)
1500 func CheckErrorsNewSprintf(pass *analysis.Pass) (interface{}, error) {
1501 fn := func(node ast.Node) {
1502 if _, edits, ok := code.MatchAndEdit(pass, checkErrorsNewSprintfQ, checkErrorsNewSprintfR, node); ok {
1503 // TODO(dh): the suggested fix may leave an unused import behind
1504 report.Report(pass, node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))",
1505 report.FilterGenerated(),
1506 report.Fixes(edit.Fix("use fmt.Errorf", edits...)))
1509 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1513 func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
1514 return sharedcheck.CheckRangeStringRunes(pass)
1517 var checkNilCheckAroundRangeQ = pattern.MustParse(`
1520 (BinaryExpr x@(Object _) "!=" (Builtin "nil"))
1521 [(RangeStmt _ _ _ x _)]
1524 func CheckNilCheckAroundRange(pass *analysis.Pass) (interface{}, error) {
1525 fn := func(node ast.Node) {
1526 m, ok := code.Match(pass, checkNilCheckAroundRangeQ, node)
1530 switch m.State["x"].(types.Object).Type().Underlying().(type) {
1531 case *types.Slice, *types.Map:
1532 report.Report(pass, node, "unnecessary nil check around range",
1533 report.ShortRange(),
1534 report.FilterGenerated())
1538 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1542 func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool {
1543 call := node.(*ast.CallExpr)
1544 typeconv, ok := call.Args[0].(*ast.CallExpr)
1549 sel, ok := typeconv.Fun.(*ast.SelectorExpr)
1553 name := code.SelectorName(pass, sel)
1555 case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice":
1563 func CheckSortHelpers(pass *analysis.Pass) (interface{}, error) {
1568 var allErrors []Error
1569 fn := func(node ast.Node) {
1570 var body *ast.BlockStmt
1571 switch node := node.(type) {
1577 lint.ExhaustiveTypeSwitch(node)
1584 permissible := false
1585 fnSorts := func(node ast.Node) bool {
1589 if !code.IsCallTo(pass, node, "sort.Sort") {
1592 if isPermissibleSort(pass, node) {
1596 call := node.(*ast.CallExpr)
1597 typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr)
1598 sel := typeconv.Fun.(*ast.SelectorExpr)
1599 name := code.SelectorName(pass, sel)
1602 case "sort.IntSlice":
1603 errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"})
1604 case "sort.Float64Slice":
1605 errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"})
1606 case "sort.StringSlice":
1607 errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"})
1611 ast.Inspect(body, fnSorts)
1616 allErrors = append(allErrors, errors...)
1618 code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil))
1619 sort.Slice(allErrors, func(i, j int) bool {
1620 return allErrors[i].node.Pos() < allErrors[j].node.Pos()
1623 for _, err := range allErrors {
1624 if err.node.Pos() == prev {
1627 prev = err.node.Pos()
1628 report.Report(pass, err.node, err.msg, report.FilterGenerated())
1633 var checkGuardedDeleteQ = pattern.MustParse(`
1636 [(Ident "_") ok@(Ident _)]
1640 [call@(CallExpr (Builtin "delete") [m key])]
1643 func CheckGuardedDelete(pass *analysis.Pass) (interface{}, error) {
1644 fn := func(node ast.Node) {
1645 if m, ok := code.Match(pass, checkGuardedDeleteQ, node); ok {
1646 report.Report(pass, node, "unnecessary guard around call to delete",
1647 report.ShortRange(),
1648 report.FilterGenerated(),
1649 report.Fixes(edit.Fix("remove guard", edit.ReplaceWithNode(pass.Fset, node, m.State["call"].(ast.Node)))))
1653 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1658 checkSimplifyTypeSwitchQ = pattern.MustParse(`
1661 expr@(TypeAssertExpr ident@(Ident _) _)
1663 checkSimplifyTypeSwitchR = pattern.MustParse(`(AssignStmt ident ":=" expr)`)
1666 func CheckSimplifyTypeSwitch(pass *analysis.Pass) (interface{}, error) {
1667 fn := func(node ast.Node) {
1668 m, ok := code.Match(pass, checkSimplifyTypeSwitchQ, node)
1672 stmt := node.(*ast.TypeSwitchStmt)
1673 expr := m.State["expr"].(ast.Node)
1674 ident := m.State["ident"].(*ast.Ident)
1676 x := pass.TypesInfo.ObjectOf(ident)
1677 var allOffenders []*ast.TypeAssertExpr
1678 canSuggestFix := true
1679 for _, clause := range stmt.Body.List {
1680 clause := clause.(*ast.CaseClause)
1681 if len(clause.List) != 1 {
1684 hasUnrelatedAssertion := false
1685 var offenders []*ast.TypeAssertExpr
1686 ast.Inspect(clause, func(node ast.Node) bool {
1687 assert2, ok := node.(*ast.TypeAssertExpr)
1691 ident, ok := assert2.X.(*ast.Ident)
1693 hasUnrelatedAssertion = true
1696 if pass.TypesInfo.ObjectOf(ident) != x {
1697 hasUnrelatedAssertion = true
1701 if !types.Identical(pass.TypesInfo.TypeOf(clause.List[0]), pass.TypesInfo.TypeOf(assert2.Type)) {
1702 hasUnrelatedAssertion = true
1705 offenders = append(offenders, assert2)
1708 if !hasUnrelatedAssertion {
1709 // don't flag cases that have other type assertions
1710 // unrelated to the one in the case clause. often
1711 // times, this is done for symmetry, when two
1712 // different values have to be asserted to the same
1714 allOffenders = append(allOffenders, offenders...)
1716 canSuggestFix = canSuggestFix && !hasUnrelatedAssertion
1718 if len(allOffenders) != 0 {
1719 var opts []report.Option
1720 for _, offender := range allOffenders {
1721 opts = append(opts, report.Related(offender, "could eliminate this type assertion"))
1723 opts = append(opts, report.FilterGenerated())
1725 msg := fmt.Sprintf("assigning the result of this type assertion to a variable (switch %s := %s.(type)) could eliminate type assertions in switch cases",
1726 report.Render(pass, ident), report.Render(pass, ident))
1728 var edits []analysis.TextEdit
1729 edits = append(edits, edit.ReplaceWithPattern(pass, checkSimplifyTypeSwitchR, m.State, expr))
1730 for _, offender := range allOffenders {
1731 edits = append(edits, edit.ReplaceWithNode(pass.Fset, offender, offender.X))
1733 opts = append(opts, report.Fixes(edit.Fix("simplify type switch", edits...)))
1734 report.Report(pass, expr, msg, opts...)
1736 report.Report(pass, expr, msg, opts...)
1740 code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
1744 func CheckRedundantCanonicalHeaderKey(pass *analysis.Pass) (interface{}, error) {
1745 fn := func(node ast.Node) {
1746 call := node.(*ast.CallExpr)
1747 callName := code.CallName(pass, call)
1749 case "(net/http.Header).Add", "(net/http.Header).Del", "(net/http.Header).Get", "(net/http.Header).Set":
1754 if !code.IsCallTo(pass, call.Args[0], "net/http.CanonicalHeaderKey") {
1758 report.Report(pass, call,
1759 fmt.Sprintf("calling net/http.CanonicalHeaderKey on the 'key' argument of %s is redundant", callName),
1760 report.FilterGenerated(),
1761 report.Fixes(edit.Fix("remove call to CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, call.Args[0], call.Args[0].(*ast.CallExpr).Args[0]))))
1763 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1767 var checkUnnecessaryGuardQ = pattern.MustParse(`
1770 (AssignStmt [(Ident "_") ok@(Ident _)] ":=" indexexpr@(IndexExpr _ _))
1772 set@(AssignStmt indexexpr "=" (CallExpr (Builtin "append") indexexpr:values))
1773 (AssignStmt indexexpr "=" (CompositeLit _ values)))
1775 (AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _))
1777 set@(AssignStmt indexexpr "+=" value)
1778 (AssignStmt indexexpr "=" value))
1780 (AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _))
1782 set@(IncDecStmt indexexpr "++")
1783 (AssignStmt indexexpr "=" (BasicLit "INT" "1"))))`)
1785 func CheckUnnecessaryGuard(pass *analysis.Pass) (interface{}, error) {
1786 fn := func(node ast.Node) {
1787 if m, ok := code.Match(pass, checkUnnecessaryGuardQ, node); ok {
1788 if code.MayHaveSideEffects(pass, m.State["indexexpr"].(ast.Expr), nil) {
1791 report.Report(pass, node, "unnecessary guard around map access",
1792 report.ShortRange(),
1793 report.Fixes(edit.Fix("simplify map access", edit.ReplaceWithNode(pass.Fset, node, m.State["set"].(ast.Node)))))
1796 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1801 checkElaborateSleepQ = pattern.MustParse(`(SelectStmt (CommClause (UnaryExpr "<-" (CallExpr (Function "time.After") [arg])) body))`)
1802 checkElaborateSleepR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Sleep")) [arg])`)
1805 func CheckElaborateSleep(pass *analysis.Pass) (interface{}, error) {
1806 fn := func(node ast.Node) {
1807 if m, ok := code.Match(pass, checkElaborateSleepQ, node); ok {
1808 if body, ok := m.State["body"].([]ast.Stmt); ok && len(body) == 0 {
1809 report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping",
1810 report.ShortRange(),
1811 report.FilterGenerated(),
1812 report.Fixes(edit.Fix("Use time.Sleep", edit.ReplaceWithPattern(pass, checkElaborateSleepR, m.State, node))))
1814 // TODO(dh): we could make a suggested fix if the body
1815 // doesn't declare or shadow any identifiers
1816 report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping",
1817 report.ShortRange(),
1818 report.FilterGenerated())
1822 code.Preorder(pass, fn, (*ast.SelectStmt)(nil))
1826 var checkPrintSprintQ = pattern.MustParse(`
1830 (Function "fmt.Print")
1831 (Function "fmt.Sprint")
1832 (Function "fmt.Println")
1833 (Function "fmt.Sprintln"))
1834 [(CallExpr (Function "fmt.Sprintf") f:_)])
1837 (Function "fmt.Fprint")
1838 (Function "fmt.Fprintln"))
1839 [_ (CallExpr (Function "fmt.Sprintf") f:_)]))`)
1841 func CheckPrintSprintf(pass *analysis.Pass) (interface{}, error) {
1842 fn := func(node ast.Node) {
1843 m, ok := code.Match(pass, checkPrintSprintQ, node)
1848 name := m.State["fn"].(*types.Func).Name()
1851 case "Print", "Fprint", "Sprint":
1852 newname := name + "f"
1853 msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...))", newname, name)
1854 case "Println", "Fprintln", "Sprintln":
1855 if _, ok := m.State["f"].(*ast.BasicLit); !ok {
1856 // This may be an instance of
1857 // fmt.Println(fmt.Sprintf(arg, ...)) where arg is an
1858 // externally provided format string and the caller
1859 // cannot guarantee that the format string ends with a
1863 newname := name[:len(name)-2] + "f"
1864 msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...)) (but don't forget the newline)", newname, name)
1866 report.Report(pass, node, msg,
1867 report.FilterGenerated())
1869 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1873 var checkSprintLiteralQ = pattern.MustParse(`
1876 (Function "fmt.Sprint")
1877 (Function "fmt.Sprintf"))
1878 [lit@(BasicLit "STRING" _)])`)
1880 func CheckSprintLiteral(pass *analysis.Pass) (interface{}, error) {
1881 // We only flag calls with string literals, not expressions of
1882 // type string, because some people use fmt.Sprint(s) as a pattern
1883 // for copying strings, which may be useful when extracing a small
1884 // substring from a large string.
1885 fn := func(node ast.Node) {
1886 m, ok := code.Match(pass, checkSprintLiteralQ, node)
1890 callee := m.State["fn"].(*types.Func)
1891 lit := m.State["lit"].(*ast.BasicLit)
1892 if callee.Name() == "Sprintf" {
1893 if strings.ContainsRune(lit.Value, '%') {
1894 // This might be a format string
1898 report.Report(pass, node, fmt.Sprintf("unnecessary use of fmt.%s", callee.Name()),
1899 report.FilterGenerated(),
1900 report.Fixes(edit.Fix("Replace with string literal", edit.ReplaceWithNode(pass.Fset, node, lit))))
1902 code.Preorder(pass, fn, (*ast.CallExpr)(nil))