Fixtures from ESLint rule
Incorporates the fixtures from eslint-plugin-react-hooks using a script, so that we can easily update them in the future. For each fixture we run the compiler with and without hooks validation first so that we know if the fixture is expected to pass — we have some false positives and false negatives that i can work through. For example we accidentally think that `userFetch()` is a hook, oops. Fixtures that should pass but error, or that should error but pass, are marked as `todo.<name>` or `todo.error.<name>`. While i was here i added the ability to have fixtures in subdirectories for grouping purposes.
Joe Savona committed
May 8, 2023 at 12:10 UTC
fd007be0d07e37ccbd38b805842a89faf7a16e04
120 files changed
+3126
-15
compiler/forget/package.json
+1
@@ -49,6 +49,7 @@
49
"@babel/traverse": "^7.19.1",
50
"@testing-library/react": "^13.4.0",
51
"@tsconfig/node16-strictest": "^1.0.3",
52
+ "@types/glob": "^8.1.0",
53
"@types/invariant": "^2.2.35",
54
"@types/jest": "^29.0.3",
55
"@types/node": "^18.7.18",
compiler/forget/scripts/build-react-hooks-fixures.js
new
+91
@@ -0,0 +1,91 @@
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
+const { tests } = require("./eslint-plugin-react-hooks-test-cases");
11
+const {
12
+ runReactForgetBabelPlugin,
13
+} = require("../dist/Babel/RunReactForgetBabelPlugin");
14
+const fs = require("fs");
15
+const path = require("path");
16
+const prettier = require("prettier");
17
+const prettierConfigPath = require.resolve("../.prettierrc");
18
+const process = require("process");
19
+
20
+const FIXTURES_DIR = path.join(
21
+ process.cwd(),
22
+ "src",
23
+ "__tests__",
24
+ "fixtures",
25
+ "compiler",
26
+ "rules-of-hooks"
27
+);
28
+
29
+const fixtures = [];
30
+for (const test of tests.valid) {
31
+ fixtures.push({ code: test.code, valid: true });
32
+}
33
+for (const test of tests.invalid) {
34
+ fixtures.push({ code: test.code, valid: false });
35
+}
36
+
37
+let index = 0;
38
+for (const fixture of fixtures) {
39
+ let error = null;
40
+ let passes = true;
41
+ try {
42
+ // Does the fixture pass with hooks validation disabled? if not skip it
43
+ runReactForgetBabelPlugin(fixture.code, "rules-of-hooks.js", "typescript", {
44
+ environment: {
45
+ validateHooksUsage: false,
46
+ },
47
+ });
48
+ // Does the fixture pass with hooks validation enabled?
49
+ try {
50
+ runReactForgetBabelPlugin(
51
+ fixture.code,
52
+ "rules-of-hooks.js",
53
+ "typescript",
54
+ {
55
+ environment: {
56
+ validateHooksUsage: true,
57
+ },
58
+ }
59
+ );
60
+ } catch (e) {
61
+ passes = false;
62
+ }
63
+ } catch (e) {
64
+ error = e;
65
+ }
66
+ let name = `rules-of-hooks-${index}.js`;
67
+ let code = fixture.code;
68
+ if (error !== null) {
69
+ name = `todo.${name}`;
70
+ code = `// @skip\n${code}`;
71
+ } else if (fixture.valid === false) {
72
+ name = `error.${name}`;
73
+ if (passes) {
74
+ // oops, passed when we expected an error
75
+ name = `todo.${name}`;
76
+ code = `// @skip\n${code}`;
77
+ }
78
+ } else if (!passes) {
79
+ // oops, error when it should have passed
80
+ name = `todo.${name}`;
81
+ code = `// @skip\n${code}`;
82
+ }
83
+
84
+ const fixturePath = path.join(FIXTURES_DIR, name);
85
+ const options = prettier.resolveConfig.sync(fixturePath, {
86
+ config: prettierConfigPath,
87
+ });
88
+ const formatted = prettier.format(code, options);
89
+ fs.writeFileSync(fixturePath, formatted, "utf8");
90
+ index++;
91
+}
compiler/forget/scripts/eslint-plugin-react-hooks-test-cases.js
new
+985
@@ -0,0 +1,985 @@
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
+};
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ if (props.cond) {
7
+ return null;
8
+ }
9
+ return useHook();
10
+}
11
+
12
+```
13
+
14
+
15
+## Error
16
+
17
+```
18
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
19
+```
20
+
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.js
renamed
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md
new
+26
@@ -0,0 +1,26 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let i = 0;
7
+ for (let x = 0; useHook(x) < 10; useHook(i), x++) {
8
+ i += useHook(x);
9
+ }
10
+ return i;
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (3:3)
20
+
21
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
22
+
23
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (3:3)
24
+```
25
+
26
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.js
renamed
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.expect.md
new
+23
@@ -0,0 +1,23 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = null;
7
+ if (props.cond) {
8
+ } else {
9
+ x = useHook();
10
+ }
11
+ return x;
12
+}
13
+
14
+```
15
+
16
+
17
+## Error
18
+
19
+```
20
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
21
+```
22
+
23
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.js
renamed
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.expect.md
new
+22
@@ -0,0 +1,22 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = null;
7
+ if (props.cond) {
8
+ x = useHook();
9
+ }
10
+ return x;
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
20
+```
21
+
22
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.js
renamed
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.expect.md
new
+22
@@ -0,0 +1,22 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function ComponentWithConditionalHook() {
8
+ if (cond) {
9
+ useConditionalHook();
10
+ }
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
20
+```
21
+
22
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.js
new
+7
@@ -0,0 +1,7 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function ComponentWithConditionalHook() {
4
+ if (cond) {
5
+ useConditionalHook();
6
+ }
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.expect.md
new
+22
@@ -0,0 +1,22 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHookWithConditionalHook() {
8
+ if (cond) {
9
+ useConditionalHook();
10
+ }
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
20
+```
21
+
22
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.js
new
+7
@@ -0,0 +1,7 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHookWithConditionalHook() {
4
+ if (cond) {
5
+ useConditionalHook();
6
+ }
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.expect.md
new
+20
@@ -0,0 +1,20 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function ComponentWithTernaryHook() {
8
+ cond ? useTernaryHook() : null;
9
+}
10
+
11
+```
12
+
13
+
14
+## Error
15
+
16
+```
17
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
18
+```
19
+
20
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.js
new
+5
@@ -0,0 +1,5 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function ComponentWithTernaryHook() {
4
+ cond ? useTernaryHook() : null;
5
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.expect.md
new
+22
@@ -0,0 +1,22 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function ComponentWithHookInsideLoop() {
8
+ while (cond) {
9
+ useHookInsideLoop();
10
+ }
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
20
+```
21
+
22
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.js
new
+7
@@ -0,0 +1,7 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function ComponentWithHookInsideLoop() {
4
+ while (cond) {
5
+ useHookInsideLoop();
6
+ }
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.expect.md
new
+22
@@ -0,0 +1,22 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function normalFunctionWithConditionalHook() {
8
+ if (cond) {
9
+ useHookInsideNormalFunction();
10
+ }
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
20
+```
21
+
22
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.js
new
+7
@@ -0,0 +1,7 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function normalFunctionWithConditionalHook() {
4
+ if (cond) {
5
+ useHookInsideNormalFunction();
6
+ }
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.expect.md
new
+35
@@ -0,0 +1,35 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHookInLoops() {
8
+ while (a) {
9
+ useHook1();
10
+ if (b) return;
11
+ useHook2();
12
+ }
13
+ while (c) {
14
+ useHook3();
15
+ if (d) return;
16
+ useHook4();
17
+ }
18
+}
19
+
20
+```
21
+
22
+
23
+## Error
24
+
25
+```
26
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
27
+
28
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (7:7)
29
+
30
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
31
+
32
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (12:12)
33
+```
34
+
35
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.js
new
+14
@@ -0,0 +1,14 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHookInLoops() {
4
+ while (a) {
5
+ useHook1();
6
+ if (b) return;
7
+ useHook2();
8
+ }
9
+ while (c) {
10
+ useHook3();
11
+ if (d) return;
12
+ useHook4();
13
+ }
14
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.expect.md
new
+26
@@ -0,0 +1,26 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHookInLoops() {
8
+ while (a) {
9
+ useHook1();
10
+ if (b) continue;
11
+ useHook2();
12
+ }
13
+}
14
+
15
+```
16
+
17
+
18
+## Error
19
+
20
+```
21
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
22
+
23
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (7:7)
24
+```
25
+
26
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.js
new
+9
@@ -0,0 +1,9 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHookInLoops() {
4
+ while (a) {
5
+ useHook1();
6
+ if (b) continue;
7
+ useHook2();
8
+ }
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.expect.md
new
+23
@@ -0,0 +1,23 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useLabeledBlock() {
8
+ label: {
9
+ if (a) break label;
10
+ useHook();
11
+ }
12
+}
13
+
14
+```
15
+
16
+
17
+## Error
18
+
19
+```
20
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6)
21
+```
22
+
23
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.js
new
+8
@@ -0,0 +1,8 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useLabeledBlock() {
4
+ label: {
5
+ if (a) break label;
6
+ useHook();
7
+ }
8
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHook() {
8
+ if (a) return;
9
+ useState();
10
+}
11
+
12
+```
13
+
14
+
15
+## Error
16
+
17
+```
18
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
19
+```
20
+
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.js
new
+6
@@ -0,0 +1,6 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHook() {
4
+ if (a) return;
5
+ useState();
6
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.expect.md
new
+26
@@ -0,0 +1,26 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHook() {
8
+ if (a) return;
9
+ if (b) {
10
+ console.log("true");
11
+ } else {
12
+ console.log("false");
13
+ }
14
+ useState();
15
+}
16
+
17
+```
18
+
19
+
20
+## Error
21
+
22
+```
23
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
24
+```
25
+
26
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.js
new
+11
@@ -0,0 +1,11 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHook() {
4
+ if (a) return;
5
+ if (b) {
6
+ console.log("true");
7
+ } else {
8
+ console.log("false");
9
+ }
10
+ useState();
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.expect.md
new
+26
@@ -0,0 +1,26 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHook() {
8
+ if (b) {
9
+ console.log("true");
10
+ } else {
11
+ console.log("false");
12
+ }
13
+ if (a) return;
14
+ useState();
15
+}
16
+
17
+```
18
+
19
+
20
+## Error
21
+
22
+```
23
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
24
+```
25
+
26
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.js
new
+11
@@ -0,0 +1,11 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHook() {
4
+ if (b) {
5
+ console.log("true");
6
+ } else {
7
+ console.log("false");
8
+ }
9
+ if (a) return;
10
+ useState();
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.expect.md
new
+23
@@ -0,0 +1,23 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Invalid because it's dangerous and might not warn otherwise.
6
+// This *must* be invalid.
7
+function useHook() {
8
+ a && useHook1();
9
+ b && useHook2();
10
+}
11
+
12
+```
13
+
14
+
15
+## Error
16
+
17
+```
18
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
19
+
20
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
21
+```
22
+
23
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.js
new
+6
@@ -0,0 +1,6 @@
1
+// Invalid because it's dangerous and might not warn otherwise.
2
+// This *must* be invalid.
3
+function useHook() {
4
+ a && useHook1();
5
+ b && useHook2();
6
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because components can use hooks.
6
+function ComponentWithHook() {
7
+ useHook();
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because components can use hooks.
16
+function ComponentWithHook() {
17
+ useHook();
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because components can use hooks.
2
+function ComponentWithHook() {
3
+ useHook();
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.expect.md
new
+33
@@ -0,0 +1,33 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because components can use hooks.
6
+function createComponentWithHook() {
7
+ return function ComponentWithHook() {
8
+ useHook();
9
+ };
10
+}
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+import { unstable_useMemoCache as useMemoCache } from "react"; // Valid because components can use hooks.
18
+function createComponentWithHook() {
19
+ const $ = useMemoCache(1);
20
+ let t0;
21
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
22
+ t0 = function ComponentWithHook() {
23
+ useHook();
24
+ };
25
+ $[0] = t0;
26
+ } else {
27
+ t0 = $[0];
28
+ }
29
+ return t0;
30
+}
31
+
32
+```
33
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid because components can use hooks.
2
+function createComponentWithHook() {
3
+ return function ComponentWithHook() {
4
+ useHook();
5
+ };
6
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.expect.md
new
+23
@@ -0,0 +1,23 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can call hooks.
6
+function useHook() {
7
+ useHook1();
8
+ useHook2();
9
+}
10
+
11
+```
12
+
13
+## Code
14
+
15
+```javascript
16
+// Valid because hooks can call hooks.
17
+function useHook() {
18
+ useHook1();
19
+ useHook2();
20
+}
21
+
22
+```
23
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.js
new
+5
@@ -0,0 +1,5 @@
1
+// Valid because hooks can call hooks.
2
+function useHook() {
3
+ useHook1();
4
+ useHook2();
5
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.expect.md
new
+35
@@ -0,0 +1,35 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can call hooks.
6
+function createHook() {
7
+ return function useHook() {
8
+ useHook1();
9
+ useHook2();
10
+ };
11
+}
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+import { unstable_useMemoCache as useMemoCache } from "react"; // Valid because hooks can call hooks.
19
+function createHook() {
20
+ const $ = useMemoCache(1);
21
+ let t0;
22
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23
+ t0 = function useHook() {
24
+ useHook1();
25
+ useHook2();
26
+ };
27
+ $[0] = t0;
28
+ } else {
29
+ t0 = $[0];
30
+ }
31
+ return t0;
32
+}
33
+
34
+```
35
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.js
new
+7
@@ -0,0 +1,7 @@
1
+// Valid because hooks can call hooks.
2
+function createHook() {
3
+ return function useHook() {
4
+ useHook1();
5
+ useHook2();
6
+ };
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can call hooks.
6
+function useHook() {
7
+ useState() && a;
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because hooks can call hooks.
16
+function useHook() {
17
+ useState() && a;
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because hooks can call hooks.
2
+function useHook() {
3
+ useState() && a;
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can call hooks.
6
+function useHook() {
7
+ return useHook1() + useHook2();
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because hooks can call hooks.
16
+function useHook() {
17
+ return useHook1() + useHook2();
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because hooks can call hooks.
2
+function useHook() {
3
+ return useHook1() + useHook2();
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can call hooks.
6
+function useHook() {
7
+ return useHook1(useHook2());
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because hooks can call hooks.
16
+function useHook() {
17
+ return useHook1(useHook2());
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because hooks can call hooks.
2
+function useHook() {
3
+ return useHook1(useHook2());
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can be used in anonymous function arguments to
6
+// forwardRef.
7
+const FancyButton = React.forwardRef(function (props, ref) {
8
+ useHook();
9
+ return <button {...props} ref={ref} />;
10
+});
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+// Valid because hooks can be used in anonymous function arguments to
18
+// forwardRef.
19
+const FancyButton = React.forwardRef(function (props, ref) {
20
+ useHook();
21
+ return <button {...props} ref={ref} />;
22
+});
23
+
24
+```
25
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid because hooks can be used in anonymous function arguments to
2
+// forwardRef.
3
+const FancyButton = React.forwardRef(function (props, ref) {
4
+ useHook();
5
+ return <button {...props} ref={ref} />;
6
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can be used in anonymous function arguments to
6
+// forwardRef.
7
+const FancyButton = forwardRef(function (props, ref) {
8
+ useHook();
9
+ return <button {...props} ref={ref} />;
10
+});
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+// Valid because hooks can be used in anonymous function arguments to
18
+// forwardRef.
19
+const FancyButton = forwardRef(function (props, ref) {
20
+ useHook();
21
+ return <button {...props} ref={ref} />;
22
+});
23
+
24
+```
25
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid because hooks can be used in anonymous function arguments to
2
+// forwardRef.
3
+const FancyButton = forwardRef(function (props, ref) {
4
+ useHook();
5
+ return <button {...props} ref={ref} />;
6
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can be used in anonymous function arguments to
6
+// memo.
7
+const MemoizedFunction = memo(function (props) {
8
+ useHook();
9
+ return <button {...props} />;
10
+});
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+// Valid because hooks can be used in anonymous function arguments to
18
+// memo.
19
+const MemoizedFunction = memo(function (props) {
20
+ useHook();
21
+ return <button {...props} />;
22
+});
23
+
24
+```
25
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid because hooks can be used in anonymous function arguments to
2
+// memo.
3
+const MemoizedFunction = memo(function (props) {
4
+ useHook();
5
+ return <button {...props} />;
6
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can use hooks.
6
+function useHookWithHook() {
7
+ useHook();
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because hooks can use hooks.
16
+function useHookWithHook() {
17
+ useHook();
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because hooks can use hooks.
2
+function useHookWithHook() {
3
+ useHook();
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.expect.md
new
+29
@@ -0,0 +1,29 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because classes can call functions.
6
+// We don't consider these to be hooks.
7
+class C {
8
+ m() {
9
+ this.useHook();
10
+ super.useHook();
11
+ }
12
+}
13
+
14
+```
15
+
16
+## Code
17
+
18
+```javascript
19
+// Valid because classes can call functions.
20
+// We don't consider these to be hooks.
21
+class C {
22
+ m() {
23
+ this.useHook();
24
+ super.useHook();
25
+ }
26
+}
27
+
28
+```
29
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.js
new
+8
@@ -0,0 +1,8 @@
1
+// Valid because classes can call functions.
2
+// We don't consider these to be hooks.
3
+class C {
4
+ m() {
5
+ this.useHook();
6
+ super.useHook();
7
+ }
8
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.expect.md
new
+27
@@ -0,0 +1,27 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because they're not matching use[A-Z].
6
+fooState();
7
+_use();
8
+_useState();
9
+use_hook();
10
+// also valid because it's not matching the PascalCase namespace
11
+jest.useFakeTimer();
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+// Valid because they're not matching use[A-Z].
19
+fooState();
20
+_use();
21
+_useState();
22
+use_hook();
23
+// also valid because it's not matching the PascalCase namespace
24
+jest.useFakeTimer();
25
+
26
+```
27
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.js
new
+7
@@ -0,0 +1,7 @@
1
+// Valid because they're not matching use[A-Z].
2
+fooState();
3
+_use();
4
+_useState();
5
+use_hook();
6
+// also valid because it's not matching the PascalCase namespace
7
+jest.useFakeTimer();
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.expect.md
new
+35
@@ -0,0 +1,35 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Regression test for some internal code.
6
+// This shows how the "callback rule" is more relaxed,
7
+// and doesn't kick in unless we're confident we're in
8
+// a component or a hook.
9
+function makeListener(instance) {
10
+ each(pixelsWithInferredEvents, (pixel) => {
11
+ if (useExtendedSelector(pixel.id) && extendedButton) {
12
+ foo();
13
+ }
14
+ });
15
+}
16
+
17
+```
18
+
19
+## Code
20
+
21
+```javascript
22
+// Regression test for some internal code.
23
+// This shows how the "callback rule" is more relaxed,
24
+// and doesn't kick in unless we're confident we're in
25
+// a component or a hook.
26
+function makeListener(instance) {
27
+ each(pixelsWithInferredEvents, (pixel) => {
28
+ if (useExtendedSelector(pixel.id) && extendedButton) {
29
+ foo();
30
+ }
31
+ });
32
+}
33
+
34
+```
35
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.js
new
+11
@@ -0,0 +1,11 @@
1
+// Regression test for some internal code.
2
+// This shows how the "callback rule" is more relaxed,
3
+// and doesn't kick in unless we're confident we're in
4
+// a component or a hook.
5
+function makeListener(instance) {
6
+ each(pixelsWithInferredEvents, (pixel) => {
7
+ if (useExtendedSelector(pixel.id) && extendedButton) {
8
+ foo();
9
+ }
10
+ });
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.expect.md
new
+27
@@ -0,0 +1,27 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// This is valid because "use"-prefixed functions called in
6
+// unnamed function arguments are not assumed to be hooks.
7
+unknownFunction(function (foo, bar) {
8
+ if (foo) {
9
+ useNotAHook(bar);
10
+ }
11
+});
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+// This is valid because "use"-prefixed functions called in
19
+// unnamed function arguments are not assumed to be hooks.
20
+unknownFunction(function (foo, bar) {
21
+ if (foo) {
22
+ useNotAHook(bar);
23
+ }
24
+});
25
+
26
+```
27
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.js
new
+7
@@ -0,0 +1,7 @@
1
+// This is valid because "use"-prefixed functions called in
2
+// unnamed function arguments are not assumed to be hooks.
3
+unknownFunction(function (foo, bar) {
4
+ if (foo) {
5
+ useNotAHook(bar);
6
+ }
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.expect.md
new
+243
@@ -0,0 +1,243 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Is valid but hard to compute by brute-forcing
6
+function MyComponent() {
7
+ // 40 conditions
8
+ if (c) {
9
+ } else {
10
+ }
11
+ if (c) {
12
+ } else {
13
+ }
14
+ if (c) {
15
+ } else {
16
+ }
17
+ if (c) {
18
+ } else {
19
+ }
20
+ if (c) {
21
+ } else {
22
+ }
23
+ if (c) {
24
+ } else {
25
+ }
26
+ if (c) {
27
+ } else {
28
+ }
29
+ if (c) {
30
+ } else {
31
+ }
32
+ if (c) {
33
+ } else {
34
+ }
35
+ if (c) {
36
+ } else {
37
+ }
38
+ if (c) {
39
+ } else {
40
+ }
41
+ if (c) {
42
+ } else {
43
+ }
44
+ if (c) {
45
+ } else {
46
+ }
47
+ if (c) {
48
+ } else {
49
+ }
50
+ if (c) {
51
+ } else {
52
+ }
53
+ if (c) {
54
+ } else {
55
+ }
56
+ if (c) {
57
+ } else {
58
+ }
59
+ if (c) {
60
+ } else {
61
+ }
62
+ if (c) {
63
+ } else {
64
+ }
65
+ if (c) {
66
+ } else {
67
+ }
68
+ if (c) {
69
+ } else {
70
+ }
71
+ if (c) {
72
+ } else {
73
+ }
74
+ if (c) {
75
+ } else {
76
+ }
77
+ if (c) {
78
+ } else {
79
+ }
80
+ if (c) {
81
+ } else {
82
+ }
83
+ if (c) {
84
+ } else {
85
+ }
86
+ if (c) {
87
+ } else {
88
+ }
89
+ if (c) {
90
+ } else {
91
+ }
92
+ if (c) {
93
+ } else {
94
+ }
95
+ if (c) {
96
+ } else {
97
+ }
98
+ if (c) {
99
+ } else {
100
+ }
101
+ if (c) {
102
+ } else {
103
+ }
104
+ if (c) {
105
+ } else {
106
+ }
107
+ if (c) {
108
+ } else {
109
+ }
110
+ if (c) {
111
+ } else {
112
+ }
113
+ if (c) {
114
+ } else {
115
+ }
116
+ if (c) {
117
+ } else {
118
+ }
119
+ if (c) {
120
+ } else {
121
+ }
122
+ if (c) {
123
+ } else {
124
+ }
125
+ if (c) {
126
+ } else {
127
+ }
128
+
129
+ // 10 hooks
130
+ useHook();
131
+ useHook();
132
+ useHook();
133
+ useHook();
134
+ useHook();
135
+ useHook();
136
+ useHook();
137
+ useHook();
138
+ useHook();
139
+ useHook();
140
+}
141
+
142
+```
143
+
144
+## Code
145
+
146
+```javascript
147
+// Is valid but hard to compute by brute-forcing
148
+function MyComponent() {
149
+ if (c) {
150
+ }
151
+ if (c) {
152
+ }
153
+ if (c) {
154
+ }
155
+ if (c) {
156
+ }
157
+ if (c) {
158
+ }
159
+ if (c) {
160
+ }
161
+ if (c) {
162
+ }
163
+ if (c) {
164
+ }
165
+ if (c) {
166
+ }
167
+ if (c) {
168
+ }
169
+ if (c) {
170
+ }
171
+ if (c) {
172
+ }
173
+ if (c) {
174
+ }
175
+ if (c) {
176
+ }
177
+ if (c) {
178
+ }
179
+ if (c) {
180
+ }
181
+ if (c) {
182
+ }
183
+ if (c) {
184
+ }
185
+ if (c) {
186
+ }
187
+ if (c) {
188
+ }
189
+ if (c) {
190
+ }
191
+ if (c) {
192
+ }
193
+ if (c) {
194
+ }
195
+ if (c) {
196
+ }
197
+ if (c) {
198
+ }
199
+ if (c) {
200
+ }
201
+ if (c) {
202
+ }
203
+ if (c) {
204
+ }
205
+ if (c) {
206
+ }
207
+ if (c) {
208
+ }
209
+ if (c) {
210
+ }
211
+ if (c) {
212
+ }
213
+ if (c) {
214
+ }
215
+ if (c) {
216
+ }
217
+ if (c) {
218
+ }
219
+ if (c) {
220
+ }
221
+ if (c) {
222
+ }
223
+ if (c) {
224
+ }
225
+ if (c) {
226
+ }
227
+ if (c) {
228
+ }
229
+
230
+ useHook();
231
+ useHook();
232
+ useHook();
233
+ useHook();
234
+ useHook();
235
+ useHook();
236
+ useHook();
237
+ useHook();
238
+ useHook();
239
+ useHook();
240
+}
241
+
242
+```
243
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.js
new
+136
@@ -0,0 +1,136 @@
1
+// Is valid but hard to compute by brute-forcing
2
+function MyComponent() {
3
+ // 40 conditions
4
+ if (c) {
5
+ } else {
6
+ }
7
+ if (c) {
8
+ } else {
9
+ }
10
+ if (c) {
11
+ } else {
12
+ }
13
+ if (c) {
14
+ } else {
15
+ }
16
+ if (c) {
17
+ } else {
18
+ }
19
+ if (c) {
20
+ } else {
21
+ }
22
+ if (c) {
23
+ } else {
24
+ }
25
+ if (c) {
26
+ } else {
27
+ }
28
+ if (c) {
29
+ } else {
30
+ }
31
+ if (c) {
32
+ } else {
33
+ }
34
+ if (c) {
35
+ } else {
36
+ }
37
+ if (c) {
38
+ } else {
39
+ }
40
+ if (c) {
41
+ } else {
42
+ }
43
+ if (c) {
44
+ } else {
45
+ }
46
+ if (c) {
47
+ } else {
48
+ }
49
+ if (c) {
50
+ } else {
51
+ }
52
+ if (c) {
53
+ } else {
54
+ }
55
+ if (c) {
56
+ } else {
57
+ }
58
+ if (c) {
59
+ } else {
60
+ }
61
+ if (c) {
62
+ } else {
63
+ }
64
+ if (c) {
65
+ } else {
66
+ }
67
+ if (c) {
68
+ } else {
69
+ }
70
+ if (c) {
71
+ } else {
72
+ }
73
+ if (c) {
74
+ } else {
75
+ }
76
+ if (c) {
77
+ } else {
78
+ }
79
+ if (c) {
80
+ } else {
81
+ }
82
+ if (c) {
83
+ } else {
84
+ }
85
+ if (c) {
86
+ } else {
87
+ }
88
+ if (c) {
89
+ } else {
90
+ }
91
+ if (c) {
92
+ } else {
93
+ }
94
+ if (c) {
95
+ } else {
96
+ }
97
+ if (c) {
98
+ } else {
99
+ }
100
+ if (c) {
101
+ } else {
102
+ }
103
+ if (c) {
104
+ } else {
105
+ }
106
+ if (c) {
107
+ } else {
108
+ }
109
+ if (c) {
110
+ } else {
111
+ }
112
+ if (c) {
113
+ } else {
114
+ }
115
+ if (c) {
116
+ } else {
117
+ }
118
+ if (c) {
119
+ } else {
120
+ }
121
+ if (c) {
122
+ } else {
123
+ }
124
+
125
+ // 10 hooks
126
+ useHook();
127
+ useHook();
128
+ useHook();
129
+ useHook();
130
+ useHook();
131
+ useHook();
132
+ useHook();
133
+ useHook();
134
+ useHook();
135
+ useHook();
136
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.expect.md
new
+33
@@ -0,0 +1,33 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because hooks can use hooks.
6
+function createHook() {
7
+ return function useHookWithHook() {
8
+ useHook();
9
+ };
10
+}
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+import { unstable_useMemoCache as useMemoCache } from "react"; // Valid because hooks can use hooks.
18
+function createHook() {
19
+ const $ = useMemoCache(1);
20
+ let t0;
21
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
22
+ t0 = function useHookWithHook() {
23
+ useHook();
24
+ };
25
+ $[0] = t0;
26
+ } else {
27
+ t0 = $[0];
28
+ }
29
+ return t0;
30
+}
31
+
32
+```
33
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid because hooks can use hooks.
2
+function createHook() {
3
+ return function useHookWithHook() {
4
+ useHook();
5
+ };
6
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because components can call functions.
6
+function ComponentWithNormalFunction() {
7
+ doSomething();
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because components can call functions.
16
+function ComponentWithNormalFunction() {
17
+ doSomething();
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because components can call functions.
2
+function ComponentWithNormalFunction() {
3
+ doSomething();
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.expect.md
new
+21
@@ -0,0 +1,21 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because functions can call functions.
6
+function normalFunctionWithNormalFunction() {
7
+ doSomething();
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+// Valid because functions can call functions.
16
+function normalFunctionWithNormalFunction() {
17
+ doSomething();
18
+}
19
+
20
+```
21
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.js
new
+4
@@ -0,0 +1,4 @@
1
+// Valid because functions can call functions.
2
+function normalFunctionWithNormalFunction() {
3
+ doSomething();
4
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid because functions can call functions.
6
+function normalFunctionWithConditionalFunction() {
7
+ if (cond) {
8
+ doSomething();
9
+ }
10
+}
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+// Valid because functions can call functions.
18
+function normalFunctionWithConditionalFunction() {
19
+ if (cond) {
20
+ doSomething();
21
+ }
22
+}
23
+
24
+```
25
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid because functions can call functions.
2
+function normalFunctionWithConditionalFunction() {
3
+ if (cond) {
4
+ doSomething();
5
+ }
6
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.expect.md
new
+22
@@ -0,0 +1,22 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// Valid although unconditional return doesn't make sense and would fail other rules.
6
+// We could make it invalid but it doesn't matter.
7
+function useUnreachable() {
8
+ return;
9
+ useHook();
10
+}
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+// Valid although unconditional return doesn't make sense and would fail other rules.
18
+// We could make it invalid but it doesn't matter.
19
+function useUnreachable() {}
20
+
21
+```
22
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.js
new
+6
@@ -0,0 +1,6 @@
1
+// Valid although unconditional return doesn't make sense and would fail other rules.
2
+// We could make it invalid but it doesn't matter.
3
+function useUnreachable() {
4
+ return;
5
+ useHook();
6
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-33.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+Hook.useState();
4
+Hook._useState();
5
+Hook.use42();
6
+Hook.useHook();
7
+Hook.use_hook();
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-34.js
new
+8
@@ -0,0 +1,8 @@
1
+// @skip
2
+
3
+class C {
4
+ m() {
5
+ This.useHook();
6
+ Super.useHook();
7
+ }
8
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-35.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// This is a false positive (it's valid) that unfortunately
4
+// we cannot avoid. Prefer to rename it to not start with "use"
5
+class Foo extends Component {
6
+ render() {
7
+ if (cond) {
8
+ FooStore.useFeatureFlag();
9
+ }
10
+ }
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-36.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+function ComponentWithConditionalHook() {
6
+ if (cond) {
7
+ Namespace.useConditionalHook();
8
+ }
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-37.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+function createComponent() {
6
+ return function ComponentWithConditionalHook() {
7
+ if (cond) {
8
+ useConditionalHook();
9
+ }
10
+ };
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-39.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+function createHook() {
6
+ return function useHookWithConditionalHook() {
7
+ if (cond) {
8
+ useConditionalHook();
9
+ }
10
+ };
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-41.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// Invalid because it's a common misunderstanding.
4
+// We *could* make it valid but the runtime error could be confusing.
5
+function ComponentWithHookInsideCallback() {
6
+ useEffect(() => {
7
+ useHookInsideCallback();
8
+ });
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-42.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// Invalid because it's a common misunderstanding.
4
+// We *could* make it valid but the runtime error could be confusing.
5
+function createComponent() {
6
+ return function ComponentWithHookInsideCallback() {
7
+ useEffect(() => {
8
+ useHookInsideCallback();
9
+ });
10
+ };
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-45.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// Invalid because it's a common misunderstanding.
4
+// We *could* make it valid but the runtime error could be confusing.
5
+function ComponentWithHookInsideCallback() {
6
+ function handleClick() {
7
+ useState();
8
+ }
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-46.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// Invalid because it's a common misunderstanding.
4
+// We *could* make it valid but the runtime error could be confusing.
5
+function createComponent() {
6
+ return function ComponentWithHookInsideCallback() {
7
+ function handleClick() {
8
+ useState();
9
+ }
10
+ };
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-48.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+function renderItem() {
6
+ useState();
7
+}
8
+
9
+function List(props) {
10
+ return props.items.map(renderItem);
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-49.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// Currently invalid because it violates the convention and removes the "taint"
4
+// from a hook. We *could* make it valid to avoid some false positives but let's
5
+// ensure that we don't break the "renderItem" and "normalFunctionWithConditionalHook"
6
+// cases which must remain invalid.
7
+function normalFunctionWithHook() {
8
+ useHookInsideNormalFunction();
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-50.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// These are neither functions nor hooks.
4
+function _normalFunctionWithHook() {
5
+ useHookInsideNormalFunction();
6
+}
7
+function _useNotAHook() {
8
+ useHookInsideNormalFunction();
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-63.js
new
+10
@@ -0,0 +1,10 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+const FancyButton = forwardRef(function (props, ref) {
6
+ if (props.fancy) {
7
+ useCustomHook();
8
+ }
9
+ return <button ref={ref}>{props.children}</button>;
10
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-64.js
new
+10
@@ -0,0 +1,10 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+const MemoizedButton = memo(function (props) {
6
+ if (props.fancy) {
7
+ useCustomHook();
8
+ }
9
+ return <button>{props.children}</button>;
10
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-65.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+// This is invalid because "use"-prefixed functions used in named
4
+// functions are assumed to be hooks.
5
+React.unknownFunction(function notAComponent(foo, bar) {
6
+ useProbablyAHook(bar);
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-66.js
new
+13
@@ -0,0 +1,13 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous.
4
+// Normally, this would crash, but not if you use inline requires.
5
+// This *must* be invalid.
6
+// It's expected to have some false positives, but arguably
7
+// they are confusing anyway due to the use*() convention
8
+// already being associated with Hooks.
9
+useState();
10
+if (foo) {
11
+ const foo = React.useCallback(() => {});
12
+}
13
+useCustomHook();
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-67.js
new
+14
@@ -0,0 +1,14 @@
1
+// @skip
2
+
3
+// Technically this is a false positive.
4
+// We *could* make it valid (and it used to be).
5
+//
6
+// However, top-level Hook-like calls can be very dangerous
7
+// in environments with inline requires because they can mask
8
+// the runtime error by accident.
9
+// So we prefer to disallow it despite the false positive.
10
+
11
+const { createHistory, useBasename } = require("history-2.1.2");
12
+const browserHistory = useBasename(createHistory)({
13
+ basename: "/",
14
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-68.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+class ClassComponentWithFeatureFlag extends React.Component {
4
+ render() {
5
+ if (foo) {
6
+ useFeatureFlag();
7
+ }
8
+ }
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-69.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+class ClassComponentWithHook extends React.Component {
4
+ render() {
5
+ React.useState();
6
+ }
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-70.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+(class {
4
+ useHook = () => {
5
+ useState();
6
+ };
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-71.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+(class {
4
+ useHook() {
5
+ useState();
6
+ }
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-72.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+(class {
4
+ h = () => {
5
+ useState();
6
+ };
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-73.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+(class {
4
+ i() {
5
+ useState();
6
+ }
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-15.js
new
+8
@@ -0,0 +1,8 @@
1
+// @skip
2
+
3
+// Valid because hooks can be used in anonymous arrow-function arguments
4
+// to forwardRef.
5
+const FancyButton = React.forwardRef((props, ref) => {
6
+ useHook();
7
+ return <button {...props} ref={ref} />;
8
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-18.js
new
+8
@@ -0,0 +1,8 @@
1
+// @skip
2
+
3
+// Valid because hooks can be used in anonymous function arguments to
4
+// React.memo.
5
+const MemoizedFunction = React.memo((props) => {
6
+ useHook();
7
+ return <button {...props} />;
8
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-21.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+// Valid -- this is a regression test.
4
+jest.useFakeTimers();
5
+beforeEach(() => {
6
+ jest.useRealTimers();
7
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-24.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// This is valid because "use"-prefixed functions called in
4
+// unnamed function arguments are not assumed to be hooks.
5
+React.unknownFunction((foo, bar) => {
6
+ if (foo) {
7
+ useNotAHook(bar);
8
+ }
9
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-26.js
new
+7
@@ -0,0 +1,7 @@
1
+// @skip
2
+
3
+// Regression test for incorrectly flagged valid code.
4
+function RegressionTest() {
5
+ const foo = cond ? a : b;
6
+ useState();
7
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-27.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// Valid because exceptions abort rendering
4
+function RegressionTest() {
5
+ if (page == null) {
6
+ throw new Error("oh no!");
7
+ }
8
+ useState();
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-28.js
new
+11
@@ -0,0 +1,11 @@
1
+// @skip
2
+
3
+// Valid because the loop doesn't change the order of hooks calls.
4
+function RegressionTest() {
5
+ const res = [];
6
+ const additionalCond = true;
7
+ for (let i = 0; i !== 10 && additionalCond; ++i) {
8
+ res.push(i);
9
+ }
10
+ React.useLayoutEffect(() => {});
11
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-30.js
new
+69
@@ -0,0 +1,69 @@
1
+// @skip
2
+
3
+// Valid because the neither the conditions before or after the hook affect the hook call
4
+// Failed prior to implementing BigInt because pathsFromStartToEnd and allPathsFromStartToEnd were too big and had rounding errors
5
+const useSomeHook = () => {};
6
+
7
+const SomeName = () => {
8
+ const filler = FILLER ?? FILLER ?? FILLER;
9
+ const filler2 = FILLER ?? FILLER ?? FILLER;
10
+ const filler3 = FILLER ?? FILLER ?? FILLER;
11
+ const filler4 = FILLER ?? FILLER ?? FILLER;
12
+ const filler5 = FILLER ?? FILLER ?? FILLER;
13
+ const filler6 = FILLER ?? FILLER ?? FILLER;
14
+ const filler7 = FILLER ?? FILLER ?? FILLER;
15
+ const filler8 = FILLER ?? FILLER ?? FILLER;
16
+
17
+ useSomeHook();
18
+
19
+ if (anyConditionCanEvenBeFalse) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <React.Fragment>
25
+ {FILLER ? FILLER : FILLER}
26
+ {FILLER ? FILLER : FILLER}
27
+ {FILLER ? FILLER : FILLER}
28
+ {FILLER ? FILLER : FILLER}
29
+ {FILLER ? FILLER : FILLER}
30
+ {FILLER ? FILLER : FILLER}
31
+ {FILLER ? FILLER : FILLER}
32
+ {FILLER ? FILLER : FILLER}
33
+ {FILLER ? FILLER : FILLER}
34
+ {FILLER ? FILLER : FILLER}
35
+ {FILLER ? FILLER : FILLER}
36
+ {FILLER ? FILLER : FILLER}
37
+ {FILLER ? FILLER : FILLER}
38
+ {FILLER ? FILLER : FILLER}
39
+ {FILLER ? FILLER : FILLER}
40
+ {FILLER ? FILLER : FILLER}
41
+ {FILLER ? FILLER : FILLER}
42
+ {FILLER ? FILLER : FILLER}
43
+ {FILLER ? FILLER : FILLER}
44
+ {FILLER ? FILLER : FILLER}
45
+ {FILLER ? FILLER : FILLER}
46
+ {FILLER ? FILLER : FILLER}
47
+ {FILLER ? FILLER : FILLER}
48
+ {FILLER ? FILLER : FILLER}
49
+ {FILLER ? FILLER : FILLER}
50
+ {FILLER ? FILLER : FILLER}
51
+ {FILLER ? FILLER : FILLER}
52
+ {FILLER ? FILLER : FILLER}
53
+ {FILLER ? FILLER : FILLER}
54
+ {FILLER ? FILLER : FILLER}
55
+ {FILLER ? FILLER : FILLER}
56
+ {FILLER ? FILLER : FILLER}
57
+ {FILLER ? FILLER : FILLER}
58
+ {FILLER ? FILLER : FILLER}
59
+ {FILLER ? FILLER : FILLER}
60
+ {FILLER ? FILLER : FILLER}
61
+ {FILLER ? FILLER : FILLER}
62
+ {FILLER ? FILLER : FILLER}
63
+ {FILLER ? FILLER : FILLER}
64
+ {FILLER ? FILLER : FILLER}
65
+ {FILLER ? FILLER : FILLER}
66
+ {FILLER ? FILLER : FILLER}
67
+ </React.Fragment>
68
+ );
69
+};
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-31.js
new
+12
@@ -0,0 +1,12 @@
1
+// @skip
2
+
3
+// Valid because the neither the condition nor the loop affect the hook call.
4
+function App(props) {
5
+ const someObject = { propA: true };
6
+ for (const propName in someObject) {
7
+ if (propName === true) {
8
+ } else {
9
+ }
10
+ }
11
+ const [myState, setMyState] = useState(null);
12
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-43.js
new
+10
@@ -0,0 +1,10 @@
1
+// @skip
2
+
3
+// Invalid because it's a common misunderstanding.
4
+// We *could* make it valid but the runtime error could be confusing.
5
+const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
6
+ useEffect(() => {
7
+ useHookInsideCallback();
8
+ });
9
+ return <button {...props} ref={ref} />;
10
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-44.js
new
+10
@@ -0,0 +1,10 @@
1
+// @skip
2
+
3
+// Invalid because it's a common misunderstanding.
4
+// We *could* make it valid but the runtime error could be confusing.
5
+const ComponentWithHookInsideCallback = React.memo((props) => {
6
+ useEffect(() => {
7
+ useHookInsideCallback();
8
+ });
9
+ return <button {...props} />;
10
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-55.js
new
+40
@@ -0,0 +1,40 @@
1
+// @skip
2
+
3
+// Currently invalid.
4
+// These are variations capturing the current heuristic--
5
+// we only allow hooks in PascalCase or useFoo functions.
6
+// We *could* make some of these valid. But before doing it,
7
+// consider specific cases documented above that contain reasoning.
8
+function a() {
9
+ useState();
10
+}
11
+const whatever = function b() {
12
+ useState();
13
+};
14
+const c = () => {
15
+ useState();
16
+};
17
+let d = () => useState();
18
+e = () => {
19
+ useState();
20
+};
21
+({
22
+ f: () => {
23
+ useState();
24
+ },
25
+});
26
+({
27
+ g() {
28
+ useState();
29
+ },
30
+});
31
+const {
32
+ j = () => {
33
+ useState();
34
+ },
35
+} = {};
36
+({
37
+ k = () => {
38
+ useState();
39
+ },
40
+} = {});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-60.js
new
+10
@@ -0,0 +1,10 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+function useHook() {
6
+ try {
7
+ f();
8
+ useState();
9
+ } catch {}
10
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-61.js
new
+9
@@ -0,0 +1,9 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+function useHook({ bar }) {
6
+ let foo1 = bar && useState();
7
+ let foo2 = bar || useState();
8
+ let foo3 = bar ?? useState();
9
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-62.js
new
+10
@@ -0,0 +1,10 @@
1
+// @skip
2
+
3
+// Invalid because it's dangerous and might not warn otherwise.
4
+// This *must* be invalid.
5
+const FancyButton = React.forwardRef((props, ref) => {
6
+ if (props.fancy) {
7
+ useCustomHook();
8
+ }
9
+ return <button ref={ref}>{props.children}</button>;
10
+});
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-7.js
new
+8
@@ -0,0 +1,8 @@
1
+// @skip
2
+
3
+// Valid because functions can call functions.
4
+function functionThatStartsWithUseButIsntAHook() {
5
+ if (cond) {
6
+ userFetch();
7
+ }
8
+}
compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-9.js
new
+39
@@ -0,0 +1,39 @@
1
+// @skip
2
+
3
+// Valid because hooks can call hooks.
4
+function useHook() {
5
+ useState();
6
+}
7
+const whatever = function useHook() {
8
+ useState();
9
+};
10
+const useHook1 = () => {
11
+ useState();
12
+};
13
+let useHook2 = () => useState();
14
+useHook2 = () => {
15
+ useState();
16
+};
17
+({
18
+ useHook: () => {
19
+ useState();
20
+ },
21
+});
22
+({
23
+ useHook() {
24
+ useState();
25
+ },
26
+});
27
+const {
28
+ useHook3 = () => {
29
+ useState();
30
+ },
31
+} = {};
32
+({
33
+ useHook = () => {
34
+ useState();
35
+ },
36
+} = {});
37
+Namespace.useHook = () => {
38
+ useState();
39
+};
compiler/forget/src/__tests__/test-utils/generateTestsFromFixtures.ts
+10
-6
@@ -10,6 +10,7 @@
10
/* global expect,test */
11
12
import fs from "fs";
13
+import glob from "glob";
14
import path from "path";
15
import { GatingOptions, PluginOptions } from "../../Babel/PluginOptions";
16
@@ -55,7 +56,9 @@ export default function generateTestsFromFixtures(
56
) {
57
let files: Array<string>;
58
try {
58
- files = fs.readdirSync(fixturesPath);
59
+ files = glob.sync("**/*.{js,md}", {
60
+ cwd: fixturesPath,
61
+ });
62
} catch (e) {
63
if (e.code === "ENOENT") {
64
files = [];
@@ -233,11 +236,12 @@ function matchInputOutputFixtures(files: string[], fixturesPath: string) {
236
);
237
}
238
entry.input = resolvedPath;
236
- const outputFile = path.format({
237
- dir: fixturesPath,
238
- name: basename,
239
- ext: EXPECT_SUFFIX,
240
- });
239
+ const outputName = `${basename}${EXPECT_SUFFIX}`;
240
+ const outputFile = path.join(
241
+ fixturesPath,
242
+ path.dirname(file),
243
+ outputName
244
+ );
245
entry.output = outputFile;
246
}
247
}
compiler/forget/yarn.lock
+56
-9
@@ -81,7 +81,7 @@
81
source-map "^0.5.0"
82
trim-right "^1.0.1"
83
84
-"@babel/generator@^7.0.0", "@babel/generator@^7.21.4":
84
+"@babel/generator@^7.0.0":
85
version "7.21.4"
86
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
87
integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==
@@ -100,6 +100,16 @@
100
"@jridgewell/gen-mapping" "^0.3.2"
101
jsesc "^2.5.1"
102
103
+"@babel/generator@^7.21.5":
104
+ version "7.21.5"
105
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
106
+ integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
107
+ dependencies:
108
+ "@babel/types" "^7.21.5"
109
+ "@jridgewell/gen-mapping" "^0.3.2"
110
+ "@jridgewell/trace-mapping" "^0.3.17"
111
+ jsesc "^2.5.1"
112
+
113
"@babel/helper-annotate-as-pure@^7.18.6":
114
version "7.18.6"
115
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@@ -135,6 +145,11 @@
145
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
146
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
147
148
+"@babel/helper-environment-visitor@^7.21.5":
149
+ version "7.21.5"
150
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
151
+ integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
152
+
153
"@babel/helper-function-name@^7.1.0", "@babel/helper-function-name@^7.19.0":
154
version "7.19.0"
155
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
@@ -259,6 +274,11 @@
274
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
275
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
276
277
+"@babel/helper-string-parser@^7.21.5":
278
+ version "7.21.5"
279
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
280
+ integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
281
+
282
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
283
version "7.19.1"
284
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
@@ -307,7 +327,12 @@
327
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3"
328
integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==
329
310
-"@babel/parser@^7.21.4", "@babel/parser@^7.7.4":
330
+"@babel/parser@^7.21.5":
331
+ version "7.21.8"
332
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
333
+ integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
334
+
335
+"@babel/parser@^7.7.4":
336
version "7.21.4"
337
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
338
integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
@@ -582,18 +607,18 @@
607
lodash "^4.17.10"
608
609
"@babel/traverse@^7.19.1":
585
- version "7.21.4"
586
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
587
- integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
610
+ version "7.21.5"
611
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133"
612
+ integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==
613
dependencies:
614
"@babel/code-frame" "^7.21.4"
590
- "@babel/generator" "^7.21.4"
591
- "@babel/helper-environment-visitor" "^7.18.9"
615
+ "@babel/generator" "^7.21.5"
616
+ "@babel/helper-environment-visitor" "^7.21.5"
617
"@babel/helper-function-name" "^7.21.0"
618
"@babel/helper-hoist-variables" "^7.18.6"
619
"@babel/helper-split-export-declaration" "^7.18.6"
595
- "@babel/parser" "^7.21.4"
596
- "@babel/types" "^7.21.4"
620
+ "@babel/parser" "^7.21.5"
621
+ "@babel/types" "^7.21.5"
622
debug "^4.1.0"
623
globals "^11.1.0"
624
@@ -659,6 +684,15 @@
684
"@babel/helper-validator-identifier" "^7.19.1"
685
to-fast-properties "^2.0.0"
686
687
+"@babel/types@^7.21.5":
688
+ version "7.21.5"
689
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
690
+ integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
691
+ dependencies:
692
+ "@babel/helper-string-parser" "^7.21.5"
693
+ "@babel/helper-validator-identifier" "^7.19.1"
694
+ to-fast-properties "^2.0.0"
695
+
696
"@bcoe/v8-coverage@^0.2.3":
697
version "0.2.3"
698
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1110,6 +1144,14 @@
1144
dependencies:
1145
"@babel/types" "^7.3.0"
1146
1147
+"@types/glob@^8.1.0":
1148
+ version "8.1.0"
1149
+ resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc"
1150
+ integrity sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==
1151
+ dependencies:
1152
+ "@types/minimatch" "^5.1.2"
1153
+ "@types/node" "*"
1154
+
1155
"@types/graceful-fs@^4.1.3":
1156
version "4.1.5"
1157
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -1171,6 +1213,11 @@
1213
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
1214
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
1215
1216
+"@types/minimatch@^5.1.2":
1217
+ version "5.1.2"
1218
+ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
1219
+ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
1220
+
1221
"@types/node@*", "@types/node@^18.7.18":
1222
version "18.7.19"
1223
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.19.tgz#ad83aa9b7af470fab7e0f562be87e97dc8ffe08e"