[ESLint] Suggest moving inside a Hook or useCallback when bare function is a dependency (#15026)
* Warn about bare function deps and suggest moving or useCallback * Clearer wording
Dan Abramov committed
Mar 6, 2019 at 23:50 UTC
9b7e1d1389e080d19e71680bbbe979ec58fa7389
2 files changed
+579
-33
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+448
-31
@@ -313,7 +313,7 @@ const tests = {
313
},
314
{
315
code: `
316
- function MyComponent({ maybeRef2 }) {
316
+ function MyComponent({ maybeRef2, foo }) {
317
const definitelyRef1 = useRef();
318
const definitelyRef2 = useRef();
319
const maybeRef1 = useSomeOtherRefyThing();
@@ -323,8 +323,8 @@ const tests = {
323
const [state4, dispatch2] = React.useReducer();
324
const [state5, maybeSetState] = useFunnyState();
325
const [state6, maybeDispatch] = useFunnyReducer();
326
- function mySetState() {}
327
- function myDispatch() {}
326
+ const mySetState = useCallback(() => {}, []);
327
+ let myDispatch = useCallback(() => {}, []);
328
329
useEffect(() => {
330
// Known to be static
@@ -380,8 +380,8 @@ const tests = {
380
const [state5, maybeSetState] = useFunnyState();
381
const [state6, maybeDispatch] = useFunnyReducer();
382
383
- function mySetState() {}
384
- function myDispatch() {}
383
+ const mySetState = useCallback(() => {}, []);
384
+ let myDispatch = useCallback(() => {}, []);
385
386
useEffect(() => {
387
// Known to be static
@@ -662,30 +662,6 @@ const tests = {
662
}
663
`,
664
},
665
- {
666
- code: `
667
- function MyComponent(props) {
668
- function handleNext1() {
669
- console.log('hello');
670
- }
671
- const handleNext2 = () => {
672
- console.log('hello');
673
- };
674
- let handleNext3 = function() {
675
- console.log('hello');
676
- };
677
- useEffect(() => {
678
- return Store.subscribe(handleNext1);
679
- }, [handleNext1]);
680
- useLayoutEffect(() => {
681
- return Store.subscribe(handleNext2);
682
- }, [handleNext2]);
683
- useMemo(() => {
684
- return Store.subscribe(handleNext3);
685
- }, [handleNext3]);
686
- }
687
- `,
688
- },
665
{
666
// Declaring handleNext is optional because
667
// it doesn't use anything in the function scope.
@@ -950,8 +926,12 @@ const tests = {
926
}
927
`,
928
errors: [
953
- "React Hook useMemo doesn't serve any purpose without a dependency array as a second argument.",
954
- "React Hook useCallback doesn't serve any purpose without a dependency array as a second argument.",
929
+ "React Hook useMemo doesn't serve any purpose without a dependency array. " +
930
+ 'To enable this optimization, pass an array of values used by the inner ' +
931
+ 'function as the second argument to useMemo.',
932
+ "React Hook useCallback doesn't serve any purpose without a dependency array. " +
933
+ 'To enable this optimization, pass an array of values used by the inner ' +
934
+ 'function as the second argument to useCallback.',
935
],
936
},
937
{
@@ -3313,6 +3293,443 @@ const tests = {
3293
'Either include it or remove the dependency array.',
3294
],
3295
},
3296
+ {
3297
+ // Even if the function only references static values,
3298
+ // once you specify it in deps, it will invalidate them.
3299
+ code: `
3300
+ function MyComponent(props) {
3301
+ let [, setState] = useState();
3302
+
3303
+ function handleNext(value) {
3304
+ setState(value);
3305
+ }
3306
+
3307
+ useEffect(() => {
3308
+ return Store.subscribe(handleNext);
3309
+ }, [handleNext]);
3310
+ }
3311
+ `,
3312
+ // Not gonna autofix a function definition
3313
+ // because it's not always safe due to hoisting.
3314
+ output: `
3315
+ function MyComponent(props) {
3316
+ let [, setState] = useState();
3317
+
3318
+ function handleNext(value) {
3319
+ setState(value);
3320
+ }
3321
+
3322
+ useEffect(() => {
3323
+ return Store.subscribe(handleNext);
3324
+ }, [handleNext]);
3325
+ }
3326
+ `,
3327
+ errors: [
3328
+ `The 'handleNext' function makes the dependencies of ` +
3329
+ `useEffect Hook (at line 11) change on every render. ` +
3330
+ `To fix this, move the 'handleNext' function ` +
3331
+ `inside the useEffect callback. Alternatively, ` +
3332
+ `wrap the 'handleNext' definition into its own useCallback() Hook.`,
3333
+ ],
3334
+ },
3335
+ {
3336
+ // Even if the function only references static values,
3337
+ // once you specify it in deps, it will invalidate them.
3338
+ code: `
3339
+ function MyComponent(props) {
3340
+ let [, setState] = useState();
3341
+
3342
+ const handleNext = (value) => {
3343
+ setState(value);
3344
+ };
3345
+
3346
+ useEffect(() => {
3347
+ return Store.subscribe(handleNext);
3348
+ }, [handleNext]);
3349
+ }
3350
+ `,
3351
+ // We don't autofix moving (too invasive). But that's the suggested fix
3352
+ // when only effect uses this function. Otherwise, we'd useCallback.
3353
+ output: `
3354
+ function MyComponent(props) {
3355
+ let [, setState] = useState();
3356
+
3357
+ const handleNext = (value) => {
3358
+ setState(value);
3359
+ };
3360
+
3361
+ useEffect(() => {
3362
+ return Store.subscribe(handleNext);
3363
+ }, [handleNext]);
3364
+ }
3365
+ `,
3366
+ errors: [
3367
+ `The 'handleNext' function makes the dependencies of ` +
3368
+ `useEffect Hook (at line 11) change on every render. ` +
3369
+ `To fix this, move the 'handleNext' function ` +
3370
+ `inside the useEffect callback. Alternatively, ` +
3371
+ `wrap the 'handleNext' definition into its own useCallback() Hook.`,
3372
+ ],
3373
+ },
3374
+ {
3375
+ // Even if the function only references static values,
3376
+ // once you specify it in deps, it will invalidate them.
3377
+ // However, we can't suggest moving handleNext into the
3378
+ // effect because it is *also* used outside of it.
3379
+ // So our suggestion is useCallback().
3380
+ code: `
3381
+ function MyComponent(props) {
3382
+ let [, setState] = useState();
3383
+
3384
+ const handleNext = (value) => {
3385
+ setState(value);
3386
+ };
3387
+
3388
+ useEffect(() => {
3389
+ return Store.subscribe(handleNext);
3390
+ }, [handleNext]);
3391
+
3392
+ return <div onClick={handleNext} />;
3393
+ }
3394
+ `,
3395
+ // We autofix this one with useCallback since it's
3396
+ // the easy fix and you can't just move it into effect.
3397
+ output: `
3398
+ function MyComponent(props) {
3399
+ let [, setState] = useState();
3400
+
3401
+ const handleNext = useCallback((value) => {
3402
+ setState(value);
3403
+ });
3404
+
3405
+ useEffect(() => {
3406
+ return Store.subscribe(handleNext);
3407
+ }, [handleNext]);
3408
+
3409
+ return <div onClick={handleNext} />;
3410
+ }
3411
+ `,
3412
+ errors: [
3413
+ `The 'handleNext' function makes the dependencies of ` +
3414
+ `useEffect Hook (at line 11) change on every render. ` +
3415
+ `To fix this, wrap the 'handleNext' definition into its own useCallback() Hook.`,
3416
+ ],
3417
+ },
3418
+ {
3419
+ code: `
3420
+ function MyComponent(props) {
3421
+ function handleNext1() {
3422
+ console.log('hello');
3423
+ }
3424
+ const handleNext2 = () => {
3425
+ console.log('hello');
3426
+ };
3427
+ let handleNext3 = function() {
3428
+ console.log('hello');
3429
+ };
3430
+ useEffect(() => {
3431
+ return Store.subscribe(handleNext1);
3432
+ }, [handleNext1]);
3433
+ useLayoutEffect(() => {
3434
+ return Store.subscribe(handleNext2);
3435
+ }, [handleNext2]);
3436
+ useMemo(() => {
3437
+ return Store.subscribe(handleNext3);
3438
+ }, [handleNext3]);
3439
+ }
3440
+ `,
3441
+ // Autofix doesn't wrap into useCallback here
3442
+ // because they are only referenced by effect itself.
3443
+ output: `
3444
+ function MyComponent(props) {
3445
+ function handleNext1() {
3446
+ console.log('hello');
3447
+ }
3448
+ const handleNext2 = () => {
3449
+ console.log('hello');
3450
+ };
3451
+ let handleNext3 = function() {
3452
+ console.log('hello');
3453
+ };
3454
+ useEffect(() => {
3455
+ return Store.subscribe(handleNext1);
3456
+ }, [handleNext1]);
3457
+ useLayoutEffect(() => {
3458
+ return Store.subscribe(handleNext2);
3459
+ }, [handleNext2]);
3460
+ useMemo(() => {
3461
+ return Store.subscribe(handleNext3);
3462
+ }, [handleNext3]);
3463
+ }
3464
+ `,
3465
+ errors: [
3466
+ "The 'handleNext1' function makes the dependencies of useEffect Hook " +
3467
+ "(at line 14) change on every render. To fix this, move the 'handleNext1' " +
3468
+ 'function inside the useEffect callback. Alternatively, wrap the ' +
3469
+ "'handleNext1' definition into its own useCallback() Hook.",
3470
+ "The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
3471
+ "(at line 17) change on every render. To fix this, move the 'handleNext2' " +
3472
+ 'function inside the useLayoutEffect callback. Alternatively, wrap the ' +
3473
+ "'handleNext2' definition into its own useCallback() Hook.",
3474
+ "The 'handleNext3' function makes the dependencies of useMemo Hook " +
3475
+ "(at line 20) change on every render. To fix this, move the 'handleNext3' " +
3476
+ 'function inside the useMemo callback. Alternatively, wrap the ' +
3477
+ "'handleNext3' definition into its own useCallback() Hook.",
3478
+ ],
3479
+ },
3480
+ {
3481
+ code: `
3482
+ function MyComponent(props) {
3483
+ function handleNext1() {
3484
+ console.log('hello');
3485
+ }
3486
+ const handleNext2 = () => {
3487
+ console.log('hello');
3488
+ };
3489
+ let handleNext3 = function() {
3490
+ console.log('hello');
3491
+ };
3492
+ useEffect(() => {
3493
+ handleNext1();
3494
+ return Store.subscribe(() => handleNext1());
3495
+ }, [handleNext1]);
3496
+ useLayoutEffect(() => {
3497
+ handleNext2();
3498
+ return Store.subscribe(() => handleNext2());
3499
+ }, [handleNext2]);
3500
+ useMemo(() => {
3501
+ handleNext3();
3502
+ return Store.subscribe(() => handleNext3());
3503
+ }, [handleNext3]);
3504
+ }
3505
+ `,
3506
+ // Autofix doesn't wrap into useCallback here
3507
+ // because they are only referenced by effect itself.
3508
+ output: `
3509
+ function MyComponent(props) {
3510
+ function handleNext1() {
3511
+ console.log('hello');
3512
+ }
3513
+ const handleNext2 = () => {
3514
+ console.log('hello');
3515
+ };
3516
+ let handleNext3 = function() {
3517
+ console.log('hello');
3518
+ };
3519
+ useEffect(() => {
3520
+ handleNext1();
3521
+ return Store.subscribe(() => handleNext1());
3522
+ }, [handleNext1]);
3523
+ useLayoutEffect(() => {
3524
+ handleNext2();
3525
+ return Store.subscribe(() => handleNext2());
3526
+ }, [handleNext2]);
3527
+ useMemo(() => {
3528
+ handleNext3();
3529
+ return Store.subscribe(() => handleNext3());
3530
+ }, [handleNext3]);
3531
+ }
3532
+ `,
3533
+ errors: [
3534
+ "The 'handleNext1' function makes the dependencies of useEffect Hook " +
3535
+ "(at line 15) change on every render. To fix this, move the 'handleNext1' " +
3536
+ 'function inside the useEffect callback. Alternatively, wrap the ' +
3537
+ "'handleNext1' definition into its own useCallback() Hook.",
3538
+ "The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
3539
+ "(at line 19) change on every render. To fix this, move the 'handleNext2' " +
3540
+ 'function inside the useLayoutEffect callback. Alternatively, wrap the ' +
3541
+ "'handleNext2' definition into its own useCallback() Hook.",
3542
+ "The 'handleNext3' function makes the dependencies of useMemo Hook " +
3543
+ "(at line 23) change on every render. To fix this, move the 'handleNext3' " +
3544
+ 'function inside the useMemo callback. Alternatively, wrap the ' +
3545
+ "'handleNext3' definition into its own useCallback() Hook.",
3546
+ ],
3547
+ },
3548
+ {
3549
+ code: `
3550
+ function MyComponent(props) {
3551
+ function handleNext1() {
3552
+ console.log('hello');
3553
+ }
3554
+ const handleNext2 = () => {
3555
+ console.log('hello');
3556
+ };
3557
+ let handleNext3 = function() {
3558
+ console.log('hello');
3559
+ };
3560
+ useEffect(() => {
3561
+ handleNext1();
3562
+ return Store.subscribe(() => handleNext1());
3563
+ }, [handleNext1]);
3564
+ useLayoutEffect(() => {
3565
+ handleNext2();
3566
+ return Store.subscribe(() => handleNext2());
3567
+ }, [handleNext2]);
3568
+ useMemo(() => {
3569
+ handleNext3();
3570
+ return Store.subscribe(() => handleNext3());
3571
+ }, [handleNext3]);
3572
+ return (
3573
+ <div
3574
+ onClick={() => {
3575
+ handleNext1();
3576
+ setTimeout(handleNext2);
3577
+ setTimeout(() => {
3578
+ handleNext3();
3579
+ });
3580
+ }}
3581
+ />
3582
+ );
3583
+ }
3584
+ `,
3585
+ // Autofix wraps into useCallback where possible (variables only)
3586
+ // because they are only referenced outside the effect.
3587
+ output: `
3588
+ function MyComponent(props) {
3589
+ function handleNext1() {
3590
+ console.log('hello');
3591
+ }
3592
+ const handleNext2 = useCallback(() => {
3593
+ console.log('hello');
3594
+ });
3595
+ let handleNext3 = useCallback(function() {
3596
+ console.log('hello');
3597
+ });
3598
+ useEffect(() => {
3599
+ handleNext1();
3600
+ return Store.subscribe(() => handleNext1());
3601
+ }, [handleNext1]);
3602
+ useLayoutEffect(() => {
3603
+ handleNext2();
3604
+ return Store.subscribe(() => handleNext2());
3605
+ }, [handleNext2]);
3606
+ useMemo(() => {
3607
+ handleNext3();
3608
+ return Store.subscribe(() => handleNext3());
3609
+ }, [handleNext3]);
3610
+ return (
3611
+ <div
3612
+ onClick={() => {
3613
+ handleNext1();
3614
+ setTimeout(handleNext2);
3615
+ setTimeout(() => {
3616
+ handleNext3();
3617
+ });
3618
+ }}
3619
+ />
3620
+ );
3621
+ }
3622
+ `,
3623
+ errors: [
3624
+ "The 'handleNext1' function makes the dependencies of useEffect Hook " +
3625
+ '(at line 15) change on every render. To fix this, wrap the ' +
3626
+ "'handleNext1' definition into its own useCallback() Hook.",
3627
+ "The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
3628
+ '(at line 19) change on every render. To fix this, wrap the ' +
3629
+ "'handleNext2' definition into its own useCallback() Hook.",
3630
+ "The 'handleNext3' function makes the dependencies of useMemo Hook " +
3631
+ '(at line 23) change on every render. To fix this, wrap the ' +
3632
+ "'handleNext3' definition into its own useCallback() Hook.",
3633
+ ],
3634
+ },
3635
+ {
3636
+ code: `
3637
+ function MyComponent(props) {
3638
+ const handleNext1 = () => {
3639
+ console.log('hello');
3640
+ };
3641
+ function handleNext2() {
3642
+ console.log('hello');
3643
+ }
3644
+ useEffect(() => {
3645
+ return Store.subscribe(handleNext1);
3646
+ return Store.subscribe(handleNext2);
3647
+ }, [handleNext1, handleNext2]);
3648
+ useEffect(() => {
3649
+ return Store.subscribe(handleNext1);
3650
+ return Store.subscribe(handleNext2);
3651
+ }, [handleNext1, handleNext2]);
3652
+ }
3653
+ `,
3654
+ // Normally we'd suggest moving handleNext inside an
3655
+ // effect. But it's used by more than one. So we
3656
+ // suggest useCallback() and use it for the autofix
3657
+ // where possible (variable but not declaration).
3658
+ output: `
3659
+ function MyComponent(props) {
3660
+ const handleNext1 = useCallback(() => {
3661
+ console.log('hello');
3662
+ });
3663
+ function handleNext2() {
3664
+ console.log('hello');
3665
+ }
3666
+ useEffect(() => {
3667
+ return Store.subscribe(handleNext1);
3668
+ return Store.subscribe(handleNext2);
3669
+ }, [handleNext1, handleNext2]);
3670
+ useEffect(() => {
3671
+ return Store.subscribe(handleNext1);
3672
+ return Store.subscribe(handleNext2);
3673
+ }, [handleNext1, handleNext2]);
3674
+ }
3675
+ `,
3676
+ // TODO: we could coalesce messages for the same function if it affects multiple Hooks.
3677
+ errors: [
3678
+ "The 'handleNext1' function makes the dependencies of useEffect Hook " +
3679
+ '(at line 12) change on every render. To fix this, wrap the ' +
3680
+ "'handleNext1' definition into its own useCallback() Hook.",
3681
+ "The 'handleNext1' function makes the dependencies of useEffect Hook " +
3682
+ '(at line 16) change on every render. To fix this, wrap the ' +
3683
+ "'handleNext1' definition into its own useCallback() Hook.",
3684
+ "The 'handleNext2' function makes the dependencies of useEffect Hook " +
3685
+ '(at line 12) change on every render. To fix this, wrap the ' +
3686
+ "'handleNext2' definition into its own useCallback() Hook.",
3687
+ "The 'handleNext2' function makes the dependencies of useEffect Hook " +
3688
+ '(at line 16) change on every render. To fix this, wrap the ' +
3689
+ "'handleNext2' definition into its own useCallback() Hook.",
3690
+ ],
3691
+ },
3692
+ {
3693
+ code: `
3694
+ function MyComponent(props) {
3695
+ let [, setState] = useState();
3696
+ let taint = props.foo;
3697
+
3698
+ function handleNext(value) {
3699
+ let value2 = value * taint;
3700
+ setState(value2);
3701
+ console.log('hello');
3702
+ }
3703
+
3704
+ useEffect(() => {
3705
+ return Store.subscribe(handleNext);
3706
+ }, [handleNext]);
3707
+ }
3708
+ `,
3709
+ output: `
3710
+ function MyComponent(props) {
3711
+ let [, setState] = useState();
3712
+ let taint = props.foo;
3713
+
3714
+ function handleNext(value) {
3715
+ let value2 = value * taint;
3716
+ setState(value2);
3717
+ console.log('hello');
3718
+ }
3719
+
3720
+ useEffect(() => {
3721
+ return Store.subscribe(handleNext);
3722
+ }, [handleNext]);
3723
+ }
3724
+ `,
3725
+ errors: [
3726
+ `The 'handleNext' function makes the dependencies of ` +
3727
+ `useEffect Hook (at line 14) change on every render. ` +
3728
+ `To fix this, move the 'handleNext' function inside ` +
3729
+ `the useEffect callback. Alternatively, wrap the ` +
3730
+ `'handleNext' definition into its own useCallback() Hook.`,
3731
+ ],
3732
+ },
3733
{
3734
code: `
3735
function Counter() {
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+131
-2
@@ -93,10 +93,12 @@ export default {
93
reactiveHookName === 'useCallback'
94
) {
95
context.report({
96
- node: node,
96
+ node: node.parent.callee,
97
message:
98
`React Hook ${reactiveHookName} doesn't serve any purpose ` +
99
- `without a dependency array as a second argument.`,
99
+ `without a dependency array. To enable ` +
100
+ `this optimization, pass an array of values used by the ` +
101
+ `inner function as the second argument to ${reactiveHookName}.`,
102
});
103
}
104
return;
@@ -550,7 +552,57 @@ export default {
552
duplicateDependencies.size +
553
missingDependencies.size +
554
unnecessaryDependencies.size;
555
+
556
if (problemCount === 0) {
557
+ // If nothing else to report, check if some callbacks
558
+ // are bare and would invalidate on every render.
559
+ const bareFunctions = scanForDeclaredBareFunctions({
560
+ declaredDependencies,
561
+ declaredDependenciesNode,
562
+ componentScope,
563
+ scope,
564
+ });
565
+ bareFunctions.forEach(({fn, suggestUseCallback}) => {
566
+ let message =
567
+ `The '${fn.name.name}' function makes the dependencies of ` +
568
+ `${reactiveHookName} Hook (at line ${
569
+ declaredDependenciesNode.loc.start.line
570
+ }) ` +
571
+ `change on every render.`;
572
+ if (suggestUseCallback) {
573
+ message +=
574
+ ` To fix this, ` +
575
+ `wrap the '${
576
+ fn.name.name
577
+ }' definition into its own useCallback() Hook.`;
578
+ } else {
579
+ message +=
580
+ ` To fix this, move the '${fn.name.name}' function ` +
581
+ `inside the ${reactiveHookName} callback. Alternatively, ` +
582
+ `wrap the '${
583
+ fn.name.name
584
+ }' definition into its own useCallback() Hook.`;
585
+ }
586
+ context.report({
587
+ node: fn.node,
588
+ message,
589
+ fix(fixer) {
590
+ // Only handle the simple case: arrow functions.
591
+ // Wrapping function declarations can mess up hoisting.
592
+ if (suggestUseCallback && fn.type === 'Variable') {
593
+ return [
594
+ // TODO: also add an import?
595
+ fixer.insertTextBefore(fn.node.init, 'useCallback('),
596
+ // TODO: ideally we'd gather deps here but it would
597
+ // require restructuring the rule code. For now,
598
+ // this is fine. Note we're intentionally not adding
599
+ // [] because that changes semantics.
600
+ fixer.insertTextAfter(fn.node.init, ')'),
601
+ ];
602
+ }
603
+ },
604
+ });
605
+ });
606
return;
607
}
608
@@ -858,6 +910,83 @@ function collectRecommendations({
910
};
911
}
912
913
+// Finds functions declared as dependencies
914
+// that would invalidate on every render.
915
+function scanForDeclaredBareFunctions({
916
+ declaredDependencies,
917
+ declaredDependenciesNode,
918
+ componentScope,
919
+ scope,
920
+}) {
921
+ const bareFunctions = declaredDependencies
922
+ .map(({key}) => {
923
+ const fnRef = componentScope.set.get(key);
924
+ if (fnRef == null) {
925
+ return null;
926
+ }
927
+ let fnNode = fnRef.defs[0];
928
+ if (fnNode == null) {
929
+ return null;
930
+ }
931
+ // const handleChange = function () {}
932
+ // const handleChange = () => {}
933
+ if (
934
+ fnNode.type === 'Variable' &&
935
+ fnNode.node.type === 'VariableDeclarator' &&
936
+ fnNode.node.init != null &&
937
+ (fnNode.node.init.type === 'ArrowFunctionExpression' ||
938
+ fnNode.node.init.type === 'FunctionExpression')
939
+ ) {
940
+ return fnRef;
941
+ }
942
+ // function handleChange() {}
943
+ if (
944
+ fnNode.type === 'FunctionName' &&
945
+ fnNode.node.type === 'FunctionDeclaration'
946
+ ) {
947
+ return fnRef;
948
+ }
949
+ return null;
950
+ })
951
+ .filter(Boolean);
952
+
953
+ function isUsedOutsideOfHook(fnRef) {
954
+ let foundWriteExpr = false;
955
+ for (let i = 0; i < fnRef.references.length; i++) {
956
+ const reference = fnRef.references[i];
957
+ if (reference.writeExpr) {
958
+ if (foundWriteExpr) {
959
+ // Two writes to the same function.
960
+ return true;
961
+ } else {
962
+ // Ignore first write as it's not usage.
963
+ foundWriteExpr = true;
964
+ continue;
965
+ }
966
+ }
967
+ let currentScope = reference.from;
968
+ while (currentScope !== scope && currentScope != null) {
969
+ currentScope = currentScope.upper;
970
+ }
971
+ if (currentScope !== scope) {
972
+ // This reference is outside the Hook callback.
973
+ // It can only be legit if it's the deps array.
974
+ if (isAncestorNodeOf(declaredDependenciesNode, reference.identifier)) {
975
+ continue;
976
+ } else {
977
+ return true;
978
+ }
979
+ }
980
+ }
981
+ return false;
982
+ }
983
+
984
+ return bareFunctions.map(fnRef => ({
985
+ fn: fnRef.defs[0],
986
+ suggestUseCallback: isUsedOutsideOfHook(fnRef),
987
+ }));
988
+}
989
+
990
/**
991
* Assuming () means the passed/returned node:
992
* (props) => (props)