1 // Package simple contains a linter for Go source code.
2 package simple // import "honnef.co/go/tools/simple"
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/types/typeutil"
17 . "honnef.co/go/tools/arg"
18 "honnef.co/go/tools/code"
19 "honnef.co/go/tools/edit"
20 "honnef.co/go/tools/internal/passes/buildir"
21 "honnef.co/go/tools/internal/sharedcheck"
22 . "honnef.co/go/tools/lint/lintdsl"
23 "honnef.co/go/tools/pattern"
24 "honnef.co/go/tools/report"
28 checkSingleCaseSelectQ1 = pattern.MustParse(`
35 (AssignStmt _ _ (UnaryExpr "<-" _)))
37 checkSingleCaseSelectQ2 = pattern.MustParse(`(SelectStmt (CommClause _ _))`)
40 func CheckSingleCaseSelect(pass *analysis.Pass) (interface{}, error) {
41 seen := map[ast.Node]struct{}{}
42 fn := func(node ast.Node) {
43 if m, ok := Match(pass, checkSingleCaseSelectQ1, node); ok {
44 seen[m.State["select"].(ast.Node)] = struct{}{}
45 report.Report(pass, node, "should use for range instead of for { select {} }", report.FilterGenerated())
46 } else if _, ok := Match(pass, checkSingleCaseSelectQ2, node); ok {
47 if _, ok := seen[node]; !ok {
48 report.Report(pass, node, "should use a simple channel send/receive instead of select with a single case",
50 report.FilterGenerated())
54 code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.SelectStmt)(nil))
59 checkLoopCopyQ = pattern.MustParse(`
62 key value ":=" src@(Ident _)
64 (IndexExpr dst@(Ident _) key)
68 key nil ":=" src@(Ident _)
70 (IndexExpr dst@(Ident _) key)
72 (IndexExpr src key))]))`)
73 checkLoopCopyR = pattern.MustParse(`(CallExpr (Ident "copy") [dst src])`)
76 func CheckLoopCopy(pass *analysis.Pass) (interface{}, error) {
77 fn := func(node ast.Node) {
78 m, edits, ok := MatchAndEdit(pass, checkLoopCopyQ, checkLoopCopyR, node)
82 t1 := pass.TypesInfo.TypeOf(m.State["src"].(*ast.Ident))
83 t2 := pass.TypesInfo.TypeOf(m.State["dst"].(*ast.Ident))
84 if _, ok := t1.Underlying().(*types.Slice); !ok {
87 if !types.Identical(t1, t2) {
91 tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy")
92 if err == nil && tv.IsBuiltin() {
93 report.Report(pass, node,
94 "should use copy() instead of a loop",
96 report.FilterGenerated(),
97 report.Fixes(edit.Fix("replace loop with call to copy()", edits...)))
99 report.Report(pass, node, "should use copy() instead of a loop", report.FilterGenerated())
102 code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
106 func CheckIfBoolCmp(pass *analysis.Pass) (interface{}, error) {
107 fn := func(node ast.Node) {
108 if code.IsInTest(pass, node) {
112 expr := node.(*ast.BinaryExpr)
113 if expr.Op != token.EQL && expr.Op != token.NEQ {
116 x := code.IsBoolConst(pass, expr.X)
117 y := code.IsBoolConst(pass, expr.Y)
124 val = code.BoolConst(pass, expr.X)
127 val = code.BoolConst(pass, expr.Y)
130 basic, ok := pass.TypesInfo.TypeOf(other).Underlying().(*types.Basic)
131 if !ok || basic.Kind() != types.Bool {
135 if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) {
138 r := op + report.Render(pass, other)
140 r = strings.TrimLeft(r, "!")
141 if (l1-len(r))%2 == 1 {
144 report.Report(pass, expr, fmt.Sprintf("should omit comparison to bool constant, can be simplified to %s", r),
145 report.FilterGenerated(),
146 report.Fixes(edit.Fix("simplify bool comparison", edit.ReplaceWithString(pass.Fset, expr, r))))
148 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
153 checkBytesBufferConversionsQ = pattern.MustParse(`(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])`)
154 checkBytesBufferConversionsRs = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "String")) [])`)
155 checkBytesBufferConversionsRb = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "Bytes")) [])`)
158 func CheckBytesBufferConversions(pass *analysis.Pass) (interface{}, error) {
159 if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
160 // The bytes package can use itself however it wants
163 fn := func(node ast.Node) {
164 m, ok := Match(pass, checkBytesBufferConversionsQ, node)
168 call := node.(*ast.CallExpr)
169 sel := m.State["sel"].(*ast.SelectorExpr)
171 typ := pass.TypesInfo.TypeOf(call.Fun)
172 if typ == types.Universe.Lookup("string").Type() && code.IsCallToAST(pass, call.Args[0], "(*bytes.Buffer).Bytes") {
173 report.Report(pass, call, fmt.Sprintf("should use %v.String() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
174 report.FilterGenerated(),
175 report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass, checkBytesBufferConversionsRs, m.State, node))))
176 } else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && code.IsCallToAST(pass, call.Args[0], "(*bytes.Buffer).String") {
177 report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
178 report.FilterGenerated(),
179 report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass, checkBytesBufferConversionsRb, m.State, node))))
183 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
187 func CheckStringsContains(pass *analysis.Pass) (interface{}, error) {
188 // map of value to token to bool value
189 allowed := map[int64]map[token.Token]bool{
190 -1: {token.GTR: true, token.NEQ: true, token.EQL: false},
191 0: {token.GEQ: true, token.LSS: false},
193 fn := func(node ast.Node) {
194 expr := node.(*ast.BinaryExpr)
196 case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL:
201 value, ok := code.ExprToInt(pass, expr.Y)
206 allowedOps, ok := allowed[value]
210 b, ok := allowedOps[expr.Op]
215 call, ok := expr.X.(*ast.CallExpr)
219 sel, ok := call.Fun.(*ast.SelectorExpr)
223 pkgIdent, ok := sel.X.(*ast.Ident)
228 if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" {
233 switch funIdent.Name {
235 r = &ast.SelectorExpr{
237 Sel: &ast.Ident{Name: "ContainsRune"},
240 r = &ast.SelectorExpr{
242 Sel: &ast.Ident{Name: "ContainsAny"},
245 r = &ast.SelectorExpr{
247 Sel: &ast.Ident{Name: "Contains"},
264 report.Report(pass, node, fmt.Sprintf("should use %s instead", report.Render(pass, r)),
265 report.FilterGenerated(),
266 report.Fixes(edit.Fix(fmt.Sprintf("simplify use of %s", report.Render(pass, call.Fun)), edit.ReplaceWithNode(pass.Fset, node, r))))
268 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
273 checkBytesCompareQ = pattern.MustParse(`(BinaryExpr (CallExpr (Function "bytes.Compare") args) op@(Or "==" "!=") (BasicLit "INT" "0"))`)
274 checkBytesCompareRn = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args)`)
275 checkBytesCompareRe = pattern.MustParse(`(UnaryExpr "!" (CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args))`)
278 func CheckBytesCompare(pass *analysis.Pass) (interface{}, error) {
279 if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
280 // the bytes package is free to use bytes.Compare as it sees fit
283 fn := func(node ast.Node) {
284 m, ok := Match(pass, checkBytesCompareQ, node)
289 args := report.RenderArgs(pass, m.State["args"].([]ast.Expr))
291 if m.State["op"].(token.Token) == token.NEQ {
295 var fix analysis.SuggestedFix
296 switch tok := m.State["op"].(token.Token); tok {
298 fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass, checkBytesCompareRe, m.State, node))
300 fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass, checkBytesCompareRn, m.State, node))
302 panic(fmt.Sprintf("unexpected token %v", tok))
304 report.Report(pass, node, fmt.Sprintf("should use %sbytes.Equal(%s) instead", prefix, args), report.FilterGenerated(), report.Fixes(fix))
306 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
310 func CheckForTrue(pass *analysis.Pass) (interface{}, error) {
311 fn := func(node ast.Node) {
312 loop := node.(*ast.ForStmt)
313 if loop.Init != nil || loop.Post != nil {
316 if !code.IsBoolConst(pass, loop.Cond) || !code.BoolConst(pass, loop.Cond) {
319 report.Report(pass, loop, "should use for {} instead of for true {}",
321 report.FilterGenerated())
323 code.Preorder(pass, fn, (*ast.ForStmt)(nil))
327 func CheckRegexpRaw(pass *analysis.Pass) (interface{}, error) {
328 fn := func(node ast.Node) {
329 call := node.(*ast.CallExpr)
330 if !code.IsCallToAnyAST(pass, call, "regexp.MustCompile", "regexp.Compile") {
333 sel, ok := call.Fun.(*ast.SelectorExpr)
337 lit, ok := call.Args[Arg("regexp.Compile.expr")].(*ast.BasicLit)
339 // TODO(dominikh): support string concat, maybe support constants
342 if lit.Kind != token.STRING {
343 // invalid function call
346 if lit.Value[0] != '"' {
347 // already a raw string
351 if !strings.Contains(val, `\\`) {
354 if strings.Contains(val, "`") {
359 for _, c := range val {
360 if !bs && c == '\\' {
369 // backslash followed by non-backslash -> escape sequence
374 report.Report(pass, call, fmt.Sprintf("should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name), report.FilterGenerated())
376 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
381 checkIfReturnQIf = pattern.MustParse(`(IfStmt nil cond [(ReturnStmt [ret@(Ident _)])] nil)`)
382 checkIfReturnQRet = pattern.MustParse(`(ReturnStmt [ret@(Ident _)])`)
385 func CheckIfReturn(pass *analysis.Pass) (interface{}, error) {
386 fn := func(node ast.Node) {
387 block := node.(*ast.BlockStmt)
392 n1, n2 := block.List[l-2], block.List[l-1]
394 if len(block.List) >= 3 {
395 if _, ok := block.List[l-3].(*ast.IfStmt); ok {
396 // Do not flag a series of if statements
400 m1, ok := Match(pass, checkIfReturnQIf, n1)
404 m2, ok := Match(pass, checkIfReturnQRet, n2)
409 if op, ok := m1.State["cond"].(*ast.BinaryExpr); ok {
411 case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
417 ret1 := m1.State["ret"].(*ast.Ident)
418 if !code.IsBoolConst(pass, ret1) {
421 ret2 := m2.State["ret"].(*ast.Ident)
422 if !code.IsBoolConst(pass, ret2) {
426 if ret1.Name == ret2.Name {
427 // we want the function to return true and false, not the
428 // same value both times.
432 cond := m1.State["cond"].(ast.Expr)
434 if ret1.Name == "false" {
437 report.Report(pass, n1,
438 fmt.Sprintf("should use 'return %s' instead of 'if %s { return %s }; return %s'",
439 report.Render(pass, cond),
440 report.Render(pass, origCond), report.Render(pass, ret1), report.Render(pass, ret2)),
441 report.FilterGenerated())
443 code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
447 func negate(expr ast.Expr) ast.Expr {
448 switch expr := expr.(type) {
449 case *ast.BinaryExpr:
466 case *ast.Ident, *ast.CallExpr, *ast.IndexExpr:
467 return &ast.UnaryExpr{
472 return &ast.UnaryExpr{
481 // CheckRedundantNilCheckWithLen checks for the following redundant nil-checks:
483 // if x == nil || len(x) == 0 {}
484 // if x != nil && len(x) != 0 {}
485 // if x != nil && len(x) == N {} (where N != 0)
486 // if x != nil && len(x) > N {}
487 // if x != nil && len(x) >= N {} (where N != 0)
489 func CheckRedundantNilCheckWithLen(pass *analysis.Pass) (interface{}, error) {
490 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) {
491 _, ok := expr.(*ast.BasicLit)
493 return true, code.IsIntLiteral(expr, "0")
495 id, ok := expr.(*ast.Ident)
499 c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const)
503 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0"
506 fn := func(node ast.Node) {
507 // check that expr is "x || y" or "x && y"
508 expr := node.(*ast.BinaryExpr)
509 if expr.Op != token.LOR && expr.Op != token.LAND {
512 eqNil := expr.Op == token.LOR
514 // check that x is "xx == nil" or "xx != nil"
515 x, ok := expr.X.(*ast.BinaryExpr)
519 if eqNil && x.Op != token.EQL {
522 if !eqNil && x.Op != token.NEQ {
525 xx, ok := x.X.(*ast.Ident)
529 if !code.IsNil(pass, x.Y) {
533 // check that y is "len(xx) == 0" or "len(xx) ... "
534 y, ok := expr.Y.(*ast.BinaryExpr)
538 if eqNil && y.Op != token.EQL { // must be len(xx) *==* 0
541 yx, ok := y.X.(*ast.CallExpr)
545 yxFun, ok := yx.Fun.(*ast.Ident)
546 if !ok || yxFun.Name != "len" || len(yx.Args) != 1 {
549 yxArg, ok := yx.Args[Arg("len.v")].(*ast.Ident)
553 if yxArg.Name != xx.Name {
557 if eqNil && !code.IsIntLiteral(y.Y, "0") { // must be len(x) == *0*
562 isConst, isZero := isConstZero(y.Y)
568 // avoid false positive for "xx != nil && len(xx) == 0"
573 // avoid false positive for "xx != nil && len(xx) >= 0"
578 // avoid false positive for "xx != nil && len(xx) != <non-zero>"
589 // finally check that xx type is one of array, slice, map or chan
590 // this is to prevent false positive in case if xx is a pointer to an array
592 switch pass.TypesInfo.TypeOf(xx).(type) {
594 nilType = "nil slices"
598 nilType = "nil channels"
602 report.Report(pass, expr, fmt.Sprintf("should omit nil check; len() for %s is defined as zero", nilType), report.FilterGenerated())
604 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
608 var checkSlicingQ = pattern.MustParse(`(SliceExpr x@(Object _) low (CallExpr (Builtin "len") [x]) nil)`)
610 func CheckSlicing(pass *analysis.Pass) (interface{}, error) {
611 fn := func(node ast.Node) {
612 if _, ok := Match(pass, checkSlicingQ, node); ok {
613 expr := node.(*ast.SliceExpr)
614 report.Report(pass, expr.High,
615 "should omit second index in slice, s[a:len(s)] is identical to s[a:]",
616 report.FilterGenerated(),
617 report.Fixes(edit.Fix("simplify slice expression", edit.Delete(expr.High))))
620 code.Preorder(pass, fn, (*ast.SliceExpr)(nil))
624 func refersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
626 fn := func(node ast.Node) bool {
627 ident2, ok := node.(*ast.Ident)
631 if ident == pass.TypesInfo.ObjectOf(ident2) {
637 ast.Inspect(expr, fn)
641 var checkLoopAppendQ = pattern.MustParse(`
647 [(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]) `)
649 func CheckLoopAppend(pass *analysis.Pass) (interface{}, error) {
650 fn := func(node ast.Node) {
651 m, ok := Match(pass, checkLoopAppendQ, node)
656 val := m.State["val"].(types.Object)
657 if refersTo(pass, m.State["lhs"].(ast.Expr), val) {
661 src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr))
662 dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr))
663 if !types.Identical(src, dst) {
667 r := &ast.AssignStmt{
668 Lhs: []ast.Expr{m.State["lhs"].(ast.Expr)},
672 Fun: &ast.Ident{Name: "append"},
674 m.State["lhs"].(ast.Expr),
675 m.State["x"].(ast.Expr),
682 report.Report(pass, node, fmt.Sprintf("should replace loop with %s", report.Render(pass, r)),
684 report.FilterGenerated(),
685 report.Fixes(edit.Fix("replace loop with call to append", edit.ReplaceWithNode(pass.Fset, node, r))))
687 code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
692 checkTimeSinceQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (Function "time.Now") []) (Function "(time.Time).Sub")) [arg])`)
693 checkTimeSinceR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Since")) [arg])`)
696 func CheckTimeSince(pass *analysis.Pass) (interface{}, error) {
697 fn := func(node ast.Node) {
698 if _, edits, ok := MatchAndEdit(pass, checkTimeSinceQ, checkTimeSinceR, node); ok {
699 report.Report(pass, node, "should use time.Since instead of time.Now().Sub",
700 report.FilterGenerated(),
701 report.Fixes(edit.Fix("replace with call to time.Since", edits...)))
704 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
709 checkTimeUntilQ = pattern.MustParse(`(CallExpr (Function "(time.Time).Sub") [(CallExpr (Function "time.Now") [])])`)
710 checkTimeUntilR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Until")) [arg])`)
713 func CheckTimeUntil(pass *analysis.Pass) (interface{}, error) {
714 if !code.IsGoVersion(pass, 8) {
717 fn := func(node ast.Node) {
718 if _, ok := Match(pass, checkTimeUntilQ, node); ok {
719 if sel, ok := node.(*ast.CallExpr).Fun.(*ast.SelectorExpr); ok {
720 r := pattern.NodeToAST(checkTimeUntilR.Root, map[string]interface{}{"arg": sel.X}).(ast.Node)
721 report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())",
722 report.FilterGenerated(),
723 report.Fixes(edit.Fix("replace with call to time.Until", edit.ReplaceWithNode(pass.Fset, node, r))))
725 report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())", report.FilterGenerated())
729 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
734 checkUnnecessaryBlankQ1 = pattern.MustParse(`
740 (UnaryExpr "<-" _))) `)
741 checkUnnecessaryBlankQ2 = pattern.MustParse(`
743 (Ident "_") _ recv@(UnaryExpr "<-" _))`)
746 func CheckUnnecessaryBlank(pass *analysis.Pass) (interface{}, error) {
747 fn1 := func(node ast.Node) {
748 if _, ok := Match(pass, checkUnnecessaryBlankQ1, node); ok {
749 r := *node.(*ast.AssignStmt)
751 report.Report(pass, node, "unnecessary assignment to the blank identifier",
752 report.FilterGenerated(),
753 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.ReplaceWithNode(pass.Fset, node, &r))))
754 } else if m, ok := Match(pass, checkUnnecessaryBlankQ2, node); ok {
755 report.Report(pass, node, "unnecessary assignment to the blank identifier",
756 report.FilterGenerated(),
757 report.Fixes(edit.Fix("simplify channel receive operation", edit.ReplaceWithNode(pass.Fset, node, m.State["recv"].(ast.Node)))))
761 fn3 := func(node ast.Node) {
762 rs := node.(*ast.RangeStmt)
765 if rs.Value == nil && code.IsBlank(rs.Key) {
766 report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier",
767 report.FilterGenerated(),
768 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1}))))
772 if code.IsBlank(rs.Key) && code.IsBlank(rs.Value) {
773 // FIXME we should mark both key and value
774 report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier",
775 report.FilterGenerated(),
776 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1}))))
780 if !code.IsBlank(rs.Key) && code.IsBlank(rs.Value) {
781 report.Report(pass, rs.Value, "unnecessary assignment to the blank identifier",
782 report.FilterGenerated(),
783 report.Fixes(edit.Fix("remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.End(), rs.Value.End()}))))
787 code.Preorder(pass, fn1, (*ast.AssignStmt)(nil))
788 if code.IsGoVersion(pass, 4) {
789 code.Preorder(pass, fn3, (*ast.RangeStmt)(nil))
794 func CheckSimplerStructConversion(pass *analysis.Pass) (interface{}, error) {
796 fn := func(node ast.Node) {
797 // Do not suggest type conversion between pointers
798 if unary, ok := node.(*ast.UnaryExpr); ok && unary.Op == token.AND {
799 if lit, ok := unary.X.(*ast.CompositeLit); ok {
809 lit, ok := node.(*ast.CompositeLit)
813 typ1, _ := pass.TypesInfo.TypeOf(lit.Type).(*types.Named)
817 s1, ok := typ1.Underlying().(*types.Struct)
822 var typ2 *types.Named
824 getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) {
825 sel, ok := expr.(*ast.SelectorExpr)
827 return nil, nil, false
829 ident, ok := sel.X.(*ast.Ident)
831 return nil, nil, false
833 typ := pass.TypesInfo.TypeOf(sel.X)
834 return typ, ident, typ != nil
836 if len(lit.Elts) == 0 {
839 if s1.NumFields() != len(lit.Elts) {
842 for i, elt := range lit.Elts {
846 switch elt := elt.(type) {
847 case *ast.SelectorExpr:
848 t, id, ok = getSelType(elt)
852 if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name {
855 case *ast.KeyValueExpr:
856 var sel *ast.SelectorExpr
857 sel, ok = elt.Value.(*ast.SelectorExpr)
862 if elt.Key.(*ast.Ident).Name != sel.Sel.Name {
865 t, id, ok = getSelType(elt.Value)
870 // All fields must be initialized from the same object
871 if ident != nil && ident.Obj != id.Obj {
874 typ2, _ = t.(*types.Named)
885 if typ1.Obj().Pkg() != typ2.Obj().Pkg() {
886 // Do not suggest type conversions between different
887 // packages. Types in different packages might only match
888 // by coincidence. Furthermore, if the dependency ever
889 // adds more fields to its type, it could break the code
890 // that relies on the type conversion to work.
894 s2, ok := typ2.Underlying().(*types.Struct)
901 if code.IsGoVersion(pass, 8) {
902 if !types.IdenticalIgnoreTags(s1, s2) {
906 if !types.Identical(s1, s2) {
913 Args: []ast.Expr{ident},
915 report.Report(pass, node,
916 fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, typ2.Obj().Name(), typ1.Obj().Name()),
917 report.FilterGenerated(),
918 report.Fixes(edit.Fix("use type conversion", edit.ReplaceWithNode(pass.Fset, node, r))))
920 code.Preorder(pass, fn, (*ast.UnaryExpr)(nil), (*ast.CompositeLit)(nil))
924 func CheckTrim(pass *analysis.Pass) (interface{}, error) {
925 sameNonDynamic := func(node1, node2 ast.Node) bool {
926 if reflect.TypeOf(node1) != reflect.TypeOf(node2) {
930 switch node1 := node1.(type) {
932 return node1.Obj == node2.(*ast.Ident).Obj
933 case *ast.SelectorExpr:
934 return report.Render(pass, node1) == report.Render(pass, node2)
936 return report.Render(pass, node1) == report.Render(pass, node2)
941 isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool {
942 call, ok := fn.(*ast.CallExpr)
946 if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "len" {
949 if len(call.Args) != 1 {
952 return sameNonDynamic(call.Args[Arg("len.v")], ident)
955 fn := func(node ast.Node) {
959 ifstmt := node.(*ast.IfStmt)
960 if ifstmt.Init != nil {
963 if ifstmt.Else != nil {
966 if len(ifstmt.Body.List) != 1 {
969 condCall, ok := ifstmt.Cond.(*ast.CallExpr)
974 condCallName := code.CallNameAST(pass, condCall)
975 switch condCallName {
976 case "strings.HasPrefix":
979 case "strings.HasSuffix":
982 case "strings.Contains":
985 case "bytes.HasPrefix":
988 case "bytes.HasSuffix":
991 case "bytes.Contains":
998 assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt)
1002 if assign.Tok != token.ASSIGN {
1005 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
1008 if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
1012 switch rhs := assign.Rhs[0].(type) {
1014 if len(rhs.Args) < 2 || !sameNonDynamic(condCall.Args[0], rhs.Args[0]) || !sameNonDynamic(condCall.Args[1], rhs.Args[1]) {
1018 rhsName := code.CallNameAST(pass, rhs)
1019 if condCallName == "strings.HasPrefix" && rhsName == "strings.TrimPrefix" ||
1020 condCallName == "strings.HasSuffix" && rhsName == "strings.TrimSuffix" ||
1021 condCallName == "strings.Contains" && rhsName == "strings.Replace" ||
1022 condCallName == "bytes.HasPrefix" && rhsName == "bytes.TrimPrefix" ||
1023 condCallName == "bytes.HasSuffix" && rhsName == "bytes.TrimSuffix" ||
1024 condCallName == "bytes.Contains" && rhsName == "bytes.Replace" {
1025 report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s", rhsName), report.FilterGenerated())
1028 case *ast.SliceExpr:
1036 if !sameNonDynamic(slice.X, condCall.Args[0]) {
1042 // TODO(dh) We could detect a High that is len(s), but another
1043 // rule will already flag that, anyway.
1044 if slice.High != nil {
1049 if slice.Low != nil {
1050 n, ok := code.ExprToInt(pass, slice.Low)
1058 switch index := index.(type) {
1060 if fun != "HasPrefix" {
1063 if fn, ok := index.Fun.(*ast.Ident); !ok || fn.Name != "len" {
1066 if len(index.Args) != 1 {
1069 id3 := index.Args[Arg("len.v")]
1070 switch oid3 := condCall.Args[1].(type) {
1072 if pkg != "strings" {
1075 lit, ok := id3.(*ast.BasicLit)
1079 s1, ok1 := code.ExprToString(pass, lit)
1080 s2, ok2 := code.ExprToString(pass, condCall.Args[1])
1081 if !ok1 || !ok2 || s1 != s2 {
1085 if !sameNonDynamic(id3, oid3) {
1089 case *ast.BasicLit, *ast.Ident:
1090 if fun != "HasPrefix" {
1093 if pkg != "strings" {
1096 string, ok1 := code.ExprToString(pass, condCall.Args[1])
1097 int, ok2 := code.ExprToInt(pass, slice.Low)
1098 if !ok1 || !ok2 || int != int64(len(string)) {
1101 case *ast.BinaryExpr:
1102 if fun != "HasSuffix" {
1105 if index.Op != token.SUB {
1108 if !isLenOnIdent(index.X, condCall.Args[0]) ||
1109 !isLenOnIdent(index.Y, condCall.Args[1]) {
1116 var replacement string
1119 replacement = "TrimPrefix"
1121 replacement = "TrimSuffix"
1123 report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s.%s", pkg, replacement),
1124 report.ShortRange(),
1125 report.FilterGenerated())
1128 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1133 checkLoopSlideQ = pattern.MustParse(`
1135 (AssignStmt initvar@(Ident _) _ (BasicLit "INT" "0"))
1136 (BinaryExpr initvar "<" limit@(Ident _))
1137 (IncDecStmt initvar "++")
1139 (IndexExpr slice@(Ident _) initvar)
1141 (IndexExpr slice (BinaryExpr offset@(Ident _) "+" initvar)))])`)
1142 checkLoopSlideR = pattern.MustParse(`
1145 [(SliceExpr slice nil limit nil)
1146 (SliceExpr slice offset nil nil)])`)
1149 func CheckLoopSlide(pass *analysis.Pass) (interface{}, error) {
1150 // TODO(dh): detect bs[i+offset] in addition to bs[offset+i]
1151 // TODO(dh): consider merging this function with LintLoopCopy
1152 // TODO(dh): detect length that is an expression, not a variable name
1153 // TODO(dh): support sliding to a different offset than the beginning of the slice
1155 fn := func(node ast.Node) {
1156 loop := node.(*ast.ForStmt)
1157 m, edits, ok := MatchAndEdit(pass, checkLoopSlideQ, checkLoopSlideR, loop)
1161 if _, ok := pass.TypesInfo.TypeOf(m.State["slice"].(*ast.Ident)).Underlying().(*types.Slice); !ok {
1165 report.Report(pass, loop, "should use copy() instead of loop for sliding slice elements",
1166 report.ShortRange(),
1167 report.FilterGenerated(),
1168 report.Fixes(edit.Fix("use copy() instead of loop", edits...)))
1170 code.Preorder(pass, fn, (*ast.ForStmt)(nil))
1175 checkMakeLenCapQ1 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size@(BasicLit "INT" "0")])`)
1176 checkMakeLenCapQ2 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size size])`)
1179 func CheckMakeLenCap(pass *analysis.Pass) (interface{}, error) {
1180 fn := func(node ast.Node) {
1181 if pass.Pkg.Path() == "runtime_test" && filepath.Base(pass.Fset.Position(node.Pos()).Filename) == "map_test.go" {
1182 // special case of runtime tests testing map creation
1185 if m, ok := Match(pass, checkMakeLenCapQ1, node); ok {
1186 T := m.State["typ"].(ast.Expr)
1187 size := m.State["size"].(ast.Node)
1188 if _, ok := pass.TypesInfo.TypeOf(T).Underlying().(*types.Slice); ok {
1191 report.Report(pass, size, fmt.Sprintf("should use make(%s) instead", report.Render(pass, T)), report.FilterGenerated())
1192 } else if m, ok := Match(pass, checkMakeLenCapQ2, node); ok {
1193 // TODO(dh): don't consider sizes identical if they're
1194 // dynamic. for example: make(T, <-ch, <-ch).
1195 T := m.State["typ"].(ast.Expr)
1196 size := m.State["size"].(ast.Node)
1197 report.Report(pass, size,
1198 fmt.Sprintf("should use make(%s, %s) instead", report.Render(pass, T), report.Render(pass, size)),
1199 report.FilterGenerated())
1202 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1207 checkAssertNotNilFn1Q = pattern.MustParse(`
1209 (AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr assert@(Object _) _)])
1211 (BinaryExpr ok "&&" (BinaryExpr assert "!=" (Builtin "nil")))
1212 (BinaryExpr (BinaryExpr assert "!=" (Builtin "nil")) "&&" ok))
1215 checkAssertNotNilFn2Q = pattern.MustParse(`
1218 (BinaryExpr lhs@(Object _) "!=" (Builtin "nil"))
1221 (AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr lhs _)])
1229 func CheckAssertNotNil(pass *analysis.Pass) (interface{}, error) {
1230 fn1 := func(node ast.Node) {
1231 m, ok := Match(pass, checkAssertNotNilFn1Q, node)
1235 assert := m.State["assert"].(types.Object)
1236 assign := m.State["ok"].(types.Object)
1237 report.Report(pass, node, fmt.Sprintf("when %s is true, %s can't be nil", assign.Name(), assert.Name()),
1238 report.ShortRange(),
1239 report.FilterGenerated())
1241 fn2 := func(node ast.Node) {
1242 m, ok := Match(pass, checkAssertNotNilFn2Q, node)
1246 ifstmt := m.State["ifstmt"].(*ast.IfStmt)
1247 lhs := m.State["lhs"].(types.Object)
1248 assignIdent := m.State["ok"].(types.Object)
1249 report.Report(pass, ifstmt, fmt.Sprintf("when %s is true, %s can't be nil", assignIdent.Name(), lhs.Name()),
1250 report.ShortRange(),
1251 report.FilterGenerated())
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 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 *typeutil.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 !code.IsType(sig.Results().At(0).Type(), "string") {
1410 var checkRedundantSprintfQ = pattern.MustParse(`(CallExpr (Function "fmt.Sprintf") [format arg])`)
1412 func CheckRedundantSprintf(pass *analysis.Pass) (interface{}, error) {
1413 fn := func(node ast.Node) {
1414 m, ok := Match(pass, checkRedundantSprintfQ, node)
1419 format := m.State["format"].(ast.Expr)
1420 arg := m.State["arg"].(ast.Expr)
1421 if s, ok := code.ExprToString(pass, format); !ok || s != "%s" {
1424 typ := pass.TypesInfo.TypeOf(arg)
1426 irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
1427 if types.TypeString(typ, nil) != "reflect.Value" && isStringer(typ, &irpkg.Prog.MethodSets) {
1428 replacement := &ast.CallExpr{
1429 Fun: &ast.SelectorExpr{
1431 Sel: &ast.Ident{Name: "String"},
1434 report.Report(pass, node, "should use String() instead of fmt.Sprintf",
1435 report.Fixes(edit.Fix("replace with call to String method", edit.ReplaceWithNode(pass.Fset, node, replacement))))
1439 if typ.Underlying() == types.Universe.Lookup("string").Type() {
1440 if typ == types.Universe.Lookup("string").Type() {
1441 report.Report(pass, node, "the argument is already a string, there's no need to use fmt.Sprintf",
1442 report.FilterGenerated(),
1443 report.Fixes(edit.Fix("remove unnecessary call to fmt.Sprintf", edit.ReplaceWithNode(pass.Fset, node, arg))))
1445 replacement := &ast.CallExpr{
1446 Fun: &ast.Ident{Name: "string"},
1447 Args: []ast.Expr{arg},
1449 report.Report(pass, node, "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf",
1450 report.FilterGenerated(),
1451 report.Fixes(edit.Fix("replace with conversion to string", edit.ReplaceWithNode(pass.Fset, node, replacement))))
1455 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1460 checkErrorsNewSprintfQ = pattern.MustParse(`(CallExpr (Function "errors.New") [(CallExpr (Function "fmt.Sprintf") args)])`)
1461 checkErrorsNewSprintfR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "fmt") (Ident "Errorf")) args)`)
1464 func CheckErrorsNewSprintf(pass *analysis.Pass) (interface{}, error) {
1465 fn := func(node ast.Node) {
1466 if _, edits, ok := MatchAndEdit(pass, checkErrorsNewSprintfQ, checkErrorsNewSprintfR, node); ok {
1467 // TODO(dh): the suggested fix may leave an unused import behind
1468 report.Report(pass, node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))",
1469 report.FilterGenerated(),
1470 report.Fixes(edit.Fix("use fmt.Errorf", edits...)))
1473 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1477 func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
1478 return sharedcheck.CheckRangeStringRunes(pass)
1481 var checkNilCheckAroundRangeQ = pattern.MustParse(`
1484 (BinaryExpr x@(Object _) "!=" (Builtin "nil"))
1485 [(RangeStmt _ _ _ x _)]
1488 func CheckNilCheckAroundRange(pass *analysis.Pass) (interface{}, error) {
1489 fn := func(node ast.Node) {
1490 m, ok := Match(pass, checkNilCheckAroundRangeQ, node)
1494 switch m.State["x"].(types.Object).Type().Underlying().(type) {
1495 case *types.Slice, *types.Map:
1496 report.Report(pass, node, "unnecessary nil check around range",
1497 report.ShortRange(),
1498 report.FilterGenerated())
1502 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1506 func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool {
1507 call := node.(*ast.CallExpr)
1508 typeconv, ok := call.Args[0].(*ast.CallExpr)
1513 sel, ok := typeconv.Fun.(*ast.SelectorExpr)
1517 name := code.SelectorName(pass, sel)
1519 case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice":
1527 func CheckSortHelpers(pass *analysis.Pass) (interface{}, error) {
1532 var allErrors []Error
1533 fn := func(node ast.Node) {
1534 var body *ast.BlockStmt
1535 switch node := node.(type) {
1541 ExhaustiveTypeSwitch(node)
1548 permissible := false
1549 fnSorts := func(node ast.Node) bool {
1553 if !code.IsCallToAST(pass, node, "sort.Sort") {
1556 if isPermissibleSort(pass, node) {
1560 call := node.(*ast.CallExpr)
1561 typeconv := call.Args[Arg("sort.Sort.data")].(*ast.CallExpr)
1562 sel := typeconv.Fun.(*ast.SelectorExpr)
1563 name := code.SelectorName(pass, sel)
1566 case "sort.IntSlice":
1567 errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"})
1568 case "sort.Float64Slice":
1569 errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"})
1570 case "sort.StringSlice":
1571 errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"})
1575 ast.Inspect(body, fnSorts)
1580 allErrors = append(allErrors, errors...)
1582 code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil))
1583 sort.Slice(allErrors, func(i, j int) bool {
1584 return allErrors[i].node.Pos() < allErrors[j].node.Pos()
1587 for _, err := range allErrors {
1588 if err.node.Pos() == prev {
1591 prev = err.node.Pos()
1592 report.Report(pass, err.node, err.msg, report.FilterGenerated())
1597 var checkGuardedDeleteQ = pattern.MustParse(`
1600 [(Ident "_") ok@(Ident _)]
1604 [call@(CallExpr (Builtin "delete") [m key])]
1607 func CheckGuardedDelete(pass *analysis.Pass) (interface{}, error) {
1608 fn := func(node ast.Node) {
1609 if m, ok := Match(pass, checkGuardedDeleteQ, node); ok {
1610 report.Report(pass, node, "unnecessary guard around call to delete",
1611 report.ShortRange(),
1612 report.FilterGenerated(),
1613 report.Fixes(edit.Fix("remove guard", edit.ReplaceWithNode(pass.Fset, node, m.State["call"].(ast.Node)))))
1617 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1622 checkSimplifyTypeSwitchQ = pattern.MustParse(`
1625 expr@(TypeAssertExpr ident@(Ident _) _)
1627 checkSimplifyTypeSwitchR = pattern.MustParse(`(AssignStmt ident ":=" expr)`)
1630 func CheckSimplifyTypeSwitch(pass *analysis.Pass) (interface{}, error) {
1631 fn := func(node ast.Node) {
1632 m, ok := Match(pass, checkSimplifyTypeSwitchQ, node)
1636 stmt := node.(*ast.TypeSwitchStmt)
1637 expr := m.State["expr"].(ast.Node)
1638 ident := m.State["ident"].(*ast.Ident)
1640 x := pass.TypesInfo.ObjectOf(ident)
1641 var allOffenders []*ast.TypeAssertExpr
1642 canSuggestFix := true
1643 for _, clause := range stmt.Body.List {
1644 clause := clause.(*ast.CaseClause)
1645 if len(clause.List) != 1 {
1648 hasUnrelatedAssertion := false
1649 var offenders []*ast.TypeAssertExpr
1650 ast.Inspect(clause, func(node ast.Node) bool {
1651 assert2, ok := node.(*ast.TypeAssertExpr)
1655 ident, ok := assert2.X.(*ast.Ident)
1657 hasUnrelatedAssertion = true
1660 if pass.TypesInfo.ObjectOf(ident) != x {
1661 hasUnrelatedAssertion = true
1665 if !types.Identical(pass.TypesInfo.TypeOf(clause.List[0]), pass.TypesInfo.TypeOf(assert2.Type)) {
1666 hasUnrelatedAssertion = true
1669 offenders = append(offenders, assert2)
1672 if !hasUnrelatedAssertion {
1673 // don't flag cases that have other type assertions
1674 // unrelated to the one in the case clause. often
1675 // times, this is done for symmetry, when two
1676 // different values have to be asserted to the same
1678 allOffenders = append(allOffenders, offenders...)
1680 canSuggestFix = canSuggestFix && !hasUnrelatedAssertion
1682 if len(allOffenders) != 0 {
1683 var opts []report.Option
1684 for _, offender := range allOffenders {
1685 opts = append(opts, report.Related(offender, "could eliminate this type assertion"))
1687 opts = append(opts, report.FilterGenerated())
1689 msg := fmt.Sprintf("assigning the result of this type assertion to a variable (switch %s := %s.(type)) could eliminate type assertions in switch cases",
1690 report.Render(pass, ident), report.Render(pass, ident))
1692 var edits []analysis.TextEdit
1693 edits = append(edits, edit.ReplaceWithPattern(pass, checkSimplifyTypeSwitchR, m.State, expr))
1694 for _, offender := range allOffenders {
1695 edits = append(edits, edit.ReplaceWithNode(pass.Fset, offender, offender.X))
1697 opts = append(opts, report.Fixes(edit.Fix("simplify type switch", edits...)))
1698 report.Report(pass, expr, msg, opts...)
1700 report.Report(pass, expr, msg, opts...)
1704 code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
1708 func CheckRedundantCanonicalHeaderKey(pass *analysis.Pass) (interface{}, error) {
1709 fn := func(node ast.Node) {
1710 call := node.(*ast.CallExpr)
1711 callName := code.CallNameAST(pass, call)
1713 case "(net/http.Header).Add", "(net/http.Header).Del", "(net/http.Header).Get", "(net/http.Header).Set":
1718 if !code.IsCallToAST(pass, call.Args[0], "net/http.CanonicalHeaderKey") {
1722 report.Report(pass, call,
1723 fmt.Sprintf("calling net/http.CanonicalHeaderKey on the 'key' argument of %s is redundant", callName),
1724 report.FilterGenerated(),
1725 report.Fixes(edit.Fix("remove call to CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, call.Args[0], call.Args[0].(*ast.CallExpr).Args[0]))))
1727 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1731 var checkUnnecessaryGuardQ = pattern.MustParse(`
1734 (AssignStmt [(Ident "_") ok@(Ident _)] ":=" indexexpr@(IndexExpr _ _))
1736 set@(AssignStmt indexexpr "=" (CallExpr (Builtin "append") indexexpr:values))
1737 (AssignStmt indexexpr "=" (CompositeLit _ values)))
1739 (AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _))
1741 set@(AssignStmt indexexpr "+=" value)
1742 (AssignStmt indexexpr "=" value))
1744 (AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _))
1746 set@(IncDecStmt indexexpr "++")
1747 (AssignStmt indexexpr "=" (BasicLit "INT" "1"))))`)
1749 func CheckUnnecessaryGuard(pass *analysis.Pass) (interface{}, error) {
1750 fn := func(node ast.Node) {
1751 if m, ok := Match(pass, checkUnnecessaryGuardQ, node); ok {
1752 if code.MayHaveSideEffects(pass, m.State["indexexpr"].(ast.Expr), nil) {
1755 report.Report(pass, node, "unnecessary guard around map access",
1756 report.ShortRange(),
1757 report.Fixes(edit.Fix("simplify map access", edit.ReplaceWithNode(pass.Fset, node, m.State["set"].(ast.Node)))))
1760 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
1765 checkElaborateSleepQ = pattern.MustParse(`(SelectStmt (CommClause (UnaryExpr "<-" (CallExpr (Function "time.After") [arg])) body))`)
1766 checkElaborateSleepR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Sleep")) [arg])`)
1769 func CheckElaborateSleep(pass *analysis.Pass) (interface{}, error) {
1770 fn := func(node ast.Node) {
1771 if m, ok := Match(pass, checkElaborateSleepQ, node); ok {
1772 if body, ok := m.State["body"].([]ast.Stmt); ok && len(body) == 0 {
1773 report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping",
1774 report.ShortRange(),
1775 report.FilterGenerated(),
1776 report.Fixes(edit.Fix("Use time.Sleep", edit.ReplaceWithPattern(pass, checkElaborateSleepR, m.State, node))))
1778 // TODO(dh): we could make a suggested fix if the body
1779 // doesn't declare or shadow any identifiers
1780 report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping",
1781 report.ShortRange(),
1782 report.FilterGenerated())
1786 code.Preorder(pass, fn, (*ast.SelectStmt)(nil))
1790 var checkPrintSprintQ = pattern.MustParse(`
1794 (Function "fmt.Print")
1795 (Function "fmt.Sprint")
1796 (Function "fmt.Println")
1797 (Function "fmt.Sprintln"))
1798 [(CallExpr (Function "fmt.Sprintf") f:_)])
1801 (Function "fmt.Fprint")
1802 (Function "fmt.Fprintln"))
1803 [_ (CallExpr (Function "fmt.Sprintf") f:_)]))`)
1805 func CheckPrintSprintf(pass *analysis.Pass) (interface{}, error) {
1806 fn := func(node ast.Node) {
1807 m, ok := Match(pass, checkPrintSprintQ, node)
1812 name := m.State["fn"].(*types.Func).Name()
1815 case "Print", "Fprint", "Sprint":
1816 newname := name + "f"
1817 msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...))", newname, name)
1818 case "Println", "Fprintln", "Sprintln":
1819 if _, ok := m.State["f"].(*ast.BasicLit); !ok {
1820 // This may be an instance of
1821 // fmt.Println(fmt.Sprintf(arg, ...)) where arg is an
1822 // externally provided format string and the caller
1823 // cannot guarantee that the format string ends with a
1827 newname := name[:len(name)-2] + "f"
1828 msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...)) (but don't forget the newline)", newname, name)
1830 report.Report(pass, node, msg,
1831 report.FilterGenerated())
1833 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
1837 var checkSprintLiteralQ = pattern.MustParse(`
1840 (Function "fmt.Sprint")
1841 (Function "fmt.Sprintf"))
1842 [lit@(BasicLit "STRING" _)])`)
1844 func CheckSprintLiteral(pass *analysis.Pass) (interface{}, error) {
1845 // We only flag calls with string literals, not expressions of
1846 // type string, because some people use fmt.Sprint(s) as a pattern
1847 // for copying strings, which may be useful when extracing a small
1848 // substring from a large string.
1849 fn := func(node ast.Node) {
1850 m, ok := Match(pass, checkSprintLiteralQ, node)
1854 callee := m.State["fn"].(*types.Func)
1855 lit := m.State["lit"].(*ast.BasicLit)
1856 if callee.Name() == "Sprintf" {
1857 if strings.ContainsRune(lit.Value, '%') {
1858 // This might be a format string
1862 report.Report(pass, node, fmt.Sprintf("unnecessary use of fmt.%s", callee.Name()),
1863 report.FilterGenerated(),
1864 report.Fixes(edit.Fix("Replace with string literal", edit.ReplaceWithNode(pass.Fset, node, lit))))
1866 code.Preorder(pass, fn, (*ast.CallExpr)(nil))