1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and 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
+// NOTE: Extracted from https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
11
+
12
+/**
13
+ * A string template tag that removes padding from the left side of multi-line strings
14
+ */
15
+function normalizeIndent(strings) {
16
+ const codeLines = strings[0].split("\n");
17
+ const leftPadding = codeLines[1].match(/\s+/)[0];
18
+ return codeLines.map((line) => line.slice(leftPadding.length)).join("\n");
19
+}
20
+
21
+module.exports.tests = {
22
+ valid: [
23
+ {
24
+ code: normalizeIndent`
25
+ // Valid because components can use hooks.
26
+ function ComponentWithHook() {
27
+ useHook();
28
+ }
29
+ `,
30
+ },
31
+ {
32
+ code: normalizeIndent`
33
+ // Valid because components can use hooks.
34
+ function createComponentWithHook() {
35
+ return function ComponentWithHook() {
36
+ useHook();
37
+ };
38
+ }
39
+ `,
40
+ },
41
+ {
42
+ code: normalizeIndent`
43
+ // Valid because hooks can use hooks.
44
+ function useHookWithHook() {
45
+ useHook();
46
+ }
47
+ `,
48
+ },
49
+ {
50
+ code: normalizeIndent`
51
+ // Valid because hooks can use hooks.
52
+ function createHook() {
53
+ return function useHookWithHook() {
54
+ useHook();
55
+ }
56
+ }
57
+ `,
58
+ },
59
+ {
60
+ code: normalizeIndent`
61
+ // Valid because components can call functions.
62
+ function ComponentWithNormalFunction() {
63
+ doSomething();
64
+ }
65
+ `,
66
+ },
67
+ {
68
+ code: normalizeIndent`
69
+ // Valid because functions can call functions.
70
+ function normalFunctionWithNormalFunction() {
71
+ doSomething();
72
+ }
73
+ `,
74
+ },
75
+ {
76
+ code: normalizeIndent`
77
+ // Valid because functions can call functions.
78
+ function normalFunctionWithConditionalFunction() {
79
+ if (cond) {
80
+ doSomething();
81
+ }
82
+ }
83
+ `,
84
+ },
85
+ {
86
+ code: normalizeIndent`
87
+ // Valid because functions can call functions.
88
+ function functionThatStartsWithUseButIsntAHook() {
89
+ if (cond) {
90
+ userFetch();
91
+ }
92
+ }
93
+ `,
94
+ },
95
+ {
96
+ code: normalizeIndent`
97
+ // Valid although unconditional return doesn't make sense and would fail other rules.
98
+ // We could make it invalid but it doesn't matter.
99
+ function useUnreachable() {
100
+ return;
101
+ useHook();
102
+ }
103
+ `,
104
+ },
105
+ {
106
+ code: normalizeIndent`
107
+ // Valid because hooks can call hooks.
108
+ function useHook() { useState(); }
109
+ const whatever = function useHook() { useState(); };
110
+ const useHook1 = () => { useState(); };
111
+ let useHook2 = () => useState();
112
+ useHook2 = () => { useState(); };
113
+ ({useHook: () => { useState(); }});
114
+ ({useHook() { useState(); }});
115
+ const {useHook3 = () => { useState(); }} = {};
116
+ ({useHook = () => { useState(); }} = {});
117
+ Namespace.useHook = () => { useState(); };
118
+ `,
119
+ },
120
+ {
121
+ code: normalizeIndent`
122
+ // Valid because hooks can call hooks.
123
+ function useHook() {
124
+ useHook1();
125
+ useHook2();
126
+ }
127
+ `,
128
+ },
129
+ {
130
+ code: normalizeIndent`
131
+ // Valid because hooks can call hooks.
132
+ function createHook() {
133
+ return function useHook() {
134
+ useHook1();
135
+ useHook2();
136
+ };
137
+ }
138
+ `,
139
+ },
140
+ {
141
+ code: normalizeIndent`
142
+ // Valid because hooks can call hooks.
143
+ function useHook() {
144
+ useState() && a;
145
+ }
146
+ `,
147
+ },
148
+ {
149
+ code: normalizeIndent`
150
+ // Valid because hooks can call hooks.
151
+ function useHook() {
152
+ return useHook1() + useHook2();
153
+ }
154
+ `,
155
+ },
156
+ {
157
+ code: normalizeIndent`
158
+ // Valid because hooks can call hooks.
159
+ function useHook() {
160
+ return useHook1(useHook2());
161
+ }
162
+ `,
163
+ },
164
+ {
165
+ code: normalizeIndent`
166
+ // Valid because hooks can be used in anonymous arrow-function arguments
167
+ // to forwardRef.
168
+ const FancyButton = React.forwardRef((props, ref) => {
169
+ useHook();
170
+ return <button {...props} ref={ref} />
171
+ });
172
+ `,
173
+ },
174
+ {
175
+ code: normalizeIndent`
176
+ // Valid because hooks can be used in anonymous function arguments to
177
+ // forwardRef.
178
+ const FancyButton = React.forwardRef(function (props, ref) {
179
+ useHook();
180
+ return <button {...props} ref={ref} />
181
+ });
182
+ `,
183
+ },
184
+ {
185
+ code: normalizeIndent`
186
+ // Valid because hooks can be used in anonymous function arguments to
187
+ // forwardRef.
188
+ const FancyButton = forwardRef(function (props, ref) {
189
+ useHook();
190
+ return <button {...props} ref={ref} />
191
+ });
192
+ `,
193
+ },
194
+ {
195
+ code: normalizeIndent`
196
+ // Valid because hooks can be used in anonymous function arguments to
197
+ // React.memo.
198
+ const MemoizedFunction = React.memo(props => {
199
+ useHook();
200
+ return <button {...props} />
201
+ });
202
+ `,
203
+ },
204
+ {
205
+ code: normalizeIndent`
206
+ // Valid because hooks can be used in anonymous function arguments to
207
+ // memo.
208
+ const MemoizedFunction = memo(function (props) {
209
+ useHook();
210
+ return <button {...props} />
211
+ });
212
+ `,
213
+ },
214
+ {
215
+ code: normalizeIndent`
216
+ // Valid because classes can call functions.
217
+ // We don't consider these to be hooks.
218
+ class C {
219
+ m() {
220
+ this.useHook();
221
+ super.useHook();
222
+ }
223
+ }
224
+ `,
225
+ },
226
+ {
227
+ code: normalizeIndent`
228
+ // Valid -- this is a regression test.
229
+ jest.useFakeTimers();
230
+ beforeEach(() => {
231
+ jest.useRealTimers();
232
+ })
233
+ `,
234
+ },
235
+ {
236
+ code: normalizeIndent`
237
+ // Valid because they're not matching use[A-Z].
238
+ fooState();
239
+ _use();
240
+ _useState();
241
+ use_hook();
242
+ // also valid because it's not matching the PascalCase namespace
243
+ jest.useFakeTimer()
244
+ `,
245
+ },
246
+ {
247
+ code: normalizeIndent`
248
+ // Regression test for some internal code.
249
+ // This shows how the "callback rule" is more relaxed,
250
+ // and doesn't kick in unless we're confident we're in
251
+ // a component or a hook.
252
+ function makeListener(instance) {
253
+ each(pixelsWithInferredEvents, pixel => {
254
+ if (useExtendedSelector(pixel.id) && extendedButton) {
255
+ foo();
256
+ }
257
+ });
258
+ }
259
+ `,
260
+ },
261
+ {
262
+ code: normalizeIndent`
263
+ // This is valid because "use"-prefixed functions called in
264
+ // unnamed function arguments are not assumed to be hooks.
265
+ React.unknownFunction((foo, bar) => {
266
+ if (foo) {
267
+ useNotAHook(bar)
268
+ }
269
+ });
270
+ `,
271
+ },
272
+ {
273
+ code: normalizeIndent`
274
+ // This is valid because "use"-prefixed functions called in
275
+ // unnamed function arguments are not assumed to be hooks.
276
+ unknownFunction(function(foo, bar) {
277
+ if (foo) {
278
+ useNotAHook(bar)
279
+ }
280
+ });
281
+ `,
282
+ },
283
+ {
284
+ code: normalizeIndent`
285
+ // Regression test for incorrectly flagged valid code.
286
+ function RegressionTest() {
287
+ const foo = cond ? a : b;
288
+ useState();
289
+ }
290
+ `,
291
+ },
292
+ {
293
+ code: normalizeIndent`
294
+ // Valid because exceptions abort rendering
295
+ function RegressionTest() {
296
+ if (page == null) {
297
+ throw new Error('oh no!');
298
+ }
299
+ useState();
300
+ }
301
+ `,
302
+ },
303
+ {
304
+ code: normalizeIndent`
305
+ // Valid because the loop doesn't change the order of hooks calls.
306
+ function RegressionTest() {
307
+ const res = [];
308
+ const additionalCond = true;
309
+ for (let i = 0; i !== 10 && additionalCond; ++i ) {
310
+ res.push(i);
311
+ }
312
+ React.useLayoutEffect(() => {});
313
+ }
314
+ `,
315
+ },
316
+ {
317
+ code: normalizeIndent`
318
+ // Is valid but hard to compute by brute-forcing
319
+ function MyComponent() {
320
+ // 40 conditions
321
+ if (c) {} else {}
322
+ if (c) {} else {}
323
+ if (c) {} else {}
324
+ if (c) {} else {}
325
+ if (c) {} else {}
326
+ if (c) {} else {}
327
+ if (c) {} else {}
328
+ if (c) {} else {}
329
+ if (c) {} else {}
330
+ if (c) {} else {}
331
+ if (c) {} else {}
332
+ if (c) {} else {}
333
+ if (c) {} else {}
334
+ if (c) {} else {}
335
+ if (c) {} else {}
336
+ if (c) {} else {}
337
+ if (c) {} else {}
338
+ if (c) {} else {}
339
+ if (c) {} else {}
340
+ if (c) {} else {}
341
+ if (c) {} else {}
342
+ if (c) {} else {}
343
+ if (c) {} else {}
344
+ if (c) {} else {}
345
+ if (c) {} else {}
346
+ if (c) {} else {}
347
+ if (c) {} else {}
348
+ if (c) {} else {}
349
+ if (c) {} else {}
350
+ if (c) {} else {}
351
+ if (c) {} else {}
352
+ if (c) {} else {}
353
+ if (c) {} else {}
354
+ if (c) {} else {}
355
+ if (c) {} else {}
356
+ if (c) {} else {}
357
+ if (c) {} else {}
358
+ if (c) {} else {}
359
+ if (c) {} else {}
360
+ if (c) {} else {}
361
+
362
+ // 10 hooks
363
+ useHook();
364
+ useHook();
365
+ useHook();
366
+ useHook();
367
+ useHook();
368
+ useHook();
369
+ useHook();
370
+ useHook();
371
+ useHook();
372
+ useHook();
373
+ }
374
+ `,
375
+ },
376
+ {
377
+ code: normalizeIndent`
378
+ // Valid because the neither the conditions before or after the hook affect the hook call
379
+ // Failed prior to implementing BigInt because pathsFromStartToEnd and allPathsFromStartToEnd were too big and had rounding errors
380
+ const useSomeHook = () => {};
381
+
382
+ const SomeName = () => {
383
+ const filler = FILLER ?? FILLER ?? FILLER;
384
+ const filler2 = FILLER ?? FILLER ?? FILLER;
385
+ const filler3 = FILLER ?? FILLER ?? FILLER;
386
+ const filler4 = FILLER ?? FILLER ?? FILLER;
387
+ const filler5 = FILLER ?? FILLER ?? FILLER;
388
+ const filler6 = FILLER ?? FILLER ?? FILLER;
389
+ const filler7 = FILLER ?? FILLER ?? FILLER;
390
+ const filler8 = FILLER ?? FILLER ?? FILLER;
391
+
392
+ useSomeHook();
393
+
394
+ if (anyConditionCanEvenBeFalse) {
395
+ return null;
396
+ }
397
+
398
+ return (
399
+ <React.Fragment>
400
+ {FILLER ? FILLER : FILLER}
401
+ {FILLER ? FILLER : FILLER}
402
+ {FILLER ? FILLER : FILLER}
403
+ {FILLER ? FILLER : FILLER}
404
+ {FILLER ? FILLER : FILLER}
405
+ {FILLER ? FILLER : FILLER}
406
+ {FILLER ? FILLER : FILLER}
407
+ {FILLER ? FILLER : FILLER}
408
+ {FILLER ? FILLER : FILLER}
409
+ {FILLER ? FILLER : FILLER}
410
+ {FILLER ? FILLER : FILLER}
411
+ {FILLER ? FILLER : FILLER}
412
+ {FILLER ? FILLER : FILLER}
413
+ {FILLER ? FILLER : FILLER}
414
+ {FILLER ? FILLER : FILLER}
415
+ {FILLER ? FILLER : FILLER}
416
+ {FILLER ? FILLER : FILLER}
417
+ {FILLER ? FILLER : FILLER}
418
+ {FILLER ? FILLER : FILLER}
419
+ {FILLER ? FILLER : FILLER}
420
+ {FILLER ? FILLER : FILLER}
421
+ {FILLER ? FILLER : FILLER}
422
+ {FILLER ? FILLER : FILLER}
423
+ {FILLER ? FILLER : FILLER}
424
+ {FILLER ? FILLER : FILLER}
425
+ {FILLER ? FILLER : FILLER}
426
+ {FILLER ? FILLER : FILLER}
427
+ {FILLER ? FILLER : FILLER}
428
+ {FILLER ? FILLER : FILLER}
429
+ {FILLER ? FILLER : FILLER}
430
+ {FILLER ? FILLER : FILLER}
431
+ {FILLER ? FILLER : FILLER}
432
+ {FILLER ? FILLER : FILLER}
433
+ {FILLER ? FILLER : FILLER}
434
+ {FILLER ? FILLER : FILLER}
435
+ {FILLER ? FILLER : FILLER}
436
+ {FILLER ? FILLER : FILLER}
437
+ {FILLER ? FILLER : FILLER}
438
+ {FILLER ? FILLER : FILLER}
439
+ {FILLER ? FILLER : FILLER}
440
+ {FILLER ? FILLER : FILLER}
441
+ {FILLER ? FILLER : FILLER}
442
+ </React.Fragment>
443
+ );
444
+ };
445
+ `,
446
+ },
447
+ {
448
+ code: normalizeIndent`
449
+ // Valid because the neither the condition nor the loop affect the hook call.
450
+ function App(props) {
451
+ const someObject = {propA: true};
452
+ for (const propName in someObject) {
453
+ if (propName === true) {
454
+ } else {
455
+ }
456
+ }
457
+ const [myState, setMyState] = useState(null);
458
+ }
459
+ `,
460
+ },
461
+ ],
462
+ invalid: [
463
+ {
464
+ code: normalizeIndent`
465
+ // Invalid because it's dangerous and might not warn otherwise.
466
+ // This *must* be invalid.
467
+ function ComponentWithConditionalHook() {
468
+ if (cond) {
469
+ useConditionalHook();
470
+ }
471
+ }
472
+ `,
473
+ errors: [],
474
+ },
475
+ {
476
+ code: normalizeIndent`
477
+ Hook.useState();
478
+ Hook._useState();
479
+ Hook.use42();
480
+ Hook.useHook();
481
+ Hook.use_hook();
482
+ `,
483
+ errors: [],
484
+ },
485
+ {
486
+ code: normalizeIndent`
487
+ class C {
488
+ m() {
489
+ This.useHook();
490
+ Super.useHook();
491
+ }
492
+ }
493
+ `,
494
+ errors: [],
495
+ },
496
+ {
497
+ code: normalizeIndent`
498
+ // This is a false positive (it's valid) that unfortunately
499
+ // we cannot avoid. Prefer to rename it to not start with "use"
500
+ class Foo extends Component {
501
+ render() {
502
+ if (cond) {
503
+ FooStore.useFeatureFlag();
504
+ }
505
+ }
506
+ }
507
+ `,
508
+ errors: [],
509
+ },
510
+ {
511
+ code: normalizeIndent`
512
+ // Invalid because it's dangerous and might not warn otherwise.
513
+ // This *must* be invalid.
514
+ function ComponentWithConditionalHook() {
515
+ if (cond) {
516
+ Namespace.useConditionalHook();
517
+ }
518
+ }
519
+ `,
520
+ errors: [],
521
+ },
522
+ {
523
+ code: normalizeIndent`
524
+ // Invalid because it's dangerous and might not warn otherwise.
525
+ // This *must* be invalid.
526
+ function createComponent() {
527
+ return function ComponentWithConditionalHook() {
528
+ if (cond) {
529
+ useConditionalHook();
530
+ }
531
+ }
532
+ }
533
+ `,
534
+ errors: [],
535
+ },
536
+ {
537
+ code: normalizeIndent`
538
+ // Invalid because it's dangerous and might not warn otherwise.
539
+ // This *must* be invalid.
540
+ function useHookWithConditionalHook() {
541
+ if (cond) {
542
+ useConditionalHook();
543
+ }
544
+ }
545
+ `,
546
+ errors: [],
547
+ },
548
+ {
549
+ code: normalizeIndent`
550
+ // Invalid because it's dangerous and might not warn otherwise.
551
+ // This *must* be invalid.
552
+ function createHook() {
553
+ return function useHookWithConditionalHook() {
554
+ if (cond) {
555
+ useConditionalHook();
556
+ }
557
+ }
558
+ }
559
+ `,
560
+ errors: [],
561
+ },
562
+ {
563
+ code: normalizeIndent`
564
+ // Invalid because it's dangerous and might not warn otherwise.
565
+ // This *must* be invalid.
566
+ function ComponentWithTernaryHook() {
567
+ cond ? useTernaryHook() : null;
568
+ }
569
+ `,
570
+ errors: [],
571
+ },
572
+ {
573
+ code: normalizeIndent`
574
+ // Invalid because it's a common misunderstanding.
575
+ // We *could* make it valid but the runtime error could be confusing.
576
+ function ComponentWithHookInsideCallback() {
577
+ useEffect(() => {
578
+ useHookInsideCallback();
579
+ });
580
+ }
581
+ `,
582
+ errors: [],
583
+ },
584
+ {
585
+ code: normalizeIndent`
586
+ // Invalid because it's a common misunderstanding.
587
+ // We *could* make it valid but the runtime error could be confusing.
588
+ function createComponent() {
589
+ return function ComponentWithHookInsideCallback() {
590
+ useEffect(() => {
591
+ useHookInsideCallback();
592
+ });
593
+ }
594
+ }
595
+ `,
596
+ errors: [],
597
+ },
598
+ {
599
+ code: normalizeIndent`
600
+ // Invalid because it's a common misunderstanding.
601
+ // We *could* make it valid but the runtime error could be confusing.
602
+ const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
603
+ useEffect(() => {
604
+ useHookInsideCallback();
605
+ });
606
+ return <button {...props} ref={ref} />
607
+ });
608
+ `,
609
+ errors: [],
610
+ },
611
+ {
612
+ code: normalizeIndent`
613
+ // Invalid because it's a common misunderstanding.
614
+ // We *could* make it valid but the runtime error could be confusing.
615
+ const ComponentWithHookInsideCallback = React.memo(props => {
616
+ useEffect(() => {
617
+ useHookInsideCallback();
618
+ });
619
+ return <button {...props} />
620
+ });
621
+ `,
622
+ errors: [],
623
+ },
624
+ {
625
+ code: normalizeIndent`
626
+ // Invalid because it's a common misunderstanding.
627
+ // We *could* make it valid but the runtime error could be confusing.
628
+ function ComponentWithHookInsideCallback() {
629
+ function handleClick() {
630
+ useState();
631
+ }
632
+ }
633
+ `,
634
+ errors: [],
635
+ },
636
+ {
637
+ code: normalizeIndent`
638
+ // Invalid because it's a common misunderstanding.
639
+ // We *could* make it valid but the runtime error could be confusing.
640
+ function createComponent() {
641
+ return function ComponentWithHookInsideCallback() {
642
+ function handleClick() {
643
+ useState();
644
+ }
645
+ }
646
+ }
647
+ `,
648
+ errors: [],
649
+ },
650
+ {
651
+ code: normalizeIndent`
652
+ // Invalid because it's dangerous and might not warn otherwise.
653
+ // This *must* be invalid.
654
+ function ComponentWithHookInsideLoop() {
655
+ while (cond) {
656
+ useHookInsideLoop();
657
+ }
658
+ }
659
+ `,
660
+ errors: [],
661
+ },
662
+ {
663
+ code: normalizeIndent`
664
+ // Invalid because it's dangerous and might not warn otherwise.
665
+ // This *must* be invalid.
666
+ function renderItem() {
667
+ useState();
668
+ }
669
+
670
+ function List(props) {
671
+ return props.items.map(renderItem);
672
+ }
673
+ `,
674
+ errors: [],
675
+ },
676
+ {
677
+ code: normalizeIndent`
678
+ // Currently invalid because it violates the convention and removes the "taint"
679
+ // from a hook. We *could* make it valid to avoid some false positives but let's
680
+ // ensure that we don't break the "renderItem" and "normalFunctionWithConditionalHook"
681
+ // cases which must remain invalid.
682
+ function normalFunctionWithHook() {
683
+ useHookInsideNormalFunction();
684
+ }
685
+ `,
686
+ errors: [],
687
+ },
688
+ {
689
+ code: normalizeIndent`
690
+ // These are neither functions nor hooks.
691
+ function _normalFunctionWithHook() {
692
+ useHookInsideNormalFunction();
693
+ }
694
+ function _useNotAHook() {
695
+ useHookInsideNormalFunction();
696
+ }
697
+ `,
698
+ errors: [],
699
+ },
700
+ {
701
+ code: normalizeIndent`
702
+ // Invalid because it's dangerous and might not warn otherwise.
703
+ // This *must* be invalid.
704
+ function normalFunctionWithConditionalHook() {
705
+ if (cond) {
706
+ useHookInsideNormalFunction();
707
+ }
708
+ }
709
+ `,
710
+ errors: [],
711
+ },
712
+ {
713
+ code: normalizeIndent`
714
+ // Invalid because it's dangerous and might not warn otherwise.
715
+ // This *must* be invalid.
716
+ function useHookInLoops() {
717
+ while (a) {
718
+ useHook1();
719
+ if (b) return;
720
+ useHook2();
721
+ }
722
+ while (c) {
723
+ useHook3();
724
+ if (d) return;
725
+ useHook4();
726
+ }
727
+ }
728
+ `,
729
+ errors: [],
730
+ },
731
+ {
732
+ code: normalizeIndent`
733
+ // Invalid because it's dangerous and might not warn otherwise.
734
+ // This *must* be invalid.
735
+ function useHookInLoops() {
736
+ while (a) {
737
+ useHook1();
738
+ if (b) continue;
739
+ useHook2();
740
+ }
741
+ }
742
+ `,
743
+ errors: [],
744
+ },
745
+ {
746
+ code: normalizeIndent`
747
+ // Invalid because it's dangerous and might not warn otherwise.
748
+ // This *must* be invalid.
749
+ function useLabeledBlock() {
750
+ label: {
751
+ if (a) break label;
752
+ useHook();
753
+ }
754
+ }
755
+ `,
756
+ errors: [],
757
+ },
758
+ {
759
+ code: normalizeIndent`
760
+ // Currently invalid.
761
+ // These are variations capturing the current heuristic--
762
+ // we only allow hooks in PascalCase or useFoo functions.
763
+ // We *could* make some of these valid. But before doing it,
764
+ // consider specific cases documented above that contain reasoning.
765
+ function a() { useState(); }
766
+ const whatever = function b() { useState(); };
767
+ const c = () => { useState(); };
768
+ let d = () => useState();
769
+ e = () => { useState(); };
770
+ ({f: () => { useState(); }});
771
+ ({g() { useState(); }});
772
+ const {j = () => { useState(); }} = {};
773
+ ({k = () => { useState(); }} = {});
774
+ `,
775
+ errors: [],
776
+ },
777
+ {
778
+ code: normalizeIndent`
779
+ // Invalid because it's dangerous and might not warn otherwise.
780
+ // This *must* be invalid.
781
+ function useHook() {
782
+ if (a) return;
783
+ useState();
784
+ }
785
+ `,
786
+ errors: [],
787
+ },
788
+ {
789
+ code: normalizeIndent`
790
+ // Invalid because it's dangerous and might not warn otherwise.
791
+ // This *must* be invalid.
792
+ function useHook() {
793
+ if (a) return;
794
+ if (b) {
795
+ console.log('true');
796
+ } else {
797
+ console.log('false');
798
+ }
799
+ useState();
800
+ }
801
+ `,
802
+ errors: [],
803
+ },
804
+ {
805
+ code: normalizeIndent`
806
+ // Invalid because it's dangerous and might not warn otherwise.
807
+ // This *must* be invalid.
808
+ function useHook() {
809
+ if (b) {
810
+ console.log('true');
811
+ } else {
812
+ console.log('false');
813
+ }
814
+ if (a) return;
815
+ useState();
816
+ }
817
+ `,
818
+ errors: [],
819
+ },
820
+ {
821
+ code: normalizeIndent`
822
+ // Invalid because it's dangerous and might not warn otherwise.
823
+ // This *must* be invalid.
824
+ function useHook() {
825
+ a && useHook1();
826
+ b && useHook2();
827
+ }
828
+ `,
829
+ errors: [],
830
+ },
831
+ {
832
+ code: normalizeIndent`
833
+ // Invalid because it's dangerous and might not warn otherwise.
834
+ // This *must* be invalid.
835
+ function useHook() {
836
+ try {
837
+ f();
838
+ useState();
839
+ } catch {}
840
+ }
841
+ `,
842
+ errors: [],
843
+ },
844
+ {
845
+ code: normalizeIndent`
846
+ // Invalid because it's dangerous and might not warn otherwise.
847
+ // This *must* be invalid.
848
+ function useHook({ bar }) {
849
+ let foo1 = bar && useState();
850
+ let foo2 = bar || useState();
851
+ let foo3 = bar ?? useState();
852
+ }
853
+ `,
854
+ errors: [],
855
+ },
856
+ {
857
+ code: normalizeIndent`
858
+ // Invalid because it's dangerous and might not warn otherwise.
859
+ // This *must* be invalid.
860
+ const FancyButton = React.forwardRef((props, ref) => {
861
+ if (props.fancy) {
862
+ useCustomHook();
863
+ }
864
+ return <button ref={ref}>{props.children}</button>;
865
+ });
866
+ `,
867
+ errors: [],
868
+ },
869
+ {
870
+ code: normalizeIndent`
871
+ // Invalid because it's dangerous and might not warn otherwise.
872
+ // This *must* be invalid.
873
+ const FancyButton = forwardRef(function(props, ref) {
874
+ if (props.fancy) {
875
+ useCustomHook();
876
+ }
877
+ return <button ref={ref}>{props.children}</button>;
878
+ });
879
+ `,
880
+ errors: [],
881
+ },
882
+ {
883
+ code: normalizeIndent`
884
+ // Invalid because it's dangerous and might not warn otherwise.
885
+ // This *must* be invalid.
886
+ const MemoizedButton = memo(function(props) {
887
+ if (props.fancy) {
888
+ useCustomHook();
889
+ }
890
+ return <button>{props.children}</button>;
891
+ });
892
+ `,
893
+ errors: [],
894
+ },
895
+ {
896
+ code: normalizeIndent`
897
+ // This is invalid because "use"-prefixed functions used in named
898
+ // functions are assumed to be hooks.
899
+ React.unknownFunction(function notAComponent(foo, bar) {
900
+ useProbablyAHook(bar)
901
+ });
902
+ `,
903
+ errors: [],
904
+ },
905
+ {
906
+ code: normalizeIndent`
907
+ // Invalid because it's dangerous.
908
+ // Normally, this would crash, but not if you use inline requires.
909
+ // This *must* be invalid.
910
+ // It's expected to have some false positives, but arguably
911
+ // they are confusing anyway due to the use*() convention
912
+ // already being associated with Hooks.
913
+ useState();
914
+ if (foo) {
915
+ const foo = React.useCallback(() => {});
916
+ }
917
+ useCustomHook();
918
+ `,
919
+ errors: [],
920
+ },
921
+ {
922
+ code: normalizeIndent`
923
+ // Technically this is a false positive.
924
+ // We *could* make it valid (and it used to be).
925
+ //
926
+ // However, top-level Hook-like calls can be very dangerous
927
+ // in environments with inline requires because they can mask
928
+ // the runtime error by accident.
929
+ // So we prefer to disallow it despite the false positive.
930
+
931
+ const {createHistory, useBasename} = require('history-2.1.2');
932
+ const browserHistory = useBasename(createHistory)({
933
+ basename: '/',
934
+ });
935
+ `,
936
+ errors: [],
937
+ },
938
+ {
939
+ code: normalizeIndent`
940
+ class ClassComponentWithFeatureFlag extends React.Component {
941
+ render() {
942
+ if (foo) {
943
+ useFeatureFlag();
944
+ }
945
+ }
946
+ }
947
+ `,
948
+ errors: [],
949
+ },
950
+ {
951
+ code: normalizeIndent`
952
+ class ClassComponentWithHook extends React.Component {
953
+ render() {
954
+ React.useState();
955
+ }
956
+ }
957
+ `,
958
+ errors: [],
959
+ },
960
+ {
961
+ code: normalizeIndent`
962
+ (class {useHook = () => { useState(); }});
963
+ `,
964
+ errors: [],
965
+ },
966
+ {
967
+ code: normalizeIndent`
968
+ (class {useHook() { useState(); }});
969
+ `,
970
+ errors: [],
971
+ },
972
+ {
973
+ code: normalizeIndent`
974
+ (class {h = () => { useState(); }});
975
+ `,
976
+ errors: [],
977
+ },
978
+ {
979
+ code: normalizeIndent`
980
+ (class {i() { useState(); }});
981
+ `,
982
+ errors: [],
983
+ },
984
+ ],
985
+};