github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/simple/lint.go (about) 1 // Package simple contains a linter for Go source code. 2 package simple // import "github.com/golangci/go-tools/simple" 3 4 import ( 5 "go/ast" 6 "go/constant" 7 "go/token" 8 "go/types" 9 "reflect" 10 "strings" 11 12 . "github.com/golangci/go-tools/arg" 13 "github.com/golangci/go-tools/internal/sharedcheck" 14 "github.com/golangci/go-tools/lint" 15 . "github.com/golangci/go-tools/lint/lintdsl" 16 17 "golang.org/x/tools/go/types/typeutil" 18 ) 19 20 type Checker struct { 21 CheckGenerated bool 22 MS *typeutil.MethodSetCache 23 } 24 25 func NewChecker() *Checker { 26 return &Checker{ 27 MS: &typeutil.MethodSetCache{}, 28 } 29 } 30 31 func (*Checker) Name() string { return "gosimple" } 32 func (*Checker) Prefix() string { return "S" } 33 34 func (c *Checker) Init(prog *lint.Program) {} 35 36 func (c *Checker) Checks() []lint.Check { 37 return []lint.Check{ 38 {ID: "S1000", FilterGenerated: true, Fn: c.LintSingleCaseSelect}, 39 {ID: "S1001", FilterGenerated: true, Fn: c.LintLoopCopy}, 40 {ID: "S1002", FilterGenerated: true, Fn: c.LintIfBoolCmp}, 41 {ID: "S1003", FilterGenerated: true, Fn: c.LintStringsContains}, 42 {ID: "S1004", FilterGenerated: true, Fn: c.LintBytesCompare}, 43 {ID: "S1005", FilterGenerated: true, Fn: c.LintUnnecessaryBlank}, 44 {ID: "S1006", FilterGenerated: true, Fn: c.LintForTrue}, 45 {ID: "S1007", FilterGenerated: true, Fn: c.LintRegexpRaw}, 46 {ID: "S1008", FilterGenerated: true, Fn: c.LintIfReturn}, 47 {ID: "S1009", FilterGenerated: true, Fn: c.LintRedundantNilCheckWithLen}, 48 {ID: "S1010", FilterGenerated: true, Fn: c.LintSlicing}, 49 {ID: "S1011", FilterGenerated: true, Fn: c.LintLoopAppend}, 50 {ID: "S1012", FilterGenerated: true, Fn: c.LintTimeSince}, 51 {ID: "S1016", FilterGenerated: true, Fn: c.LintSimplerStructConversion}, 52 {ID: "S1017", FilterGenerated: true, Fn: c.LintTrim}, 53 {ID: "S1018", FilterGenerated: true, Fn: c.LintLoopSlide}, 54 {ID: "S1019", FilterGenerated: true, Fn: c.LintMakeLenCap}, 55 {ID: "S1020", FilterGenerated: true, Fn: c.LintAssertNotNil}, 56 {ID: "S1021", FilterGenerated: true, Fn: c.LintDeclareAssign}, 57 {ID: "S1023", FilterGenerated: true, Fn: c.LintRedundantBreak}, 58 {ID: "S1024", FilterGenerated: true, Fn: c.LintTimeUntil}, 59 {ID: "S1025", FilterGenerated: true, Fn: c.LintRedundantSprintf}, 60 {ID: "S1028", FilterGenerated: true, Fn: c.LintErrorsNewSprintf}, 61 {ID: "S1029", FilterGenerated: false, Fn: c.LintRangeStringRunes}, 62 {ID: "S1030", FilterGenerated: true, Fn: c.LintBytesBufferConversions}, 63 {ID: "S1031", FilterGenerated: true, Fn: c.LintNilCheckAroundRange}, 64 {ID: "S1032", FilterGenerated: true, Fn: c.LintSortHelpers}, 65 } 66 } 67 68 func (c *Checker) LintSingleCaseSelect(j *lint.Job) { 69 isSingleSelect := func(node ast.Node) bool { 70 v, ok := node.(*ast.SelectStmt) 71 if !ok { 72 return false 73 } 74 return len(v.Body.List) == 1 75 } 76 77 seen := map[ast.Node]struct{}{} 78 fn := func(node ast.Node) bool { 79 switch v := node.(type) { 80 case *ast.ForStmt: 81 if len(v.Body.List) != 1 { 82 return true 83 } 84 if !isSingleSelect(v.Body.List[0]) { 85 return true 86 } 87 if _, ok := v.Body.List[0].(*ast.SelectStmt).Body.List[0].(*ast.CommClause).Comm.(*ast.SendStmt); ok { 88 // Don't suggest using range for channel sends 89 return true 90 } 91 seen[v.Body.List[0]] = struct{}{} 92 j.Errorf(node, "should use for range instead of for { select {} }") 93 case *ast.SelectStmt: 94 if _, ok := seen[v]; ok { 95 return true 96 } 97 if !isSingleSelect(v) { 98 return true 99 } 100 j.Errorf(node, "should use a simple channel send/receive instead of select with a single case") 101 return true 102 } 103 return true 104 } 105 for _, f := range j.Program.Files { 106 ast.Inspect(f, fn) 107 } 108 } 109 110 func (c *Checker) LintLoopCopy(j *lint.Job) { 111 fn := func(node ast.Node) bool { 112 loop, ok := node.(*ast.RangeStmt) 113 if !ok { 114 return true 115 } 116 117 if loop.Key == nil { 118 return true 119 } 120 if len(loop.Body.List) != 1 { 121 return true 122 } 123 stmt, ok := loop.Body.List[0].(*ast.AssignStmt) 124 if !ok { 125 return true 126 } 127 if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 { 128 return true 129 } 130 lhs, ok := stmt.Lhs[0].(*ast.IndexExpr) 131 if !ok { 132 return true 133 } 134 135 if _, ok := TypeOf(j, lhs.X).(*types.Slice); !ok { 136 return true 137 } 138 lidx, ok := lhs.Index.(*ast.Ident) 139 if !ok { 140 return true 141 } 142 key, ok := loop.Key.(*ast.Ident) 143 if !ok { 144 return true 145 } 146 if TypeOf(j, lhs) == nil || TypeOf(j, stmt.Rhs[0]) == nil { 147 return true 148 } 149 if ObjectOf(j, lidx) != ObjectOf(j, key) { 150 return true 151 } 152 if !types.Identical(TypeOf(j, lhs), TypeOf(j, stmt.Rhs[0])) { 153 return true 154 } 155 if _, ok := TypeOf(j, loop.X).(*types.Slice); !ok { 156 return true 157 } 158 159 if rhs, ok := stmt.Rhs[0].(*ast.IndexExpr); ok { 160 rx, ok := rhs.X.(*ast.Ident) 161 _ = rx 162 if !ok { 163 return true 164 } 165 ridx, ok := rhs.Index.(*ast.Ident) 166 if !ok { 167 return true 168 } 169 if ObjectOf(j, ridx) != ObjectOf(j, key) { 170 return true 171 } 172 } else if rhs, ok := stmt.Rhs[0].(*ast.Ident); ok { 173 value, ok := loop.Value.(*ast.Ident) 174 if !ok { 175 return true 176 } 177 if ObjectOf(j, rhs) != ObjectOf(j, value) { 178 return true 179 } 180 } else { 181 return true 182 } 183 j.Errorf(loop, "should use copy() instead of a loop") 184 return true 185 } 186 for _, f := range j.Program.Files { 187 ast.Inspect(f, fn) 188 } 189 } 190 191 func (c *Checker) LintIfBoolCmp(j *lint.Job) { 192 fn := func(node ast.Node) bool { 193 expr, ok := node.(*ast.BinaryExpr) 194 if !ok || (expr.Op != token.EQL && expr.Op != token.NEQ) { 195 return true 196 } 197 x := IsBoolConst(j, expr.X) 198 y := IsBoolConst(j, expr.Y) 199 if !x && !y { 200 return true 201 } 202 var other ast.Expr 203 var val bool 204 if x { 205 val = BoolConst(j, expr.X) 206 other = expr.Y 207 } else { 208 val = BoolConst(j, expr.Y) 209 other = expr.X 210 } 211 basic, ok := TypeOf(j, other).Underlying().(*types.Basic) 212 if !ok || basic.Kind() != types.Bool { 213 return true 214 } 215 op := "" 216 if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) { 217 op = "!" 218 } 219 r := op + Render(j, other) 220 l1 := len(r) 221 r = strings.TrimLeft(r, "!") 222 if (l1-len(r))%2 == 1 { 223 r = "!" + r 224 } 225 if IsInTest(j, node) { 226 return true 227 } 228 j.Errorf(expr, "should omit comparison to bool constant, can be simplified to %s", r) 229 return true 230 } 231 for _, f := range j.Program.Files { 232 ast.Inspect(f, fn) 233 } 234 } 235 236 func (c *Checker) LintBytesBufferConversions(j *lint.Job) { 237 fn := func(node ast.Node) bool { 238 call, ok := node.(*ast.CallExpr) 239 if !ok || len(call.Args) != 1 { 240 return true 241 } 242 243 argCall, ok := call.Args[0].(*ast.CallExpr) 244 if !ok { 245 return true 246 } 247 sel, ok := argCall.Fun.(*ast.SelectorExpr) 248 if !ok { 249 return true 250 } 251 252 typ := TypeOf(j, call.Fun) 253 if typ == types.Universe.Lookup("string").Type() && IsCallToAST(j, call.Args[0], "(*bytes.Buffer).Bytes") { 254 j.Errorf(call, "should use %v.String() instead of %v", Render(j, sel.X), Render(j, call)) 255 } else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && IsCallToAST(j, call.Args[0], "(*bytes.Buffer).String") { 256 j.Errorf(call, "should use %v.Bytes() instead of %v", Render(j, sel.X), Render(j, call)) 257 } 258 259 return true 260 } 261 for _, f := range j.Program.Files { 262 ast.Inspect(f, fn) 263 } 264 } 265 266 func (c *Checker) LintStringsContains(j *lint.Job) { 267 // map of value to token to bool value 268 allowed := map[int64]map[token.Token]bool{ 269 -1: {token.GTR: true, token.NEQ: true, token.EQL: false}, 270 0: {token.GEQ: true, token.LSS: false}, 271 } 272 fn := func(node ast.Node) bool { 273 expr, ok := node.(*ast.BinaryExpr) 274 if !ok { 275 return true 276 } 277 switch expr.Op { 278 case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL: 279 default: 280 return true 281 } 282 283 value, ok := ExprToInt(j, expr.Y) 284 if !ok { 285 return true 286 } 287 288 allowedOps, ok := allowed[value] 289 if !ok { 290 return true 291 } 292 b, ok := allowedOps[expr.Op] 293 if !ok { 294 return true 295 } 296 297 call, ok := expr.X.(*ast.CallExpr) 298 if !ok { 299 return true 300 } 301 sel, ok := call.Fun.(*ast.SelectorExpr) 302 if !ok { 303 return true 304 } 305 pkgIdent, ok := sel.X.(*ast.Ident) 306 if !ok { 307 return true 308 } 309 funIdent := sel.Sel 310 if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" { 311 return true 312 } 313 newFunc := "" 314 switch funIdent.Name { 315 case "IndexRune": 316 newFunc = "ContainsRune" 317 case "IndexAny": 318 newFunc = "ContainsAny" 319 case "Index": 320 newFunc = "Contains" 321 default: 322 return true 323 } 324 325 prefix := "" 326 if !b { 327 prefix = "!" 328 } 329 j.Errorf(node, "should use %s%s.%s(%s) instead", prefix, pkgIdent.Name, newFunc, RenderArgs(j, call.Args)) 330 331 return true 332 } 333 for _, f := range j.Program.Files { 334 ast.Inspect(f, fn) 335 } 336 } 337 338 func (c *Checker) LintBytesCompare(j *lint.Job) { 339 fn := func(node ast.Node) bool { 340 expr, ok := node.(*ast.BinaryExpr) 341 if !ok { 342 return true 343 } 344 if expr.Op != token.NEQ && expr.Op != token.EQL { 345 return true 346 } 347 call, ok := expr.X.(*ast.CallExpr) 348 if !ok { 349 return true 350 } 351 if !IsCallToAST(j, call, "bytes.Compare") { 352 return true 353 } 354 value, ok := ExprToInt(j, expr.Y) 355 if !ok || value != 0 { 356 return true 357 } 358 args := RenderArgs(j, call.Args) 359 prefix := "" 360 if expr.Op == token.NEQ { 361 prefix = "!" 362 } 363 j.Errorf(node, "should use %sbytes.Equal(%s) instead", prefix, args) 364 return true 365 } 366 for _, f := range j.Program.Files { 367 ast.Inspect(f, fn) 368 } 369 } 370 371 func (c *Checker) LintForTrue(j *lint.Job) { 372 fn := func(node ast.Node) bool { 373 loop, ok := node.(*ast.ForStmt) 374 if !ok { 375 return true 376 } 377 if loop.Init != nil || loop.Post != nil { 378 return true 379 } 380 if !IsBoolConst(j, loop.Cond) || !BoolConst(j, loop.Cond) { 381 return true 382 } 383 j.Errorf(loop, "should use for {} instead of for true {}") 384 return true 385 } 386 for _, f := range j.Program.Files { 387 ast.Inspect(f, fn) 388 } 389 } 390 391 func (c *Checker) LintRegexpRaw(j *lint.Job) { 392 fn := func(node ast.Node) bool { 393 call, ok := node.(*ast.CallExpr) 394 if !ok { 395 return true 396 } 397 if !IsCallToAST(j, call, "regexp.MustCompile") && 398 !IsCallToAST(j, call, "regexp.Compile") { 399 return true 400 } 401 sel, ok := call.Fun.(*ast.SelectorExpr) 402 if !ok { 403 return true 404 } 405 if len(call.Args) != 1 { 406 // invalid function call 407 return true 408 } 409 lit, ok := call.Args[Arg("regexp.Compile.expr")].(*ast.BasicLit) 410 if !ok { 411 // TODO(dominikh): support string concat, maybe support constants 412 return true 413 } 414 if lit.Kind != token.STRING { 415 // invalid function call 416 return true 417 } 418 if lit.Value[0] != '"' { 419 // already a raw string 420 return true 421 } 422 val := lit.Value 423 if !strings.Contains(val, `\\`) { 424 return true 425 } 426 if strings.Contains(val, "`") { 427 return true 428 } 429 430 bs := false 431 for _, c := range val { 432 if !bs && c == '\\' { 433 bs = true 434 continue 435 } 436 if bs && c == '\\' { 437 bs = false 438 continue 439 } 440 if bs { 441 // backslash followed by non-backslash -> escape sequence 442 return true 443 } 444 } 445 446 j.Errorf(call, "should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name) 447 return true 448 } 449 for _, f := range j.Program.Files { 450 ast.Inspect(f, fn) 451 } 452 } 453 454 func (c *Checker) LintIfReturn(j *lint.Job) { 455 fn := func(node ast.Node) bool { 456 block, ok := node.(*ast.BlockStmt) 457 if !ok { 458 return true 459 } 460 l := len(block.List) 461 if l < 2 { 462 return true 463 } 464 n1, n2 := block.List[l-2], block.List[l-1] 465 466 if len(block.List) >= 3 { 467 if _, ok := block.List[l-3].(*ast.IfStmt); ok { 468 // Do not flag a series of if statements 469 return true 470 } 471 } 472 // if statement with no init, no else, a single condition 473 // checking an identifier or function call and just a return 474 // statement in the body, that returns a boolean constant 475 ifs, ok := n1.(*ast.IfStmt) 476 if !ok { 477 return true 478 } 479 if ifs.Else != nil || ifs.Init != nil { 480 return true 481 } 482 if len(ifs.Body.List) != 1 { 483 return true 484 } 485 if op, ok := ifs.Cond.(*ast.BinaryExpr); ok { 486 switch op.Op { 487 case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: 488 default: 489 return true 490 } 491 } 492 ret1, ok := ifs.Body.List[0].(*ast.ReturnStmt) 493 if !ok { 494 return true 495 } 496 if len(ret1.Results) != 1 { 497 return true 498 } 499 if !IsBoolConst(j, ret1.Results[0]) { 500 return true 501 } 502 503 ret2, ok := n2.(*ast.ReturnStmt) 504 if !ok { 505 return true 506 } 507 if len(ret2.Results) != 1 { 508 return true 509 } 510 if !IsBoolConst(j, ret2.Results[0]) { 511 return true 512 } 513 j.Errorf(n1, "should use 'return <expr>' instead of 'if <expr> { return <bool> }; return <bool>'") 514 return true 515 } 516 for _, f := range j.Program.Files { 517 ast.Inspect(f, fn) 518 } 519 } 520 521 // LintRedundantNilCheckWithLen checks for the following reduntant nil-checks: 522 // 523 // if x == nil || len(x) == 0 {} 524 // if x != nil && len(x) != 0 {} 525 // if x != nil && len(x) == N {} (where N != 0) 526 // if x != nil && len(x) > N {} 527 // if x != nil && len(x) >= N {} (where N != 0) 528 // 529 func (c *Checker) LintRedundantNilCheckWithLen(j *lint.Job) { 530 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) { 531 _, ok := expr.(*ast.BasicLit) 532 if ok { 533 return true, IsZero(expr) 534 } 535 id, ok := expr.(*ast.Ident) 536 if !ok { 537 return false, false 538 } 539 c, ok := ObjectOf(j, id).(*types.Const) 540 if !ok { 541 return false, false 542 } 543 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0" 544 } 545 546 fn := func(node ast.Node) bool { 547 // check that expr is "x || y" or "x && y" 548 expr, ok := node.(*ast.BinaryExpr) 549 if !ok { 550 return true 551 } 552 if expr.Op != token.LOR && expr.Op != token.LAND { 553 return true 554 } 555 eqNil := expr.Op == token.LOR 556 557 // check that x is "xx == nil" or "xx != nil" 558 x, ok := expr.X.(*ast.BinaryExpr) 559 if !ok { 560 return true 561 } 562 if eqNil && x.Op != token.EQL { 563 return true 564 } 565 if !eqNil && x.Op != token.NEQ { 566 return true 567 } 568 xx, ok := x.X.(*ast.Ident) 569 if !ok { 570 return true 571 } 572 if !IsNil(j, x.Y) { 573 return true 574 } 575 576 // check that y is "len(xx) == 0" or "len(xx) ... " 577 y, ok := expr.Y.(*ast.BinaryExpr) 578 if !ok { 579 return true 580 } 581 if eqNil && y.Op != token.EQL { // must be len(xx) *==* 0 582 return false 583 } 584 yx, ok := y.X.(*ast.CallExpr) 585 if !ok { 586 return true 587 } 588 yxFun, ok := yx.Fun.(*ast.Ident) 589 if !ok || yxFun.Name != "len" || len(yx.Args) != 1 { 590 return true 591 } 592 yxArg, ok := yx.Args[Arg("len.v")].(*ast.Ident) 593 if !ok { 594 return true 595 } 596 if yxArg.Name != xx.Name { 597 return true 598 } 599 600 if eqNil && !IsZero(y.Y) { // must be len(x) == *0* 601 return true 602 } 603 604 if !eqNil { 605 isConst, isZero := isConstZero(y.Y) 606 if !isConst { 607 return true 608 } 609 switch y.Op { 610 case token.EQL: 611 // avoid false positive for "xx != nil && len(xx) == 0" 612 if isZero { 613 return true 614 } 615 case token.GEQ: 616 // avoid false positive for "xx != nil && len(xx) >= 0" 617 if isZero { 618 return true 619 } 620 case token.NEQ: 621 // avoid false positive for "xx != nil && len(xx) != <non-zero>" 622 if !isZero { 623 return true 624 } 625 case token.GTR: 626 // ok 627 default: 628 return true 629 } 630 } 631 632 // finally check that xx type is one of array, slice, map or chan 633 // this is to prevent false positive in case if xx is a pointer to an array 634 var nilType string 635 switch TypeOf(j, xx).(type) { 636 case *types.Slice: 637 nilType = "nil slices" 638 case *types.Map: 639 nilType = "nil maps" 640 case *types.Chan: 641 nilType = "nil channels" 642 default: 643 return true 644 } 645 j.Errorf(expr, "should omit nil check; len() for %s is defined as zero", nilType) 646 return true 647 } 648 for _, f := range j.Program.Files { 649 ast.Inspect(f, fn) 650 } 651 } 652 653 func (c *Checker) LintSlicing(j *lint.Job) { 654 fn := func(node ast.Node) bool { 655 n, ok := node.(*ast.SliceExpr) 656 if !ok { 657 return true 658 } 659 if n.Max != nil { 660 return true 661 } 662 s, ok := n.X.(*ast.Ident) 663 if !ok || s.Obj == nil { 664 return true 665 } 666 call, ok := n.High.(*ast.CallExpr) 667 if !ok || len(call.Args) != 1 || call.Ellipsis.IsValid() { 668 return true 669 } 670 fun, ok := call.Fun.(*ast.Ident) 671 if !ok || fun.Name != "len" { 672 return true 673 } 674 if _, ok := ObjectOf(j, fun).(*types.Builtin); !ok { 675 return true 676 } 677 arg, ok := call.Args[Arg("len.v")].(*ast.Ident) 678 if !ok || arg.Obj != s.Obj { 679 return true 680 } 681 j.Errorf(n, "should omit second index in slice, s[a:len(s)] is identical to s[a:]") 682 return true 683 } 684 for _, f := range j.Program.Files { 685 ast.Inspect(f, fn) 686 } 687 } 688 689 func refersTo(j *lint.Job, expr ast.Expr, ident *ast.Ident) bool { 690 found := false 691 fn := func(node ast.Node) bool { 692 ident2, ok := node.(*ast.Ident) 693 if !ok { 694 return true 695 } 696 if ObjectOf(j, ident) == ObjectOf(j, ident2) { 697 found = true 698 return false 699 } 700 return true 701 } 702 ast.Inspect(expr, fn) 703 return found 704 } 705 706 func (c *Checker) LintLoopAppend(j *lint.Job) { 707 fn := func(node ast.Node) bool { 708 loop, ok := node.(*ast.RangeStmt) 709 if !ok { 710 return true 711 } 712 if !IsBlank(loop.Key) { 713 return true 714 } 715 val, ok := loop.Value.(*ast.Ident) 716 if !ok { 717 return true 718 } 719 if len(loop.Body.List) != 1 { 720 return true 721 } 722 stmt, ok := loop.Body.List[0].(*ast.AssignStmt) 723 if !ok { 724 return true 725 } 726 if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 { 727 return true 728 } 729 if refersTo(j, stmt.Lhs[0], val) { 730 return true 731 } 732 call, ok := stmt.Rhs[0].(*ast.CallExpr) 733 if !ok { 734 return true 735 } 736 if len(call.Args) != 2 || call.Ellipsis.IsValid() { 737 return true 738 } 739 fun, ok := call.Fun.(*ast.Ident) 740 if !ok { 741 return true 742 } 743 obj := ObjectOf(j, fun) 744 fn, ok := obj.(*types.Builtin) 745 if !ok || fn.Name() != "append" { 746 return true 747 } 748 749 src := TypeOf(j, loop.X) 750 dst := TypeOf(j, call.Args[Arg("append.slice")]) 751 // TODO(dominikh) remove nil check once Go issue #15173 has 752 // been fixed 753 if src == nil { 754 return true 755 } 756 if !types.Identical(src, dst) { 757 return true 758 } 759 760 if Render(j, stmt.Lhs[0]) != Render(j, call.Args[Arg("append.slice")]) { 761 return true 762 } 763 764 el, ok := call.Args[Arg("append.elems")].(*ast.Ident) 765 if !ok { 766 return true 767 } 768 if ObjectOf(j, val) != ObjectOf(j, el) { 769 return true 770 } 771 j.Errorf(loop, "should replace loop with %s = append(%s, %s...)", 772 Render(j, stmt.Lhs[0]), Render(j, call.Args[Arg("append.slice")]), Render(j, loop.X)) 773 return true 774 } 775 for _, f := range j.Program.Files { 776 ast.Inspect(f, fn) 777 } 778 } 779 780 func (c *Checker) LintTimeSince(j *lint.Job) { 781 fn := func(node ast.Node) bool { 782 call, ok := node.(*ast.CallExpr) 783 if !ok { 784 return true 785 } 786 sel, ok := call.Fun.(*ast.SelectorExpr) 787 if !ok { 788 return true 789 } 790 if !IsCallToAST(j, sel.X, "time.Now") { 791 return true 792 } 793 if sel.Sel.Name != "Sub" { 794 return true 795 } 796 j.Errorf(call, "should use time.Since instead of time.Now().Sub") 797 return true 798 } 799 for _, f := range j.Program.Files { 800 ast.Inspect(f, fn) 801 } 802 } 803 804 func (c *Checker) LintTimeUntil(j *lint.Job) { 805 if !IsGoVersion(j, 8) { 806 return 807 } 808 fn := func(node ast.Node) bool { 809 call, ok := node.(*ast.CallExpr) 810 if !ok { 811 return true 812 } 813 if !IsCallToAST(j, call, "(time.Time).Sub") { 814 return true 815 } 816 if !IsCallToAST(j, call.Args[Arg("(time.Time).Sub.u")], "time.Now") { 817 return true 818 } 819 j.Errorf(call, "should use time.Until instead of t.Sub(time.Now())") 820 return true 821 } 822 for _, f := range j.Program.Files { 823 ast.Inspect(f, fn) 824 } 825 } 826 827 func (c *Checker) LintUnnecessaryBlank(j *lint.Job) { 828 fn1 := func(node ast.Node) { 829 assign, ok := node.(*ast.AssignStmt) 830 if !ok { 831 return 832 } 833 if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 { 834 return 835 } 836 if !IsBlank(assign.Lhs[1]) { 837 return 838 } 839 switch rhs := assign.Rhs[0].(type) { 840 case *ast.IndexExpr: 841 // The type-checker should make sure that it's a map, but 842 // let's be safe. 843 if _, ok := TypeOf(j, rhs.X).Underlying().(*types.Map); !ok { 844 return 845 } 846 case *ast.UnaryExpr: 847 if rhs.Op != token.ARROW { 848 return 849 } 850 default: 851 return 852 } 853 cp := *assign 854 cp.Lhs = cp.Lhs[0:1] 855 j.Errorf(assign, "should write %s instead of %s", Render(j, &cp), Render(j, assign)) 856 } 857 858 fn2 := func(node ast.Node) { 859 stmt, ok := node.(*ast.AssignStmt) 860 if !ok { 861 return 862 } 863 if len(stmt.Lhs) != len(stmt.Rhs) { 864 return 865 } 866 for i, lh := range stmt.Lhs { 867 rh := stmt.Rhs[i] 868 if !IsBlank(lh) { 869 continue 870 } 871 expr, ok := rh.(*ast.UnaryExpr) 872 if !ok { 873 continue 874 } 875 if expr.Op != token.ARROW { 876 continue 877 } 878 j.Errorf(lh, "'_ = <-ch' can be simplified to '<-ch'") 879 } 880 } 881 882 fn3 := func(node ast.Node) { 883 rs, ok := node.(*ast.RangeStmt) 884 if !ok { 885 return 886 } 887 888 // for x, _ 889 if !IsBlank(rs.Key) && IsBlank(rs.Value) { 890 j.Errorf(rs.Value, "should omit value from range; this loop is equivalent to `for %s %s range ...`", Render(j, rs.Key), rs.Tok) 891 } 892 // for _, _ || for _ 893 if IsBlank(rs.Key) && (IsBlank(rs.Value) || rs.Value == nil) { 894 j.Errorf(rs.Key, "should omit values from range; this loop is equivalent to `for range ...`") 895 } 896 } 897 898 fn := func(node ast.Node) bool { 899 fn1(node) 900 fn2(node) 901 if IsGoVersion(j, 4) { 902 fn3(node) 903 } 904 return true 905 } 906 for _, f := range j.Program.Files { 907 ast.Inspect(f, fn) 908 } 909 } 910 911 func (c *Checker) LintSimplerStructConversion(j *lint.Job) { 912 var skip ast.Node 913 fn := func(node ast.Node) bool { 914 // Do not suggest type conversion between pointers 915 if unary, ok := node.(*ast.UnaryExpr); ok && unary.Op == token.AND { 916 if lit, ok := unary.X.(*ast.CompositeLit); ok { 917 skip = lit 918 } 919 return true 920 } 921 922 if node == skip { 923 return true 924 } 925 926 lit, ok := node.(*ast.CompositeLit) 927 if !ok { 928 return true 929 } 930 typ1, _ := TypeOf(j, lit.Type).(*types.Named) 931 if typ1 == nil { 932 return true 933 } 934 s1, ok := typ1.Underlying().(*types.Struct) 935 if !ok { 936 return true 937 } 938 939 var typ2 *types.Named 940 var ident *ast.Ident 941 getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) { 942 sel, ok := expr.(*ast.SelectorExpr) 943 if !ok { 944 return nil, nil, false 945 } 946 ident, ok := sel.X.(*ast.Ident) 947 if !ok { 948 return nil, nil, false 949 } 950 typ := TypeOf(j, sel.X) 951 return typ, ident, typ != nil 952 } 953 if len(lit.Elts) == 0 { 954 return true 955 } 956 if s1.NumFields() != len(lit.Elts) { 957 return true 958 } 959 for i, elt := range lit.Elts { 960 var t types.Type 961 var id *ast.Ident 962 var ok bool 963 switch elt := elt.(type) { 964 case *ast.SelectorExpr: 965 t, id, ok = getSelType(elt) 966 if !ok { 967 return true 968 } 969 if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name { 970 return true 971 } 972 case *ast.KeyValueExpr: 973 var sel *ast.SelectorExpr 974 sel, ok = elt.Value.(*ast.SelectorExpr) 975 if !ok { 976 return true 977 } 978 979 if elt.Key.(*ast.Ident).Name != sel.Sel.Name { 980 return true 981 } 982 t, id, ok = getSelType(elt.Value) 983 } 984 if !ok { 985 return true 986 } 987 // All fields must be initialized from the same object 988 if ident != nil && ident.Obj != id.Obj { 989 return true 990 } 991 typ2, _ = t.(*types.Named) 992 if typ2 == nil { 993 return true 994 } 995 ident = id 996 } 997 998 if typ2 == nil { 999 return true 1000 } 1001 1002 if typ1.Obj().Pkg() != typ2.Obj().Pkg() { 1003 // Do not suggest type conversions between different 1004 // packages. Types in different packages might only match 1005 // by coincidence. Furthermore, if the dependency ever 1006 // adds more fields to its type, it could break the code 1007 // that relies on the type conversion to work. 1008 return true 1009 } 1010 1011 s2, ok := typ2.Underlying().(*types.Struct) 1012 if !ok { 1013 return true 1014 } 1015 if typ1 == typ2 { 1016 return true 1017 } 1018 if IsGoVersion(j, 8) { 1019 if !types.IdenticalIgnoreTags(s1, s2) { 1020 return true 1021 } 1022 } else { 1023 if !types.Identical(s1, s2) { 1024 return true 1025 } 1026 } 1027 j.Errorf(node, "should convert %s (type %s) to %s instead of using struct literal", 1028 ident.Name, typ2.Obj().Name(), typ1.Obj().Name()) 1029 return true 1030 } 1031 for _, f := range j.Program.Files { 1032 ast.Inspect(f, fn) 1033 } 1034 } 1035 1036 func (c *Checker) LintTrim(j *lint.Job) { 1037 sameNonDynamic := func(node1, node2 ast.Node) bool { 1038 if reflect.TypeOf(node1) != reflect.TypeOf(node2) { 1039 return false 1040 } 1041 1042 switch node1 := node1.(type) { 1043 case *ast.Ident: 1044 return node1.Obj == node2.(*ast.Ident).Obj 1045 case *ast.SelectorExpr: 1046 return Render(j, node1) == Render(j, node2) 1047 case *ast.IndexExpr: 1048 return Render(j, node1) == Render(j, node2) 1049 } 1050 return false 1051 } 1052 1053 isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool { 1054 call, ok := fn.(*ast.CallExpr) 1055 if !ok { 1056 return false 1057 } 1058 if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "len" { 1059 return false 1060 } 1061 if len(call.Args) != 1 { 1062 return false 1063 } 1064 return sameNonDynamic(call.Args[Arg("len.v")], ident) 1065 } 1066 1067 fn := func(node ast.Node) bool { 1068 var pkg string 1069 var fun string 1070 1071 ifstmt, ok := node.(*ast.IfStmt) 1072 if !ok { 1073 return true 1074 } 1075 if ifstmt.Init != nil { 1076 return true 1077 } 1078 if ifstmt.Else != nil { 1079 return true 1080 } 1081 if len(ifstmt.Body.List) != 1 { 1082 return true 1083 } 1084 condCall, ok := ifstmt.Cond.(*ast.CallExpr) 1085 if !ok { 1086 return true 1087 } 1088 call, ok := condCall.Fun.(*ast.SelectorExpr) 1089 if !ok { 1090 return true 1091 } 1092 if IsIdent(call.X, "strings") { 1093 pkg = "strings" 1094 } else if IsIdent(call.X, "bytes") { 1095 pkg = "bytes" 1096 } else { 1097 return true 1098 } 1099 if IsIdent(call.Sel, "HasPrefix") { 1100 fun = "HasPrefix" 1101 } else if IsIdent(call.Sel, "HasSuffix") { 1102 fun = "HasSuffix" 1103 } else { 1104 return true 1105 } 1106 1107 assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt) 1108 if !ok { 1109 return true 1110 } 1111 if assign.Tok != token.ASSIGN { 1112 return true 1113 } 1114 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { 1115 return true 1116 } 1117 if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) { 1118 return true 1119 } 1120 slice, ok := assign.Rhs[0].(*ast.SliceExpr) 1121 if !ok { 1122 return true 1123 } 1124 if slice.Slice3 { 1125 return true 1126 } 1127 if !sameNonDynamic(slice.X, condCall.Args[0]) { 1128 return true 1129 } 1130 var index ast.Expr 1131 switch fun { 1132 case "HasPrefix": 1133 // TODO(dh) We could detect a High that is len(s), but another 1134 // rule will already flag that, anyway. 1135 if slice.High != nil { 1136 return true 1137 } 1138 index = slice.Low 1139 case "HasSuffix": 1140 if slice.Low != nil { 1141 n, ok := ExprToInt(j, slice.Low) 1142 if !ok || n != 0 { 1143 return true 1144 } 1145 } 1146 index = slice.High 1147 } 1148 1149 switch index := index.(type) { 1150 case *ast.CallExpr: 1151 if fun != "HasPrefix" { 1152 return true 1153 } 1154 if fn, ok := index.Fun.(*ast.Ident); !ok || fn.Name != "len" { 1155 return true 1156 } 1157 if len(index.Args) != 1 { 1158 return true 1159 } 1160 id3 := index.Args[Arg("len.v")] 1161 switch oid3 := condCall.Args[1].(type) { 1162 case *ast.BasicLit: 1163 if pkg != "strings" { 1164 return false 1165 } 1166 lit, ok := id3.(*ast.BasicLit) 1167 if !ok { 1168 return true 1169 } 1170 s1, ok1 := ExprToString(j, lit) 1171 s2, ok2 := ExprToString(j, condCall.Args[1]) 1172 if !ok1 || !ok2 || s1 != s2 { 1173 return true 1174 } 1175 default: 1176 if !sameNonDynamic(id3, oid3) { 1177 return true 1178 } 1179 } 1180 case *ast.BasicLit, *ast.Ident: 1181 if fun != "HasPrefix" { 1182 return true 1183 } 1184 if pkg != "strings" { 1185 return true 1186 } 1187 string, ok1 := ExprToString(j, condCall.Args[1]) 1188 int, ok2 := ExprToInt(j, slice.Low) 1189 if !ok1 || !ok2 || int != int64(len(string)) { 1190 return true 1191 } 1192 case *ast.BinaryExpr: 1193 if fun != "HasSuffix" { 1194 return true 1195 } 1196 if index.Op != token.SUB { 1197 return true 1198 } 1199 if !isLenOnIdent(index.X, condCall.Args[0]) || 1200 !isLenOnIdent(index.Y, condCall.Args[1]) { 1201 return true 1202 } 1203 default: 1204 return true 1205 } 1206 1207 var replacement string 1208 switch fun { 1209 case "HasPrefix": 1210 replacement = "TrimPrefix" 1211 case "HasSuffix": 1212 replacement = "TrimSuffix" 1213 } 1214 j.Errorf(ifstmt, "should replace this if statement with an unconditional %s.%s", pkg, replacement) 1215 return true 1216 } 1217 for _, f := range j.Program.Files { 1218 ast.Inspect(f, fn) 1219 } 1220 } 1221 1222 func (c *Checker) LintLoopSlide(j *lint.Job) { 1223 // TODO(dh): detect bs[i+offset] in addition to bs[offset+i] 1224 // TODO(dh): consider merging this function with LintLoopCopy 1225 // TODO(dh): detect length that is an expression, not a variable name 1226 // TODO(dh): support sliding to a different offset than the beginning of the slice 1227 1228 fn := func(node ast.Node) bool { 1229 /* 1230 for i := 0; i < n; i++ { 1231 bs[i] = bs[offset+i] 1232 } 1233 1234 ↓ 1235 1236 copy(bs[:n], bs[offset:offset+n]) 1237 */ 1238 1239 loop, ok := node.(*ast.ForStmt) 1240 if !ok || len(loop.Body.List) != 1 || loop.Init == nil || loop.Cond == nil || loop.Post == nil { 1241 return true 1242 } 1243 assign, ok := loop.Init.(*ast.AssignStmt) 1244 if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 || !IsZero(assign.Rhs[0]) { 1245 return true 1246 } 1247 initvar, ok := assign.Lhs[0].(*ast.Ident) 1248 if !ok { 1249 return true 1250 } 1251 post, ok := loop.Post.(*ast.IncDecStmt) 1252 if !ok || post.Tok != token.INC { 1253 return true 1254 } 1255 postvar, ok := post.X.(*ast.Ident) 1256 if !ok || ObjectOf(j, postvar) != ObjectOf(j, initvar) { 1257 return true 1258 } 1259 bin, ok := loop.Cond.(*ast.BinaryExpr) 1260 if !ok || bin.Op != token.LSS { 1261 return true 1262 } 1263 binx, ok := bin.X.(*ast.Ident) 1264 if !ok || ObjectOf(j, binx) != ObjectOf(j, initvar) { 1265 return true 1266 } 1267 biny, ok := bin.Y.(*ast.Ident) 1268 if !ok { 1269 return true 1270 } 1271 1272 assign, ok = loop.Body.List[0].(*ast.AssignStmt) 1273 if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 || assign.Tok != token.ASSIGN { 1274 return true 1275 } 1276 lhs, ok := assign.Lhs[0].(*ast.IndexExpr) 1277 if !ok { 1278 return true 1279 } 1280 rhs, ok := assign.Rhs[0].(*ast.IndexExpr) 1281 if !ok { 1282 return true 1283 } 1284 1285 bs1, ok := lhs.X.(*ast.Ident) 1286 if !ok { 1287 return true 1288 } 1289 bs2, ok := rhs.X.(*ast.Ident) 1290 if !ok { 1291 return true 1292 } 1293 obj1 := ObjectOf(j, bs1) 1294 obj2 := ObjectOf(j, bs2) 1295 if obj1 != obj2 { 1296 return true 1297 } 1298 if _, ok := obj1.Type().Underlying().(*types.Slice); !ok { 1299 return true 1300 } 1301 1302 index1, ok := lhs.Index.(*ast.Ident) 1303 if !ok || ObjectOf(j, index1) != ObjectOf(j, initvar) { 1304 return true 1305 } 1306 index2, ok := rhs.Index.(*ast.BinaryExpr) 1307 if !ok || index2.Op != token.ADD { 1308 return true 1309 } 1310 add1, ok := index2.X.(*ast.Ident) 1311 if !ok { 1312 return true 1313 } 1314 add2, ok := index2.Y.(*ast.Ident) 1315 if !ok || ObjectOf(j, add2) != ObjectOf(j, initvar) { 1316 return true 1317 } 1318 1319 j.Errorf(loop, "should use copy(%s[:%s], %s[%s:]) instead", Render(j, bs1), Render(j, biny), Render(j, bs1), Render(j, add1)) 1320 return true 1321 } 1322 for _, f := range j.Program.Files { 1323 ast.Inspect(f, fn) 1324 } 1325 } 1326 1327 func (c *Checker) LintMakeLenCap(j *lint.Job) { 1328 fn := func(node ast.Node) bool { 1329 call, ok := node.(*ast.CallExpr) 1330 if !ok { 1331 return true 1332 } 1333 if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "make" { 1334 // FIXME check whether make is indeed the built-in function 1335 return true 1336 } 1337 switch len(call.Args) { 1338 case 2: 1339 // make(T, len) 1340 if _, ok := TypeOf(j, call.Args[Arg("make.t")]).Underlying().(*types.Slice); ok { 1341 break 1342 } 1343 if IsZero(call.Args[Arg("make.size[0]")]) { 1344 j.Errorf(call.Args[Arg("make.size[0]")], "should use make(%s) instead", Render(j, call.Args[Arg("make.t")])) 1345 } 1346 case 3: 1347 // make(T, len, cap) 1348 if Render(j, call.Args[Arg("make.size[0]")]) == Render(j, call.Args[Arg("make.size[1]")]) { 1349 j.Errorf(call.Args[Arg("make.size[0]")], 1350 "should use make(%s, %s) instead", 1351 Render(j, call.Args[Arg("make.t")]), Render(j, call.Args[Arg("make.size[0]")])) 1352 } 1353 } 1354 return false 1355 } 1356 for _, f := range j.Program.Files { 1357 ast.Inspect(f, fn) 1358 } 1359 } 1360 1361 func (c *Checker) LintAssertNotNil(j *lint.Job) { 1362 isNilCheck := func(ident *ast.Ident, expr ast.Expr) bool { 1363 xbinop, ok := expr.(*ast.BinaryExpr) 1364 if !ok || xbinop.Op != token.NEQ { 1365 return false 1366 } 1367 xident, ok := xbinop.X.(*ast.Ident) 1368 if !ok || xident.Obj != ident.Obj { 1369 return false 1370 } 1371 if !IsNil(j, xbinop.Y) { 1372 return false 1373 } 1374 return true 1375 } 1376 isOKCheck := func(ident *ast.Ident, expr ast.Expr) bool { 1377 yident, ok := expr.(*ast.Ident) 1378 if !ok || yident.Obj != ident.Obj { 1379 return false 1380 } 1381 return true 1382 } 1383 fn := func(node ast.Node) bool { 1384 ifstmt, ok := node.(*ast.IfStmt) 1385 if !ok { 1386 return true 1387 } 1388 assign, ok := ifstmt.Init.(*ast.AssignStmt) 1389 if !ok || len(assign.Lhs) != 2 || len(assign.Rhs) != 1 || !IsBlank(assign.Lhs[0]) { 1390 return true 1391 } 1392 assert, ok := assign.Rhs[0].(*ast.TypeAssertExpr) 1393 if !ok { 1394 return true 1395 } 1396 binop, ok := ifstmt.Cond.(*ast.BinaryExpr) 1397 if !ok || binop.Op != token.LAND { 1398 return true 1399 } 1400 assertIdent, ok := assert.X.(*ast.Ident) 1401 if !ok { 1402 return true 1403 } 1404 assignIdent, ok := assign.Lhs[1].(*ast.Ident) 1405 if !ok { 1406 return true 1407 } 1408 if !(isNilCheck(assertIdent, binop.X) && isOKCheck(assignIdent, binop.Y)) && 1409 !(isNilCheck(assertIdent, binop.Y) && isOKCheck(assignIdent, binop.X)) { 1410 return true 1411 } 1412 j.Errorf(ifstmt, "when %s is true, %s can't be nil", Render(j, assignIdent), Render(j, assertIdent)) 1413 return true 1414 } 1415 for _, f := range j.Program.Files { 1416 ast.Inspect(f, fn) 1417 } 1418 } 1419 1420 func (c *Checker) LintDeclareAssign(j *lint.Job) { 1421 fn := func(node ast.Node) bool { 1422 block, ok := node.(*ast.BlockStmt) 1423 if !ok { 1424 return true 1425 } 1426 if len(block.List) < 2 { 1427 return true 1428 } 1429 for i, stmt := range block.List[:len(block.List)-1] { 1430 _ = i 1431 decl, ok := stmt.(*ast.DeclStmt) 1432 if !ok { 1433 continue 1434 } 1435 gdecl, ok := decl.Decl.(*ast.GenDecl) 1436 if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 { 1437 continue 1438 } 1439 vspec, ok := gdecl.Specs[0].(*ast.ValueSpec) 1440 if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 { 1441 continue 1442 } 1443 1444 assign, ok := block.List[i+1].(*ast.AssignStmt) 1445 if !ok || assign.Tok != token.ASSIGN { 1446 continue 1447 } 1448 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { 1449 continue 1450 } 1451 ident, ok := assign.Lhs[0].(*ast.Ident) 1452 if !ok { 1453 continue 1454 } 1455 if vspec.Names[0].Obj != ident.Obj { 1456 continue 1457 } 1458 1459 if refersTo(j, assign.Rhs[0], ident) { 1460 continue 1461 } 1462 j.Errorf(decl, "should merge variable declaration with assignment on next line") 1463 } 1464 return true 1465 } 1466 for _, f := range j.Program.Files { 1467 ast.Inspect(f, fn) 1468 } 1469 } 1470 1471 func (c *Checker) LintRedundantBreak(j *lint.Job) { 1472 fn1 := func(node ast.Node) { 1473 clause, ok := node.(*ast.CaseClause) 1474 if !ok { 1475 return 1476 } 1477 if len(clause.Body) < 2 { 1478 return 1479 } 1480 branch, ok := clause.Body[len(clause.Body)-1].(*ast.BranchStmt) 1481 if !ok || branch.Tok != token.BREAK || branch.Label != nil { 1482 return 1483 } 1484 j.Errorf(branch, "redundant break statement") 1485 } 1486 fn2 := func(node ast.Node) { 1487 var ret *ast.FieldList 1488 var body *ast.BlockStmt 1489 switch x := node.(type) { 1490 case *ast.FuncDecl: 1491 ret = x.Type.Results 1492 body = x.Body 1493 case *ast.FuncLit: 1494 ret = x.Type.Results 1495 body = x.Body 1496 default: 1497 return 1498 } 1499 // if the func has results, a return can't be redundant. 1500 // similarly, if there are no statements, there can be 1501 // no return. 1502 if ret != nil || body == nil || len(body.List) < 1 { 1503 return 1504 } 1505 rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt) 1506 if !ok { 1507 return 1508 } 1509 // we don't need to check rst.Results as we already 1510 // checked x.Type.Results to be nil. 1511 j.Errorf(rst, "redundant return statement") 1512 } 1513 fn := func(node ast.Node) bool { 1514 fn1(node) 1515 fn2(node) 1516 return true 1517 } 1518 for _, f := range j.Program.Files { 1519 ast.Inspect(f, fn) 1520 } 1521 } 1522 1523 func (c *Checker) Implements(j *lint.Job, typ types.Type, iface string) bool { 1524 // OPT(dh): we can cache the type lookup 1525 idx := strings.IndexRune(iface, '.') 1526 var scope *types.Scope 1527 var ifaceName string 1528 if idx == -1 { 1529 scope = types.Universe 1530 ifaceName = iface 1531 } else { 1532 pkgName := iface[:idx] 1533 pkg := j.Program.Package(pkgName) 1534 if pkg == nil { 1535 return false 1536 } 1537 scope = pkg.Types.Scope() 1538 ifaceName = iface[idx+1:] 1539 } 1540 1541 obj := scope.Lookup(ifaceName) 1542 if obj == nil { 1543 return false 1544 } 1545 i, ok := obj.Type().Underlying().(*types.Interface) 1546 if !ok { 1547 return false 1548 } 1549 return types.Implements(typ, i) 1550 } 1551 1552 func (c *Checker) LintRedundantSprintf(j *lint.Job) { 1553 fn := func(node ast.Node) bool { 1554 call, ok := node.(*ast.CallExpr) 1555 if !ok { 1556 return true 1557 } 1558 if !IsCallToAST(j, call, "fmt.Sprintf") { 1559 return true 1560 } 1561 if len(call.Args) != 2 { 1562 return true 1563 } 1564 if s, ok := ExprToString(j, call.Args[Arg("fmt.Sprintf.format")]); !ok || s != "%s" { 1565 return true 1566 } 1567 arg := call.Args[Arg("fmt.Sprintf.a[0]")] 1568 typ := TypeOf(j, arg) 1569 1570 if c.Implements(j, typ, "fmt.Stringer") { 1571 j.Errorf(call, "should use String() instead of fmt.Sprintf") 1572 return true 1573 } 1574 1575 if typ.Underlying() == types.Universe.Lookup("string").Type() { 1576 if typ == types.Universe.Lookup("string").Type() { 1577 j.Errorf(call, "the argument is already a string, there's no need to use fmt.Sprintf") 1578 } else { 1579 j.Errorf(call, "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf") 1580 } 1581 } 1582 return true 1583 } 1584 for _, f := range j.Program.Files { 1585 ast.Inspect(f, fn) 1586 } 1587 } 1588 1589 func (c *Checker) LintErrorsNewSprintf(j *lint.Job) { 1590 fn := func(node ast.Node) bool { 1591 if !IsCallToAST(j, node, "errors.New") { 1592 return true 1593 } 1594 call := node.(*ast.CallExpr) 1595 if !IsCallToAST(j, call.Args[Arg("errors.New.text")], "fmt.Sprintf") { 1596 return true 1597 } 1598 j.Errorf(node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))") 1599 return true 1600 } 1601 for _, f := range j.Program.Files { 1602 ast.Inspect(f, fn) 1603 } 1604 } 1605 1606 func (c *Checker) LintRangeStringRunes(j *lint.Job) { 1607 sharedcheck.CheckRangeStringRunes(j) 1608 } 1609 1610 func (c *Checker) LintNilCheckAroundRange(j *lint.Job) { 1611 fn := func(node ast.Node) bool { 1612 ifstmt, ok := node.(*ast.IfStmt) 1613 if !ok { 1614 return true 1615 } 1616 1617 cond, ok := ifstmt.Cond.(*ast.BinaryExpr) 1618 if !ok { 1619 return true 1620 } 1621 1622 if cond.Op != token.NEQ || !IsNil(j, cond.Y) || len(ifstmt.Body.List) != 1 { 1623 return true 1624 } 1625 1626 loop, ok := ifstmt.Body.List[0].(*ast.RangeStmt) 1627 if !ok { 1628 return true 1629 } 1630 ifXIdent, ok := cond.X.(*ast.Ident) 1631 if !ok { 1632 return true 1633 } 1634 rangeXIdent, ok := loop.X.(*ast.Ident) 1635 if !ok { 1636 return true 1637 } 1638 if ifXIdent.Obj != rangeXIdent.Obj { 1639 return true 1640 } 1641 switch TypeOf(j, rangeXIdent).(type) { 1642 case *types.Slice, *types.Map: 1643 j.Errorf(node, "unnecessary nil check around range") 1644 } 1645 return true 1646 } 1647 for _, f := range j.Program.Files { 1648 ast.Inspect(f, fn) 1649 } 1650 } 1651 1652 func isPermissibleSort(j *lint.Job, node ast.Node) bool { 1653 call := node.(*ast.CallExpr) 1654 typeconv, ok := call.Args[0].(*ast.CallExpr) 1655 if !ok { 1656 return true 1657 } 1658 1659 sel, ok := typeconv.Fun.(*ast.SelectorExpr) 1660 if !ok { 1661 return true 1662 } 1663 name := SelectorName(j, sel) 1664 switch name { 1665 case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice": 1666 default: 1667 return true 1668 } 1669 1670 return false 1671 } 1672 1673 func (c *Checker) LintSortHelpers(j *lint.Job) { 1674 fnFuncs := func(node ast.Node) bool { 1675 var body *ast.BlockStmt 1676 switch node := node.(type) { 1677 case *ast.FuncLit: 1678 body = node.Body 1679 case *ast.FuncDecl: 1680 body = node.Body 1681 default: 1682 return true 1683 } 1684 if body == nil { 1685 return true 1686 } 1687 1688 type Error struct { 1689 node lint.Positioner 1690 msg string 1691 } 1692 var errors []Error 1693 permissible := false 1694 fnSorts := func(node ast.Node) bool { 1695 if permissible { 1696 return false 1697 } 1698 if !IsCallToAST(j, node, "sort.Sort") { 1699 return true 1700 } 1701 if isPermissibleSort(j, node) { 1702 permissible = true 1703 return false 1704 } 1705 call := node.(*ast.CallExpr) 1706 typeconv := call.Args[Arg("sort.Sort.data")].(*ast.CallExpr) 1707 sel := typeconv.Fun.(*ast.SelectorExpr) 1708 name := SelectorName(j, sel) 1709 1710 switch name { 1711 case "sort.IntSlice": 1712 errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"}) 1713 case "sort.Float64Slice": 1714 errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"}) 1715 case "sort.StringSlice": 1716 errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"}) 1717 } 1718 return true 1719 } 1720 ast.Inspect(body, fnSorts) 1721 1722 if permissible { 1723 return false 1724 } 1725 for _, err := range errors { 1726 j.Errorf(err.node, "%s", err.msg) 1727 } 1728 return false 1729 } 1730 1731 for _, f := range j.Program.Files { 1732 ast.Inspect(f, fnFuncs) 1733 } 1734 }