github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simplecode/simple/lint.go (about) 1 // Package simple contains a linter for Go source code. 2 package simple // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simplecode/simple" 3 4 import ( 5 "go/ast" 6 "go/constant" 7 "go/token" 8 "go/types" 9 "math" 10 "reflect" 11 "strconv" 12 "strings" 13 14 "github.com/360EntSecGroup-Skylar/goreporter/linters/simplecode/lint" 15 ) 16 17 var Funcs = []lint.Func{ 18 // LintSingleCaseSelect, 19 LintLoopCopy, 20 LintIfBoolCmp, 21 LintStringsContains, 22 LintBytesCompare, 23 LintRanges, 24 LintForTrue, 25 LintRegexpRaw, 26 LintIfReturn, 27 LintRedundantNilCheckWithLen, 28 LintSlicing, 29 LintLoopAppend, 30 LintTimeSince, 31 LintSimplerReturn, 32 LintReceiveIntoBlank, 33 LintFormatInt, 34 LintSimplerStructConversion, 35 LintTrim, 36 } 37 38 func LintSingleCaseSelect(f *lint.File) { 39 isSingleSelect := func(node ast.Node) bool { 40 v, ok := node.(*ast.SelectStmt) 41 if !ok { 42 return false 43 } 44 return len(v.Body.List) == 1 45 } 46 47 seen := map[ast.Node]struct{}{} 48 f.Walk(func(node ast.Node) bool { 49 switch v := node.(type) { 50 case *ast.ForStmt: 51 if len(v.Body.List) != 1 { 52 return true 53 } 54 if !isSingleSelect(v.Body.List[0]) { 55 return true 56 } 57 if _, ok := v.Body.List[0].(*ast.SelectStmt).Body.List[0].(*ast.CommClause).Comm.(*ast.SendStmt); ok { 58 // Don't suggest using range for channel sends 59 return true 60 } 61 seen[v.Body.List[0]] = struct{}{} 62 f.Errorf(node, 1, lint.Category("range-loop"), "should use for range instead of for { select {} }") 63 case *ast.SelectStmt: 64 if _, ok := seen[v]; ok { 65 return true 66 } 67 if !isSingleSelect(v) { 68 return true 69 } 70 f.Errorf(node, 1, lint.Category("FIXME"), "should use a simple channel send/receive instead of select with a single case") 71 return true 72 } 73 return true 74 }) 75 } 76 77 func LintLoopCopy(f *lint.File) { 78 fn := func(node ast.Node) bool { 79 loop, ok := node.(*ast.RangeStmt) 80 if !ok { 81 return true 82 } 83 84 if loop.Key == nil { 85 return true 86 } 87 if len(loop.Body.List) != 1 { 88 return true 89 } 90 stmt, ok := loop.Body.List[0].(*ast.AssignStmt) 91 if !ok { 92 return true 93 } 94 if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 { 95 return true 96 } 97 lhs, ok := stmt.Lhs[0].(*ast.IndexExpr) 98 if !ok { 99 return true 100 } 101 if _, ok := f.Pkg.TypesInfo.TypeOf(lhs.X).(*types.Slice); !ok { 102 return true 103 } 104 lidx, ok := lhs.Index.(*ast.Ident) 105 if !ok { 106 return true 107 } 108 key, ok := loop.Key.(*ast.Ident) 109 if !ok { 110 return true 111 } 112 if f.Pkg.TypesInfo.TypeOf(lhs) == nil || f.Pkg.TypesInfo.TypeOf(stmt.Rhs[0]) == nil { 113 return true 114 } 115 if f.Pkg.TypesInfo.ObjectOf(lidx) != f.Pkg.TypesInfo.ObjectOf(key) { 116 return true 117 } 118 if !types.Identical(f.Pkg.TypesInfo.TypeOf(lhs), f.Pkg.TypesInfo.TypeOf(stmt.Rhs[0])) { 119 return true 120 } 121 if _, ok := f.Pkg.TypesInfo.TypeOf(loop.X).(*types.Slice); !ok { 122 return true 123 } 124 125 if rhs, ok := stmt.Rhs[0].(*ast.IndexExpr); ok { 126 rx, ok := rhs.X.(*ast.Ident) 127 _ = rx 128 if !ok { 129 return true 130 } 131 ridx, ok := rhs.Index.(*ast.Ident) 132 if !ok { 133 return true 134 } 135 if f.Pkg.TypesInfo.ObjectOf(ridx) != f.Pkg.TypesInfo.ObjectOf(key) { 136 return true 137 } 138 } else if rhs, ok := stmt.Rhs[0].(*ast.Ident); ok { 139 value, ok := loop.Value.(*ast.Ident) 140 if !ok { 141 return true 142 } 143 if f.Pkg.TypesInfo.ObjectOf(rhs) != f.Pkg.TypesInfo.ObjectOf(value) { 144 return true 145 } 146 } else { 147 return true 148 } 149 f.Errorf(loop, 1, lint.Category("FIXME"), "should use copy() instead of a loop") 150 return true 151 } 152 f.Walk(fn) 153 } 154 155 func LintIfBoolCmp(f *lint.File) { 156 fn := func(node ast.Node) bool { 157 expr, ok := node.(*ast.BinaryExpr) 158 if !ok || (expr.Op != token.EQL && expr.Op != token.NEQ) { 159 return true 160 } 161 x := f.IsBoolConst(expr.X) 162 y := f.IsBoolConst(expr.Y) 163 if x || y { 164 var other ast.Expr 165 var val bool 166 if x { 167 val = f.BoolConst(expr.X) 168 other = expr.Y 169 } else { 170 val = f.BoolConst(expr.Y) 171 other = expr.X 172 } 173 op := "" 174 if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) { 175 op = "!" 176 } 177 f.Errorf(expr, 1, lint.Category("FIXME"), "should omit comparison to bool constant, can be simplified to %s%s", 178 op, f.Render(other)) 179 } 180 return true 181 } 182 f.Walk(fn) 183 } 184 185 func LintStringsContains(f *lint.File) { 186 // map of value to token to bool value 187 allowed := map[string]map[token.Token]bool{ 188 "-1": {token.GTR: true, token.NEQ: true, token.EQL: false}, 189 "0": {token.GEQ: true, token.LSS: false}, 190 } 191 fn := func(node ast.Node) bool { 192 expr, ok := node.(*ast.BinaryExpr) 193 if !ok { 194 return true 195 } 196 switch expr.Op { 197 case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL: 198 default: 199 return true 200 } 201 202 value, ok := lint.ExprToInt(expr.Y) 203 if !ok { 204 return true 205 } 206 207 allowedOps, ok := allowed[value] 208 if !ok { 209 return true 210 } 211 b, ok := allowedOps[expr.Op] 212 if !ok { 213 return true 214 } 215 216 call, ok := expr.X.(*ast.CallExpr) 217 if !ok { 218 return true 219 } 220 sel, ok := call.Fun.(*ast.SelectorExpr) 221 if !ok { 222 return true 223 } 224 pkgIdent, ok := sel.X.(*ast.Ident) 225 if !ok { 226 return true 227 } 228 funIdent := sel.Sel 229 if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" { 230 return true 231 } 232 newFunc := "" 233 switch funIdent.Name { 234 case "IndexRune": 235 newFunc = "ContainsRune" 236 case "IndexAny": 237 newFunc = "ContainsAny" 238 case "Index": 239 newFunc = "Contains" 240 default: 241 return true 242 } 243 244 prefix := "" 245 if !b { 246 prefix = "!" 247 } 248 f.Errorf(node, 1, "should use %s%s.%s(%s) instead", prefix, pkgIdent.Name, newFunc, f.RenderArgs(call.Args)) 249 250 return true 251 } 252 f.Walk(fn) 253 } 254 255 func LintBytesCompare(f *lint.File) { 256 fn := func(node ast.Node) bool { 257 expr, ok := node.(*ast.BinaryExpr) 258 if !ok { 259 return true 260 } 261 if expr.Op != token.NEQ && expr.Op != token.EQL { 262 return true 263 } 264 call, ok := expr.X.(*ast.CallExpr) 265 if !ok { 266 return true 267 } 268 if !lint.IsPkgDot(call.Fun, "bytes", "Compare") { 269 return true 270 } 271 value, ok := lint.ExprToInt(expr.Y) 272 if !ok { 273 return true 274 } 275 if value != "0" { 276 return true 277 } 278 args := f.RenderArgs(call.Args) 279 prefix := "" 280 if expr.Op == token.NEQ { 281 prefix = "!" 282 } 283 f.Errorf(node, 1, lint.Category("FIXME"), "should use %sbytes.Equal(%s) instead", prefix, args) 284 return true 285 } 286 f.Walk(fn) 287 } 288 289 func LintRanges(f *lint.File) { 290 f.Walk(func(node ast.Node) bool { 291 rs, ok := node.(*ast.RangeStmt) 292 if !ok { 293 return true 294 } 295 if lint.IsIdent(rs.Key, "_") && (rs.Value == nil || lint.IsIdent(rs.Value, "_")) { 296 f.Errorf(rs.Key, 1, lint.Category("range-loop"), "should omit values from range; this loop is equivalent to `for range ...`") 297 } 298 299 return true 300 }) 301 } 302 303 func LintForTrue(f *lint.File) { 304 fn := func(node ast.Node) bool { 305 loop, ok := node.(*ast.ForStmt) 306 if !ok { 307 return true 308 } 309 if loop.Init != nil || loop.Post != nil { 310 return true 311 } 312 if !f.IsBoolConst(loop.Cond) || !f.BoolConst(loop.Cond) { 313 return true 314 } 315 f.Errorf(loop, 1, lint.Category("FIXME"), "should use for {} instead of for true {}") 316 return true 317 } 318 f.Walk(fn) 319 } 320 321 func LintRegexpRaw(f *lint.File) { 322 fn := func(node ast.Node) bool { 323 call, ok := node.(*ast.CallExpr) 324 if !ok { 325 return true 326 } 327 sel, ok := call.Fun.(*ast.SelectorExpr) 328 if !ok { 329 return true 330 } 331 if !lint.IsPkgDot(call.Fun, "regexp", "MustCompile") && !lint.IsPkgDot(call.Fun, "regexp", "Compile") { 332 return true 333 } 334 if len(call.Args) != 1 { 335 // invalid function call 336 return true 337 } 338 lit, ok := call.Args[0].(*ast.BasicLit) 339 if !ok { 340 // TODO(dominikh): support string concat, maybe support constants 341 return true 342 } 343 if lit.Kind != token.STRING { 344 // invalid function call 345 return true 346 } 347 if f.Src[f.Fset.Position(lit.Pos()).Offset] != '"' { 348 // already a raw string 349 return true 350 } 351 val := lit.Value 352 if !strings.Contains(val, `\\`) { 353 return true 354 } 355 356 bs := false 357 for _, c := range val { 358 if !bs && c == '\\' { 359 bs = true 360 continue 361 } 362 if bs && c == '\\' { 363 bs = false 364 continue 365 } 366 if bs { 367 // backslash followed by non-backslash -> escape sequence 368 return true 369 } 370 } 371 372 f.Errorf(call, 1, lint.Category("FIXME"), "should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name) 373 return true 374 } 375 f.Walk(fn) 376 } 377 378 func LintIfReturn(f *lint.File) { 379 fn := func(node ast.Node) bool { 380 block, ok := node.(*ast.BlockStmt) 381 if !ok { 382 return true 383 } 384 l := len(block.List) 385 if l < 2 { 386 return true 387 } 388 n1, n2 := block.List[l-2], block.List[l-1] 389 390 if len(block.List) >= 3 { 391 if _, ok := block.List[l-3].(*ast.IfStmt); ok { 392 // Do not flag a series of if statements 393 return true 394 } 395 } 396 // if statement with no init, no else, a single condition 397 // checking an identifier or function call and just a return 398 // statement in the body, that returns a boolean constant 399 ifs, ok := n1.(*ast.IfStmt) 400 if !ok { 401 return true 402 } 403 if ifs.Else != nil || ifs.Init != nil { 404 return true 405 } 406 if len(ifs.Body.List) != 1 { 407 return true 408 } 409 if op, ok := ifs.Cond.(*ast.BinaryExpr); ok { 410 switch op.Op { 411 case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: 412 default: 413 return true 414 } 415 } 416 ret1, ok := ifs.Body.List[0].(*ast.ReturnStmt) 417 if !ok { 418 return true 419 } 420 if len(ret1.Results) != 1 { 421 return true 422 } 423 if !f.IsBoolConst(ret1.Results[0]) { 424 return true 425 } 426 427 ret2, ok := n2.(*ast.ReturnStmt) 428 if !ok { 429 return true 430 } 431 if len(ret2.Results) != 1 { 432 return true 433 } 434 if !f.IsBoolConst(ret2.Results[0]) { 435 return true 436 } 437 f.Errorf(n1, 1, lint.Category("FIXME"), "should use 'return <expr>' instead of 'if <expr> { return <bool> }; return <bool>'") 438 return true 439 } 440 f.Walk(fn) 441 } 442 443 // LintRedundantNilCheckWithLen checks for the following reduntant nil-checks: 444 // 445 // if x == nil || len(x) == 0 {} 446 // if x != nil && len(x) != 0 {} 447 // if x != nil && len(x) == N {} (where N != 0) 448 // if x != nil && len(x) > N {} 449 // if x != nil && len(x) >= N {} (where N != 0) 450 // 451 func LintRedundantNilCheckWithLen(f *lint.File) { 452 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) { 453 lit, ok := expr.(*ast.BasicLit) 454 if ok { 455 return true, lit.Kind == token.INT && lit.Value == "0" 456 } 457 id, ok := expr.(*ast.Ident) 458 if !ok { 459 return false, false 460 } 461 c, ok := f.Pkg.TypesInfo.ObjectOf(id).(*types.Const) 462 if !ok { 463 return false, false 464 } 465 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0" 466 } 467 468 fn := func(node ast.Node) bool { 469 // check that expr is "x || y" or "x && y" 470 expr, ok := node.(*ast.BinaryExpr) 471 if !ok { 472 return true 473 } 474 if expr.Op != token.LOR && expr.Op != token.LAND { 475 return true 476 } 477 eqNil := expr.Op == token.LOR 478 479 // check that x is "xx == nil" or "xx != nil" 480 x, ok := expr.X.(*ast.BinaryExpr) 481 if !ok { 482 return true 483 } 484 if eqNil && x.Op != token.EQL { 485 return true 486 } 487 if !eqNil && x.Op != token.NEQ { 488 return true 489 } 490 xx, ok := x.X.(*ast.Ident) 491 if !ok { 492 return true 493 } 494 if !lint.IsNil(x.Y) { 495 return true 496 } 497 498 // check that y is "len(xx) == 0" or "len(xx) ... " 499 y, ok := expr.Y.(*ast.BinaryExpr) 500 if !ok { 501 return true 502 } 503 if eqNil && y.Op != token.EQL { // must be len(xx) *==* 0 504 return false 505 } 506 yx, ok := y.X.(*ast.CallExpr) 507 if !ok { 508 return true 509 } 510 yxFun, ok := yx.Fun.(*ast.Ident) 511 if !ok || yxFun.Name != "len" || len(yx.Args) != 1 { 512 return true 513 } 514 yxArg, ok := yx.Args[0].(*ast.Ident) 515 if !ok { 516 return true 517 } 518 if yxArg.Name != xx.Name { 519 return true 520 } 521 522 if eqNil && !lint.IsZero(y.Y) { // must be len(x) == *0* 523 return true 524 } 525 526 if !eqNil { 527 isConst, isZero := isConstZero(y.Y) 528 if !isConst { 529 return true 530 } 531 switch y.Op { 532 case token.EQL: 533 // avoid false positive for "xx != nil && len(xx) == 0" 534 if isZero { 535 return true 536 } 537 case token.GEQ: 538 // avoid false positive for "xx != nil && len(xx) >= 0" 539 if isZero { 540 return true 541 } 542 case token.NEQ: 543 // avoid false positive for "xx != nil && len(xx) != <non-zero>" 544 if !isZero { 545 return true 546 } 547 case token.GTR: 548 // ok 549 default: 550 return true 551 } 552 } 553 554 // finally check that xx type is one of array, slice, map or chan 555 // this is to prevent false positive in case if xx is a pointer to an array 556 var nilType string 557 switch f.Pkg.TypesInfo.TypeOf(xx).(type) { 558 case *types.Slice: 559 nilType = "nil slices" 560 case *types.Map: 561 nilType = "nil maps" 562 case *types.Chan: 563 nilType = "nil channels" 564 default: 565 return true 566 } 567 f.Errorf(expr, 1, lint.Category("FIXME"), "should omit nil check; len() for %s is defined as zero", nilType) 568 return true 569 } 570 f.Walk(fn) 571 } 572 573 func LintSlicing(f *lint.File) { 574 fn := func(node ast.Node) bool { 575 n, ok := node.(*ast.SliceExpr) 576 if !ok { 577 return true 578 } 579 if n.Max != nil { 580 return true 581 } 582 s, ok := n.X.(*ast.Ident) 583 if !ok || s.Obj == nil { 584 return true 585 } 586 call, ok := n.High.(*ast.CallExpr) 587 if !ok || len(call.Args) != 1 || call.Ellipsis.IsValid() { 588 return true 589 } 590 fun, ok := call.Fun.(*ast.Ident) 591 if !ok || fun.Name != "len" { 592 return true 593 } 594 if _, ok := f.Pkg.TypesInfo.ObjectOf(fun).(*types.Builtin); !ok { 595 return true 596 } 597 arg, ok := call.Args[0].(*ast.Ident) 598 if !ok || arg.Obj != s.Obj { 599 return true 600 } 601 f.Errorf(n, 1, "should omit second index in slice, s[a:len(s)] is identical to s[a:]") 602 return true 603 } 604 f.Walk(fn) 605 } 606 607 func refersTo(info *types.Info, expr ast.Expr, ident *ast.Ident) bool { 608 found := false 609 fn := func(node ast.Node) bool { 610 ident2, ok := node.(*ast.Ident) 611 if !ok { 612 return true 613 } 614 if info.ObjectOf(ident) == info.ObjectOf(ident2) { 615 found = true 616 return false 617 } 618 return true 619 } 620 ast.Inspect(expr, fn) 621 return found 622 } 623 624 func LintLoopAppend(f *lint.File) { 625 fn := func(node ast.Node) bool { 626 loop, ok := node.(*ast.RangeStmt) 627 if !ok { 628 return true 629 } 630 if !lint.IsBlank(loop.Key) { 631 return true 632 } 633 val, ok := loop.Value.(*ast.Ident) 634 if !ok { 635 return true 636 } 637 if len(loop.Body.List) != 1 { 638 return true 639 } 640 stmt, ok := loop.Body.List[0].(*ast.AssignStmt) 641 if !ok { 642 return true 643 } 644 if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 { 645 return true 646 } 647 if refersTo(f.Pkg.TypesInfo, stmt.Lhs[0], val) { 648 return true 649 } 650 call, ok := stmt.Rhs[0].(*ast.CallExpr) 651 if !ok { 652 return true 653 } 654 if len(call.Args) != 2 || call.Ellipsis.IsValid() { 655 return true 656 } 657 fun, ok := call.Fun.(*ast.Ident) 658 if !ok { 659 return true 660 } 661 obj := f.Pkg.TypesInfo.ObjectOf(fun) 662 fn, ok := obj.(*types.Builtin) 663 if !ok || fn.Name() != "append" { 664 return true 665 } 666 667 src := f.Pkg.TypesInfo.TypeOf(loop.X) 668 dst := f.Pkg.TypesInfo.TypeOf(call.Args[0]) 669 // TODO(dominikh) remove nil check once Go issue #15173 has 670 // been fixed 671 if src == nil { 672 return true 673 } 674 if !types.Identical(src, dst) { 675 return true 676 } 677 678 if f.Render(stmt.Lhs[0]) != f.Render(call.Args[0]) { 679 return true 680 } 681 682 el, ok := call.Args[1].(*ast.Ident) 683 if !ok { 684 return true 685 } 686 if f.Pkg.TypesInfo.ObjectOf(val) != f.Pkg.TypesInfo.ObjectOf(el) { 687 return true 688 } 689 f.Errorf(loop, 1, "should replace loop with %s = append(%s, %s...)", 690 f.Render(stmt.Lhs[0]), f.Render(call.Args[0]), f.Render(loop.X)) 691 return true 692 } 693 f.Walk(fn) 694 } 695 696 func LintTimeSince(f *lint.File) { 697 fn := func(node ast.Node) bool { 698 call, ok := node.(*ast.CallExpr) 699 if !ok { 700 return true 701 } 702 sel, ok := call.Fun.(*ast.SelectorExpr) 703 if !ok { 704 return true 705 } 706 subcall, ok := sel.X.(*ast.CallExpr) 707 if !ok { 708 return true 709 } 710 if !lint.IsPkgDot(subcall.Fun, "time", "Now") { 711 return true 712 } 713 if sel.Sel.Name != "Sub" { 714 return true 715 } 716 f.Errorf(call, 1, "should use time.Since instead of time.Now().Sub") 717 return true 718 } 719 f.Walk(fn) 720 } 721 722 func LintSimplerReturn(f *lint.File) { 723 fn1 := func(node ast.Node) bool { 724 var ret *ast.FieldList 725 switch x := node.(type) { 726 case *ast.FuncDecl: 727 ret = x.Type.Results 728 case *ast.FuncLit: 729 ret = x.Type.Results 730 default: 731 return true 732 } 733 if ret == nil { 734 return true 735 } 736 737 fn2 := func(node ast.Node) bool { 738 block, ok := node.(*ast.BlockStmt) 739 if !ok { 740 return true 741 } 742 if len(block.List) < 2 { 743 return true 744 } 745 746 outer: 747 for i, stmt := range block.List { 748 if i == len(block.List)-1 { 749 break 750 } 751 if i > 0 { 752 // don't flag an if in a series of ifs 753 if _, ok := block.List[i-1].(*ast.IfStmt); ok { 754 continue 755 } 756 } 757 758 // if <id1> != nil 759 ifs, ok := stmt.(*ast.IfStmt) 760 if !ok || len(ifs.Body.List) != 1 || ifs.Else != nil { 761 continue 762 } 763 expr, ok := ifs.Cond.(*ast.BinaryExpr) 764 if !ok || expr.Op != token.NEQ || !lint.IsNil(expr.Y) { 765 continue 766 } 767 id1, ok := expr.X.(*ast.Ident) 768 if !ok { 769 continue 770 } 771 772 // return ..., <id1> 773 ret1, ok := ifs.Body.List[0].(*ast.ReturnStmt) 774 if !ok || len(ret1.Results) == 0 { 775 continue 776 } 777 var results1 []types.Object 778 for _, res := range ret1.Results { 779 ident, ok := res.(*ast.Ident) 780 if !ok { 781 continue outer 782 } 783 results1 = append(results1, f.Pkg.TypesInfo.ObjectOf(ident)) 784 } 785 if results1[len(results1)-1] != f.Pkg.TypesInfo.ObjectOf(id1) { 786 continue 787 } 788 789 // return ..., [<id1> | nil] 790 ret2, ok := block.List[i+1].(*ast.ReturnStmt) 791 if !ok || len(ret2.Results) == 0 { 792 continue 793 } 794 var results2 []types.Object 795 for _, res := range ret2.Results { 796 ident, ok := res.(*ast.Ident) 797 if !ok { 798 continue outer 799 } 800 results2 = append(results2, f.Pkg.TypesInfo.ObjectOf(ident)) 801 } 802 _, isNil := results2[len(results2)-1].(*types.Nil) 803 if results2[len(results2)-1] != f.Pkg.TypesInfo.ObjectOf(id1) && 804 !isNil { 805 continue 806 } 807 for i, v := range results1[:len(results1)-1] { 808 if v != results2[i] { 809 continue outer 810 } 811 } 812 813 id1Obj := f.Pkg.TypesInfo.ObjectOf(id1) 814 if id1Obj == nil { 815 continue 816 } 817 _, idIface := id1Obj.Type().Underlying().(*types.Interface) 818 _, retIface := f.Pkg.TypesInfo.TypeOf(ret.List[len(ret.List)-1].Type).Underlying().(*types.Interface) 819 820 if retIface && !idIface { 821 // When the return value is an interface, but the 822 // identifier is not, an explicit check for nil is 823 // required to return an untyped nil. 824 continue 825 } 826 827 f.Errorf(ifs, 1, "'if %s != nil { return %s }; return %s' can be simplified to 'return %s'", 828 f.Render(expr.X), f.RenderArgs(ret1.Results), 829 f.RenderArgs(ret2.Results), f.RenderArgs(ret1.Results)) 830 } 831 return true 832 } 833 ast.Inspect(node, fn2) 834 return true 835 } 836 f.Walk(fn1) 837 } 838 839 func LintReceiveIntoBlank(f *lint.File) { 840 fn := func(node ast.Node) bool { 841 stmt, ok := node.(*ast.AssignStmt) 842 if !ok { 843 return true 844 } 845 if len(stmt.Lhs) != len(stmt.Rhs) { 846 return true 847 } 848 for i, lh := range stmt.Lhs { 849 rh := stmt.Rhs[i] 850 if !lint.IsBlank(lh) { 851 continue 852 } 853 expr, ok := rh.(*ast.UnaryExpr) 854 if !ok { 855 continue 856 } 857 if expr.Op != token.ARROW { 858 continue 859 } 860 f.Errorf(lh, 1, "'_ = <-ch' can be simplified to '<-ch'") 861 } 862 return true 863 } 864 f.Walk(fn) 865 } 866 867 func LintFormatInt(f *lint.File) { 868 checkBasic := func(v ast.Expr) bool { 869 typ, ok := f.Pkg.TypesInfo.TypeOf(v).(*types.Basic) 870 if !ok { 871 return false 872 } 873 switch typ.Kind() { 874 case types.Int, types.Int32: 875 return true 876 } 877 return false 878 } 879 checkConst := func(v *ast.Ident) bool { 880 c, ok := f.Pkg.TypesInfo.ObjectOf(v).(*types.Const) 881 if !ok { 882 return false 883 } 884 if c.Val().Kind() != constant.Int { 885 return false 886 } 887 i, _ := constant.Int64Val(c.Val()) 888 return i <= math.MaxInt32 889 } 890 checkConstStrict := func(v *ast.Ident) bool { 891 if !checkConst(v) { 892 return false 893 } 894 basic, ok := f.Pkg.TypesInfo.ObjectOf(v).(*types.Const).Type().(*types.Basic) 895 return ok && basic.Kind() == types.UntypedInt 896 } 897 898 fn := func(node ast.Node) bool { 899 call, ok := node.(*ast.CallExpr) 900 if !ok { 901 return true 902 } 903 if !lint.IsPkgDot(call.Fun, "strconv", "FormatInt") { 904 return true 905 } 906 if len(call.Args) != 2 { 907 return true 908 } 909 if lit, ok := call.Args[1].(*ast.BasicLit); !ok || lit.Value != "10" { 910 return true 911 } 912 913 matches := false 914 switch v := call.Args[0].(type) { 915 case *ast.CallExpr: 916 if len(v.Args) != 1 { 917 return true 918 } 919 ident, ok := v.Fun.(*ast.Ident) 920 if !ok { 921 return true 922 } 923 obj, ok := f.Pkg.TypesInfo.ObjectOf(ident).(*types.TypeName) 924 if !ok || obj.Parent() != types.Universe || obj.Name() != "int64" { 925 return true 926 } 927 928 switch vv := v.Args[0].(type) { 929 case *ast.BasicLit: 930 i, _ := strconv.ParseInt(vv.Value, 10, 64) 931 if i <= math.MaxInt32 { 932 matches = true 933 } 934 case *ast.Ident: 935 if checkConst(vv) || checkBasic(v.Args[0]) { 936 matches = true 937 } 938 default: 939 if checkBasic(v.Args[0]) { 940 matches = true 941 } 942 } 943 case *ast.BasicLit: 944 if v.Kind != token.INT { 945 return true 946 } 947 i, _ := strconv.ParseInt(v.Value, 10, 64) 948 if i <= math.MaxInt32 { 949 matches = true 950 } 951 case *ast.Ident: 952 if checkConstStrict(v) { 953 matches = true 954 } 955 } 956 if matches { 957 f.Errorf(call, 1, "should use strconv.Itoa instead of strconv.FormatInt") 958 } 959 return true 960 } 961 f.Walk(fn) 962 } 963 964 func LintSimplerStructConversion(f *lint.File) { 965 fn := func(node ast.Node) bool { 966 lit, ok := node.(*ast.CompositeLit) 967 if !ok { 968 return true 969 } 970 typ1 := f.Pkg.TypesInfo.TypeOf(lit.Type) 971 if typ1 == nil { 972 return true 973 } 974 // FIXME support pointer to struct 975 s1, ok := typ1.Underlying().(*types.Struct) 976 if !ok { 977 return true 978 } 979 980 n := s1.NumFields() 981 var typ2 types.Type 982 var ident *ast.Ident 983 getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) { 984 sel, ok := expr.(*ast.SelectorExpr) 985 if !ok { 986 return nil, nil, false 987 } 988 ident, ok := sel.X.(*ast.Ident) 989 if !ok { 990 return nil, nil, false 991 } 992 typ := f.Pkg.TypesInfo.TypeOf(sel.X) 993 return typ, ident, typ != nil 994 } 995 if len(lit.Elts) == 0 { 996 return true 997 } 998 for i, elt := range lit.Elts { 999 n-- 1000 var t types.Type 1001 var id *ast.Ident 1002 var ok bool 1003 switch elt := elt.(type) { 1004 case *ast.SelectorExpr: 1005 t, id, ok = getSelType(elt) 1006 if !ok { 1007 return true 1008 } 1009 if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name { 1010 return true 1011 } 1012 case *ast.KeyValueExpr: 1013 var sel *ast.SelectorExpr 1014 sel, ok = elt.Value.(*ast.SelectorExpr) 1015 if !ok { 1016 return true 1017 } 1018 1019 if elt.Key.(*ast.Ident).Name != sel.Sel.Name { 1020 return true 1021 } 1022 t, id, ok = getSelType(elt.Value) 1023 } 1024 if !ok { 1025 return true 1026 } 1027 if typ2 != nil && typ2 != t { 1028 return true 1029 } 1030 if ident != nil && ident.Obj != id.Obj { 1031 return true 1032 } 1033 typ2 = t 1034 ident = id 1035 } 1036 1037 if n != 0 { 1038 return true 1039 } 1040 1041 if typ2 == nil { 1042 return true 1043 } 1044 1045 s2, ok := typ2.Underlying().(*types.Struct) 1046 if !ok { 1047 return true 1048 } 1049 if typ1 == typ2 { 1050 return true 1051 } 1052 if !structsIdentical(s1, s2) { 1053 return true 1054 } 1055 f.Errorf(node, 1, "should use type conversion instead of struct literal") 1056 return true 1057 } 1058 f.Walk(fn) 1059 } 1060 1061 func LintTrim(f *lint.File) { 1062 // TODO(dh): implement the same for suffix 1063 sameNonDynamic := func(node1, node2 ast.Node) bool { 1064 if reflect.TypeOf(node1) != reflect.TypeOf(node2) { 1065 return false 1066 } 1067 1068 switch node1 := node1.(type) { 1069 case *ast.Ident: 1070 return node1.Obj == node2.(*ast.Ident).Obj 1071 case *ast.SelectorExpr: 1072 return f.Render(node1) == f.Render(node2) 1073 case *ast.IndexExpr: 1074 return f.Render(node1) == f.Render(node2) 1075 } 1076 return false 1077 } 1078 1079 isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool { 1080 call, ok := fn.(*ast.CallExpr) 1081 if !ok { 1082 return false 1083 } 1084 if fn, ok := call.Fun.(*ast.Ident); !ok || fn.Name != "len" { 1085 return false 1086 } 1087 if len(call.Args) != 1 { 1088 return false 1089 } 1090 return sameNonDynamic(call.Args[0], ident) 1091 } 1092 1093 fn := func(node ast.Node) bool { 1094 var pkg string 1095 var fun string 1096 1097 ifstmt, ok := node.(*ast.IfStmt) 1098 if !ok { 1099 return true 1100 } 1101 if ifstmt.Init != nil { 1102 return true 1103 } 1104 if ifstmt.Else != nil { 1105 return true 1106 } 1107 if len(ifstmt.Body.List) != 1 { 1108 return true 1109 } 1110 condCall, ok := ifstmt.Cond.(*ast.CallExpr) 1111 if !ok { 1112 return true 1113 } 1114 call, ok := condCall.Fun.(*ast.SelectorExpr) 1115 if !ok { 1116 return true 1117 } 1118 if lint.IsIdent(call.X, "strings") { 1119 pkg = "strings" 1120 } else if lint.IsIdent(call.X, "bytes") { 1121 pkg = "bytes" 1122 } else { 1123 return true 1124 } 1125 if lint.IsIdent(call.Sel, "HasPrefix") { 1126 fun = "HasPrefix" 1127 } else if lint.IsIdent(call.Sel, "HasSuffix") { 1128 fun = "HasSuffix" 1129 } else { 1130 return true 1131 } 1132 1133 assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt) 1134 if !ok { 1135 return true 1136 } 1137 if assign.Tok != token.ASSIGN { 1138 return true 1139 } 1140 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { 1141 return true 1142 } 1143 if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) { 1144 return true 1145 } 1146 slice, ok := assign.Rhs[0].(*ast.SliceExpr) 1147 if !ok { 1148 return true 1149 } 1150 if slice.Slice3 { 1151 return true 1152 } 1153 if !sameNonDynamic(slice.X, condCall.Args[0]) { 1154 return true 1155 } 1156 var index ast.Expr 1157 switch fun { 1158 case "HasPrefix": 1159 // TODO(dh) We could detect a High that is len(s), but another 1160 // rule will already flag that, anyway. 1161 if slice.High != nil { 1162 return true 1163 } 1164 index = slice.Low 1165 case "HasSuffix": 1166 if slice.Low != nil { 1167 n, ok := intLit(f, slice.Low) 1168 if !ok || n != 0 { 1169 return true 1170 } 1171 } 1172 index = slice.High 1173 } 1174 1175 switch index := index.(type) { 1176 case *ast.CallExpr: 1177 if fun != "HasPrefix" { 1178 return true 1179 } 1180 if fn, ok := index.Fun.(*ast.Ident); !ok || fn.Name != "len" { 1181 return true 1182 } 1183 if len(index.Args) != 1 { 1184 return true 1185 } 1186 id3 := index.Args[0] 1187 switch oid3 := condCall.Args[1].(type) { 1188 case *ast.BasicLit: 1189 if pkg != "strings" { 1190 return false 1191 } 1192 lit, ok := id3.(*ast.BasicLit) 1193 if !ok { 1194 return true 1195 } 1196 s1, ok1 := stringLit(f, lit) 1197 s2, ok2 := stringLit(f, condCall.Args[1]) 1198 if !ok1 || !ok2 || s1 != s2 { 1199 return true 1200 } 1201 default: 1202 if !sameNonDynamic(id3, oid3) { 1203 return true 1204 } 1205 } 1206 case *ast.BasicLit, *ast.Ident: 1207 if fun != "HasPrefix" { 1208 return true 1209 } 1210 if pkg != "strings" { 1211 return true 1212 } 1213 string, ok1 := stringLit(f, condCall.Args[1]) 1214 int, ok2 := intLit(f, slice.Low) 1215 if !ok1 || !ok2 || int != int64(len(string)) { 1216 return true 1217 } 1218 case *ast.BinaryExpr: 1219 if fun != "HasSuffix" { 1220 return true 1221 } 1222 if index.Op != token.SUB { 1223 return true 1224 } 1225 if !isLenOnIdent(index.X, condCall.Args[0]) || 1226 !isLenOnIdent(index.Y, condCall.Args[1]) { 1227 return true 1228 } 1229 default: 1230 return true 1231 } 1232 1233 var replacement string 1234 switch fun { 1235 case "HasPrefix": 1236 replacement = "TrimPrefix" 1237 case "HasSuffix": 1238 replacement = "TrimSuffix" 1239 } 1240 f.Errorf(ifstmt, 1, "should replace this if statement with an unconditional %s.%s", pkg, replacement) 1241 return true 1242 } 1243 f.Walk(fn) 1244 } 1245 1246 func stringLit(f *lint.File, expr ast.Expr) (string, bool) { 1247 tv := f.Pkg.TypesInfo.Types[expr] 1248 if tv.Value == nil { 1249 return "", false 1250 } 1251 if tv.Value.Kind() != constant.String { 1252 return "", false 1253 } 1254 return constant.StringVal(tv.Value), true 1255 } 1256 1257 func intLit(f *lint.File, expr ast.Expr) (int64, bool) { 1258 tv := f.Pkg.TypesInfo.Types[expr] 1259 if tv.Value == nil { 1260 return 0, false 1261 } 1262 if tv.Value.Kind() != constant.Int { 1263 return 0, false 1264 } 1265 val, _ := constant.Int64Val(tv.Value) 1266 return val, true 1267 }