1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+'use strict';
9
+
10
+const ESLintTester = require('eslint').RuleTester;
11
+const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
12
+const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['exhaustive-deps'];
13
+
14
+ESLintTester.setDefaultConfig({
15
+ parser: 'babel-eslint',
16
+ parserOptions: {
17
+ ecmaVersion: 6,
18
+ sourceType: 'module',
19
+ },
20
+});
21
+
22
+// ***************************************************
23
+// For easier local testing, you can add to any case:
24
+// {
25
+// skip: true,
26
+// --or--
27
+// only: true,
28
+// ...
29
+// }
30
+// ***************************************************
31
+
32
+const tests = {
33
+ valid: [
34
+ {
35
+ code: `
36
+ function MyComponent() {
37
+ const local = 42;
38
+ useEffect(() => {
39
+ console.log(local);
40
+ });
41
+ }
42
+ `,
43
+ },
44
+ {
45
+ code: `
46
+ function MyComponent() {
47
+ useEffect(() => {
48
+ const local = 42;
49
+ console.log(local);
50
+ }, []);
51
+ }
52
+ `,
53
+ },
54
+ {
55
+ code: `
56
+ function MyComponent() {
57
+ const local = 42;
58
+ useEffect(() => {
59
+ console.log(local);
60
+ }, [local]);
61
+ }
62
+ `,
63
+ },
64
+ {
65
+ // OK because `props` wasn't defined.
66
+ // We don't technically know if `props` is supposed
67
+ // to be an import that hasn't been added yet, or
68
+ // a component-level variable. Ignore it until it
69
+ // gets defined (a different rule would flag it anyway).
70
+ code: `
71
+ function MyComponent() {
72
+ useEffect(() => {
73
+ console.log(props.foo);
74
+ }, []);
75
+ }
76
+ `,
77
+ },
78
+ {
79
+ code: `
80
+ function MyComponent() {
81
+ const local1 = 42;
82
+ {
83
+ const local2 = 42;
84
+ useEffect(() => {
85
+ console.log(local1);
86
+ console.log(local2);
87
+ });
88
+ }
89
+ }
90
+ `,
91
+ },
92
+ {
93
+ code: `
94
+ function MyComponent() {
95
+ const local1 = 42;
96
+ {
97
+ const local2 = 42;
98
+ useEffect(() => {
99
+ console.log(local1);
100
+ console.log(local2);
101
+ }, [local1, local2]);
102
+ }
103
+ }
104
+ `,
105
+ },
106
+ {
107
+ code: `
108
+ function MyComponent() {
109
+ const local1 = 42;
110
+ function MyNestedComponent() {
111
+ const local2 = 42;
112
+ useEffect(() => {
113
+ console.log(local1);
114
+ console.log(local2);
115
+ }, [local2]);
116
+ }
117
+ }
118
+ `,
119
+ },
120
+ {
121
+ code: `
122
+ function MyComponent() {
123
+ const local = 42;
124
+ useEffect(() => {
125
+ console.log(local);
126
+ console.log(local);
127
+ }, [local]);
128
+ }
129
+ `,
130
+ },
131
+ {
132
+ code: `
133
+ function MyComponent() {
134
+ useEffect(() => {
135
+ console.log(unresolved);
136
+ }, []);
137
+ }
138
+ `,
139
+ },
140
+ {
141
+ code: `
142
+ function MyComponent() {
143
+ const local = 42;
144
+ useEffect(() => {
145
+ console.log(local);
146
+ }, [,,,local,,,]);
147
+ }
148
+ `,
149
+ },
150
+ {
151
+ code: `
152
+ // Regression test
153
+ function MyComponent({ foo }) {
154
+ useEffect(() => {
155
+ console.log(foo.length);
156
+ }, [foo]);
157
+ }
158
+ `,
159
+ },
160
+ {
161
+ code: `
162
+ // Regression test
163
+ function MyComponent({ foo }) {
164
+ useEffect(() => {
165
+ console.log(foo.length);
166
+ console.log(foo.slice(0));
167
+ }, [foo]);
168
+ }
169
+ `,
170
+ },
171
+ {
172
+ code: `
173
+ // Regression test
174
+ function MyComponent({ history }) {
175
+ useEffect(() => {
176
+ return history.listen();
177
+ }, [history]);
178
+ }
179
+ `,
180
+ },
181
+ {
182
+ // TODO: we might want to forbid dot-access in deps.
183
+ code: `
184
+ function MyComponent(props) {
185
+ useEffect(() => {
186
+ console.log(props.foo);
187
+ }, [props.foo]);
188
+ }
189
+ `,
190
+ },
191
+ {
192
+ // TODO: we might want to forbid dot-access in deps.
193
+ code: `
194
+ function MyComponent(props) {
195
+ useEffect(() => {
196
+ console.log(props.foo);
197
+ console.log(props.bar);
198
+ }, [props.bar, props.foo]);
199
+ }
200
+ `,
201
+ },
202
+ {
203
+ // TODO: we might want to forbid dot-access in deps.
204
+ code: `
205
+ function MyComponent(props) {
206
+ useEffect(() => {
207
+ console.log(props.foo);
208
+ console.log(props.bar);
209
+ }, [props.foo, props.bar]);
210
+ }
211
+ `,
212
+ },
213
+ {
214
+ // TODO: we might want to forbid dot-access in deps.
215
+ code: `
216
+ function MyComponent(props) {
217
+ const local = 42;
218
+ useEffect(() => {
219
+ console.log(props.foo);
220
+ console.log(props.bar);
221
+ console.log(local);
222
+ }, [props.foo, props.bar, local]);
223
+ }
224
+ `,
225
+ },
226
+ {
227
+ // TODO: we might want to warn "props.foo"
228
+ // is extraneous because we already have "props".
229
+ code: `
230
+ function MyComponent(props) {
231
+ const local = 42;
232
+ useEffect(() => {
233
+ console.log(props.foo);
234
+ console.log(props.bar);
235
+ }, [props, props.foo]);
236
+ }
237
+ `,
238
+ },
239
+ {
240
+ code: `
241
+ function MyComponent(props) {
242
+ useCustomEffect(() => {
243
+ console.log(props.foo);
244
+ });
245
+ }
246
+ `,
247
+ options: [{additionalHooks: 'useCustomEffect'}],
248
+ },
249
+ {
250
+ // TODO: we might want to forbid dot-access in deps.
251
+ code: `
252
+ function MyComponent(props) {
253
+ useCustomEffect(() => {
254
+ console.log(props.foo);
255
+ }, [props.foo]);
256
+ }
257
+ `,
258
+ options: [{additionalHooks: 'useCustomEffect'}],
259
+ },
260
+ {
261
+ code: `
262
+ // Valid because we don't care about hooks outside of components.
263
+ const local = 42;
264
+ useEffect(() => {
265
+ console.log(local);
266
+ }, []);
267
+ `,
268
+ },
269
+ {
270
+ code: `
271
+ // Valid because we don't care about hooks outside of components.
272
+ const local1 = 42;
273
+ {
274
+ const local2 = 42;
275
+ useEffect(() => {
276
+ console.log(local1);
277
+ console.log(local2);
278
+ }, []);
279
+ }
280
+ `,
281
+ },
282
+ {
283
+ code: `
284
+ function MyComponent() {
285
+ const ref = useRef();
286
+ useEffect(() => {
287
+ console.log(ref.current);
288
+ }, [ref]);
289
+ }
290
+ `,
291
+ },
292
+ {
293
+ code: `
294
+ function MyComponent() {
295
+ const ref = useRef();
296
+ useEffect(() => {
297
+ console.log(ref.current);
298
+ }, []);
299
+ }
300
+ `,
301
+ },
302
+ {
303
+ code: `
304
+ function MyComponent({ maybeRef2 }) {
305
+ const definitelyRef1 = useRef();
306
+ const definitelyRef2 = useRef();
307
+ const maybeRef1 = useSomeOtherRefyThing();
308
+ const [state1, setState1] = useState();
309
+ const [state2, setState2] = React.useState();
310
+ const [state3, dispatch1] = useReducer();
311
+ const [state4, dispatch2] = React.useReducer();
312
+ const [state5, maybeSetState] = useFunnyState();
313
+ const [state6, maybeDispatch] = useFunnyReducer();
314
+ function mySetState() {}
315
+ function myDispatch() {}
316
+
317
+ useEffect(() => {
318
+ // Known to be static
319
+ console.log(definitelyRef1.current);
320
+ console.log(definitelyRef2.current);
321
+ console.log(maybeRef1.current);
322
+ console.log(maybeRef2.current);
323
+ setState1();
324
+ setState2();
325
+ dispatch1();
326
+ dispatch2();
327
+
328
+ // Dynamic
329
+ console.log(state1);
330
+ console.log(state2);
331
+ console.log(state3);
332
+ console.log(state4);
333
+ console.log(state5);
334
+ console.log(state6);
335
+ mySetState();
336
+ myDispatch();
337
+
338
+ // Not sure; assume dynamic
339
+ maybeSetState();
340
+ maybeDispatch();
341
+ }, [
342
+ // Dynamic
343
+ state1, state2, state3, state4, state5, state6,
344
+ maybeRef1, maybeRef2,
345
+
346
+ // Not sure; assume dynamic
347
+ mySetState, myDispatch,
348
+ maybeSetState, maybeDispatch
349
+
350
+ // In this test, we don't specify static deps.
351
+ // That should be okay.
352
+ ]);
353
+ }
354
+ `,
355
+ },
356
+ {
357
+ code: `
358
+ function MyComponent({ maybeRef2 }) {
359
+ const definitelyRef1 = useRef();
360
+ const definitelyRef2 = useRef();
361
+ const maybeRef1 = useSomeOtherRefyThing();
362
+
363
+ const [state1, setState1] = useState();
364
+ const [state2, setState2] = React.useState();
365
+ const [state3, dispatch1] = useReducer();
366
+ const [state4, dispatch2] = React.useReducer();
367
+
368
+ const [state5, maybeSetState] = useFunnyState();
369
+ const [state6, maybeDispatch] = useFunnyReducer();
370
+
371
+ function mySetState() {}
372
+ function myDispatch() {}
373
+
374
+ useEffect(() => {
375
+ // Known to be static
376
+ console.log(definitelyRef1.current);
377
+ console.log(definitelyRef2.current);
378
+ console.log(maybeRef1.current);
379
+ console.log(maybeRef2.current);
380
+ setState1();
381
+ setState2();
382
+ dispatch1();
383
+ dispatch2();
384
+
385
+ // Dynamic
386
+ console.log(state1);
387
+ console.log(state2);
388
+ console.log(state3);
389
+ console.log(state4);
390
+ console.log(state5);
391
+ console.log(state6);
392
+ mySetState();
393
+ myDispatch();
394
+
395
+ // Not sure; assume dynamic
396
+ maybeSetState();
397
+ maybeDispatch();
398
+ }, [
399
+ // Dynamic
400
+ state1, state2, state3, state4, state5, state6,
401
+ maybeRef1, maybeRef2,
402
+
403
+ // Not sure; assume dynamic
404
+ mySetState, myDispatch,
405
+ maybeSetState, maybeDispatch,
406
+
407
+ // In this test, we specify static deps.
408
+ // That should be okay too!
409
+ definitelyRef1, definitelyRef2, setState1, setState2, dispatch1, dispatch2
410
+ ]);
411
+ }
412
+ `,
413
+ },
414
+ {
415
+ code: `
416
+ const MyComponent = forwardRef((props, ref) => {
417
+ useImperativeHandle(ref, () => ({
418
+ focus() {
419
+ alert(props.hello);
420
+ }
421
+ }))
422
+ });
423
+ `,
424
+ },
425
+ {
426
+ code: `
427
+ const MyComponent = forwardRef((props, ref) => {
428
+ useImperativeHandle(ref, () => ({
429
+ focus() {
430
+ alert(props.hello);
431
+ }
432
+ }), [props.hello])
433
+ });
434
+ `,
435
+ },
436
+ ],
437
+ invalid: [
438
+ {
439
+ code: `
440
+ function MyComponent() {
441
+ const local = 42;
442
+ useEffect(() => {
443
+ console.log(local);
444
+ }, []);
445
+ }
446
+ `,
447
+ output: `
448
+ function MyComponent() {
449
+ const local = 42;
450
+ useEffect(() => {
451
+ console.log(local);
452
+ }, [local]);
453
+ }
454
+ `,
455
+ errors: [
456
+ "React Hook useEffect has a missing dependency: 'local'. " +
457
+ 'Either include it or remove the dependency array.',
458
+ ],
459
+ },
460
+ {
461
+ code: `
462
+ // Regression test
463
+ function MyComponent() {
464
+ const local = 42;
465
+ useEffect(() => {
466
+ if (true) {
467
+ console.log(local);
468
+ }
469
+ }, []);
470
+ }
471
+ `,
472
+ output: `
473
+ // Regression test
474
+ function MyComponent() {
475
+ const local = 42;
476
+ useEffect(() => {
477
+ if (true) {
478
+ console.log(local);
479
+ }
480
+ }, [local]);
481
+ }
482
+ `,
483
+ errors: [
484
+ "React Hook useEffect has a missing dependency: 'local'. " +
485
+ 'Either include it or remove the dependency array.',
486
+ ],
487
+ },
488
+ {
489
+ code: `
490
+ // Regression test
491
+ function MyComponent() {
492
+ const local = 42;
493
+ useEffect(() => {
494
+ try {
495
+ console.log(local);
496
+ } finally {}
497
+ }, []);
498
+ }
499
+ `,
500
+ output: `
501
+ // Regression test
502
+ function MyComponent() {
503
+ const local = 42;
504
+ useEffect(() => {
505
+ try {
506
+ console.log(local);
507
+ } finally {}
508
+ }, [local]);
509
+ }
510
+ `,
511
+ errors: [
512
+ "React Hook useEffect has a missing dependency: 'local'. " +
513
+ 'Either include it or remove the dependency array.',
514
+ ],
515
+ },
516
+ {
517
+ code: `
518
+ // Regression test
519
+ function MyComponent() {
520
+ const local = 42;
521
+ useEffect(() => {
522
+ function inner() {
523
+ console.log(local);
524
+ }
525
+ inner();
526
+ }, []);
527
+ }
528
+ `,
529
+ output: `
530
+ // Regression test
531
+ function MyComponent() {
532
+ const local = 42;
533
+ useEffect(() => {
534
+ function inner() {
535
+ console.log(local);
536
+ }
537
+ inner();
538
+ }, [local]);
539
+ }
540
+ `,
541
+ errors: [
542
+ "React Hook useEffect has a missing dependency: 'local'. " +
543
+ 'Either include it or remove the dependency array.',
544
+ ],
545
+ },
546
+ {
547
+ code: `
548
+ function MyComponent() {
549
+ const local1 = 42;
550
+ {
551
+ const local2 = 42;
552
+ useEffect(() => {
553
+ console.log(local1);
554
+ console.log(local2);
555
+ }, []);
556
+ }
557
+ }
558
+ `,
559
+ output: `
560
+ function MyComponent() {
561
+ const local1 = 42;
562
+ {
563
+ const local2 = 42;
564
+ useEffect(() => {
565
+ console.log(local1);
566
+ console.log(local2);
567
+ }, [local1, local2]);
568
+ }
569
+ }
570
+ `,
571
+ errors: [
572
+ "React Hook useEffect has missing dependencies: 'local1' and 'local2'. " +
573
+ 'Either include them or remove the dependency array.',
574
+ ],
575
+ },
576
+ {
577
+ code: `
578
+ function MyComponent() {
579
+ const local1 = 42;
580
+ const local2 = 42;
581
+ useEffect(() => {
582
+ console.log(local1);
583
+ console.log(local2);
584
+ }, [local1]);
585
+ }
586
+ `,
587
+ output: `
588
+ function MyComponent() {
589
+ const local1 = 42;
590
+ const local2 = 42;
591
+ useEffect(() => {
592
+ console.log(local1);
593
+ console.log(local2);
594
+ }, [local1, local2]);
595
+ }
596
+ `,
597
+ errors: [
598
+ "React Hook useEffect has a missing dependency: 'local2'. " +
599
+ 'Either include it or remove the dependency array.',
600
+ ],
601
+ },
602
+ {
603
+ code: `
604
+ function MyComponent() {
605
+ const local1 = 42;
606
+ const local2 = 42;
607
+ useEffect(() => {
608
+ console.log(local1);
609
+ }, [local1, local2]);
610
+ }
611
+ `,
612
+ output: `
613
+ function MyComponent() {
614
+ const local1 = 42;
615
+ const local2 = 42;
616
+ useEffect(() => {
617
+ console.log(local1);
618
+ }, [local1]);
619
+ }
620
+ `,
621
+ errors: [
622
+ "React Hook useEffect has an unnecessary dependency: 'local2'. " +
623
+ 'Either exclude it or remove the dependency array.',
624
+ ],
625
+ },
626
+ {
627
+ // TODO: this case is weird.
628
+ // Maybe it should not consider local1 unused despite component nesting?
629
+ code: `
630
+ function MyComponent() {
631
+ const local1 = 42;
632
+ function MyNestedComponent() {
633
+ const local2 = 42;
634
+ useEffect(() => {
635
+ console.log(local1);
636
+ console.log(local2);
637
+ }, [local1]);
638
+ }
639
+ }
640
+ `,
641
+ output: `
642
+ function MyComponent() {
643
+ const local1 = 42;
644
+ function MyNestedComponent() {
645
+ const local2 = 42;
646
+ useEffect(() => {
647
+ console.log(local1);
648
+ console.log(local2);
649
+ }, [local2]);
650
+ }
651
+ }
652
+ `,
653
+ errors: [
654
+ // Focus on the more important part first (missing dep)
655
+ "React Hook useEffect has a missing dependency: 'local2'. " +
656
+ 'Either include it or remove the dependency array.',
657
+ ],
658
+ },
659
+ {
660
+ code: `
661
+ function MyComponent() {
662
+ const local = 42;
663
+ useEffect(() => {
664
+ console.log(local);
665
+ console.log(local);
666
+ }, []);
667
+ }
668
+ `,
669
+ output: `
670
+ function MyComponent() {
671
+ const local = 42;
672
+ useEffect(() => {
673
+ console.log(local);
674
+ console.log(local);
675
+ }, [local]);
676
+ }
677
+ `,
678
+ errors: [
679
+ "React Hook useEffect has a missing dependency: 'local'. " +
680
+ 'Either include it or remove the dependency array.',
681
+ ],
682
+ },
683
+ {
684
+ code: `
685
+ function MyComponent() {
686
+ const local = 42;
687
+ useEffect(() => {
688
+ console.log(local);
689
+ console.log(local);
690
+ }, [local, local]);
691
+ }
692
+ `,
693
+ output: `
694
+ function MyComponent() {
695
+ const local = 42;
696
+ useEffect(() => {
697
+ console.log(local);
698
+ console.log(local);
699
+ }, [local]);
700
+ }
701
+ `,
702
+ errors: [
703
+ "React Hook useEffect has a duplicate dependency: 'local'. " +
704
+ 'Either omit it or remove the dependency array.',
705
+ ],
706
+ },
707
+ {
708
+ code: `
709
+ function MyComponent() {
710
+ useEffect(() => {}, [local]);
711
+ }
712
+ `,
713
+ output: `
714
+ function MyComponent() {
715
+ useEffect(() => {}, []);
716
+ }
717
+ `,
718
+ errors: [
719
+ "React Hook useEffect has an unnecessary dependency: 'local'. " +
720
+ 'Either exclude it or remove the dependency array.',
721
+ ],
722
+ },
723
+ {
724
+ code: `
725
+ function MyComponent({ history }) {
726
+ useEffect(() => {
727
+ return history.listen();
728
+ }, []);
729
+ }
730
+ `,
731
+ output: `
732
+ function MyComponent({ history }) {
733
+ useEffect(() => {
734
+ return history.listen();
735
+ }, [history]);
736
+ }
737
+ `,
738
+ errors: [
739
+ "React Hook useEffect has a missing dependency: 'history'. " +
740
+ 'Either include it or remove the dependency array.',
741
+ ],
742
+ },
743
+ {
744
+ code: `
745
+ function MyComponent({ history }) {
746
+ useEffect(() => {
747
+ return [
748
+ history.foo.bar[2].dobedo.listen(),
749
+ history.foo.bar().dobedo.listen[2]
750
+ ];
751
+ }, []);
752
+ }
753
+ `,
754
+ output: `
755
+ function MyComponent({ history }) {
756
+ useEffect(() => {
757
+ return [
758
+ history.foo.bar[2].dobedo.listen(),
759
+ history.foo.bar().dobedo.listen[2]
760
+ ];
761
+ }, [history.foo]);
762
+ }
763
+ `,
764
+ errors: [
765
+ "React Hook useEffect has a missing dependency: 'history.foo'. " +
766
+ 'Either include it or remove the dependency array.',
767
+ ],
768
+ },
769
+ {
770
+ code: `
771
+ function MyComponent() {
772
+ const dependencies = [];
773
+ useEffect(() => {}, dependencies);
774
+ }
775
+ `,
776
+ output: `
777
+ function MyComponent() {
778
+ const dependencies = [];
779
+ useEffect(() => {}, dependencies);
780
+ }
781
+ `,
782
+ errors: [
783
+ 'React Hook useEffect has a second argument which is not an array ' +
784
+ "literal. This means we can't statically verify whether you've " +
785
+ 'passed the correct dependencies.',
786
+ ],
787
+ },
788
+ {
789
+ code: `
790
+ function MyComponent() {
791
+ const local = 42;
792
+ const dependencies = [local];
793
+ useEffect(() => {
794
+ console.log(local);
795
+ }, dependencies);
796
+ }
797
+ `,
798
+ // TODO: should this autofix or bail out?
799
+ output: `
800
+ function MyComponent() {
801
+ const local = 42;
802
+ const dependencies = [local];
803
+ useEffect(() => {
804
+ console.log(local);
805
+ }, [local]);
806
+ }
807
+ `,
808
+ errors: [
809
+ 'React Hook useEffect has a second argument which is not an array ' +
810
+ "literal. This means we can't statically verify whether you've " +
811
+ 'passed the correct dependencies.',
812
+ "React Hook useEffect has a missing dependency: 'local'. " +
813
+ 'Either include it or remove the dependency array.',
814
+ ],
815
+ },
816
+ {
817
+ code: `
818
+ function MyComponent() {
819
+ const local = 42;
820
+ const dependencies = [local];
821
+ useEffect(() => {
822
+ console.log(local);
823
+ }, [...dependencies]);
824
+ }
825
+ `,
826
+ // TODO: should this autofix or bail out?
827
+ output: `
828
+ function MyComponent() {
829
+ const local = 42;
830
+ const dependencies = [local];
831
+ useEffect(() => {
832
+ console.log(local);
833
+ }, [local]);
834
+ }
835
+ `,
836
+ errors: [
837
+ "React Hook useEffect has a missing dependency: 'local'. " +
838
+ 'Either include it or remove the dependency array.',
839
+ 'React Hook useEffect has a spread element in its dependency array. ' +
840
+ "This means we can't statically verify whether you've passed the " +
841
+ 'correct dependencies.',
842
+ ],
843
+ },
844
+ {
845
+ code: `
846
+ function MyComponent() {
847
+ const local = 42;
848
+ useEffect(() => {
849
+ console.log(local);
850
+ }, [local, ...dependencies]);
851
+ }
852
+ `,
853
+ output: `
854
+ function MyComponent() {
855
+ const local = 42;
856
+ useEffect(() => {
857
+ console.log(local);
858
+ }, [local, ...dependencies]);
859
+ }
860
+ `,
861
+ errors: [
862
+ 'React Hook useEffect has a spread element in its dependency array. ' +
863
+ "This means we can't statically verify whether you've passed the " +
864
+ 'correct dependencies.',
865
+ ],
866
+ },
867
+ {
868
+ code: `
869
+ function MyComponent() {
870
+ const local = 42;
871
+ useEffect(() => {
872
+ console.log(local);
873
+ }, [computeCacheKey(local)]);
874
+ }
875
+ `,
876
+ // TODO: I'm not sure this is a good idea.
877
+ // Maybe bail out?
878
+ output: `
879
+ function MyComponent() {
880
+ const local = 42;
881
+ useEffect(() => {
882
+ console.log(local);
883
+ }, [local]);
884
+ }
885
+ `,
886
+ errors: [
887
+ "React Hook useEffect has a missing dependency: 'local'. " +
888
+ 'Either include it or remove the dependency array.',
889
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
890
+ 'Extract it to a separate variable so it can be statically checked.',
891
+ ],
892
+ },
893
+ {
894
+ // only: true,
895
+ code: `
896
+ function MyComponent(props) {
897
+ useEffect(() => {
898
+ console.log(props.items[0]);
899
+ }, [props.items[0]]);
900
+ }
901
+ `,
902
+ output: `
903
+ function MyComponent(props) {
904
+ useEffect(() => {
905
+ console.log(props.items[0]);
906
+ }, [props.items]);
907
+ }
908
+ `,
909
+ errors: [
910
+ "React Hook useEffect has a missing dependency: 'props.items'. " +
911
+ 'Either include it or remove the dependency array.',
912
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
913
+ 'Extract it to a separate variable so it can be statically checked.',
914
+ ],
915
+ },
916
+ {
917
+ code: `
918
+ function MyComponent(props) {
919
+ useEffect(() => {
920
+ console.log(props.items[0]);
921
+ }, [props.items, props.items[0]]);
922
+ }
923
+ `,
924
+ // TODO: ideally autofix would remove the bad expression?
925
+ output: `
926
+ function MyComponent(props) {
927
+ useEffect(() => {
928
+ console.log(props.items[0]);
929
+ }, [props.items, props.items[0]]);
930
+ }
931
+ `,
932
+ errors: [
933
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
934
+ 'Extract it to a separate variable so it can be statically checked.',
935
+ ],
936
+ },
937
+ {
938
+ code: `
939
+ function MyComponent({ items }) {
940
+ useEffect(() => {
941
+ console.log(items[0]);
942
+ }, [items[0]]);
943
+ }
944
+ `,
945
+ output: `
946
+ function MyComponent({ items }) {
947
+ useEffect(() => {
948
+ console.log(items[0]);
949
+ }, [items]);
950
+ }
951
+ `,
952
+ errors: [
953
+ "React Hook useEffect has a missing dependency: 'items'. " +
954
+ 'Either include it or remove the dependency array.',
955
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
956
+ 'Extract it to a separate variable so it can be statically checked.',
957
+ ],
958
+ },
959
+ {
960
+ code: `
961
+ function MyComponent({ items }) {
962
+ useEffect(() => {
963
+ console.log(items[0]);
964
+ }, [items, items[0]]);
965
+ }
966
+ `,
967
+ // TODO: ideally autofix would remove the bad expression?
968
+ output: `
969
+ function MyComponent({ items }) {
970
+ useEffect(() => {
971
+ console.log(items[0]);
972
+ }, [items, items[0]]);
973
+ }
974
+ `,
975
+ errors: [
976
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
977
+ 'Extract it to a separate variable so it can be statically checked.',
978
+ ],
979
+ },
980
+ {
981
+ // TODO: need to think more about this case.
982
+ code: `
983
+ function MyComponent() {
984
+ const local = {id: 42};
985
+ useEffect(() => {
986
+ console.log(local);
987
+ }, [local.id]);
988
+ }
989
+ `,
990
+ // TODO: this may not be a good idea.
991
+ output: `
992
+ function MyComponent() {
993
+ const local = {id: 42};
994
+ useEffect(() => {
995
+ console.log(local);
996
+ }, [local]);
997
+ }
998
+ `,
999
+ errors: [
1000
+ "React Hook useEffect has a missing dependency: 'local'. " +
1001
+ 'Either include it or remove the dependency array.',
1002
+ ],
1003
+ },
1004
+ {
1005
+ code: `
1006
+ function MyComponent() {
1007
+ const local = 42;
1008
+ useEffect(() => {
1009
+ console.log(local);
1010
+ }, [local, local]);
1011
+ }
1012
+ `,
1013
+ output: `
1014
+ function MyComponent() {
1015
+ const local = 42;
1016
+ useEffect(() => {
1017
+ console.log(local);
1018
+ }, [local]);
1019
+ }
1020
+ `,
1021
+ errors: [
1022
+ "React Hook useEffect has a duplicate dependency: 'local'. " +
1023
+ 'Either omit it or remove the dependency array.',
1024
+ ],
1025
+ },
1026
+ {
1027
+ code: `
1028
+ function MyComponent() {
1029
+ const local1 = 42;
1030
+ useEffect(() => {
1031
+ const local1 = 42;
1032
+ console.log(local1);
1033
+ }, [local1]);
1034
+ }
1035
+ `,
1036
+ output: `
1037
+ function MyComponent() {
1038
+ const local1 = 42;
1039
+ useEffect(() => {
1040
+ const local1 = 42;
1041
+ console.log(local1);
1042
+ }, []);
1043
+ }
1044
+ `,
1045
+ errors: [
1046
+ "React Hook useEffect has an unnecessary dependency: 'local1'. " +
1047
+ 'Either exclude it or remove the dependency array.',
1048
+ ],
1049
+ },
1050
+ {
1051
+ code: `
1052
+ function MyComponent() {
1053
+ const local1 = 42;
1054
+ useEffect(() => {}, [local1]);
1055
+ }
1056
+ `,
1057
+ output: `
1058
+ function MyComponent() {
1059
+ const local1 = 42;
1060
+ useEffect(() => {}, []);
1061
+ }
1062
+ `,
1063
+ errors: [
1064
+ "React Hook useEffect has an unnecessary dependency: 'local1'. " +
1065
+ 'Either exclude it or remove the dependency array.',
1066
+ ],
1067
+ },
1068
+ {
1069
+ code: `
1070
+ function MyComponent(props) {
1071
+ useEffect(() => {
1072
+ console.log(props.foo);
1073
+ }, []);
1074
+ }
1075
+ `,
1076
+ // TODO: we need to think about ideal output here.
1077
+ // Should it capture by default?
1078
+ output: `
1079
+ function MyComponent(props) {
1080
+ useEffect(() => {
1081
+ console.log(props.foo);
1082
+ }, [props.foo]);
1083
+ }
1084
+ `,
1085
+ errors: [
1086
+ "React Hook useEffect has a missing dependency: 'props.foo'. " +
1087
+ 'Either include it or remove the dependency array.',
1088
+ ],
1089
+ },
1090
+ {
1091
+ code: `
1092
+ function MyComponent(props) {
1093
+ useEffect(() => {
1094
+ console.log(props.foo);
1095
+ console.log(props.bar);
1096
+ }, []);
1097
+ }
1098
+ `,
1099
+ // TODO: we need to think about ideal output here.
1100
+ // Should it capture by default?
1101
+ output: `
1102
+ function MyComponent(props) {
1103
+ useEffect(() => {
1104
+ console.log(props.foo);
1105
+ console.log(props.bar);
1106
+ }, [props.bar, props.foo]);
1107
+ }
1108
+ `,
1109
+ errors: [
1110
+ "React Hook useEffect has missing dependencies: 'props.bar' and 'props.foo'. " +
1111
+ 'Either include them or remove the dependency array.',
1112
+ ],
1113
+ },
1114
+ {
1115
+ code: `
1116
+ function MyComponent(props) {
1117
+ let a, b, c, d, e, f, g;
1118
+ useEffect(() => {
1119
+ console.log(b, e, d, c, a, g, f);
1120
+ }, [c, a, g]);
1121
+ }
1122
+ `,
1123
+ // Don't alphabetize if it wasn't alphabetized in the first place.
1124
+ output: `
1125
+ function MyComponent(props) {
1126
+ let a, b, c, d, e, f, g;
1127
+ useEffect(() => {
1128
+ console.log(b, e, d, c, a, g, f);
1129
+ }, [c, a, g, b, e, d, f]);
1130
+ }
1131
+ `,
1132
+ errors: [
1133
+ "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
1134
+ 'Either include them or remove the dependency array.',
1135
+ ],
1136
+ },
1137
+ {
1138
+ code: `
1139
+ function MyComponent(props) {
1140
+ let a, b, c, d, e, f, g;
1141
+ useEffect(() => {
1142
+ console.log(b, e, d, c, a, g, f);
1143
+ }, [a, c, g]);
1144
+ }
1145
+ `,
1146
+ // Alphabetize if it was alphabetized.
1147
+ output: `
1148
+ function MyComponent(props) {
1149
+ let a, b, c, d, e, f, g;
1150
+ useEffect(() => {
1151
+ console.log(b, e, d, c, a, g, f);
1152
+ }, [a, b, c, d, e, f, g]);
1153
+ }
1154
+ `,
1155
+ errors: [
1156
+ "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
1157
+ 'Either include them or remove the dependency array.',
1158
+ ],
1159
+ },
1160
+ {
1161
+ code: `
1162
+ function MyComponent(props) {
1163
+ let a, b, c, d, e, f, g;
1164
+ useEffect(() => {
1165
+ console.log(b, e, d, c, a, g, f);
1166
+ }, []);
1167
+ }
1168
+ `,
1169
+ // Alphabetize if it was empty.
1170
+ output: `
1171
+ function MyComponent(props) {
1172
+ let a, b, c, d, e, f, g;
1173
+ useEffect(() => {
1174
+ console.log(b, e, d, c, a, g, f);
1175
+ }, [a, b, c, d, e, f, g]);
1176
+ }
1177
+ `,
1178
+ errors: [
1179
+ "React Hook useEffect has missing dependencies: 'a', 'b', 'c', 'd', 'e', 'f', and 'g'. " +
1180
+ 'Either include them or remove the dependency array.',
1181
+ ],
1182
+ },
1183
+ {
1184
+ code: `
1185
+ function MyComponent(props) {
1186
+ const local = 42;
1187
+ useEffect(() => {
1188
+ console.log(props.foo);
1189
+ console.log(props.bar);
1190
+ console.log(local);
1191
+ }, []);
1192
+ }
1193
+ `,
1194
+ // TODO: we need to think about ideal output here.
1195
+ // Should it capture by default?
1196
+ output: `
1197
+ function MyComponent(props) {
1198
+ const local = 42;
1199
+ useEffect(() => {
1200
+ console.log(props.foo);
1201
+ console.log(props.bar);
1202
+ console.log(local);
1203
+ }, [local, props.bar, props.foo]);
1204
+ }
1205
+ `,
1206
+ errors: [
1207
+ "React Hook useEffect has missing dependencies: 'local', 'props.bar', and 'props.foo'. " +
1208
+ 'Either include them or remove the dependency array.',
1209
+ ],
1210
+ },
1211
+ {
1212
+ code: `
1213
+ function MyComponent(props) {
1214
+ const local = 42;
1215
+ useEffect(() => {
1216
+ console.log(props.foo);
1217
+ console.log(props.bar);
1218
+ console.log(local);
1219
+ }, [props]);
1220
+ }
1221
+ `,
1222
+ output: `
1223
+ function MyComponent(props) {
1224
+ const local = 42;
1225
+ useEffect(() => {
1226
+ console.log(props.foo);
1227
+ console.log(props.bar);
1228
+ console.log(local);
1229
+ }, [local, props]);
1230
+ }
1231
+ `,
1232
+ errors: [
1233
+ "React Hook useEffect has a missing dependency: 'local'. " +
1234
+ 'Either include it or remove the dependency array.',
1235
+ ],
1236
+ },
1237
+ {
1238
+ code: `
1239
+ function MyComponent(props) {
1240
+ useEffect(() => {
1241
+ console.log(props.foo);
1242
+ }, []);
1243
+ useCallback(() => {
1244
+ console.log(props.foo);
1245
+ }, []);
1246
+ useMemo(() => {
1247
+ console.log(props.foo);
1248
+ }, []);
1249
+ React.useEffect(() => {
1250
+ console.log(props.foo);
1251
+ }, []);
1252
+ React.useCallback(() => {
1253
+ console.log(props.foo);
1254
+ }, []);
1255
+ React.useMemo(() => {
1256
+ console.log(props.foo);
1257
+ }, []);
1258
+ React.notReactiveHook(() => {
1259
+ console.log(props.foo);
1260
+ }, []);
1261
+ }
1262
+ `,
1263
+ output: `
1264
+ function MyComponent(props) {
1265
+ useEffect(() => {
1266
+ console.log(props.foo);
1267
+ }, [props.foo]);
1268
+ useCallback(() => {
1269
+ console.log(props.foo);
1270
+ }, [props.foo]);
1271
+ useMemo(() => {
1272
+ console.log(props.foo);
1273
+ }, [props.foo]);
1274
+ React.useEffect(() => {
1275
+ console.log(props.foo);
1276
+ }, [props.foo]);
1277
+ React.useCallback(() => {
1278
+ console.log(props.foo);
1279
+ }, [props.foo]);
1280
+ React.useMemo(() => {
1281
+ console.log(props.foo);
1282
+ }, [props.foo]);
1283
+ React.notReactiveHook(() => {
1284
+ console.log(props.foo);
1285
+ }, []);
1286
+ }
1287
+ `,
1288
+ errors: [
1289
+ "React Hook useEffect has a missing dependency: 'props.foo'. " +
1290
+ 'Either include it or remove the dependency array.',
1291
+ "React Hook useCallback has a missing dependency: 'props.foo'. " +
1292
+ 'Either include it or remove the dependency array.',
1293
+ "React Hook useMemo has a missing dependency: 'props.foo'. " +
1294
+ 'Either include it or remove the dependency array.',
1295
+ "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
1296
+ 'Either include it or remove the dependency array.',
1297
+ "React Hook React.useCallback has a missing dependency: 'props.foo'. " +
1298
+ 'Either include it or remove the dependency array.',
1299
+ "React Hook React.useMemo has a missing dependency: 'props.foo'. " +
1300
+ 'Either include it or remove the dependency array.',
1301
+ ],
1302
+ },
1303
+ {
1304
+ code: `
1305
+ function MyComponent(props) {
1306
+ useCustomEffect(() => {
1307
+ console.log(props.foo);
1308
+ }, []);
1309
+ useEffect(() => {
1310
+ console.log(props.foo);
1311
+ }, []);
1312
+ React.useEffect(() => {
1313
+ console.log(props.foo);
1314
+ }, []);
1315
+ React.useCustomEffect(() => {
1316
+ console.log(props.foo);
1317
+ }, []);
1318
+ }
1319
+ `,
1320
+ output: `
1321
+ function MyComponent(props) {
1322
+ useCustomEffect(() => {
1323
+ console.log(props.foo);
1324
+ }, [props.foo]);
1325
+ useEffect(() => {
1326
+ console.log(props.foo);
1327
+ }, [props.foo]);
1328
+ React.useEffect(() => {
1329
+ console.log(props.foo);
1330
+ }, [props.foo]);
1331
+ React.useCustomEffect(() => {
1332
+ console.log(props.foo);
1333
+ }, []);
1334
+ }
1335
+ `,
1336
+ options: [{additionalHooks: 'useCustomEffect'}],
1337
+ errors: [
1338
+ "React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
1339
+ 'Either include it or remove the dependency array.',
1340
+ "React Hook useEffect has a missing dependency: 'props.foo'. " +
1341
+ 'Either include it or remove the dependency array.',
1342
+ "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
1343
+ 'Either include it or remove the dependency array.',
1344
+ ],
1345
+ },
1346
+ {
1347
+ code: `
1348
+ function MyComponent() {
1349
+ const local = 42;
1350
+ useEffect(() => {
1351
+ console.log(local);
1352
+ }, [a ? local : b]);
1353
+ }
1354
+ `,
1355
+ // TODO: should we bail out instead?
1356
+ output: `
1357
+ function MyComponent() {
1358
+ const local = 42;
1359
+ useEffect(() => {
1360
+ console.log(local);
1361
+ }, [local]);
1362
+ }
1363
+ `,
1364
+ errors: [
1365
+ "React Hook useEffect has a missing dependency: 'local'. " +
1366
+ 'Either include it or remove the dependency array.',
1367
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
1368
+ 'Extract it to a separate variable so it can be statically checked.',
1369
+ ],
1370
+ },
1371
+ {
1372
+ code: `
1373
+ function MyComponent() {
1374
+ const local = 42;
1375
+ useEffect(() => {
1376
+ console.log(local);
1377
+ }, [a && local]);
1378
+ }
1379
+ `,
1380
+ // TODO: should we bail out instead?
1381
+ output: `
1382
+ function MyComponent() {
1383
+ const local = 42;
1384
+ useEffect(() => {
1385
+ console.log(local);
1386
+ }, [local]);
1387
+ }
1388
+ `,
1389
+ errors: [
1390
+ "React Hook useEffect has a missing dependency: 'local'. " +
1391
+ 'Either include it or remove the dependency array.',
1392
+ 'React Hook useEffect has a complex expression in the dependency array. ' +
1393
+ 'Extract it to a separate variable so it can be statically checked.',
1394
+ ],
1395
+ },
1396
+ {
1397
+ code: `
1398
+ function MyComponent() {
1399
+ const ref = useRef();
1400
+ const [state, setState] = useState();
1401
+ useEffect(() => {
1402
+ ref.current = 42;
1403
+ setState(state + 1);
1404
+ }, []);
1405
+ }
1406
+ `,
1407
+ output: `
1408
+ function MyComponent() {
1409
+ const ref = useRef();
1410
+ const [state, setState] = useState();
1411
+ useEffect(() => {
1412
+ ref.current = 42;
1413
+ setState(state + 1);
1414
+ }, [state]);
1415
+ }
1416
+ `,
1417
+ errors: [
1418
+ "React Hook useEffect has a missing dependency: 'state'. " +
1419
+ 'Either include it or remove the dependency array.',
1420
+ ],
1421
+ },
1422
+ {
1423
+ code: `
1424
+ function MyComponent() {
1425
+ const ref = useRef();
1426
+ const [state, setState] = useState();
1427
+ useEffect(() => {
1428
+ ref.current = 42;
1429
+ setState(state + 1);
1430
+ }, [ref]);
1431
+ }
1432
+ `,
1433
+ // We don't ask to remove static deps but don't add them either.
1434
+ // Don't suggest removing "ref" (it's fine either way)
1435
+ // but *do* add "state". *Don't* add "setState" ourselves.
1436
+ output: `
1437
+ function MyComponent() {
1438
+ const ref = useRef();
1439
+ const [state, setState] = useState();
1440
+ useEffect(() => {
1441
+ ref.current = 42;
1442
+ setState(state + 1);
1443
+ }, [ref, state]);
1444
+ }
1445
+ `,
1446
+ errors: [
1447
+ "React Hook useEffect has a missing dependency: 'state'. " +
1448
+ 'Either include it or remove the dependency array.',
1449
+ ],
1450
+ },
1451
+ {
1452
+ code: `
1453
+ function MyComponent(props) {
1454
+ const ref1 = useRef();
1455
+ const ref2 = useRef();
1456
+ useEffect(() => {
1457
+ ref1.current.focus();
1458
+ console.log(ref2.current.textContent);
1459
+ alert(props.someOtherRefs.current.innerHTML);
1460
+ fetch(props.color);
1461
+ }, []);
1462
+ }
1463
+ `,
1464
+ output: `
1465
+ function MyComponent(props) {
1466
+ const ref1 = useRef();
1467
+ const ref2 = useRef();
1468
+ useEffect(() => {
1469
+ ref1.current.focus();
1470
+ console.log(ref2.current.textContent);
1471
+ alert(props.someOtherRefs.current.innerHTML);
1472
+ fetch(props.color);
1473
+ }, [props.color, props.someOtherRefs]);
1474
+ }
1475
+ `,
1476
+ errors: [
1477
+ "React Hook useEffect has missing dependencies: 'props.color' and 'props.someOtherRefs'. " +
1478
+ 'Either include them or remove the dependency array.',
1479
+ ],
1480
+ },
1481
+ {
1482
+ code: `
1483
+ function MyComponent(props) {
1484
+ const ref1 = useRef();
1485
+ const ref2 = useRef();
1486
+ useEffect(() => {
1487
+ ref1.current.focus();
1488
+ console.log(ref2.current.textContent);
1489
+ alert(props.someOtherRefs.current.innerHTML);
1490
+ fetch(props.color);
1491
+ }, [ref1.current, ref2.current, props.someOtherRefs, props.color]);
1492
+ }
1493
+ `,
1494
+ output: `
1495
+ function MyComponent(props) {
1496
+ const ref1 = useRef();
1497
+ const ref2 = useRef();
1498
+ useEffect(() => {
1499
+ ref1.current.focus();
1500
+ console.log(ref2.current.textContent);
1501
+ alert(props.someOtherRefs.current.innerHTML);
1502
+ fetch(props.color);
1503
+ }, [props.someOtherRefs, props.color]);
1504
+ }
1505
+ `,
1506
+ errors: [
1507
+ "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
1508
+ 'Either exclude them or remove the dependency array. ' +
1509
+ "Mutable values like 'ref1.current' aren't valid dependencies " +
1510
+ "because their mutation doesn't re-render the component.",
1511
+ ],
1512
+ },
1513
+ {
1514
+ code: `
1515
+ function MyComponent() {
1516
+ const ref = useRef();
1517
+ useEffect(() => {
1518
+ console.log(ref.current);
1519
+ }, [ref.current]);
1520
+ }
1521
+ `,
1522
+ output: `
1523
+ function MyComponent() {
1524
+ const ref = useRef();
1525
+ useEffect(() => {
1526
+ console.log(ref.current);
1527
+ }, []);
1528
+ }
1529
+ `,
1530
+ errors: [
1531
+ "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
1532
+ 'Either exclude it or remove the dependency array. ' +
1533
+ "Mutable values like 'ref.current' aren't valid dependencies " +
1534
+ "because their mutation doesn't re-render the component.",
1535
+ ],
1536
+ },
1537
+ {
1538
+ code: `
1539
+ function MyComponent() {
1540
+ const ref = useRef();
1541
+ useEffect(() => {
1542
+ console.log(ref.current);
1543
+ }, [ref.current, ref]);
1544
+ }
1545
+ `,
1546
+ output: `
1547
+ function MyComponent() {
1548
+ const ref = useRef();
1549
+ useEffect(() => {
1550
+ console.log(ref.current);
1551
+ }, [ref]);
1552
+ }
1553
+ `,
1554
+ errors: [
1555
+ "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
1556
+ 'Either exclude it or remove the dependency array. ' +
1557
+ "Mutable values like 'ref.current' aren't valid dependencies " +
1558
+ "because their mutation doesn't re-render the component.",
1559
+ ],
1560
+ },
1561
+ {
1562
+ code: `
1563
+ const MyComponent = forwardRef((props, ref) => {
1564
+ useImperativeHandle(ref, () => ({
1565
+ focus() {
1566
+ alert(props.hello);
1567
+ }
1568
+ }), [])
1569
+ });
1570
+ `,
1571
+ output: `
1572
+ const MyComponent = forwardRef((props, ref) => {
1573
+ useImperativeHandle(ref, () => ({
1574
+ focus() {
1575
+ alert(props.hello);
1576
+ }
1577
+ }), [props.hello])
1578
+ });
1579
+ `,
1580
+ errors: [
1581
+ "React Hook useImperativeHandle has a missing dependency: 'props.hello'. " +
1582
+ 'Either include it or remove the dependency array.',
1583
+ ],
1584
+ },
1585
+ {
1586
+ code: `
1587
+ function MyComponent(props) {
1588
+ useEffect(() => {
1589
+ if (props.onChange) {
1590
+ props.onChange();
1591
+ }
1592
+ }, []);
1593
+ }
1594
+ `,
1595
+ // TODO: [props.onChange] is superfluous. Fix to just [props.onChange].
1596
+ output: `
1597
+ function MyComponent(props) {
1598
+ useEffect(() => {
1599
+ if (props.onChange) {
1600
+ props.onChange();
1601
+ }
1602
+ }, [props, props.onChange]);
1603
+ }
1604
+ `,
1605
+ errors: [
1606
+ // TODO: reporting props separately is superfluous. Fix to just props.onChange.
1607
+ "React Hook useEffect has missing dependencies: 'props' and 'props.onChange'. " +
1608
+ 'Either include them or remove the dependency array.',
1609
+ ],
1610
+ },
1611
+ ],
1612
+};
1613
+
1614
+// For easier local testing
1615
+if (!process.env.CI) {
1616
+ let only = [];
1617
+ let skipped = [];
1618
+ [...tests.valid, ...tests.invalid].forEach(t => {
1619
+ if (t.skip) {
1620
+ delete t.skip;
1621
+ skipped.push(t);
1622
+ }
1623
+ if (t.only) {
1624
+ delete t.only;
1625
+ only.push(t);
1626
+ }
1627
+ });
1628
+ const predicate = t => {
1629
+ if (only.length > 0) {
1630
+ return only.indexOf(t) !== -1;
1631
+ }
1632
+ if (skipped.length > 0) {
1633
+ return skipped.indexOf(t) === -1;
1634
+ }
1635
+ return true;
1636
+ };
1637
+ tests.valid = tests.valid.filter(predicate);
1638
+ tests.invalid = tests.invalid.filter(predicate);
1639
+}
1640
+
1641
+const eslintTester = new ESLintTester();
1642
+eslintTester.run('react-hooks', ReactHooksESLintRule, tests);