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