1package wsl 2 3import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9) 10 11func TestGenericHandling(t *testing.T) { 12 cases := []struct { 13 description string 14 code []byte 15 expectedErrorStrings []string 16 }{ 17 { 18 description: "a file must start with package declaration", 19 code: []byte(`func a() { 20 fmt.Println("this is function a") 21 }`), 22 expectedErrorStrings: []string{"invalid syntax, file cannot be linted"}, 23 }, 24 { 25 description: "comments are ignored, ignore this crappy code", 26 code: []byte(`package main 27 28 /* 29 Multi line comments ignore errors 30 if true { 31 32 fmt.Println("Well hello blankies!") 33 34 } 35 */ 36 func main() { 37 // Also signel comments are ignored 38 // foo := false 39 // if true { 40 // 41 // foo = true 42 // 43 // } 44 }`), 45 }, 46 { 47 description: "do not panic on empty blocks", 48 code: []byte(`package main 49 50 func x(r string) (x uintptr)`), 51 }, 52 { 53 description: "no false positives for comments", 54 code: []byte(`package main 55 56 func main() { // This is a comment, not the first line 57 fmt.Println("hello, world") 58 }`), 59 }, 60 { 61 description: "no false positives for one line functions", 62 code: []byte(`package main 63 64 func main() { 65 s := func() error { return nil } 66 }`), 67 }, 68 } 69 70 for _, tc := range cases { 71 t.Run(tc.description, func(t *testing.T) { 72 p := NewProcessor() 73 p.process("unit-test", tc.code) 74 75 require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found") 76 77 for i := range tc.expectedErrorStrings { 78 assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found") 79 } 80 }) 81 } 82} 83 84func TestShouldRemoveEmptyLines(t *testing.T) { 85 cases := []struct { 86 description string 87 code []byte 88 expectedErrorStrings []string 89 }{ 90 { 91 description: "if statements cannot start or end with newlines", 92 code: []byte(`package main 93 func main() { 94 if true { 95 96 fmt.Println("this violates the rules") 97 98 } 99 }`), 100 expectedErrorStrings: []string{"block should not start with a whitespace", reasonBlockEndsWithWS}, 101 }, 102 { 103 description: "whitespaces parsed correct even with comments (whitespace found)", 104 code: []byte(`package main 105 func main() { 106 if true { 107 // leading comment, still too much whitespace 108 109 fmt.Println("this violates the rules") 110 111 // trailing comment, still too much whitespace 112 } 113 }`), 114 expectedErrorStrings: []string{"block should not start with a whitespace", reasonBlockEndsWithWS}, 115 }, 116 { 117 description: "whitespaces parsed correct even with comments (whitespace NOT found)", 118 code: []byte(`package main 119 120 func main() { 121 if true { 122 // leading comment, still too much whitespace 123 // more comments 124 fmt.Println("this violates the rules") 125 } 126 }`), 127 }, 128 { 129 description: "Example tests parsed correct when ending with comments", 130 code: []byte(`package foo 131 132 func ExampleFoo() { 133 fmt.Println("hello world") 134 135 // Output: hello world 136 } 137 138 func ExampleBar() { 139 fmt.Println("hello world") 140 // Output: hello world 141 }`), 142 }, 143 { 144 description: "whitespaces parsed correctly in case blocks", 145 code: []byte(`package main 146 func main() { 147 switch true { 148 case 1: 149 150 fmt.Println("this is too much...") 151 152 case 2: 153 fmt.Println("this is too much...") 154 155 } 156 }`), 157 expectedErrorStrings: []string{ 158 reasonBlockEndsWithWS, 159 "block should not start with a whitespace", 160 }, 161 }, 162 { 163 description: "whitespaces parsed correctly in case blocks with comments (whitespace NOT found)", 164 code: []byte(`package main 165 func main() { 166 switch true { 167 case 1: 168 // Some leading comment here 169 fmt.Println("this is too much...") 170 case 2: 171 fmt.Println("this is too much...") 172 } 173 }`), 174 }, 175 { 176 description: "declarations may never be cuddled", 177 code: []byte(`package main 178 179 func main() { 180 var b = true 181 182 if b { 183 // b is true here 184 } 185 var a = false 186 }`), 187 expectedErrorStrings: []string{reasonNeverCuddleDeclare}, 188 }, 189 { 190 description: "nested if statements should not be seen as cuddled", 191 code: []byte(`package main 192 193 func main() { 194 if true { 195 if false { 196 if true && false { 197 // Whelp?! 198 } 199 } 200 } 201 }`), 202 }, 203 } 204 205 for _, tc := range cases { 206 t.Run(tc.description, func(t *testing.T) { 207 p := NewProcessor() 208 p.process("unit-test", tc.code) 209 210 require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found") 211 212 for i := range tc.expectedErrorStrings { 213 assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found") 214 } 215 }) 216 } 217} 218 219func TestShouldAddEmptyLines(t *testing.T) { 220 cases := []struct { 221 description string 222 skip bool 223 code []byte 224 expectedErrorStrings []string 225 }{ 226 { 227 description: "ok to cuddled if statements directly after assignments multiple values", 228 code: []byte(`package main 229 230 func main() { 231 v, err := strconv.Atoi("1") 232 if err != nil { 233 fmt.Println(v) 234 } 235 }`), 236 }, 237 { 238 description: "ok to cuddled if statements directly after assignments single value", 239 code: []byte(`package main 240 241 func main() { 242 a := true 243 if a { 244 fmt.Println("I'm OK") 245 } 246 }`), 247 }, 248 { 249 description: "ok to cuddled if statements directly after assignments single value, negations", 250 code: []byte(`package main 251 252 func main() { 253 a, b := true, false 254 if !a { 255 fmt.Println("I'm OK") 256 } 257 }`), 258 }, 259 { 260 description: "cannot cuddled if assignments not used", 261 code: []byte(`package main 262 263 func main() { 264 err := ProduceError() 265 266 a, b := true, false 267 if err != nil { 268 fmt.Println("I'm OK") 269 } 270 }`), 271 expectedErrorStrings: []string{reasonOnlyCuddleWithUsedAssign}, 272 }, 273 { 274 description: "cannot cuddled with other things than assignments", 275 code: []byte(`package main 276 277 func main() { 278 if true { 279 fmt.Println("I'm OK") 280 } 281 if false { 282 fmt.Println("I'm OK") 283 } 284 }`), 285 expectedErrorStrings: []string{reasonOnlyCuddleIfWithAssign}, 286 }, 287 { 288 description: "if statement with multiple assignments above and multiple conditions", 289 code: []byte(`package main 290 291 func main() { 292 c := true 293 fooBar := "string" 294 a, b := true, false 295 if a && b || !c && len(fooBar) > 2 { 296 return false 297 } 298 299 a, b, c := someFunc() 300 if z && len(y) > 0 || 3 == 4 { 301 return true 302 } 303 }`), 304 expectedErrorStrings: []string{"only one cuddle assignment allowed before if statement", reasonOnlyCuddleWithUsedAssign}, 305 }, 306 { 307 description: "if statement with multiple assignments, at least one used", 308 code: []byte(`package main 309 310 func main() { 311 c := true 312 fooBar := "string" 313 314 a, b := true, false 315 if c && b || !c && len(fooBar) > 2 { 316 return false 317 } 318 }`), 319 }, 320 { 321 description: "return can never be cuddleded", 322 code: []byte(`package main 323 324 func main() { 325 var foo = true 326 327 if foo { 328 fmt.Println("return") 329 } 330 return 331 }`), 332 expectedErrorStrings: []string{reasonOnlyCuddle2LineReturn}, 333 }, 334 { 335 description: "assignments should only be cuddled with assignments (negative)", 336 code: []byte(`package main 337 338 func main() { 339 if true { 340 fmt.Println("this is bad...") 341 } 342 foo := true 343 }`), 344 expectedErrorStrings: []string{reasonAssignsCuddleAssign}, 345 }, 346 { 347 description: "assignments should only be cuddled with assignments", 348 code: []byte(`package main 349 350 func main() { 351 bar := false 352 foo := true 353 biz := true 354 355 return 356 }`), 357 }, 358 { 359 description: "expressions cannot be cuddled with declarations", 360 code: []byte(`package main 361 362 func main() { 363 var a bool 364 fmt.Println(a) 365 366 return 367 }`), 368 expectedErrorStrings: []string{reasonExpressionCuddledWithDeclOrRet}, 369 }, 370 { 371 description: "expressions can be cuddlede with assignments", 372 code: []byte(`package main 373 374 func main() { 375 a := true 376 fmt.Println(a) 377 378 return 379 }`), 380 }, 381 { 382 description: "ranges cannot be cuddled with assignments not used in the range", 383 code: []byte(`package main 384 385 func main() { 386 anotherList := make([]string, 5) 387 388 myList := make([]int, 10) 389 for i := range anotherList { 390 fmt.Println(i) 391 } 392 }`), 393 expectedErrorStrings: []string{reasonRangeCuddledWithoutUse}, 394 }, 395 { 396 description: "ranges can be cuddled if iteration is of assignment above", 397 code: []byte(`package main 398 399 func main() { 400 myList := make([]string, 5) 401 402 anotherList := make([]int, 10) 403 for i := range anotherList { 404 fmt.Println(i) 405 } 406 }`), 407 }, 408 { 409 description: "ranges can be cuddled if multiple assignments and/or values iterated", 410 code: []byte(`package main 411 412 func main() { 413 someList, anotherList := GetTwoListsOfStrings() 414 for i := range append(someList, anotherList...) { 415 fmt.Println(i) 416 } 417 418 aThirdList := GetList() 419 for i := range append(someList, aThirdList...) { 420 fmt.Println(i) 421 } 422 }`), 423 }, 424 { 425 description: "allow cuddle for immediate assignment in block", 426 code: []byte(`package main 427 428 func main() { 429 // This should be allowed 430 idx := i 431 if i > 0 { 432 idx = i - 1 433 } 434 435 vals := map[int]struct{}{} 436 for i := range make([]int, 5) { 437 vals[i] = struct{}{} 438 } 439 440 // This last issue fails. 441 x := []int{} 442 443 vals := map[int]struct{}{} 444 for i := range make([]int, 5) { 445 x = append(x, i) 446 } 447 }`), 448 expectedErrorStrings: []string{reasonRangeCuddledWithoutUse}, 449 }, 450 { 451 description: "can cuddle only one assignment", 452 code: []byte(`package main 453 454 func main() { 455 // This is allowed 456 foo := true 457 bar := false 458 459 biz := true || false 460 if biz { 461 return true 462 } 463 464 // And this is allowed 465 466 foo := true 467 bar := false 468 biz := true || false 469 470 if biz { 471 return true 472 } 473 474 // But not this 475 476 foo := true 477 bar := false 478 biz := true || false 479 if biz { 480 return false 481 } 482 }`), 483 expectedErrorStrings: []string{"only one cuddle assignment allowed before if statement"}, 484 }, 485 { 486 description: "using identifies with indicies", 487 code: []byte(`package main 488 489 func main() { 490 // This should be OK 491 runes := []rune{'+', '-'} 492 if runes[0] == '+' || runes[0] == '-' { 493 return string(runes[1:]) 494 } 495 496 // And this 497 listTwo := []string{} 498 499 listOne := []string{"one"} 500 if listOne[0] == "two" { 501 return "not allowed" 502 } 503 }`), 504 }, 505 { 506 description: "simple defer statements", 507 code: []byte(`package main 508 509 func main() { 510 // OK 511 thingOne := getOne() 512 thingTwo := getTwo() 513 514 defer thingOne.Close() 515 defer thingTwo.Close() 516 517 // OK 518 thingOne := getOne() 519 defer thingOne.Close() 520 521 thingTwo := getTwo() 522 defer thingTwo.Close() 523 524 // NOT OK 525 thingOne := getOne() 526 defer thingOne.Close() 527 thingTwo := getTwo() 528 defer thingTwo.Close() 529 530 // NOT OK 531 thingOne := getOne() 532 thingTwo := getTwo() 533 defer thingOne.Close() 534 defer thingTwo.Close() 535 }`), 536 expectedErrorStrings: []string{ 537 reasonAssignsCuddleAssign, 538 reasonOneCuddleBeforeDefer, 539 reasonOneCuddleBeforeDefer, 540 }, 541 }, 542 { 543 description: "defer statements can be cuddled", 544 code: []byte(`package main 545 546 import "sync" 547 548 func main() { 549 m := sync.Mutex{} 550 551 // This should probably be OK 552 m.Lock() 553 defer m.Unlock() 554 555 foo := true 556 defer func(b bool) { 557 fmt.Printf("%v", b) 558 }() 559 }`), 560 expectedErrorStrings: []string{ 561 reasonDeferCuddledWithOtherVar, 562 }, 563 }, 564 { 565 description: "selector expressions are handled like variables", 566 code: []byte(`package main 567 568 type T struct { 569 List []string 570 } 571 572 func main() { 573 t := makeT() 574 for _, x := range t.List { 575 return "this is not like a variable" 576 } 577 } 578 579 func makeT() T { 580 return T{ 581 List: []string{"one", "two"}, 582 } 583 }`), 584 }, 585 { 586 description: "return statements may be cuddled if the block is less than three lines", 587 code: []byte(`package main 588 589 type T struct { 590 Name string 591 X bool 592 } 593 594 func main() { 595 t := &T{"someT"} 596 597 t.SetX() 598 } 599 600 func (t *T) SetX() *T { 601 t.X = true 602 return t 603 }`), 604 expectedErrorStrings: []string{}, 605 }, 606 { 607 description: "handle ForStmt", 608 code: []byte(`package main 609 610 func main() { 611 bool := true 612 for { 613 fmt.Println("should not be allowed") 614 615 if bool { 616 break 617 } 618 } 619 }`), 620 expectedErrorStrings: []string{reasonForWithoutCondition}, 621 }, 622 { 623 description: "support usage if chained", 624 code: []byte(`package main 625 626 func main() { 627 go func() { 628 panic("is this real life?") 629 }() 630 631 fooFunc := func () {} 632 go fooFunc() 633 634 barFunc := func () {} 635 go fooFunc() 636 637 go func() { 638 fmt.Println("hey") 639 }() 640 641 cuddled := true 642 go func() { 643 fmt.Println("hey") 644 }() 645 }`), 646 expectedErrorStrings: []string{ 647 reasonGoFuncWithoutAssign, 648 reasonGoFuncWithoutAssign, 649 }, 650 }, 651 { 652 description: "switch statements", 653 code: []byte(`package main 654 655 func main() { 656 // OK 657 var b bool 658 switch b { 659 case true: 660 return "a" 661 case false: 662 return "b" 663 } 664 665 // OK 666 t := time.Now() 667 668 switch { 669 case t.Hour() < 12: 670 fmt.Println("It's before noon") 671 default: 672 fmt.Println("It's after noon") 673 } 674 675 // Not ok 676 var b bool 677 switch anotherBool { 678 case true: 679 return "a" 680 case false: 681 return "b" 682 } 683 684 // Not ok 685 t := time.Now() 686 switch { 687 case t.Hour() < 12: 688 fmt.Println("It's before noon") 689 default: 690 fmt.Println("It's after noon") 691 } 692 }`), 693 expectedErrorStrings: []string{ 694 reasonSwitchCuddledWithoutUse, 695 reasonAnonSwitchCuddled, 696 }, 697 }, 698 { 699 description: "type switch statements", 700 code: []byte(`package main 701 702 func main() { 703 // Ok! 704 x := GetSome() 705 switch v := x.(type) { 706 case int: 707 return "got int" 708 default: 709 return "got other" 710 } 711 712 // Ok? 713 var id string 714 switch i := objectID.(type) { 715 case int: 716 id = strconv.Itoa(i) 717 case uint32: 718 id = strconv.Itoa(int(i)) 719 case string: 720 id = i 721 } 722 723 // Not ok 724 var b bool 725 switch AnotherVal.(type) { 726 case int: 727 return "a" 728 case string: 729 return "b" 730 } 731 }`), 732 expectedErrorStrings: []string{ 733 reasonTypeSwitchCuddledWithoutUse, 734 }, 735 }, 736 { 737 description: "only cuddle append if appended", 738 code: []byte(`package main 739 740 func main() { 741 var ( 742 someList = []string{} 743 ) 744 745 // Should be OK 746 bar := "baz" 747 someList = append(someList, bar) 748 749 // But not this 750 bar := "baz" 751 someList = append(someList, "notBar") 752 }`), 753 expectedErrorStrings: []string{reasonAppendCuddledWithoutUse}, 754 }, 755 { 756 description: "cuddle expressions to assignments", 757 code: []byte(`package main 758 759 func main() { 760 // This should be OK 761 foo := true 762 someFunc(foo) 763 764 // And not this 765 foo := true 766 someFunc(false) 767 }`), 768 expectedErrorStrings: []string{reasonExprCuddlingNonAssignedVar}, 769 }, 770 { 771 description: "channels and select, no false positives", 772 code: []byte(`package main 773 774 func main() { 775 timeoutCh := time.After(timeout) 776 777 for range make([]int, 10) { 778 select { 779 case <-timeoutCh: 780 return true 781 case <-time.After(10 * time.Millisecond): 782 return false 783 } 784 } 785 }`), 786 }, 787 { 788 description: "switch statements", 789 code: []byte(`package main 790 791 func main() { 792 t := GetT() 793 switch t.GetField() { 794 case 1: 795 return 0 796 case 2: 797 return 1 798 } 799 800 notT := GetX() 801 switch notT { 802 case "x": 803 return 0 804 } 805 }`), 806 expectedErrorStrings: []string{}, 807 }, 808 { 809 description: "select statements", 810 code: []byte(`package main 811 812 func main() { 813 select { 814 case <-time.After(1*time.Second): 815 return "1s" 816 default: 817 return "are we there yet?" 818 } 819 }`), 820 }, 821 { 822 description: "branch statements (continue/break)", 823 code: []byte(`package main 824 825 func main() { 826 for { 827 // Allowed if only one statement in block. 828 if true { 829 singleLine := true 830 break 831 } 832 833 // Not allowed for multiple lines 834 if true && false { 835 multiLine := true 836 maybeVar := "var" 837 continue 838 } 839 840 // Multiple lines require newline 841 if false { 842 multiLine := true 843 maybeVar := "var" 844 845 break 846 } 847 } 848 }`), 849 expectedErrorStrings: []string{ 850 "branch statements should not be cuddled if block has more than two lines", 851 }, 852 }, 853 { 854 description: "append", 855 code: []byte(`package main 856 857 func main() { 858 var ( 859 someList = []string{} 860 ) 861 862 // Should be OK 863 bar := "baz" 864 someList = append(someList, fmt.Sprintf("%s", bar)) 865 866 // Should be OK 867 bar := "baz" 868 someList = append(someList, []string{"foo", bar}...) 869 870 // Should be OK 871 bar := "baz" 872 someList = append(someList, bar) 873 874 // But not this 875 bar := "baz" 876 someList = append(someList, "notBar") 877 878 // Ok 879 biz := "str" 880 whatever := ItsJustAppend(biz) 881 882 // Ok 883 zzz := "str" 884 whatever := SoThisIsOK(biz) 885 }`), 886 expectedErrorStrings: []string{ 887 reasonAppendCuddledWithoutUse, 888 }, 889 }, 890 { 891 description: "support usage if chained", 892 code: []byte(`package main 893 894 func main() { 895 r := map[string]interface{}{} 896 if err := json.NewDecoder(someReader).Decode(&r); err != nil { 897 return "this should be OK" 898 } 899 }`), 900 }, 901 { 902 description: "support function literals", 903 code: []byte(`package main 904 905 func main() { 906 n := 1 907 f := func() { 908 909 // This violates whitespaces 910 911 // Violates cuddle 912 notThis := false 913 if isThisTested { 914 return 915 916 } 917 } 918 919 f() 920 }`), 921 expectedErrorStrings: []string{ 922 "block should not start with a whitespace", 923 reasonBlockEndsWithWS, 924 reasonOnlyCuddleWithUsedAssign, 925 }, 926 }, 927 { 928 description: "locks", 929 code: []byte(`package main 930 931 func main() { 932 hashFileCache.Lock() 933 out, ok := hashFileCache.m[file] 934 hashFileCache.Unlock() 935 936 mu := &sync.Mutex{} 937 mu.X(y).Z.RLock() 938 x, y := someMap[someKey] 939 mu.RUnlock() 940 }`), 941 }, 942 { 943 description: "append type", 944 code: []byte(`package main 945 946 func main() { 947 s := []string{} 948 949 // Multiple append should be OK 950 s = append(s, "one") 951 s = append(s, "two") 952 s = append(s, "three") 953 954 // Both assigned and called should be allowed to be cuddled. 955 // It's not nice, but they belong' 956 p.defs = append(p.defs, x) 957 def.parseFrom(p) 958 p.defs = append(p.defs, def) 959 def.parseFrom(p) 960 def.parseFrom(p) 961 p.defs = append(p.defs, x) 962 }`), 963 }, 964 { 965 description: "AllowAssignAndCallCuddle", 966 code: []byte(`package main 967 968 func main() { 969 for i := range make([]int, 10) { 970 fmt.Println("x") 971 } 972 x.Calling() 973 }`), 974 expectedErrorStrings: []string{ 975 reasonExpressionCuddledWithBlock, 976 }, 977 }, 978 { 979 description: "indexes in maps and arrays", 980 code: []byte(`package main 981 982 func main() { 983 for i := range make([]int, 10) { 984 key := GetKey() 985 if val, ok := someMap[key]; ok { 986 fmt.Println("ok!") 987 } 988 989 someOtherMap := GetMap() 990 if val, ok := someOtherMap[key]; ok { 991 fmt.Println("ok") 992 } 993 994 someIndex := 3 995 if val := someSlice[someIndex]; val != nil { 996 retunr 997 } 998 } 999 }`), 1000 }, 1001 { 1002 description: "splice slice, concat key", 1003 code: []byte(`package main 1004 1005 func main() { 1006 start := 0 1007 if v := aSlice[start:3]; v { 1008 fmt.Println("") 1009 } 1010 1011 someKey := "str" 1012 if v, ok := someMap[obtain(someKey)+"str"]; ok { 1013 fmt.Println("Hey there") 1014 } 1015 1016 end := 10 1017 if v := arr[3:notEnd]; !v { 1018 // Error 1019 } 1020 1021 notKey := "str" 1022 if v, ok := someMap[someKey]; ok { 1023 // Error 1024 } 1025 }`), 1026 expectedErrorStrings: []string{ 1027 reasonOnlyCuddleWithUsedAssign, 1028 reasonOnlyCuddleWithUsedAssign, 1029 }, 1030 }, 1031 { 1032 description: "multiline case statements", 1033 code: []byte(`package main 1034 1035 func main() { 1036 switch { 1037 case true, 1038 false: 1039 fmt.Println("ok") 1040 case true || 1041 false: 1042 fmt.Println("ok") 1043 case true, false: 1044 fmt.Println("ok") 1045 case true || false: 1046 fmt.Println("ok") 1047 case true, 1048 false: 1049 1050 fmt.Println("starting whitespace multiline case") 1051 case true || 1052 false: 1053 1054 fmt.Println("starting whitespace multiline case") 1055 case true, 1056 false: 1057 fmt.Println("ending whitespace multiline case") 1058 1059 case true, 1060 false: 1061 fmt.Println("last case should also fail") 1062 1063 } 1064 }`), 1065 expectedErrorStrings: []string{ 1066 reasonBlockEndsWithWS, 1067 reasonBlockStartsWithWS, 1068 reasonBlockStartsWithWS, 1069 }, 1070 }, 1071 { 1072 description: "allow http body close best practice", 1073 code: []byte(`package main 1074 1075 func main() { 1076 resp, err := client.Do(req) 1077 if err != nil { 1078 return err 1079 } 1080 defer resp.Body.Close() 1081 }`), 1082 }, 1083 { 1084 description: "multiple go statements can be cuddled", 1085 code: []byte(`package main 1086 1087 func main() { 1088 t1 := NewT() 1089 t2 := NewT() 1090 t3 := NewT() 1091 1092 go t1() 1093 go t2() 1094 go t3() 1095 1096 multiCuddle1 := NewT() 1097 multiCuddle2 := NewT() 1098 go multiCuddle2() 1099 1100 multiCuddle3 := NeT() 1101 go multiCuddle1() 1102 1103 t4 := NewT() 1104 t5 := NewT() 1105 go t5() 1106 go t4() 1107 }`), 1108 expectedErrorStrings: []string{ 1109 reasonOneCuddleBeforeGo, 1110 reasonGoFuncWithoutAssign, 1111 reasonOneCuddleBeforeGo, 1112 }, 1113 }, 1114 { 1115 description: "boilerplate", 1116 code: []byte(`package main 1117 1118 func main() { 1119 counter := 0 1120 if somethingTrue { 1121 counter++ 1122 } 1123 1124 counterTwo := 0 1125 if somethingTrue { 1126 counterTwo-- 1127 } 1128 1129 notCounter := 0 1130 if somethingTrue { 1131 counter-- 1132 } 1133 }`), 1134 expectedErrorStrings: []string{ 1135 reasonOnlyCuddleWithUsedAssign, 1136 }, 1137 }, 1138 { 1139 description: "ensure blocks are found and checked for errors", 1140 code: []byte(`package main 1141 1142 func main() { 1143 var foo string 1144 1145 x := func() { 1146 var err error 1147 foo = "1" 1148 }() 1149 1150 x := func() { 1151 var err error 1152 foo = "1" 1153 } 1154 1155 func() { 1156 var err error 1157 foo = "1" 1158 }() 1159 1160 func() { 1161 var err error 1162 foo = "1" 1163 } 1164 1165 func() { 1166 func() { 1167 return func() { 1168 var err error 1169 foo = "1" 1170 } 1171 }() 1172 } 1173 1174 var x error 1175 foo, err := func() { return "", nil } 1176 1177 defer func() { 1178 var err error 1179 foo = "1" 1180 }() 1181 1182 go func() { 1183 var err error 1184 foo = "1" 1185 }() 1186 }`), 1187 expectedErrorStrings: []string{ 1188 reasonAssignsCuddleAssign, 1189 reasonAssignsCuddleAssign, 1190 reasonAssignsCuddleAssign, 1191 reasonAssignsCuddleAssign, 1192 reasonAssignsCuddleAssign, 1193 reasonAssignsCuddleAssign, 1194 reasonAssignsCuddleAssign, 1195 reasonAssignsCuddleAssign, 1196 }, 1197 }, 1198 } 1199 1200 for _, tc := range cases { 1201 t.Run(tc.description, func(t *testing.T) { 1202 if tc.skip { 1203 t.Skip("not implemented") 1204 } 1205 1206 p := NewProcessor() 1207 p.process("unit-test", tc.code) 1208 1209 require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found") 1210 1211 for i := range tc.expectedErrorStrings { 1212 assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found") 1213 } 1214 }) 1215 } 1216} 1217 1218func TestWithConfig(t *testing.T) { 1219 // This test is used to simulate code to perform TDD. This part should never 1220 // be committed with any test. 1221 cases := []struct { 1222 description string 1223 code []byte 1224 expectedErrorStrings []string 1225 customConfig *Configuration 1226 }{ 1227 { 1228 description: "AllowAssignAndCallCuddle", 1229 code: []byte(`package main 1230 1231 func main() { 1232 p.token(':') 1233 d.StartBit = p.uint() 1234 p.token('|') 1235 d.Size = p.uint() 1236 }`), 1237 customConfig: &Configuration{ 1238 AllowAssignAndCallCuddle: true, 1239 }, 1240 }, 1241 { 1242 description: "multi line assignment ok when enabled", 1243 code: []byte(`package main 1244 1245 func main() { 1246 // This should not be OK since the assignment is split at 1247 // multiple rows. 1248 err := SomeFunc( 1249 "taking multiple values", 1250 "suddendly not a single line", 1251 ) 1252 if err != nil { 1253 fmt.Println("denied") 1254 } 1255 }`), 1256 customConfig: &Configuration{ 1257 AllowMultiLineAssignCuddle: false, 1258 }, 1259 expectedErrorStrings: []string{reasonOnlyCuddleIfWithAssign}, 1260 }, 1261 { 1262 description: "multi line assignment not ok when disabled", 1263 code: []byte(`package main 1264 1265 func main() { 1266 // This should not be OK since the assignment is split at 1267 // multiple rows. 1268 err := SomeFunc( 1269 "taking multiple values", 1270 "suddenly not a single line", 1271 ) 1272 if err != nil { 1273 fmt.Println("accepted") 1274 } 1275 1276 // This should still fail since it's not the same variable 1277 noErr := SomeFunc( 1278 "taking multiple values", 1279 "suddenly not a single line", 1280 ) 1281 if err != nil { 1282 fmt.Println("rejected") 1283 } 1284 }`), 1285 customConfig: &Configuration{ 1286 AllowMultiLineAssignCuddle: true, 1287 }, 1288 expectedErrorStrings: []string{reasonOnlyCuddleWithUsedAssign}, 1289 }, 1290 { 1291 description: "strict append", 1292 code: []byte(`package main 1293 1294 func main() { 1295 x := []string{} 1296 y := "not in x" 1297 x = append(x, "not y") // Should not be allowed 1298 1299 z := "in x" 1300 x = append(x, z) // Should be allowed 1301 1302 m := transform(z) 1303 x := append(x, z) // Should be allowed 1304 }`), 1305 customConfig: &Configuration{ 1306 StrictAppend: true, 1307 }, 1308 expectedErrorStrings: []string{reasonAppendCuddledWithoutUse}, 1309 }, 1310 { 1311 description: "allow cuddle var", 1312 code: []byte(`package main 1313 1314 func main() { 1315 var t bool 1316 var err error 1317 1318 var t = true 1319 if t { 1320 fmt.Println("x") 1321 } 1322 }`), 1323 customConfig: &Configuration{ 1324 AllowCuddleDeclaration: true, 1325 }, 1326 }, 1327 { 1328 description: "support to end blocks with a comment", 1329 code: []byte(`package main 1330 1331 func main() { 1332 for i := range make([]int, 10) { 1333 fmt.Println("x") 1334 // This is OK 1335 } 1336 1337 for i := range make([]int, 10) { 1338 fmt.Println("x") 1339 // Pad 1340 // With empty lines 1341 // 1342 // This is OK 1343 } 1344 1345 for i := range make([]int, 10) { 1346 fmt.Println("x") 1347 1348 // This is NOT OK 1349 } 1350 1351 for i := range make([]int, 10) { 1352 fmt.Println("x") 1353 // This is NOT OK 1354 1355 } 1356 1357 for i := range make([]int, 10) { 1358 fmt.Println("x") 1359 /* Block comment one line OK */ 1360 } 1361 1362 for i := range make([]int, 10) { 1363 fmt.Println("x") 1364 /* 1365 Block comment one multiple lines OK 1366 */ 1367 } 1368 1369 switch { 1370 case true: 1371 fmt.Println("x") 1372 // This is OK 1373 case false: 1374 fmt.Println("y") 1375 // This is OK 1376 } 1377 }`), 1378 customConfig: &Configuration{ 1379 AllowTrailingComment: true, 1380 }, 1381 expectedErrorStrings: []string{ 1382 reasonBlockEndsWithWS, 1383 reasonBlockEndsWithWS, 1384 }, 1385 }, 1386 { 1387 description: "case blocks can end with or without newlines and comments", 1388 code: []byte(`package main 1389 1390 func main() { 1391 switch "x" { 1392 case "a correct": // Test 1393 fmt.Println("1") 1394 fmt.Println("1") 1395 case "a correct": 1396 fmt.Println("1") 1397 fmt.Println("2") 1398 1399 case "b correct": 1400 fmt.Println("1") 1401 case "b correct": 1402 fmt.Println("1") 1403 1404 case "b bad": 1405 fmt.Println("1") 1406 // I'm not allowed' 1407 case "b bad": 1408 fmt.Println("1") 1409 // I'm not allowed' 1410 1411 case "no false positives for if": 1412 fmt.Println("checking") 1413 1414 // Some comment 1415 if 1 < 2 { 1416 // Another comment 1417 if 2 > 1 { 1418 fmt.Println("really sure") 1419 } 1420 } 1421 case "end": // This is a case 1422 fmt.Println("4") 1423 } 1424 }`), 1425 customConfig: &Configuration{ 1426 ForceCaseTrailingWhitespaceLimit: 0, 1427 AllowTrailingComment: false, 1428 }, 1429 }, 1430 { 1431 description: "enforce newline at size 3", 1432 code: []byte(`package main 1433 1434 func main() { 1435 switch "x" { 1436 case "a correct": // Test 1437 fmt.Println("1") 1438 case "a ok": 1439 fmt.Println("1") 1440 1441 case "b correct": 1442 fmt.Println("1") 1443 fmt.Println("1") 1444 fmt.Println("1") 1445 1446 case "b bad": 1447 fmt.Println("1") 1448 fmt.Println("1") 1449 fmt.Println("1") 1450 case "b correct": 1451 fmt.Println("1") 1452 fmt.Println("1") 1453 fmt.Println("1") 1454 // This is OK 1455 1456 case "b bad": 1457 fmt.Println("1") 1458 fmt.Println("1") 1459 fmt.Println("1") 1460 // This is not OK, no whitespace 1461 case "b ok": 1462 fmt.Println("1") 1463 fmt.Println("1") 1464 fmt.Println("1") 1465 /* 1466 This is not OK 1467 Multiline but no whitespace 1468 */ 1469 case "end": // This is a case 1470 fmt.Println("4") 1471 } 1472 }`), 1473 customConfig: &Configuration{ 1474 ForceCaseTrailingWhitespaceLimit: 3, 1475 AllowTrailingComment: true, 1476 }, 1477 expectedErrorStrings: []string{ 1478 reasonCaseBlockTooCuddly, 1479 reasonCaseBlockTooCuddly, 1480 reasonCaseBlockTooCuddly, 1481 }, 1482 }, 1483 { 1484 description: "must cuddle error checks with the error assignment", 1485 customConfig: &Configuration{ 1486 ForceCuddleErrCheckAndAssign: true, 1487 ErrorVariableNames: []string{"err"}, 1488 }, 1489 code: []byte(`package main 1490 1491 import "errors" 1492 1493 func main() { 1494 err := errors.New("bad thing") 1495 1496 if err != nil { 1497 fmt.Println("I'm OK") 1498 } 1499 }`), 1500 expectedErrorStrings: []string{reasonMustCuddleErrCheck}, 1501 }, 1502 { 1503 description: "must cuddle error checks with the error assignment multivalue", 1504 customConfig: &Configuration{ 1505 AllowMultiLineAssignCuddle: true, 1506 ForceCuddleErrCheckAndAssign: true, 1507 ErrorVariableNames: []string{"err"}, 1508 }, 1509 code: []byte(`package main 1510 1511 import "errors" 1512 1513 func main() { 1514 b, err := FSByte(useLocal, name) 1515 1516 if err != nil { 1517 panic(err) 1518 } 1519 }`), 1520 expectedErrorStrings: []string{reasonMustCuddleErrCheck}, 1521 }, 1522 { 1523 description: "must cuddle error checks with the error assignment only on assignment", 1524 customConfig: &Configuration{ 1525 ForceCuddleErrCheckAndAssign: true, 1526 ErrorVariableNames: []string{"err"}, 1527 }, 1528 code: []byte(`package main 1529 1530 import "errors" 1531 1532 func main() { 1533 var ( 1534 err error 1535 once sync.Once 1536 ) 1537 1538 once.Do(func() { 1539 err = ProduceError() 1540 }) 1541 1542 if err != nil { 1543 return nil, err 1544 } 1545 }`), 1546 expectedErrorStrings: []string{}, 1547 }, 1548 { 1549 description: "must cuddle error checks with the error assignment multivalue NoError", 1550 customConfig: &Configuration{ 1551 AllowMultiLineAssignCuddle: true, 1552 ForceCuddleErrCheckAndAssign: true, 1553 ErrorVariableNames: []string{"err"}, 1554 }, 1555 code: []byte(`package main 1556 1557 import "errors" 1558 1559 func main() { 1560 b, err := FSByte(useLocal, name) 1561 if err != nil { 1562 panic(err) 1563 } 1564 }`), 1565 expectedErrorStrings: []string{}, 1566 }, 1567 { 1568 description: "must cuddle error checks with the error assignment known err", 1569 customConfig: &Configuration{ 1570 AllowMultiLineAssignCuddle: true, 1571 ForceCuddleErrCheckAndAssign: true, 1572 ErrorVariableNames: []string{"err"}, 1573 }, 1574 code: []byte(`package main 1575 1576 import "errors" 1577 1578 func main() { 1579 result, err := FetchSomething() 1580 1581 if err == sql.ErrNoRows { 1582 return []Model{} 1583 } 1584 }`), 1585 expectedErrorStrings: []string{reasonMustCuddleErrCheck}, 1586 }, 1587 { 1588 description: "err-check cuddle enforcement doesn't generate false-positives.", 1589 customConfig: &Configuration{ 1590 AllowMultiLineAssignCuddle: true, 1591 ForceCuddleErrCheckAndAssign: true, 1592 ErrorVariableNames: []string{"err"}, 1593 }, 1594 code: []byte(`package main 1595 1596 import "errors" 1597 1598 func main() { 1599 result, err := FetchSomething() 1600 if err == sql.ErrNoRows { 1601 return []Model{} 1602 } 1603 1604 foo := generateFoo() 1605 if foo == "bar" { 1606 handleBar() 1607 } 1608 1609 var baz []string 1610 1611 err = loadStuff(&baz) 1612 if err != nil{ 1613 return nil 1614 } 1615 1616 if err := ProduceError(); err != nil { 1617 return err 1618 } 1619 1620 return baz 1621 }`), 1622 expectedErrorStrings: []string{}, 1623 }, 1624 { 1625 description: "allow separated leading comment", 1626 customConfig: &Configuration{ 1627 AllowSeparatedLeadingComment: true, 1628 }, 1629 code: []byte(`package main 1630 1631 func main() { 1632 // These blocks should not generate error 1633 func () { 1634 // Comment 1635 1636 // Comment 1637 fmt.Println("Hello, World") 1638 } 1639 1640 func () { 1641 /* 1642 Multiline 1643 */ 1644 1645 /* 1646 Multiline 1647 */ 1648 fmt.Println("Hello, World") 1649 } 1650 1651 func () { 1652 /* 1653 Multiline 1654 */ 1655 1656 // Comment 1657 fmt.Println("Hello, World") 1658 } 1659 1660 func () { 1661 // Comment 1662 1663 /* 1664 Multiline 1665 */ 1666 fmt.Println("Hello, World") 1667 } 1668 1669 func () { // Comment 1670 /* 1671 Multiline 1672 */ 1673 fmt.Println("Hello, World") 1674 } 1675 }`), 1676 }, 1677 { 1678 description: "only warn about cuddling errors if it's an expression above", 1679 customConfig: &Configuration{ 1680 ForceCuddleErrCheckAndAssign: true, 1681 ErrorVariableNames: []string{"err"}, 1682 }, 1683 code: []byte(`package main 1684 1685 var ErrSomething error 1686 1687 func validateErr(err error) { 1688 if err == nil { 1689 return 1690 } 1691 1692 if errors.Is(err, ErrSomething) { 1693 return 1694 } 1695 1696 // Should be valid since the if actually is cuddled so the error 1697 // check assumes something right is going on here. If 1698 // anotherThingToCheck wasn't used in the if statement we would 1699 // get a regular if cuddle error. 1700 anotherThingToCheck := someFunc() 1701 if multiCheck(anotherThingToCheck, err) { 1702 fmt.Println("this must be OK, err is not assigned above") 1703 } 1704 1705 notUsedInNextIf := someFunc() 1706 1707 if multiCheck(anotherThingToCheck, err) { 1708 fmt.Println("this should not warn, we didn't assign err above") 1709 } 1710 1711 // This fails under the cuddle if rule. 1712 if err != nil { 1713 return 1714 } 1715 if !errors.Is(err, ErrSomething) { 1716 return 1717 } 1718 }`), 1719 expectedErrorStrings: []string{reasonOnlyCuddleIfWithAssign}, 1720 }, 1721 } 1722 1723 for _, tc := range cases { 1724 t.Run(tc.description, func(t *testing.T) { 1725 p := NewProcessor() 1726 if tc.customConfig != nil { 1727 p = NewProcessorWithConfig(*tc.customConfig) 1728 } 1729 1730 p.process("unit-test", tc.code) 1731 require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found") 1732 1733 for i := range tc.expectedErrorStrings { 1734 assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found") 1735 } 1736 }) 1737 } 1738} 1739 1740func TestTODO(t *testing.T) { 1741 // This test is used to simulate code to perform TDD. This part should never 1742 // be committed with any test. 1743 cases := []struct { 1744 description string 1745 code []byte 1746 expectedErrorStrings []string 1747 customConfig *Configuration 1748 }{ 1749 { 1750 description: "boilerplate", 1751 code: []byte(`package main 1752 1753 func main() { 1754 for i := range make([]int, 10) { 1755 fmt.Println("x") 1756 } 1757 }`), 1758 }, 1759 } 1760 1761 for _, tc := range cases { 1762 t.Run(tc.description, func(t *testing.T) { 1763 p := NewProcessor() 1764 if tc.customConfig != nil { 1765 p = NewProcessorWithConfig(*tc.customConfig) 1766 } 1767 1768 p.process("unit-test", tc.code) 1769 1770 t.Logf("WARNINGS: %s", p.warnings) 1771 1772 for _, r := range p.result { 1773 fmt.Println(r.String()) 1774 } 1775 1776 require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found") 1777 1778 for i := range tc.expectedErrorStrings { 1779 assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found") 1780 } 1781 }) 1782 } 1783} 1784