@samitouri / QOS-React-2 / commits / 93a229bab5

Update eslint rule exhaustive deps to use new suggestions feature (#17385)

This closes #16313

Will Douglas committed Feb 17, 2020 at 13:24 UTC 93a229bab59f94e214256582c55d3b6c1fc2b958
2 files changed +3335 -2052
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+3282 -2022
@@ -19,6 +19,16 @@ ESLintTester.setDefaultConfig({
19 },
20 });
21
22 +/**
23 + * A string template tag that removes padding from the left side of multi-line strings
24 + * @param {Array} strings array of code strings (only one expected)
25 + */
26 +function normalizeIndent(strings) {
27 + const codeLines = strings[0].split('\n');
28 + const leftPadding = codeLines[1].match(/\s+/)[0];
29 + return codeLines.map(line => line.substr(leftPadding.length)).join('\n');
30 +}
31 +
32 // ***************************************************
33 // For easier local testing, you can add to any case:
34 // {
@@ -32,7 +42,7 @@ ESLintTester.setDefaultConfig({
42 const tests = {
43 valid: [
44 {
35 - code: `
45 + code: normalizeIndent`
46 function MyComponent() {
47 const local = {};
48 useEffect(() => {
@@ -42,7 +52,7 @@ const tests = {
52 `,
53 },
54 {
45 - code: `
55 + code: normalizeIndent`
56 function MyComponent() {
57 useEffect(() => {
58 const local = {};
@@ -52,7 +62,7 @@ const tests = {
62 `,
63 },
64 {
55 - code: `
65 + code: normalizeIndent`
66 function MyComponent() {
67 const local = {};
68 useEffect(() => {
@@ -67,7 +77,7 @@ const tests = {
77 // to be an import that hasn't been added yet, or
78 // a component-level variable. Ignore it until it
79 // gets defined (a different rule would flag it anyway).
70 - code: `
80 + code: normalizeIndent`
81 function MyComponent() {
82 useEffect(() => {
83 console.log(props.foo);
@@ -76,7 +86,7 @@ const tests = {
86 `,
87 },
88 {
79 - code: `
89 + code: normalizeIndent`
90 function MyComponent() {
91 const local1 = {};
92 {
@@ -90,7 +100,7 @@ const tests = {
100 `,
101 },
102 {
93 - code: `
103 + code: normalizeIndent`
104 function MyComponent() {
105 const local1 = {};
106 {
@@ -104,7 +114,7 @@ const tests = {
114 `,
115 },
116 {
107 - code: `
117 + code: normalizeIndent`
118 function MyComponent() {
119 const local1 = {};
120 function MyNestedComponent() {
@@ -118,7 +128,7 @@ const tests = {
128 `,
129 },
130 {
121 - code: `
131 + code: normalizeIndent`
132 function MyComponent() {
133 const local = {};
134 useEffect(() => {
@@ -129,7 +139,7 @@ const tests = {
139 `,
140 },
141 {
132 - code: `
142 + code: normalizeIndent`
143 function MyComponent() {
144 useEffect(() => {
145 console.log(unresolved);
@@ -138,7 +148,7 @@ const tests = {
148 `,
149 },
150 {
141 - code: `
151 + code: normalizeIndent`
152 function MyComponent() {
153 const local = {};
154 useEffect(() => {
@@ -149,7 +159,7 @@ const tests = {
159 },
160 {
161 // Regression test
152 - code: `
162 + code: normalizeIndent`
163 function MyComponent({ foo }) {
164 useEffect(() => {
165 console.log(foo.length);
@@ -159,7 +169,7 @@ const tests = {
169 },
170 {
171 // Regression test
162 - code: `
172 + code: normalizeIndent`
173 function MyComponent({ foo }) {
174 useEffect(() => {
175 console.log(foo.length);
@@ -170,7 +180,7 @@ const tests = {
180 },
181 {
182 // Regression test
173 - code: `
183 + code: normalizeIndent`
184 function MyComponent({ history }) {
185 useEffect(() => {
186 return history.listen();
@@ -180,7 +190,7 @@ const tests = {
190 },
191 {
192 // Valid because they have meaning without deps.
183 - code: `
193 + code: normalizeIndent`
194 function MyComponent(props) {
195 useEffect(() => {});
196 useLayoutEffect(() => {});
@@ -189,7 +199,7 @@ const tests = {
199 `,
200 },
201 {
192 - code: `
202 + code: normalizeIndent`
203 function MyComponent(props) {
204 useEffect(() => {
205 console.log(props.foo);
@@ -198,7 +208,7 @@ const tests = {
208 `,
209 },
210 {
201 - code: `
211 + code: normalizeIndent`
212 function MyComponent(props) {
213 useEffect(() => {
214 console.log(props.foo);
@@ -208,7 +218,7 @@ const tests = {
218 `,
219 },
220 {
211 - code: `
221 + code: normalizeIndent`
222 function MyComponent(props) {
223 useEffect(() => {
224 console.log(props.foo);
@@ -218,7 +228,7 @@ const tests = {
228 `,
229 },
230 {
221 - code: `
231 + code: normalizeIndent`
232 function MyComponent(props) {
233 const local = {};
234 useEffect(() => {
@@ -233,7 +243,7 @@ const tests = {
243 // [props, props.foo] is technically unnecessary ('props' covers 'props.foo').
244 // However, it's valid for effects to over-specify their deps.
245 // So we don't warn about this. We *would* warn about useMemo/useCallback.
236 - code: `
246 + code: normalizeIndent`
247 function MyComponent(props) {
248 const local = {};
249 useEffect(() => {
@@ -250,7 +260,7 @@ const tests = {
260 `,
261 },
262 {
253 - code: `
263 + code: normalizeIndent`
264 function MyComponent(props) {
265 useCustomEffect(() => {
266 console.log(props.foo);
@@ -260,7 +270,7 @@ const tests = {
270 options: [{additionalHooks: 'useCustomEffect'}],
271 },
272 {
263 - code: `
273 + code: normalizeIndent`
274 function MyComponent(props) {
275 useCustomEffect(() => {
276 console.log(props.foo);
@@ -270,7 +280,7 @@ const tests = {
280 options: [{additionalHooks: 'useCustomEffect'}],
281 },
282 {
273 - code: `
283 + code: normalizeIndent`
284 function MyComponent(props) {
285 useCustomEffect(() => {
286 console.log(props.foo);
@@ -281,7 +291,7 @@ const tests = {
291 },
292 {
293 // Valid because we don't care about hooks outside of components.
284 - code: `
294 + code: normalizeIndent`
295 const local = {};
296 useEffect(() => {
297 console.log(local);
@@ -290,7 +300,7 @@ const tests = {
300 },
301 {
302 // Valid because we don't care about hooks outside of components.
293 - code: `
303 + code: normalizeIndent`
304 const local1 = {};
305 {
306 const local2 = {};
@@ -302,7 +312,7 @@ const tests = {
312 `,
313 },
314 {
305 - code: `
315 + code: normalizeIndent`
316 function MyComponent() {
317 const ref = useRef();
318 useEffect(() => {
@@ -312,7 +322,7 @@ const tests = {
322 `,
323 },
324 {
315 - code: `
325 + code: normalizeIndent`
326 function MyComponent() {
327 const ref = useRef();
328 useEffect(() => {
@@ -322,7 +332,7 @@ const tests = {
332 `,
333 },
334 {
325 - code: `
335 + code: normalizeIndent`
336 function MyComponent({ maybeRef2, foo }) {
337 const definitelyRef1 = useRef();
338 const definitelyRef2 = useRef();
@@ -376,7 +386,7 @@ const tests = {
386 `,
387 },
388 {
379 - code: `
389 + code: normalizeIndent`
390 function MyComponent({ maybeRef2 }) {
391 const definitelyRef1 = useRef();
392 const definitelyRef2 = useRef();
@@ -434,7 +444,7 @@ const tests = {
444 `,
445 },
446 {
437 - code: `
447 + code: normalizeIndent`
448 const MyComponent = forwardRef((props, ref) => {
449 useImperativeHandle(ref, () => ({
450 focus() {
@@ -445,7 +455,7 @@ const tests = {
455 `,
456 },
457 {
448 - code: `
458 + code: normalizeIndent`
459 const MyComponent = forwardRef((props, ref) => {
460 useImperativeHandle(ref, () => ({
461 focus() {
@@ -459,7 +469,7 @@ const tests = {
469 // This is not ideal but warning would likely create
470 // too many false positives. We do, however, prevent
471 // direct assignments.
462 - code: `
472 + code: normalizeIndent`
473 function MyComponent(props) {
474 let obj = {};
475 useEffect(() => {
@@ -472,7 +482,7 @@ const tests = {
482 // Valid because we assign ref.current
483 // ourselves. Therefore it's likely not
484 // a ref managed by React.
475 - code: `
485 + code: normalizeIndent`
486 function MyComponent() {
487 const myRef = useRef();
488 useEffect(() => {
@@ -490,7 +500,7 @@ const tests = {
500 // Valid because we assign ref.current
501 // ourselves. Therefore it's likely not
502 // a ref managed by React.
493 - code: `
503 + code: normalizeIndent`
504 function useMyThing(myRef) {
505 useEffect(() => {
506 const handleMove = () => {};
@@ -504,7 +514,7 @@ const tests = {
514 },
515 {
516 // Valid because the ref is captured.
507 - code: `
517 + code: normalizeIndent`
518 function MyComponent() {
519 const myRef = useRef();
520 useEffect(() => {
@@ -519,7 +529,7 @@ const tests = {
529 },
530 {
531 // Valid because the ref is captured.
522 - code: `
532 + code: normalizeIndent`
533 function useMyThing(myRef) {
534 useEffect(() => {
535 const handleMove = () => {};
@@ -533,7 +543,7 @@ const tests = {
543 },
544 {
545 // Valid because it's not an effect.
536 - code: `
546 + code: normalizeIndent`
547 function useMyThing(myRef) {
548 useCallback(() => {
549 const handleMouse = () => {};
@@ -551,7 +561,7 @@ const tests = {
561 },
562 {
563 // Valid because we read ref.current in a function that isn't cleanup.
554 - code: `
564 + code: normalizeIndent`
565 function useMyThing() {
566 const myRef = useRef();
567 useEffect(() => {
@@ -567,7 +577,7 @@ const tests = {
577 },
578 {
579 // Valid because we read ref.current in a function that isn't cleanup.
570 - code: `
580 + code: normalizeIndent`
581 function useMyThing() {
582 const myRef = useRef();
583 useEffect(() => {
@@ -583,7 +593,7 @@ const tests = {
593 },
594 {
595 // Valid because it's a primitive constant.
586 - code: `
596 + code: normalizeIndent`
597 function MyComponent() {
598 const local1 = 42;
599 const local2 = '42';
@@ -598,7 +608,7 @@ const tests = {
608 },
609 {
610 // It's not a mistake to specify constant values though.
601 - code: `
611 + code: normalizeIndent`
612 function MyComponent() {
613 const local1 = 42;
614 const local2 = '42';
@@ -613,7 +623,7 @@ const tests = {
623 },
624 {
625 // It is valid for effects to over-specify their deps.
616 - code: `
626 + code: normalizeIndent`
627 function MyComponent(props) {
628 const local = props.local;
629 useEffect(() => {}, [local]);
@@ -623,7 +633,7 @@ const tests = {
633 {
634 // Valid even though activeTab is "unused".
635 // We allow over-specifying deps for effects, but not callbacks or memo.
626 - code: `
636 + code: normalizeIndent`
637 function Foo({ activeTab }) {
638 useEffect(() => {
639 window.scrollTo(0, 0);
@@ -634,7 +644,7 @@ const tests = {
644 {
645 // It is valid to specify broader effect deps than strictly necessary.
646 // Don't warn for this.
637 - code: `
647 + code: normalizeIndent`
648 function MyComponent(props) {
649 useEffect(() => {
650 console.log(props.foo.bar.baz);
@@ -654,7 +664,7 @@ const tests = {
664 {
665 // It is *also* valid to specify broader memo/callback deps than strictly necessary.
666 // Don't warn for this either.
657 - code: `
667 + code: normalizeIndent`
668 function MyComponent(props) {
669 const fn = useCallback(() => {
670 console.log(props.foo.bar.baz);
@@ -674,7 +684,7 @@ const tests = {
684 {
685 // Declaring handleNext is optional because
686 // it doesn't use anything in the function scope.
677 - code: `
687 + code: normalizeIndent`
688 function MyComponent(props) {
689 function handleNext1() {
690 console.log('hello');
@@ -700,7 +710,7 @@ const tests = {
710 {
711 // Declaring handleNext is optional because
712 // it doesn't use anything in the function scope.
703 - code: `
713 + code: normalizeIndent`
714 function MyComponent(props) {
715 function handleNext() {
716 console.log('hello');
@@ -720,7 +730,7 @@ const tests = {
730 {
731 // Declaring handleNext is optional because
732 // everything they use is fully static.
723 - code: `
733 + code: normalizeIndent`
734 function MyComponent(props) {
735 let [, setState] = useState();
736 let [, dispatch] = React.useReducer();
@@ -751,7 +761,7 @@ const tests = {
761 `,
762 },
763 {
754 - code: `
764 + code: normalizeIndent`
765 function useInterval(callback, delay) {
766 const savedCallback = useRef();
767 useEffect(() => {
@@ -770,7 +780,7 @@ const tests = {
780 `,
781 },
782 {
773 - code: `
783 + code: normalizeIndent`
784 function Counter() {
785 const [count, setCount] = useState(0);
786
@@ -786,7 +796,7 @@ const tests = {
796 `,
797 },
798 {
789 - code: `
799 + code: normalizeIndent`
800 function Counter() {
801 const [count, setCount] = useState(0);
802
@@ -806,7 +816,7 @@ const tests = {
816 `,
817 },
818 {
809 - code: `
819 + code: normalizeIndent`
820 function Counter() {
821 const [count, dispatch] = useReducer((state, action) => {
822 if (action === 'inc') {
@@ -826,7 +836,7 @@ const tests = {
836 `,
837 },
838 {
829 - code: `
839 + code: normalizeIndent`
840 function Counter() {
841 const [count, dispatch] = useReducer((state, action) => {
842 if (action === 'inc') {
@@ -849,7 +859,7 @@ const tests = {
859 },
860 {
861 // Regression test for a crash
852 - code: `
862 + code: normalizeIndent`
863 function Podcasts() {
864 useEffect(() => {
865 setPodcasts([]);
@@ -859,7 +869,7 @@ const tests = {
869 `,
870 },
871 {
862 - code: `
872 + code: normalizeIndent`
873 function withFetch(fetchPodcasts) {
874 return function Podcasts({ id }) {
875 let [podcasts, setPodcasts] = useState(null);
@@ -871,7 +881,7 @@ const tests = {
881 `,
882 },
883 {
874 - code: `
884 + code: normalizeIndent`
885 function Podcasts({ id }) {
886 let [podcasts, setPodcasts] = useState(null);
887 useEffect(() => {
@@ -884,7 +894,7 @@ const tests = {
894 `,
895 },
896 {
887 - code: `
897 + code: normalizeIndent`
898 function Counter() {
899 let [count, setCount] = useState(0);
900
@@ -904,7 +914,7 @@ const tests = {
914 `,
915 },
916 {
907 - code: `
917 + code: normalizeIndent`
918 function Counter() {
919 let [count, setCount] = useState(0);
920
@@ -924,7 +934,7 @@ const tests = {
934 `,
935 },
936 {
927 - code: `
937 + code: normalizeIndent`
938 import increment from './increment';
939 function Counter() {
940 let [count, setCount] = useState(0);
@@ -941,7 +951,7 @@ const tests = {
951 `,
952 },
953 {
944 - code: `
954 + code: normalizeIndent`
955 function withStuff(increment) {
956 return function Counter() {
957 let [count, setCount] = useState(0);
@@ -959,7 +969,7 @@ const tests = {
969 `,
970 },
971 {
962 - code: `
972 + code: normalizeIndent`
973 function App() {
974 const [query, setQuery] = useState('react');
975 const [state, setState] = useState(null);
@@ -982,7 +992,7 @@ const tests = {
992 `,
993 },
994 {
985 - code: `
995 + code: normalizeIndent`
996 function Example() {
997 const foo = useCallback(() => {
998 foo();
@@ -991,7 +1001,7 @@ const tests = {
1001 `,
1002 },
1003 {
994 - code: `
1004 + code: normalizeIndent`
1005 function Example({ prop }) {
1006 const foo = useCallback(() => {
1007 if (prop) {
@@ -1002,7 +1012,7 @@ const tests = {
1012 `,
1013 },
1014 {
1005 - code: `
1015 + code: normalizeIndent`
1016 function Hello() {
1017 const [state, setState] = useState(0);
1018 useEffect(() => {
@@ -1015,7 +1025,7 @@ const tests = {
1025 },
1026 // Ignore Generic Type Variables for arrow functions
1027 {
1018 - code: `
1028 + code: normalizeIndent`
1029 function Example({ prop }) {
1030 const bar = useEffect(<T>(a: T): Hello => {
1031 prop();
@@ -1025,7 +1035,7 @@ const tests = {
1035 },
1036 // Ignore arguments keyword for arrow functions.
1037 {
1028 - code: `
1038 + code: normalizeIndent`
1039 function Example() {
1040 useEffect(() => {
1041 arguments
@@ -1034,7 +1044,7 @@ const tests = {
1044 `,
1045 },
1046 {
1037 - code: `
1047 + code: normalizeIndent`
1048 function Example() {
1049 useEffect(() => {
1050 const bar = () => {
@@ -1048,7 +1058,7 @@ const tests = {
1058 ],
1059 invalid: [
1060 {
1051 - code: `
1061 + code: normalizeIndent`
1062 function MyComponent() {
1063 const local = {};
1064 useEffect(() => {
@@ -1056,24 +1066,32 @@ const tests = {
1066 }, []);
1067 }
1068 `,
1059 - output: `
1060 - function MyComponent() {
1061 - const local = {};
1062 - useEffect(() => {
1063 - console.log(local);
1064 - }, [local]);
1065 - }
1066 - `,
1069 errors: [
1068 - "React Hook useEffect has a missing dependency: 'local'. " +
1069 - 'Either include it or remove the dependency array.',
1070 + {
1071 + message:
1072 + "React Hook useEffect has a missing dependency: 'local'. " +
1073 + 'Either include it or remove the dependency array.',
1074 + suggestions: [
1075 + {
1076 + desc: 'Update the dependencies array to be: [local]',
1077 + output: normalizeIndent`
1078 + function MyComponent() {
1079 + const local = {};
1080 + useEffect(() => {
1081 + console.log(local);
1082 + }, [local]);
1083 + }
1084 + `,
1085 + },
1086 + ],
1087 + },
1088 ],
1089 },
1090 {
1091 // Note: we *could* detect it's a primitive and never assigned
1092 // even though it's not a constant -- but we currently don't.
1093 // So this is an error.
1076 - code: `
1094 + code: normalizeIndent`
1095 function MyComponent() {
1096 let local = 42;
1097 useEffect(() => {
@@ -1081,22 +1099,30 @@ const tests = {
1099 }, []);
1100 }
1101 `,
1084 - output: `
1085 - function MyComponent() {
1086 - let local = 42;
1087 - useEffect(() => {
1088 - console.log(local);
1089 - }, [local]);
1090 - }
1091 - `,
1102 errors: [
1093 - "React Hook useEffect has a missing dependency: 'local'. " +
1094 - 'Either include it or remove the dependency array.',
1103 + {
1104 + message:
1105 + "React Hook useEffect has a missing dependency: 'local'. " +
1106 + 'Either include it or remove the dependency array.',
1107 + suggestions: [
1108 + {
1109 + desc: 'Update the dependencies array to be: [local]',
1110 + output: normalizeIndent`
1111 + function MyComponent() {
1112 + let local = 42;
1113 + useEffect(() => {
1114 + console.log(local);
1115 + }, [local]);
1116 + }
1117 + `,
1118 + },
1119 + ],
1120 + },
1121 ],
1122 },
1123 {
1124 // Regexes are literals but potentially stateful.
1099 - code: `
1125 + code: normalizeIndent`
1126 function MyComponent() {
1127 const local = /foo/;
1128 useEffect(() => {
@@ -1104,44 +1130,54 @@ const tests = {
1130 }, []);
1131 }
1132 `,
1107 - output: `
1108 - function MyComponent() {
1109 - const local = /foo/;
1110 - useEffect(() => {
1111 - console.log(local);
1112 - }, [local]);
1113 - }
1114 - `,
1133 errors: [
1116 - "React Hook useEffect has a missing dependency: 'local'. " +
1117 - 'Either include it or remove the dependency array.',
1134 + {
1135 + message:
1136 + "React Hook useEffect has a missing dependency: 'local'. " +
1137 + 'Either include it or remove the dependency array.',
1138 + suggestions: [
1139 + {
1140 + desc: 'Update the dependencies array to be: [local]',
1141 + output: normalizeIndent`
1142 + function MyComponent() {
1143 + const local = /foo/;
1144 + useEffect(() => {
1145 + console.log(local);
1146 + }, [local]);
1147 + }
1148 + `,
1149 + },
1150 + ],
1151 + },
1152 ],
1153 },
1154 {
1155 // Invalid because they don't have a meaning without deps.
1122 - code: `
1156 + code: normalizeIndent`
1157 function MyComponent(props) {
1158 const value = useMemo(() => { return 2*2; });
1159 const fn = useCallback(() => { alert('foo'); });
1160 }
1161 `,
1162 // We don't know what you meant.
1129 - output: `
1130 - function MyComponent(props) {
1131 - const value = useMemo(() => { return 2*2; });
1132 - const fn = useCallback(() => { alert('foo'); });
1133 - }
1134 - `,
1163 errors: [
1136 - 'React Hook useMemo does nothing when called with only one argument. ' +
1137 - 'Did you forget to pass an array of dependencies?',
1138 - 'React Hook useCallback does nothing when called with only one argument. ' +
1139 - 'Did you forget to pass an array of dependencies?',
1164 + {
1165 + message:
1166 + 'React Hook useMemo does nothing when called with only one argument. ' +
1167 + 'Did you forget to pass an array of dependencies?',
1168 + suggestions: undefined,
1169 + },
1170 + {
1171 + message:
1172 + 'React Hook useCallback does nothing when called with only one argument. ' +
1173 + 'Did you forget to pass an array of dependencies?',
1174 + suggestions: undefined,
1175 + },
1176 ],
1177 },
1178 {
1179 // Regression test
1144 - code: `
1180 + code: normalizeIndent`
1181 function MyComponent() {
1182 const local = {};
1183 useEffect(() => {
@@ -1151,24 +1187,32 @@ const tests = {
1187 }, []);
1188 }
1189 `,
1154 - output: `
1155 - function MyComponent() {
1156 - const local = {};
1157 - useEffect(() => {
1158 - if (true) {
1159 - console.log(local);
1160 - }
1161 - }, [local]);
1162 - }
1163 - `,
1190 errors: [
1165 - "React Hook useEffect has a missing dependency: 'local'. " +
1166 - 'Either include it or remove the dependency array.',
1191 + {
1192 + message:
1193 + "React Hook useEffect has a missing dependency: 'local'. " +
1194 + 'Either include it or remove the dependency array.',
1195 + suggestions: [
1196 + {
1197 + desc: 'Update the dependencies array to be: [local]',
1198 + output: normalizeIndent`
1199 + function MyComponent() {
1200 + const local = {};
1201 + useEffect(() => {
1202 + if (true) {
1203 + console.log(local);
1204 + }
1205 + }, [local]);
1206 + }
1207 + `,
1208 + },
1209 + ],
1210 + },
1211 ],
1212 },
1213 {
1214 // Regression test
1171 - code: `
1215 + code: normalizeIndent`
1216 function MyComponent() {
1217 const local = {};
1218 useEffect(() => {
@@ -1178,24 +1222,32 @@ const tests = {
1222 }, []);
1223 }
1224 `,
1181 - output: `
1182 - function MyComponent() {
1183 - const local = {};
1184 - useEffect(() => {
1185 - try {
1186 - console.log(local);
1187 - } finally {}
1188 - }, [local]);
1189 - }
1190 - `,
1225 errors: [
1192 - "React Hook useEffect has a missing dependency: 'local'. " +
1193 - 'Either include it or remove the dependency array.',
1226 + {
1227 + message:
1228 + "React Hook useEffect has a missing dependency: 'local'. " +
1229 + 'Either include it or remove the dependency array.',
1230 + suggestions: [
1231 + {
1232 + desc: 'Update the dependencies array to be: [local]',
1233 + output: normalizeIndent`
1234 + function MyComponent() {
1235 + const local = {};
1236 + useEffect(() => {
1237 + try {
1238 + console.log(local);
1239 + } finally {}
1240 + }, [local]);
1241 + }
1242 + `,
1243 + },
1244 + ],
1245 + },
1246 ],
1247 },
1248 {
1249 // Regression test
1198 - code: `
1250 + code: normalizeIndent`
1251 function MyComponent() {
1252 const local = {};
1253 useEffect(() => {
@@ -1206,24 +1258,32 @@ const tests = {
1258 }, []);
1259 }
1260 `,
1209 - output: `
1210 - function MyComponent() {
1211 - const local = {};
1212 - useEffect(() => {
1213 - function inner() {
1214 - console.log(local);
1215 - }
1216 - inner();
1217 - }, [local]);
1218 - }
1219 - `,
1261 errors: [
1221 - "React Hook useEffect has a missing dependency: 'local'. " +
1222 - 'Either include it or remove the dependency array.',
1262 + {
1263 + message:
1264 + "React Hook useEffect has a missing dependency: 'local'. " +
1265 + 'Either include it or remove the dependency array.',
1266 + suggestions: [
1267 + {
1268 + desc: 'Update the dependencies array to be: [local]',
1269 + output: normalizeIndent`
1270 + function MyComponent() {
1271 + const local = {};
1272 + useEffect(() => {
1273 + function inner() {
1274 + console.log(local);
1275 + }
1276 + inner();
1277 + }, [local]);
1278 + }
1279 + `,
1280 + },
1281 + ],
1282 + },
1283 ],
1284 },
1285 {
1226 - code: `
1286 + code: normalizeIndent`
1287 function MyComponent() {
1288 const local1 = {};
1289 {
@@ -1235,25 +1295,33 @@ const tests = {
1295 }
1296 }
1297 `,
1238 - output: `
1239 - function MyComponent() {
1240 - const local1 = {};
1241 - {
1242 - const local2 = {};
1243 - useEffect(() => {
1244 - console.log(local1);
1245 - console.log(local2);
1246 - }, [local1, local2]);
1247 - }
1248 - }
1249 - `,
1298 errors: [
1251 - "React Hook useEffect has missing dependencies: 'local1' and 'local2'. " +
1252 - 'Either include them or remove the dependency array.',
1299 + {
1300 + message:
1301 + "React Hook useEffect has missing dependencies: 'local1' and 'local2'. " +
1302 + 'Either include them or remove the dependency array.',
1303 + suggestions: [
1304 + {
1305 + desc: 'Update the dependencies array to be: [local1, local2]',
1306 + output: normalizeIndent`
1307 + function MyComponent() {
1308 + const local1 = {};
1309 + {
1310 + const local2 = {};
1311 + useEffect(() => {
1312 + console.log(local1);
1313 + console.log(local2);
1314 + }, [local1, local2]);
1315 + }
1316 + }
1317 + `,
1318 + },
1319 + ],
1320 + },
1321 ],
1322 },
1323 {
1256 - code: `
1324 + code: normalizeIndent`
1325 function MyComponent() {
1326 const local1 = {};
1327 const local2 = {};
@@ -1263,23 +1331,31 @@ const tests = {
1331 }, [local1]);
1332 }
1333 `,
1266 - output: `
1267 - function MyComponent() {
1268 - const local1 = {};
1269 - const local2 = {};
1270 - useEffect(() => {
1271 - console.log(local1);
1272 - console.log(local2);
1273 - }, [local1, local2]);
1274 - }
1275 - `,
1334 errors: [
1277 - "React Hook useEffect has a missing dependency: 'local2'. " +
1278 - 'Either include it or remove the dependency array.',
1335 + {
1336 + message:
1337 + "React Hook useEffect has a missing dependency: 'local2'. " +
1338 + 'Either include it or remove the dependency array.',
1339 + suggestions: [
1340 + {
1341 + desc: 'Update the dependencies array to be: [local1, local2]',
1342 + output: normalizeIndent`
1343 + function MyComponent() {
1344 + const local1 = {};
1345 + const local2 = {};
1346 + useEffect(() => {
1347 + console.log(local1);
1348 + console.log(local2);
1349 + }, [local1, local2]);
1350 + }
1351 + `,
1352 + },
1353 + ],
1354 + },
1355 ],
1356 },
1357 {
1282 - code: `
1358 + code: normalizeIndent`
1359 function MyComponent() {
1360 const local1 = {};
1361 const local2 = {};
@@ -1288,22 +1364,30 @@ const tests = {
1364 }, [local1, local2]);
1365 }
1366 `,
1291 - output: `
1292 - function MyComponent() {
1293 - const local1 = {};
1294 - const local2 = {};
1295 - useMemo(() => {
1296 - console.log(local1);
1297 - }, [local1]);
1298 - }
1299 - `,
1367 errors: [
1301 - "React Hook useMemo has an unnecessary dependency: 'local2'. " +
1302 - 'Either exclude it or remove the dependency array.',
1368 + {
1369 + message:
1370 + "React Hook useMemo has an unnecessary dependency: 'local2'. " +
1371 + 'Either exclude it or remove the dependency array.',
1372 + suggestions: [
1373 + {
1374 + desc: 'Update the dependencies array to be: [local1]',
1375 + output: normalizeIndent`
1376 + function MyComponent() {
1377 + const local1 = {};
1378 + const local2 = {};
1379 + useMemo(() => {
1380 + console.log(local1);
1381 + }, [local1]);
1382 + }
1383 + `,
1384 + },
1385 + ],
1386 + },
1387 ],
1388 },
1389 {
1306 - code: `
1390 + code: normalizeIndent`
1391 function MyComponent() {
1392 const local1 = {};
1393 function MyNestedComponent() {
@@ -1315,27 +1399,35 @@ const tests = {
1399 }
1400 }
1401 `,
1318 - output: `
1319 - function MyComponent() {
1320 - const local1 = {};
1321 - function MyNestedComponent() {
1322 - const local2 = {};
1323 - useCallback(() => {
1324 - console.log(local1);
1325 - console.log(local2);
1326 - }, [local2]);
1327 - }
1328 - }
1329 - `,
1402 errors: [
1331 - "React Hook useCallback has a missing dependency: 'local2'. " +
1332 - 'Either include it or remove the dependency array. ' +
1333 - "Outer scope values like 'local1' aren't valid dependencies " +
1334 - "because mutating them doesn't re-render the component.",
1403 + {
1404 + message:
1405 + "React Hook useCallback has a missing dependency: 'local2'. " +
1406 + 'Either include it or remove the dependency array. ' +
1407 + "Outer scope values like 'local1' aren't valid dependencies " +
1408 + "because mutating them doesn't re-render the component.",
1409 + suggestions: [
1410 + {
1411 + desc: 'Update the dependencies array to be: [local2]',
1412 + output: normalizeIndent`
1413 + function MyComponent() {
1414 + const local1 = {};
1415 + function MyNestedComponent() {
1416 + const local2 = {};
1417 + useCallback(() => {
1418 + console.log(local1);
1419 + console.log(local2);
1420 + }, [local2]);
1421 + }
1422 + }
1423 + `,
1424 + },
1425 + ],
1426 + },
1427 ],
1428 },
1429 {
1338 - code: `
1430 + code: normalizeIndent`
1431 function MyComponent() {
1432 const local = {};
1433 useEffect(() => {
@@ -1344,22 +1436,30 @@ const tests = {
1436 }, []);
1437 }
1438 `,
1347 - output: `
1348 - function MyComponent() {
1349 - const local = {};
1350 - useEffect(() => {
1351 - console.log(local);
1352 - console.log(local);
1353 - }, [local]);
1354 - }
1355 - `,
1439 errors: [
1357 - "React Hook useEffect has a missing dependency: 'local'. " +
1358 - 'Either include it or remove the dependency array.',
1440 + {
1441 + message:
1442 + "React Hook useEffect has a missing dependency: 'local'. " +
1443 + 'Either include it or remove the dependency array.',
1444 + suggestions: [
1445 + {
1446 + desc: 'Update the dependencies array to be: [local]',
1447 + output: normalizeIndent`
1448 + function MyComponent() {
1449 + const local = {};
1450 + useEffect(() => {
1451 + console.log(local);
1452 + console.log(local);
1453 + }, [local]);
1454 + }
1455 + `,
1456 + },
1457 + ],
1458 + },
1459 ],
1460 },
1461 {
1362 - code: `
1462 + code: normalizeIndent`
1463 function MyComponent() {
1464 const local = {};
1465 useEffect(() => {
@@ -1368,80 +1468,112 @@ const tests = {
1468 }, [local, local]);
1469 }
1470 `,
1371 - output: `
1372 - function MyComponent() {
1373 - const local = {};
1374 - useEffect(() => {
1375 - console.log(local);
1376 - console.log(local);
1377 - }, [local]);
1378 - }
1379 - `,
1471 errors: [
1381 - "React Hook useEffect has a duplicate dependency: 'local'. " +
1382 - 'Either omit it or remove the dependency array.',
1472 + {
1473 + message:
1474 + "React Hook useEffect has a duplicate dependency: 'local'. " +
1475 + 'Either omit it or remove the dependency array.',
1476 + suggestions: [
1477 + {
1478 + desc: 'Update the dependencies array to be: [local]',
1479 + output: normalizeIndent`
1480 + function MyComponent() {
1481 + const local = {};
1482 + useEffect(() => {
1483 + console.log(local);
1484 + console.log(local);
1485 + }, [local]);
1486 + }
1487 + `,
1488 + },
1489 + ],
1490 + },
1491 ],
1492 },
1493 {
1386 - code: `
1494 + code: normalizeIndent`
1495 function MyComponent() {
1496 useCallback(() => {}, [window]);
1497 }
1498 `,
1391 - output: `
1392 - function MyComponent() {
1393 - useCallback(() => {}, []);
1394 - }
1395 - `,
1499 errors: [
1397 - "React Hook useCallback has an unnecessary dependency: 'window'. " +
1398 - 'Either exclude it or remove the dependency array. ' +
1399 - "Outer scope values like 'window' aren't valid dependencies " +
1400 - "because mutating them doesn't re-render the component.",
1500 + {
1501 + message:
1502 + "React Hook useCallback has an unnecessary dependency: 'window'. " +
1503 + 'Either exclude it or remove the dependency array. ' +
1504 + "Outer scope values like 'window' aren't valid dependencies " +
1505 + "because mutating them doesn't re-render the component.",
1506 + suggestions: [
1507 + {
1508 + desc: 'Update the dependencies array to be: []',
1509 + output: normalizeIndent`
1510 + function MyComponent() {
1511 + useCallback(() => {}, []);
1512 + }
1513 + `,
1514 + },
1515 + ],
1516 + },
1517 ],
1518 },
1519 {
1520 // It is not valid for useCallback to specify extraneous deps
1521 // because it doesn't serve as a side effect trigger unlike useEffect.
1406 - code: `
1522 + code: normalizeIndent`
1523 function MyComponent(props) {
1524 let local = props.foo;
1525 useCallback(() => {}, [local]);
1526 }
1527 `,
1412 - output: `
1413 - function MyComponent(props) {
1414 - let local = props.foo;
1415 - useCallback(() => {}, []);
1416 - }
1417 - `,
1528 errors: [
1419 - "React Hook useCallback has an unnecessary dependency: 'local'. " +
1420 - 'Either exclude it or remove the dependency array.',
1529 + {
1530 + message:
1531 + "React Hook useCallback has an unnecessary dependency: 'local'. " +
1532 + 'Either exclude it or remove the dependency array.',
1533 + suggestions: [
1534 + {
1535 + desc: 'Update the dependencies array to be: []',
1536 + output: normalizeIndent`
1537 + function MyComponent(props) {
1538 + let local = props.foo;
1539 + useCallback(() => {}, []);
1540 + }
1541 + `,
1542 + },
1543 + ],
1544 + },
1545 ],
1546 },
1547 {
1424 - code: `
1548 + code: normalizeIndent`
1549 function MyComponent({ history }) {
1550 useEffect(() => {
1551 return history.listen();
1552 }, []);
1553 }
1554 `,
1431 - output: `
1432 - function MyComponent({ history }) {
1433 - useEffect(() => {
1434 - return history.listen();
1435 - }, [history]);
1436 - }
1437 - `,
1555 errors: [
1439 - "React Hook useEffect has a missing dependency: 'history'. " +
1440 - 'Either include it or remove the dependency array.',
1556 + {
1557 + message:
1558 + "React Hook useEffect has a missing dependency: 'history'. " +
1559 + 'Either include it or remove the dependency array.',
1560 + suggestions: [
1561 + {
1562 + desc: 'Update the dependencies array to be: [history]',
1563 + output: normalizeIndent`
1564 + function MyComponent({ history }) {
1565 + useEffect(() => {
1566 + return history.listen();
1567 + }, [history]);
1568 + }
1569 + `,
1570 + },
1571 + ],
1572 + },
1573 ],
1574 },
1575 {
1444 - code: `
1576 + code: normalizeIndent`
1577 function MyComponent({ history }) {
1578 useEffect(() => {
1579 return [
@@ -1451,107 +1583,148 @@ const tests = {
1583 }, []);
1584 }
1585 `,
1454 - output: `
1455 - function MyComponent({ history }) {
1456 - useEffect(() => {
1457 - return [
1458 - history.foo.bar[2].dobedo.listen(),
1459 - history.foo.bar().dobedo.listen[2]
1460 - ];
1461 - }, [history.foo]);
1462 - }
1463 - `,
1586 errors: [
1465 - "React Hook useEffect has a missing dependency: 'history.foo'. " +
1466 - 'Either include it or remove the dependency array.',
1587 + {
1588 + message:
1589 + "React Hook useEffect has a missing dependency: 'history.foo'. " +
1590 + 'Either include it or remove the dependency array.',
1591 + suggestions: [
1592 + {
1593 + desc: 'Update the dependencies array to be: [history.foo]',
1594 + output: normalizeIndent`
1595 + function MyComponent({ history }) {
1596 + useEffect(() => {
1597 + return [
1598 + history.foo.bar[2].dobedo.listen(),
1599 + history.foo.bar().dobedo.listen[2]
1600 + ];
1601 + }, [history.foo]);
1602 + }
1603 + `,
1604 + },
1605 + ],
1606 + },
1607 ],
1608 },
1609 {
1470 - code: `
1471 - function MyComponent() {
1472 - useEffect(() => {}, ['foo']);
1473 - }
1474 - `,
1475 - // TODO: we could autofix this.
1476 - output: `
1610 + code: normalizeIndent`
1611 function MyComponent() {
1612 useEffect(() => {}, ['foo']);
1613 }
1614 `,
1615 errors: [
1482 - // Don't assume user meant `foo` because it's not used in the effect.
1483 - "The 'foo' literal is not a valid dependency because it never changes. " +
1484 - 'You can safely remove it.',
1616 + {
1617 + message:
1618 + // Don't assume user meant `foo` because it's not used in the effect.
1619 + "The 'foo' literal is not a valid dependency because it never changes. " +
1620 + 'You can safely remove it.',
1621 + // TODO: provide suggestion.
1622 + suggestions: undefined,
1623 + },
1624 ],
1625 },
1626 {
1488 - code: `
1627 + code: normalizeIndent`
1628 function MyComponent({ foo, bar, baz }) {
1629 useEffect(() => {
1630 console.log(foo, bar, baz);
1631 }, ['foo', 'bar']);
1632 }
1633 `,
1495 - output: `
1496 - function MyComponent({ foo, bar, baz }) {
1497 - useEffect(() => {
1498 - console.log(foo, bar, baz);
1499 - }, [bar, baz, foo]);
1500 - }
1501 - `,
1634 errors: [
1503 - "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
1504 - 'Either include them or remove the dependency array.',
1505 - "The 'foo' literal is not a valid dependency because it never changes. " +
1506 - 'Did you mean to include foo in the array instead?',
1507 - "The 'bar' literal is not a valid dependency because it never changes. " +
1508 - 'Did you mean to include bar in the array instead?',
1635 + {
1636 + message:
1637 + "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
1638 + 'Either include them or remove the dependency array.',
1639 + suggestions: [
1640 + {
1641 + desc: 'Update the dependencies array to be: [bar, baz, foo]',
1642 + output: normalizeIndent`
1643 + function MyComponent({ foo, bar, baz }) {
1644 + useEffect(() => {
1645 + console.log(foo, bar, baz);
1646 + }, [bar, baz, foo]);
1647 + }
1648 + `,
1649 + },
1650 + ],
1651 + },
1652 + {
1653 + message:
1654 + "The 'foo' literal is not a valid dependency because it never changes. " +
1655 + 'Did you mean to include foo in the array instead?',
1656 + suggestions: undefined,
1657 + },
1658 + {
1659 + message:
1660 + "The 'bar' literal is not a valid dependency because it never changes. " +
1661 + 'Did you mean to include bar in the array instead?',
1662 + suggestions: undefined,
1663 + },
1664 ],
1665 },
1666 {
1512 - code: `
1667 + code: normalizeIndent`
1668 function MyComponent({ foo, bar, baz }) {
1669 useEffect(() => {
1670 console.log(foo, bar, baz);
1671 }, [42, false, null]);
1672 }
1673 `,
1519 - output: `
1520 - function MyComponent({ foo, bar, baz }) {
1521 - useEffect(() => {
1522 - console.log(foo, bar, baz);
1523 - }, [bar, baz, foo]);
1524 - }
1525 - `,
1674 errors: [
1527 - "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
1528 - 'Either include them or remove the dependency array.',
1529 - 'The 42 literal is not a valid dependency because it never changes. You can safely remove it.',
1530 - 'The false literal is not a valid dependency because it never changes. You can safely remove it.',
1531 - 'The null literal is not a valid dependency because it never changes. You can safely remove it.',
1675 + {
1676 + message:
1677 + "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
1678 + 'Either include them or remove the dependency array.',
1679 + suggestions: [
1680 + {
1681 + desc: 'Update the dependencies array to be: [bar, baz, foo]',
1682 + output: normalizeIndent`
1683 + function MyComponent({ foo, bar, baz }) {
1684 + useEffect(() => {
1685 + console.log(foo, bar, baz);
1686 + }, [bar, baz, foo]);
1687 + }
1688 + `,
1689 + },
1690 + ],
1691 + },
1692 + {
1693 + message:
1694 + 'The 42 literal is not a valid dependency because it never changes. You can safely remove it.',
1695 + suggestions: undefined,
1696 + },
1697 + {
1698 + message:
1699 + 'The false literal is not a valid dependency because it never changes. You can safely remove it.',
1700 + suggestions: undefined,
1701 + },
1702 + {
1703 + message:
1704 + 'The null literal is not a valid dependency because it never changes. You can safely remove it.',
1705 + suggestions: undefined,
1706 + },
1707 ],
1708 },
1709 {
1535 - code: `
1536 - function MyComponent() {
1537 - const dependencies = [];
1538 - useEffect(() => {}, dependencies);
1539 - }
1540 - `,
1541 - output: `
1710 + code: normalizeIndent`
1711 function MyComponent() {
1712 const dependencies = [];
1713 useEffect(() => {}, dependencies);
1714 }
1715 `,
1716 errors: [
1548 - 'React Hook useEffect was passed a dependency list that is not an ' +
1549 - "array literal. This means we can't statically verify whether you've " +
1550 - 'passed the correct dependencies.',
1717 + {
1718 + message:
1719 + 'React Hook useEffect was passed a dependency list that is not an ' +
1720 + "array literal. This means we can't statically verify whether you've " +
1721 + 'passed the correct dependencies.',
1722 + suggestions: undefined,
1723 + },
1724 ],
1725 },
1726 {
1554 - code: `
1727 + code: normalizeIndent`
1728 function MyComponent() {
1729 const local = {};
1730 const dependencies = [local];
@@ -1560,26 +1733,38 @@ const tests = {
1733 }, dependencies);
1734 }
1735 `,
1563 - // TODO: should this autofix or bail out?
1564 - output: `
1565 - function MyComponent() {
1566 - const local = {};
1567 - const dependencies = [local];
1568 - useEffect(() => {
1569 - console.log(local);
1570 - }, [local]);
1571 - }
1572 - `,
1736 errors: [
1574 - 'React Hook useEffect was passed a dependency list that is not an ' +
1575 - "array literal. This means we can't statically verify whether you've " +
1576 - 'passed the correct dependencies.',
1577 - "React Hook useEffect has a missing dependency: 'local'. " +
1578 - 'Either include it or remove the dependency array.',
1737 + {
1738 + message:
1739 + 'React Hook useEffect was passed a dependency list that is not an ' +
1740 + "array literal. This means we can't statically verify whether you've " +
1741 + 'passed the correct dependencies.',
1742 + // TODO: should this autofix or bail out?
1743 + suggestions: undefined,
1744 + },
1745 + {
1746 + message:
1747 + "React Hook useEffect has a missing dependency: 'local'. " +
1748 + 'Either include it or remove the dependency array.',
1749 + suggestions: [
1750 + {
1751 + desc: 'Update the dependencies array to be: [local]',
1752 + output: normalizeIndent`
1753 + function MyComponent() {
1754 + const local = {};
1755 + const dependencies = [local];
1756 + useEffect(() => {
1757 + console.log(local);
1758 + }, [local]);
1759 + }
1760 + `,
1761 + },
1762 + ],
1763 + },
1764 ],
1765 },
1766 {
1582 - code: `
1767 + code: normalizeIndent`
1768 function MyComponent() {
1769 const local = {};
1770 const dependencies = [local];
@@ -1588,34 +1773,38 @@ const tests = {
1773 }, [...dependencies]);
1774 }
1775 `,
1591 - // TODO: should this autofix or bail out?
1592 - output: `
1593 - function MyComponent() {
1594 - const local = {};
1595 - const dependencies = [local];
1596 - useEffect(() => {
1597 - console.log(local);
1598 - }, [local]);
1599 - }
1600 - `,
1776 errors: [
1602 - "React Hook useEffect has a missing dependency: 'local'. " +
1603 - 'Either include it or remove the dependency array.',
1604 - 'React Hook useEffect has a spread element in its dependency array. ' +
1605 - "This means we can't statically verify whether you've passed the " +
1606 - 'correct dependencies.',
1777 + {
1778 + message:
1779 + "React Hook useEffect has a missing dependency: 'local'. " +
1780 + 'Either include it or remove the dependency array.',
1781 + suggestions: [
1782 + {
1783 + desc: 'Update the dependencies array to be: [local]',
1784 + output: normalizeIndent`
1785 + function MyComponent() {
1786 + const local = {};
1787 + const dependencies = [local];
1788 + useEffect(() => {
1789 + console.log(local);
1790 + }, [local]);
1791 + }
1792 + `,
1793 + },
1794 + ],
1795 + },
1796 + {
1797 + message:
1798 + 'React Hook useEffect has a spread element in its dependency array. ' +
1799 + "This means we can't statically verify whether you've passed the " +
1800 + 'correct dependencies.',
1801 + // TODO: should this autofix or bail out?
1802 + suggestions: undefined,
1803 + },
1804 ],
1805 },
1806 {
1610 - code: `
1611 - function MyComponent() {
1612 - const local = {};
1613 - useEffect(() => {
1614 - console.log(local);
1615 - }, [local, ...dependencies]);
1616 - }
1617 - `,
1618 - output: `
1807 + code: normalizeIndent`
1808 function MyComponent() {
1809 const local = {};
1810 useEffect(() => {
@@ -1624,13 +1813,17 @@ const tests = {
1813 }
1814 `,
1815 errors: [
1627 - 'React Hook useEffect has a spread element in its dependency array. ' +
1628 - "This means we can't statically verify whether you've passed the " +
1629 - 'correct dependencies.',
1816 + {
1817 + message:
1818 + 'React Hook useEffect has a spread element in its dependency array. ' +
1819 + "This means we can't statically verify whether you've passed the " +
1820 + 'correct dependencies.',
1821 + suggestions: undefined,
1822 + },
1823 ],
1824 },
1825 {
1633 - code: `
1826 + code: normalizeIndent`
1827 function MyComponent() {
1828 const local = {};
1829 useEffect(() => {
@@ -1638,55 +1831,71 @@ const tests = {
1831 }, [computeCacheKey(local)]);
1832 }
1833 `,
1641 - // TODO: I'm not sure this is a good idea.
1642 - // Maybe bail out?
1643 - output: `
1644 - function MyComponent() {
1645 - const local = {};
1646 - useEffect(() => {
1647 - console.log(local);
1648 - }, [local]);
1649 - }
1650 - `,
1834 errors: [
1652 - "React Hook useEffect has a missing dependency: 'local'. " +
1653 - 'Either include it or remove the dependency array.',
1654 - 'React Hook useEffect has a complex expression in the dependency array. ' +
1655 - 'Extract it to a separate variable so it can be statically checked.',
1835 + {
1836 + message:
1837 + "React Hook useEffect has a missing dependency: 'local'. " +
1838 + 'Either include it or remove the dependency array.',
1839 + // TODO: I'm not sure this is a good idea.
1840 + // Maybe bail out?
1841 + suggestions: [
1842 + {
1843 + desc: 'Update the dependencies array to be: [local]',
1844 + output: normalizeIndent`
1845 + function MyComponent() {
1846 + const local = {};
1847 + useEffect(() => {
1848 + console.log(local);
1849 + }, [local]);
1850 + }
1851 + `,
1852 + },
1853 + ],
1854 + },
1855 + {
1856 + message:
1857 + 'React Hook useEffect has a complex expression in the dependency array. ' +
1858 + 'Extract it to a separate variable so it can be statically checked.',
1859 + suggestions: undefined,
1860 + },
1861 ],
1862 },
1863 {
1659 - code: `
1864 + code: normalizeIndent`
1865 function MyComponent(props) {
1866 useEffect(() => {
1867 console.log(props.items[0]);
1868 }, [props.items[0]]);
1869 }
1870 `,
1666 - output: `
1667 - function MyComponent(props) {
1668 - useEffect(() => {
1669 - console.log(props.items[0]);
1670 - }, [props.items]);
1671 - }
1672 - `,
1871 errors: [
1674 - "React Hook useEffect has a missing dependency: 'props.items'. " +
1675 - 'Either include it or remove the dependency array.',
1676 - 'React Hook useEffect has a complex expression in the dependency array. ' +
1677 - 'Extract it to a separate variable so it can be statically checked.',
1872 + {
1873 + message:
1874 + "React Hook useEffect has a missing dependency: 'props.items'. " +
1875 + 'Either include it or remove the dependency array.',
1876 + suggestions: [
1877 + {
1878 + desc: 'Update the dependencies array to be: [props.items]',
1879 + output: normalizeIndent`
1880 + function MyComponent(props) {
1881 + useEffect(() => {
1882 + console.log(props.items[0]);
1883 + }, [props.items]);
1884 + }
1885 + `,
1886 + },
1887 + ],
1888 + },
1889 + {
1890 + message:
1891 + 'React Hook useEffect has a complex expression in the dependency array. ' +
1892 + 'Extract it to a separate variable so it can be statically checked.',
1893 + suggestions: undefined,
1894 + },
1895 ],
1896 },
1897 {
1681 - code: `
1682 - function MyComponent(props) {
1683 - useEffect(() => {
1684 - console.log(props.items[0]);
1685 - }, [props.items, props.items[0]]);
1686 - }
1687 - `,
1688 - // TODO: ideally autofix would remove the bad expression?
1689 - output: `
1898 + code: normalizeIndent`
1899 function MyComponent(props) {
1900 useEffect(() => {
1901 console.log(props.items[0]);
@@ -1694,42 +1903,51 @@ const tests = {
1903 }
1904 `,
1905 errors: [
1697 - 'React Hook useEffect has a complex expression in the dependency array. ' +
1698 - 'Extract it to a separate variable so it can be statically checked.',
1906 + {
1907 + message:
1908 + 'React Hook useEffect has a complex expression in the dependency array. ' +
1909 + 'Extract it to a separate variable so it can be statically checked.',
1910 + // TODO: ideally suggestion would remove the bad expression?
1911 + suggestions: undefined,
1912 + },
1913 ],
1914 },
1915 {
1702 - code: `
1916 + code: normalizeIndent`
1917 function MyComponent({ items }) {
1918 useEffect(() => {
1919 console.log(items[0]);
1920 }, [items[0]]);
1921 }
1922 `,
1709 - output: `
1710 - function MyComponent({ items }) {
1711 - useEffect(() => {
1712 - console.log(items[0]);
1713 - }, [items]);
1714 - }
1715 - `,
1923 errors: [
1717 - "React Hook useEffect has a missing dependency: 'items'. " +
1718 - 'Either include it or remove the dependency array.',
1719 - 'React Hook useEffect has a complex expression in the dependency array. ' +
1720 - 'Extract it to a separate variable so it can be statically checked.',
1924 + {
1925 + message:
1926 + "React Hook useEffect has a missing dependency: 'items'. " +
1927 + 'Either include it or remove the dependency array.',
1928 + suggestions: [
1929 + {
1930 + desc: 'Update the dependencies array to be: [items]',
1931 + output: normalizeIndent`
1932 + function MyComponent({ items }) {
1933 + useEffect(() => {
1934 + console.log(items[0]);
1935 + }, [items]);
1936 + }
1937 + `,
1938 + },
1939 + ],
1940 + },
1941 + {
1942 + message:
1943 + 'React Hook useEffect has a complex expression in the dependency array. ' +
1944 + 'Extract it to a separate variable so it can be statically checked.',
1945 + suggestions: undefined,
1946 + },
1947 ],
1948 },
1949 {
1724 - code: `
1725 - function MyComponent({ items }) {
1726 - useEffect(() => {
1727 - console.log(items[0]);
1728 - }, [items, items[0]]);
1729 - }
1730 - `,
1731 - // TODO: ideally autofix would remove the bad expression?
1732 - output: `
1950 + code: normalizeIndent`
1951 function MyComponent({ items }) {
1952 useEffect(() => {
1953 console.log(items[0]);
@@ -1737,8 +1955,13 @@ const tests = {
1955 }
1956 `,
1957 errors: [
1740 - 'React Hook useEffect has a complex expression in the dependency array. ' +
1741 - 'Extract it to a separate variable so it can be statically checked.',
1958 + {
1959 + message:
1960 + 'React Hook useEffect has a complex expression in the dependency array. ' +
1961 + 'Extract it to a separate variable so it can be statically checked.',
1962 + // TODO: ideally suggeston would remove the bad expression?
1963 + suggestions: undefined,
1964 + },
1965 ],
1966 },
1967 {
@@ -1747,7 +1970,7 @@ const tests = {
1970 // However, we generally allow specifying *broader* deps as escape hatch.
1971 // So while [props, props.foo] is unnecessary, 'props' wins here as the
1972 // broader one, and this is why 'props.foo' is reported as unnecessary.
1750 - code: `
1973 + code: normalizeIndent`
1974 function MyComponent(props) {
1975 const local = {};
1976 useCallback(() => {
@@ -1756,23 +1979,31 @@ const tests = {
1979 }, [props, props.foo]);
1980 }
1981 `,
1759 - output: `
1760 - function MyComponent(props) {
1761 - const local = {};
1762 - useCallback(() => {
1763 - console.log(props.foo);
1764 - console.log(props.bar);
1765 - }, [props]);
1766 - }
1767 - `,
1982 errors: [
1769 - "React Hook useCallback has an unnecessary dependency: 'props.foo'. " +
1770 - 'Either exclude it or remove the dependency array.',
1983 + {
1984 + message:
1985 + "React Hook useCallback has an unnecessary dependency: 'props.foo'. " +
1986 + 'Either exclude it or remove the dependency array.',
1987 + suggestions: [
1988 + {
1989 + desc: 'Update the dependencies array to be: [props]',
1990 + output: normalizeIndent`
1991 + function MyComponent(props) {
1992 + const local = {};
1993 + useCallback(() => {
1994 + console.log(props.foo);
1995 + console.log(props.bar);
1996 + }, [props]);
1997 + }
1998 + `,
1999 + },
2000 + ],
2001 + },
2002 ],
2003 },
2004 {
2005 // Since we don't have 'props' in the list, we'll suggest narrow dependencies.
1775 - code: `
2006 + code: normalizeIndent`
2007 function MyComponent(props) {
2008 const local = {};
2009 useCallback(() => {
@@ -1781,24 +2012,33 @@ const tests = {
2012 }, []);
2013 }
2014 `,
1784 - output: `
1785 - function MyComponent(props) {
1786 - const local = {};
1787 - useCallback(() => {
1788 - console.log(props.foo);
1789 - console.log(props.bar);
1790 - }, [props.bar, props.foo]);
1791 - }
1792 - `,
2015 errors: [
1794 - "React Hook useCallback has missing dependencies: 'props.bar' and 'props.foo'. " +
1795 - 'Either include them or remove the dependency array.',
2016 + {
2017 + message:
2018 + "React Hook useCallback has missing dependencies: 'props.bar' and 'props.foo'. " +
2019 + 'Either include them or remove the dependency array.',
2020 + suggestions: [
2021 + {
2022 + desc:
2023 + 'Update the dependencies array to be: [props.bar, props.foo]',
2024 + output: normalizeIndent`
2025 + function MyComponent(props) {
2026 + const local = {};
2027 + useCallback(() => {
2028 + console.log(props.foo);
2029 + console.log(props.bar);
2030 + }, [props.bar, props.foo]);
2031 + }
2032 + `,
2033 + },
2034 + ],
2035 + },
2036 ],
2037 },
2038 {
2039 // Effects are allowed to over-specify deps. We'll complain about missing
2040 // 'local', but we won't remove the already-specified 'local.id' from your list.
1801 - code: `
2041 + code: normalizeIndent`
2042 function MyComponent() {
2043 const local = {id: 42};
2044 useEffect(() => {
@@ -1806,23 +2046,31 @@ const tests = {
2046 }, [local.id]);
2047 }
2048 `,
1809 - output: `
1810 - function MyComponent() {
1811 - const local = {id: 42};
1812 - useEffect(() => {
1813 - console.log(local);
1814 - }, [local, local.id]);
1815 - }
1816 - `,
2049 errors: [
1818 - "React Hook useEffect has a missing dependency: 'local'. " +
1819 - 'Either include it or remove the dependency array.',
2050 + {
2051 + message:
2052 + "React Hook useEffect has a missing dependency: 'local'. " +
2053 + 'Either include it or remove the dependency array.',
2054 + suggestions: [
2055 + {
2056 + desc: 'Update the dependencies array to be: [local, local.id]',
2057 + output: normalizeIndent`
2058 + function MyComponent() {
2059 + const local = {id: 42};
2060 + useEffect(() => {
2061 + console.log(local);
2062 + }, [local, local.id]);
2063 + }
2064 + `,
2065 + },
2066 + ],
2067 + },
2068 ],
2069 },
2070 {
2071 // Callbacks are not allowed to over-specify deps. So we'll complain about missing
2072 // 'local' and we will also *remove* 'local.id' from your list.
1825 - code: `
2073 + code: normalizeIndent`
2074 function MyComponent() {
2075 const local = {id: 42};
2076 const fn = useCallback(() => {
@@ -1830,23 +2078,31 @@ const tests = {
2078 }, [local.id]);
2079 }
2080 `,
1833 - output: `
1834 - function MyComponent() {
1835 - const local = {id: 42};
1836 - const fn = useCallback(() => {
1837 - console.log(local);
1838 - }, [local]);
1839 - }
1840 - `,
2081 errors: [
1842 - "React Hook useCallback has a missing dependency: 'local'. " +
1843 - 'Either include it or remove the dependency array.',
2082 + {
2083 + message:
2084 + "React Hook useCallback has a missing dependency: 'local'. " +
2085 + 'Either include it or remove the dependency array.',
2086 + suggestions: [
2087 + {
2088 + desc: 'Update the dependencies array to be: [local]',
2089 + output: normalizeIndent`
2090 + function MyComponent() {
2091 + const local = {id: 42};
2092 + const fn = useCallback(() => {
2093 + console.log(local);
2094 + }, [local]);
2095 + }
2096 + `,
2097 + },
2098 + ],
2099 + },
2100 ],
2101 },
2102 {
2103 // Callbacks are not allowed to over-specify deps. So we'll complain about
2104 // the unnecessary 'local.id'.
1849 - code: `
2105 + code: normalizeIndent`
2106 function MyComponent() {
2107 const local = {id: 42};
2108 const fn = useCallback(() => {
@@ -1854,41 +2110,57 @@ const tests = {
2110 }, [local.id, local]);
2111 }
2112 `,
1857 - output: `
1858 - function MyComponent() {
1859 - const local = {id: 42};
1860 - const fn = useCallback(() => {
1861 - console.log(local);
1862 - }, [local]);
1863 - }
1864 - `,
2113 errors: [
1866 - "React Hook useCallback has an unnecessary dependency: 'local.id'. " +
1867 - 'Either exclude it or remove the dependency array.',
2114 + {
2115 + message:
2116 + "React Hook useCallback has an unnecessary dependency: 'local.id'. " +
2117 + 'Either exclude it or remove the dependency array.',
2118 + suggestions: [
2119 + {
2120 + desc: 'Update the dependencies array to be: [local]',
2121 + output: normalizeIndent`
2122 + function MyComponent() {
2123 + const local = {id: 42};
2124 + const fn = useCallback(() => {
2125 + console.log(local);
2126 + }, [local]);
2127 + }
2128 + `,
2129 + },
2130 + ],
2131 + },
2132 ],
2133 },
2134 {
1871 - code: `
2135 + code: normalizeIndent`
2136 function MyComponent(props) {
2137 const fn = useCallback(() => {
2138 console.log(props.foo.bar.baz);
2139 }, []);
2140 }
2141 `,
1878 - output: `
1879 - function MyComponent(props) {
1880 - const fn = useCallback(() => {
1881 - console.log(props.foo.bar.baz);
1882 - }, [props.foo.bar.baz]);
1883 - }
1884 - `,
2142 errors: [
1886 - "React Hook useCallback has a missing dependency: 'props.foo.bar.baz'. " +
1887 - 'Either include it or remove the dependency array.',
2143 + {
2144 + message:
2145 + "React Hook useCallback has a missing dependency: 'props.foo.bar.baz'. " +
2146 + 'Either include it or remove the dependency array.',
2147 + suggestions: [
2148 + {
2149 + desc: 'Update the dependencies array to be: [props.foo.bar.baz]',
2150 + output: normalizeIndent`
2151 + function MyComponent(props) {
2152 + const fn = useCallback(() => {
2153 + console.log(props.foo.bar.baz);
2154 + }, [props.foo.bar.baz]);
2155 + }
2156 + `,
2157 + },
2158 + ],
2159 + },
2160 ],
2161 },
2162 {
1891 - code: `
2163 + code: normalizeIndent`
2164 function MyComponent(props) {
2165 let color = {}
2166 const fn = useCallback(() => {
@@ -1897,18 +2169,27 @@ const tests = {
2169 }, [props.foo, props.foo.bar.baz]);
2170 }
2171 `,
1900 - output: `
1901 - function MyComponent(props) {
1902 - let color = {}
1903 - const fn = useCallback(() => {
1904 - console.log(props.foo.bar.baz);
1905 - console.log(color);
1906 - }, [color, props.foo.bar.baz]);
1907 - }
1908 - `,
2172 errors: [
1910 - "React Hook useCallback has a missing dependency: 'color'. " +
1911 - 'Either include it or remove the dependency array.',
2173 + {
2174 + message:
2175 + "React Hook useCallback has a missing dependency: 'color'. " +
2176 + 'Either include it or remove the dependency array.',
2177 + suggestions: [
2178 + {
2179 + desc:
2180 + 'Update the dependencies array to be: [color, props.foo.bar.baz]',
2181 + output: normalizeIndent`
2182 + function MyComponent(props) {
2183 + let color = {}
2184 + const fn = useCallback(() => {
2185 + console.log(props.foo.bar.baz);
2186 + console.log(color);
2187 + }, [color, props.foo.bar.baz]);
2188 + }
2189 + `,
2190 + },
2191 + ],
2192 + },
2193 ],
2194 },
2195 {
@@ -1917,27 +2198,35 @@ const tests = {
2198 // So in this case we ask you to remove 'props.foo.bar.baz' because 'props.foo'
2199 // already covers it, and having both is unnecessary.
2200 // TODO: maybe consider suggesting a narrower one by default in these cases.
1920 - code: `
2201 + code: normalizeIndent`
2202 function MyComponent(props) {
2203 const fn = useCallback(() => {
2204 console.log(props.foo.bar.baz);
2205 }, [props.foo.bar.baz, props.foo]);
2206 }
2207 `,
1927 - output: `
1928 - function MyComponent(props) {
1929 - const fn = useCallback(() => {
1930 - console.log(props.foo.bar.baz);
1931 - }, [props.foo]);
1932 - }
1933 - `,
2208 errors: [
1935 - "React Hook useCallback has an unnecessary dependency: 'props.foo.bar.baz'. " +
1936 - 'Either exclude it or remove the dependency array.',
2209 + {
2210 + message:
2211 + "React Hook useCallback has an unnecessary dependency: 'props.foo.bar.baz'. " +
2212 + 'Either exclude it or remove the dependency array.',
2213 + suggestions: [
2214 + {
2215 + desc: 'Update the dependencies array to be: [props.foo]',
2216 + output: normalizeIndent`
2217 + function MyComponent(props) {
2218 + const fn = useCallback(() => {
2219 + console.log(props.foo.bar.baz);
2220 + }, [props.foo]);
2221 + }
2222 + `,
2223 + },
2224 + ],
2225 + },
2226 ],
2227 },
2228 {
1940 - code: `
2229 + code: normalizeIndent`
2230 function MyComponent(props) {
2231 const fn = useCallback(() => {
2232 console.log(props.foo.bar.baz);
@@ -1945,17 +2234,26 @@ const tests = {
2234 }, []);
2235 }
2236 `,
1948 - output: `
1949 - function MyComponent(props) {
1950 - const fn = useCallback(() => {
1951 - console.log(props.foo.bar.baz);
1952 - console.log(props.foo.fizz.bizz);
1953 - }, [props.foo.bar.baz, props.foo.fizz.bizz]);
1954 - }
1955 - `,
2237 errors: [
1957 - "React Hook useCallback has missing dependencies: 'props.foo.bar.baz' and 'props.foo.fizz.bizz'. " +
1958 - 'Either include them or remove the dependency array.',
2238 + {
2239 + message:
2240 + "React Hook useCallback has missing dependencies: 'props.foo.bar.baz' and 'props.foo.fizz.bizz'. " +
2241 + 'Either include them or remove the dependency array.',
2242 + suggestions: [
2243 + {
2244 + desc:
2245 + 'Update the dependencies array to be: [props.foo.bar.baz, props.foo.fizz.bizz]',
2246 + output: normalizeIndent`
2247 + function MyComponent(props) {
2248 + const fn = useCallback(() => {
2249 + console.log(props.foo.bar.baz);
2250 + console.log(props.foo.fizz.bizz);
2251 + }, [props.foo.bar.baz, props.foo.fizz.bizz]);
2252 + }
2253 + `,
2254 + },
2255 + ],
2256 + },
2257 ],
2258 },
2259 {
@@ -1965,27 +2263,35 @@ const tests = {
2263 // When we're sure there is a mistake, for callbacks we will rebuild the list
2264 // from scratch. This will set the user on a better path by default.
2265 // This is why we end up with just 'props.foo.bar', and not them both.
1968 - code: `
2266 + code: normalizeIndent`
2267 function MyComponent(props) {
2268 const fn = useCallback(() => {
2269 console.log(props.foo.bar);
2270 }, [props.foo.bar.baz]);
2271 }
2272 `,
1975 - output: `
1976 - function MyComponent(props) {
1977 - const fn = useCallback(() => {
1978 - console.log(props.foo.bar);
1979 - }, [props.foo.bar]);
1980 - }
1981 - `,
2273 errors: [
1983 - "React Hook useCallback has a missing dependency: 'props.foo.bar'. " +
1984 - 'Either include it or remove the dependency array.',
2274 + {
2275 + message:
2276 + "React Hook useCallback has a missing dependency: 'props.foo.bar'. " +
2277 + 'Either include it or remove the dependency array.',
2278 + suggestions: [
2279 + {
2280 + desc: 'Update the dependencies array to be: [props.foo.bar]',
2281 + output: normalizeIndent`
2282 + function MyComponent(props) {
2283 + const fn = useCallback(() => {
2284 + console.log(props.foo.bar);
2285 + }, [props.foo.bar]);
2286 + }
2287 + `,
2288 + },
2289 + ],
2290 + },
2291 ],
2292 },
2293 {
1988 - code: `
2294 + code: normalizeIndent`
2295 function MyComponent(props) {
2296 const fn = useCallback(() => {
2297 console.log(props);
@@ -1993,21 +2299,29 @@ const tests = {
2299 }, [props.foo.bar.baz]);
2300 }
2301 `,
1996 - output: `
1997 - function MyComponent(props) {
1998 - const fn = useCallback(() => {
1999 - console.log(props);
2000 - console.log(props.hello);
2001 - }, [props]);
2002 - }
2003 - `,
2302 errors: [
2005 - "React Hook useCallback has a missing dependency: 'props'. " +
2006 - 'Either include it or remove the dependency array.',
2303 + {
2304 + message:
2305 + "React Hook useCallback has a missing dependency: 'props'. " +
2306 + 'Either include it or remove the dependency array.',
2307 + suggestions: [
2308 + {
2309 + desc: 'Update the dependencies array to be: [props]',
2310 + output: normalizeIndent`
2311 + function MyComponent(props) {
2312 + const fn = useCallback(() => {
2313 + console.log(props);
2314 + console.log(props.hello);
2315 + }, [props]);
2316 + }
2317 + `,
2318 + },
2319 + ],
2320 + },
2321 ],
2322 },
2323 {
2010 - code: `
2324 + code: normalizeIndent`
2325 function MyComponent() {
2326 const local = {};
2327 useEffect(() => {
@@ -2015,21 +2329,29 @@ const tests = {
2329 }, [local, local]);
2330 }
2331 `,
2018 - output: `
2019 - function MyComponent() {
2020 - const local = {};
2021 - useEffect(() => {
2022 - console.log(local);
2023 - }, [local]);
2024 - }
2025 - `,
2332 errors: [
2027 - "React Hook useEffect has a duplicate dependency: 'local'. " +
2028 - 'Either omit it or remove the dependency array.',
2333 + {
2334 + message:
2335 + "React Hook useEffect has a duplicate dependency: 'local'. " +
2336 + 'Either omit it or remove the dependency array.',
2337 + suggestions: [
2338 + {
2339 + desc: 'Update the dependencies array to be: [local]',
2340 + output: normalizeIndent`
2341 + function MyComponent() {
2342 + const local = {};
2343 + useEffect(() => {
2344 + console.log(local);
2345 + }, [local]);
2346 + }
2347 + `,
2348 + },
2349 + ],
2350 + },
2351 ],
2352 },
2353 {
2032 - code: `
2354 + code: normalizeIndent`
2355 function MyComponent() {
2356 const local1 = {};
2357 useCallback(() => {
@@ -2038,60 +2360,84 @@ const tests = {
2360 }, [local1]);
2361 }
2362 `,
2041 - output: `
2042 - function MyComponent() {
2043 - const local1 = {};
2044 - useCallback(() => {
2045 - const local1 = {};
2046 - console.log(local1);
2047 - }, []);
2048 - }
2049 - `,
2363 errors: [
2051 - "React Hook useCallback has an unnecessary dependency: 'local1'. " +
2052 - 'Either exclude it or remove the dependency array.',
2364 + {
2365 + message:
2366 + "React Hook useCallback has an unnecessary dependency: 'local1'. " +
2367 + 'Either exclude it or remove the dependency array.',
2368 + suggestions: [
2369 + {
2370 + desc: 'Update the dependencies array to be: []',
2371 + output: normalizeIndent`
2372 + function MyComponent() {
2373 + const local1 = {};
2374 + useCallback(() => {
2375 + const local1 = {};
2376 + console.log(local1);
2377 + }, []);
2378 + }
2379 + `,
2380 + },
2381 + ],
2382 + },
2383 ],
2384 },
2385 {
2056 - code: `
2386 + code: normalizeIndent`
2387 function MyComponent() {
2388 const local1 = {};
2389 useCallback(() => {}, [local1]);
2390 }
2391 `,
2062 - output: `
2063 - function MyComponent() {
2064 - const local1 = {};
2065 - useCallback(() => {}, []);
2066 - }
2067 - `,
2392 errors: [
2069 - "React Hook useCallback has an unnecessary dependency: 'local1'. " +
2070 - 'Either exclude it or remove the dependency array.',
2393 + {
2394 + message:
2395 + "React Hook useCallback has an unnecessary dependency: 'local1'. " +
2396 + 'Either exclude it or remove the dependency array.',
2397 + suggestions: [
2398 + {
2399 + desc: 'Update the dependencies array to be: []',
2400 + output: normalizeIndent`
2401 + function MyComponent() {
2402 + const local1 = {};
2403 + useCallback(() => {}, []);
2404 + }
2405 + `,
2406 + },
2407 + ],
2408 + },
2409 ],
2410 },
2411 {
2074 - code: `
2412 + code: normalizeIndent`
2413 function MyComponent(props) {
2414 useEffect(() => {
2415 console.log(props.foo);
2416 }, []);
2417 }
2418 `,
2081 - output: `
2082 - function MyComponent(props) {
2083 - useEffect(() => {
2084 - console.log(props.foo);
2085 - }, [props.foo]);
2086 - }
2087 - `,
2088 - errors: [
2089 - "React Hook useEffect has a missing dependency: 'props.foo'. " +
2090 - 'Either include it or remove the dependency array.',
2091 - ],
2092 - },
2093 - {
2094 - code: `
2419 + errors: [
2420 + {
2421 + message:
2422 + "React Hook useEffect has a missing dependency: 'props.foo'. " +
2423 + 'Either include it or remove the dependency array.',
2424 + suggestions: [
2425 + {
2426 + desc: 'Update the dependencies array to be: [props.foo]',
2427 + output: normalizeIndent`
2428 + function MyComponent(props) {
2429 + useEffect(() => {
2430 + console.log(props.foo);
2431 + }, [props.foo]);
2432 + }
2433 + `,
2434 + },
2435 + ],
2436 + },
2437 + ],
2438 + },
2439 + {
2440 + code: normalizeIndent`
2441 function MyComponent(props) {
2442 useEffect(() => {
2443 console.log(props.foo);
@@ -2099,21 +2445,30 @@ const tests = {
2445 }, []);
2446 }
2447 `,
2102 - output: `
2103 - function MyComponent(props) {
2104 - useEffect(() => {
2105 - console.log(props.foo);
2106 - console.log(props.bar);
2107 - }, [props.bar, props.foo]);
2108 - }
2109 - `,
2448 errors: [
2111 - "React Hook useEffect has missing dependencies: 'props.bar' and 'props.foo'. " +
2112 - 'Either include them or remove the dependency array.',
2449 + {
2450 + message:
2451 + "React Hook useEffect has missing dependencies: 'props.bar' and 'props.foo'. " +
2452 + 'Either include them or remove the dependency array.',
2453 + suggestions: [
2454 + {
2455 + desc:
2456 + 'Update the dependencies array to be: [props.bar, props.foo]',
2457 + output: normalizeIndent`
2458 + function MyComponent(props) {
2459 + useEffect(() => {
2460 + console.log(props.foo);
2461 + console.log(props.bar);
2462 + }, [props.bar, props.foo]);
2463 + }
2464 + `,
2465 + },
2466 + ],
2467 + },
2468 ],
2469 },
2470 {
2116 - code: `
2471 + code: normalizeIndent`
2472 function MyComponent(props) {
2473 let a, b, c, d, e, f, g;
2474 useEffect(() => {
@@ -2121,22 +2476,31 @@ const tests = {
2476 }, [c, a, g]);
2477 }
2478 `,
2124 - // Don't alphabetize if it wasn't alphabetized in the first place.
2125 - output: `
2126 - function MyComponent(props) {
2127 - let a, b, c, d, e, f, g;
2128 - useEffect(() => {
2129 - console.log(b, e, d, c, a, g, f);
2130 - }, [c, a, g, b, e, d, f]);
2131 - }
2132 - `,
2479 errors: [
2134 - "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
2135 - 'Either include them or remove the dependency array.',
2480 + {
2481 + message:
2482 + "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
2483 + 'Either include them or remove the dependency array.',
2484 + // Don't alphabetize if it wasn't alphabetized in the first place.
2485 + suggestions: [
2486 + {
2487 + desc:
2488 + 'Update the dependencies array to be: [c, a, g, b, e, d, f]',
2489 + output: normalizeIndent`
2490 + function MyComponent(props) {
2491 + let a, b, c, d, e, f, g;
2492 + useEffect(() => {
2493 + console.log(b, e, d, c, a, g, f);
2494 + }, [c, a, g, b, e, d, f]);
2495 + }
2496 + `,
2497 + },
2498 + ],
2499 + },
2500 ],
2501 },
2502 {
2139 - code: `
2503 + code: normalizeIndent`
2504 function MyComponent(props) {
2505 let a, b, c, d, e, f, g;
2506 useEffect(() => {
@@ -2144,22 +2508,31 @@ const tests = {
2508 }, [a, c, g]);
2509 }
2510 `,
2147 - // Alphabetize if it was alphabetized.
2148 - output: `
2149 - function MyComponent(props) {
2150 - let a, b, c, d, e, f, g;
2151 - useEffect(() => {
2152 - console.log(b, e, d, c, a, g, f);
2153 - }, [a, b, c, d, e, f, g]);
2154 - }
2155 - `,
2511 errors: [
2157 - "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
2158 - 'Either include them or remove the dependency array.',
2512 + {
2513 + message:
2514 + "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
2515 + 'Either include them or remove the dependency array.',
2516 + // Alphabetize if it was alphabetized.
2517 + suggestions: [
2518 + {
2519 + desc:
2520 + 'Update the dependencies array to be: [a, b, c, d, e, f, g]',
2521 + output: normalizeIndent`
2522 + function MyComponent(props) {
2523 + let a, b, c, d, e, f, g;
2524 + useEffect(() => {
2525 + console.log(b, e, d, c, a, g, f);
2526 + }, [a, b, c, d, e, f, g]);
2527 + }
2528 + `,
2529 + },
2530 + ],
2531 + },
2532 ],
2533 },
2534 {
2162 - code: `
2535 + code: normalizeIndent`
2536 function MyComponent(props) {
2537 let a, b, c, d, e, f, g;
2538 useEffect(() => {
@@ -2167,22 +2540,31 @@ const tests = {
2540 }, []);
2541 }
2542 `,
2170 - // Alphabetize if it was empty.
2171 - output: `
2172 - function MyComponent(props) {
2173 - let a, b, c, d, e, f, g;
2174 - useEffect(() => {
2175 - console.log(b, e, d, c, a, g, f);
2176 - }, [a, b, c, d, e, f, g]);
2177 - }
2178 - `,
2543 errors: [
2180 - "React Hook useEffect has missing dependencies: 'a', 'b', 'c', 'd', 'e', 'f', and 'g'. " +
2181 - 'Either include them or remove the dependency array.',
2544 + {
2545 + message:
2546 + "React Hook useEffect has missing dependencies: 'a', 'b', 'c', 'd', 'e', 'f', and 'g'. " +
2547 + 'Either include them or remove the dependency array.',
2548 + // Alphabetize if it was empty.
2549 + suggestions: [
2550 + {
2551 + desc:
2552 + 'Update the dependencies array to be: [a, b, c, d, e, f, g]',
2553 + output: normalizeIndent`
2554 + function MyComponent(props) {
2555 + let a, b, c, d, e, f, g;
2556 + useEffect(() => {
2557 + console.log(b, e, d, c, a, g, f);
2558 + }, [a, b, c, d, e, f, g]);
2559 + }
2560 + `,
2561 + },
2562 + ],
2563 + },
2564 ],
2565 },
2566 {
2185 - code: `
2567 + code: normalizeIndent`
2568 function MyComponent(props) {
2569 const local = {};
2570 useEffect(() => {
@@ -2192,23 +2574,32 @@ const tests = {
2574 }, []);
2575 }
2576 `,
2195 - output: `
2196 - function MyComponent(props) {
2197 - const local = {};
2198 - useEffect(() => {
2199 - console.log(props.foo);
2200 - console.log(props.bar);
2201 - console.log(local);
2202 - }, [local, props.bar, props.foo]);
2203 - }
2204 - `,
2577 errors: [
2206 - "React Hook useEffect has missing dependencies: 'local', 'props.bar', and 'props.foo'. " +
2207 - 'Either include them or remove the dependency array.',
2578 + {
2579 + message:
2580 + "React Hook useEffect has missing dependencies: 'local', 'props.bar', and 'props.foo'. " +
2581 + 'Either include them or remove the dependency array.',
2582 + suggestions: [
2583 + {
2584 + desc:
2585 + 'Update the dependencies array to be: [local, props.bar, props.foo]',
2586 + output: normalizeIndent`
2587 + function MyComponent(props) {
2588 + const local = {};
2589 + useEffect(() => {
2590 + console.log(props.foo);
2591 + console.log(props.bar);
2592 + console.log(local);
2593 + }, [local, props.bar, props.foo]);
2594 + }
2595 + `,
2596 + },
2597 + ],
2598 + },
2599 ],
2600 },
2601 {
2211 - code: `
2602 + code: normalizeIndent`
2603 function MyComponent(props) {
2604 const local = {};
2605 useEffect(() => {
@@ -2218,23 +2609,31 @@ const tests = {
2609 }, [props]);
2610 }
2611 `,
2221 - output: `
2222 - function MyComponent(props) {
2223 - const local = {};
2224 - useEffect(() => {
2225 - console.log(props.foo);
2226 - console.log(props.bar);
2227 - console.log(local);
2228 - }, [local, props]);
2229 - }
2230 - `,
2612 errors: [
2232 - "React Hook useEffect has a missing dependency: 'local'. " +
2233 - 'Either include it or remove the dependency array.',
2613 + {
2614 + message:
2615 + "React Hook useEffect has a missing dependency: 'local'. " +
2616 + 'Either include it or remove the dependency array.',
2617 + suggestions: [
2618 + {
2619 + desc: 'Update the dependencies array to be: [local, props]',
2620 + output: normalizeIndent`
2621 + function MyComponent(props) {
2622 + const local = {};
2623 + useEffect(() => {
2624 + console.log(props.foo);
2625 + console.log(props.bar);
2626 + console.log(local);
2627 + }, [local, props]);
2628 + }
2629 + `,
2630 + },
2631 + ],
2632 + },
2633 ],
2634 },
2635 {
2237 - code: `
2636 + code: normalizeIndent`
2637 function MyComponent(props) {
2638 useEffect(() => {
2639 console.log(props.foo);
@@ -2259,48 +2658,221 @@ const tests = {
2658 }, []);
2659 }
2660 `,
2262 - output: `
2263 - function MyComponent(props) {
2264 - useEffect(() => {
2265 - console.log(props.foo);
2266 - }, [props.foo]);
2267 - useCallback(() => {
2268 - console.log(props.foo);
2269 - }, [props.foo]);
2270 - useMemo(() => {
2271 - console.log(props.foo);
2272 - }, [props.foo]);
2273 - React.useEffect(() => {
2274 - console.log(props.foo);
2275 - }, [props.foo]);
2276 - React.useCallback(() => {
2277 - console.log(props.foo);
2278 - }, [props.foo]);
2279 - React.useMemo(() => {
2280 - console.log(props.foo);
2281 - }, [props.foo]);
2282 - React.notReactiveHook(() => {
2283 - console.log(props.foo);
2284 - }, []);
2285 - }
2286 - `,
2661 errors: [
2288 - "React Hook useEffect has a missing dependency: 'props.foo'. " +
2289 - 'Either include it or remove the dependency array.',
2290 - "React Hook useCallback has a missing dependency: 'props.foo'. " +
2291 - 'Either include it or remove the dependency array.',
2292 - "React Hook useMemo has a missing dependency: 'props.foo'. " +
2293 - 'Either include it or remove the dependency array.',
2294 - "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
2295 - 'Either include it or remove the dependency array.',
2296 - "React Hook React.useCallback has a missing dependency: 'props.foo'. " +
2297 - 'Either include it or remove the dependency array.',
2298 - "React Hook React.useMemo has a missing dependency: 'props.foo'. " +
2299 - 'Either include it or remove the dependency array.',
2662 + {
2663 + message:
2664 + "React Hook useEffect has a missing dependency: 'props.foo'. " +
2665 + 'Either include it or remove the dependency array.',
2666 + suggestions: [
2667 + {
2668 + desc: 'Update the dependencies array to be: [props.foo]',
2669 + output: normalizeIndent`
2670 + function MyComponent(props) {
2671 + useEffect(() => {
2672 + console.log(props.foo);
2673 + }, [props.foo]);
2674 + useCallback(() => {
2675 + console.log(props.foo);
2676 + }, []);
2677 + useMemo(() => {
2678 + console.log(props.foo);
2679 + }, []);
2680 + React.useEffect(() => {
2681 + console.log(props.foo);
2682 + }, []);
2683 + React.useCallback(() => {
2684 + console.log(props.foo);
2685 + }, []);
2686 + React.useMemo(() => {
2687 + console.log(props.foo);
2688 + }, []);
2689 + React.notReactiveHook(() => {
2690 + console.log(props.foo);
2691 + }, []);
2692 + }
2693 + `,
2694 + },
2695 + ],
2696 + },
2697 + {
2698 + message:
2699 + "React Hook useCallback has a missing dependency: 'props.foo'. " +
2700 + 'Either include it or remove the dependency array.',
2701 + suggestions: [
2702 + {
2703 + desc: 'Update the dependencies array to be: [props.foo]',
2704 + output: normalizeIndent`
2705 + function MyComponent(props) {
2706 + useEffect(() => {
2707 + console.log(props.foo);
2708 + }, []);
2709 + useCallback(() => {
2710 + console.log(props.foo);
2711 + }, [props.foo]);
2712 + useMemo(() => {
2713 + console.log(props.foo);
2714 + }, []);
2715 + React.useEffect(() => {
2716 + console.log(props.foo);
2717 + }, []);
2718 + React.useCallback(() => {
2719 + console.log(props.foo);
2720 + }, []);
2721 + React.useMemo(() => {
2722 + console.log(props.foo);
2723 + }, []);
2724 + React.notReactiveHook(() => {
2725 + console.log(props.foo);
2726 + }, []);
2727 + }
2728 + `,
2729 + },
2730 + ],
2731 + },
2732 + {
2733 + message:
2734 + "React Hook useMemo has a missing dependency: 'props.foo'. " +
2735 + 'Either include it or remove the dependency array.',
2736 + suggestions: [
2737 + {
2738 + desc: 'Update the dependencies array to be: [props.foo]',
2739 + output: normalizeIndent`
2740 + function MyComponent(props) {
2741 + useEffect(() => {
2742 + console.log(props.foo);
2743 + }, []);
2744 + useCallback(() => {
2745 + console.log(props.foo);
2746 + }, []);
2747 + useMemo(() => {
2748 + console.log(props.foo);
2749 + }, [props.foo]);
2750 + React.useEffect(() => {
2751 + console.log(props.foo);
2752 + }, []);
2753 + React.useCallback(() => {
2754 + console.log(props.foo);
2755 + }, []);
2756 + React.useMemo(() => {
2757 + console.log(props.foo);
2758 + }, []);
2759 + React.notReactiveHook(() => {
2760 + console.log(props.foo);
2761 + }, []);
2762 + }
2763 + `,
2764 + },
2765 + ],
2766 + },
2767 + {
2768 + message:
2769 + "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
2770 + 'Either include it or remove the dependency array.',
2771 + suggestions: [
2772 + {
2773 + desc: 'Update the dependencies array to be: [props.foo]',
2774 + output: normalizeIndent`
2775 + function MyComponent(props) {
2776 + useEffect(() => {
2777 + console.log(props.foo);
2778 + }, []);
2779 + useCallback(() => {
2780 + console.log(props.foo);
2781 + }, []);
2782 + useMemo(() => {
2783 + console.log(props.foo);
2784 + }, []);
2785 + React.useEffect(() => {
2786 + console.log(props.foo);
2787 + }, [props.foo]);
2788 + React.useCallback(() => {
2789 + console.log(props.foo);
2790 + }, []);
2791 + React.useMemo(() => {
2792 + console.log(props.foo);
2793 + }, []);
2794 + React.notReactiveHook(() => {
2795 + console.log(props.foo);
2796 + }, []);
2797 + }
2798 + `,
2799 + },
2800 + ],
2801 + },
2802 + {
2803 + message:
2804 + "React Hook React.useCallback has a missing dependency: 'props.foo'. " +
2805 + 'Either include it or remove the dependency array.',
2806 + suggestions: [
2807 + {
2808 + desc: 'Update the dependencies array to be: [props.foo]',
2809 + output: normalizeIndent`
2810 + function MyComponent(props) {
2811 + useEffect(() => {
2812 + console.log(props.foo);
2813 + }, []);
2814 + useCallback(() => {
2815 + console.log(props.foo);
2816 + }, []);
2817 + useMemo(() => {
2818 + console.log(props.foo);
2819 + }, []);
2820 + React.useEffect(() => {
2821 + console.log(props.foo);
2822 + }, []);
2823 + React.useCallback(() => {
2824 + console.log(props.foo);
2825 + }, [props.foo]);
2826 + React.useMemo(() => {
2827 + console.log(props.foo);
2828 + }, []);
2829 + React.notReactiveHook(() => {
2830 + console.log(props.foo);
2831 + }, []);
2832 + }
2833 + `,
2834 + },
2835 + ],
2836 + },
2837 + {
2838 + message:
2839 + "React Hook React.useMemo has a missing dependency: 'props.foo'. " +
2840 + 'Either include it or remove the dependency array.',
2841 + suggestions: [
2842 + {
2843 + desc: 'Update the dependencies array to be: [props.foo]',
2844 + output: normalizeIndent`
2845 + function MyComponent(props) {
2846 + useEffect(() => {
2847 + console.log(props.foo);
2848 + }, []);
2849 + useCallback(() => {
2850 + console.log(props.foo);
2851 + }, []);
2852 + useMemo(() => {
2853 + console.log(props.foo);
2854 + }, []);
2855 + React.useEffect(() => {
2856 + console.log(props.foo);
2857 + }, []);
2858 + React.useCallback(() => {
2859 + console.log(props.foo);
2860 + }, []);
2861 + React.useMemo(() => {
2862 + console.log(props.foo);
2863 + }, [props.foo]);
2864 + React.notReactiveHook(() => {
2865 + console.log(props.foo);
2866 + }, []);
2867 + }
2868 + `,
2869 + },
2870 + ],
2871 + },
2872 ],
2873 },
2874 {
2303 - code: `
2875 + code: normalizeIndent`
2876 function MyComponent(props) {
2877 useCustomEffect(() => {
2878 console.log(props.foo);
@@ -2316,34 +2888,90 @@ const tests = {
2888 }, []);
2889 }
2890 `,
2319 - output: `
2320 - function MyComponent(props) {
2321 - useCustomEffect(() => {
2322 - console.log(props.foo);
2323 - }, [props.foo]);
2324 - useEffect(() => {
2325 - console.log(props.foo);
2326 - }, [props.foo]);
2327 - React.useEffect(() => {
2328 - console.log(props.foo);
2329 - }, [props.foo]);
2330 - React.useCustomEffect(() => {
2331 - console.log(props.foo);
2332 - }, []);
2333 - }
2334 - `,
2891 options: [{additionalHooks: 'useCustomEffect'}],
2892 errors: [
2337 - "React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
2338 - 'Either include it or remove the dependency array.',
2339 - "React Hook useEffect has a missing dependency: 'props.foo'. " +
2340 - 'Either include it or remove the dependency array.',
2341 - "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
2342 - 'Either include it or remove the dependency array.',
2893 + {
2894 + message:
2895 + "React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
2896 + 'Either include it or remove the dependency array.',
2897 + suggestions: [
2898 + {
2899 + desc: 'Update the dependencies array to be: [props.foo]',
2900 + output: normalizeIndent`
2901 + function MyComponent(props) {
2902 + useCustomEffect(() => {
2903 + console.log(props.foo);
2904 + }, [props.foo]);
2905 + useEffect(() => {
2906 + console.log(props.foo);
2907 + }, []);
2908 + React.useEffect(() => {
2909 + console.log(props.foo);
2910 + }, []);
2911 + React.useCustomEffect(() => {
2912 + console.log(props.foo);
2913 + }, []);
2914 + }
2915 + `,
2916 + },
2917 + ],
2918 + },
2919 + {
2920 + message:
2921 + "React Hook useEffect has a missing dependency: 'props.foo'. " +
2922 + 'Either include it or remove the dependency array.',
2923 + suggestions: [
2924 + {
2925 + desc: 'Update the dependencies array to be: [props.foo]',
2926 + output: normalizeIndent`
2927 + function MyComponent(props) {
2928 + useCustomEffect(() => {
2929 + console.log(props.foo);
2930 + }, []);
2931 + useEffect(() => {
2932 + console.log(props.foo);
2933 + }, [props.foo]);
2934 + React.useEffect(() => {
2935 + console.log(props.foo);
2936 + }, []);
2937 + React.useCustomEffect(() => {
2938 + console.log(props.foo);
2939 + }, []);
2940 + }
2941 + `,
2942 + },
2943 + ],
2944 + },
2945 + {
2946 + message:
2947 + "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
2948 + 'Either include it or remove the dependency array.',
2949 + suggestions: [
2950 + {
2951 + desc: 'Update the dependencies array to be: [props.foo]',
2952 + output: normalizeIndent`
2953 + function MyComponent(props) {
2954 + useCustomEffect(() => {
2955 + console.log(props.foo);
2956 + }, []);
2957 + useEffect(() => {
2958 + console.log(props.foo);
2959 + }, []);
2960 + React.useEffect(() => {
2961 + console.log(props.foo);
2962 + }, [props.foo]);
2963 + React.useCustomEffect(() => {
2964 + console.log(props.foo);
2965 + }, []);
2966 + }
2967 + `,
2968 + },
2969 + ],
2970 + },
2971 ],
2972 },
2973 {
2346 - code: `
2974 + code: normalizeIndent`
2975 function MyComponent() {
2976 const local = {};
2977 useEffect(() => {
@@ -2351,24 +2979,36 @@ const tests = {
2979 }, [a ? local : b]);
2980 }
2981 `,
2354 - // TODO: should we bail out instead?
2355 - output: `
2356 - function MyComponent() {
2357 - const local = {};
2358 - useEffect(() => {
2359 - console.log(local);
2360 - }, [local]);
2361 - }
2362 - `,
2982 errors: [
2364 - "React Hook useEffect has a missing dependency: 'local'. " +
2365 - 'Either include it or remove the dependency array.',
2366 - 'React Hook useEffect has a complex expression in the dependency array. ' +
2367 - 'Extract it to a separate variable so it can be statically checked.',
2983 + {
2984 + message:
2985 + "React Hook useEffect has a missing dependency: 'local'. " +
2986 + 'Either include it or remove the dependency array.',
2987 + // TODO: should we bail out instead?
2988 + suggestions: [
2989 + {
2990 + desc: 'Update the dependencies array to be: [local]',
2991 + output: normalizeIndent`
2992 + function MyComponent() {
2993 + const local = {};
2994 + useEffect(() => {
2995 + console.log(local);
2996 + }, [local]);
2997 + }
2998 + `,
2999 + },
3000 + ],
3001 + },
3002 + {
3003 + message:
3004 + 'React Hook useEffect has a complex expression in the dependency array. ' +
3005 + 'Extract it to a separate variable so it can be statically checked.',
3006 + suggestions: undefined,
3007 + },
3008 ],
3009 },
3010 {
2371 - code: `
3011 + code: normalizeIndent`
3012 function MyComponent() {
3013 const local = {};
3014 useEffect(() => {
@@ -2376,24 +3016,36 @@ const tests = {
3016 }, [a && local]);
3017 }
3018 `,
2379 - // TODO: should we bail out instead?
2380 - output: `
2381 - function MyComponent() {
2382 - const local = {};
2383 - useEffect(() => {
2384 - console.log(local);
2385 - }, [local]);
2386 - }
2387 - `,
3019 errors: [
2389 - "React Hook useEffect has a missing dependency: 'local'. " +
2390 - 'Either include it or remove the dependency array.',
2391 - 'React Hook useEffect has a complex expression in the dependency array. ' +
2392 - 'Extract it to a separate variable so it can be statically checked.',
3020 + {
3021 + message:
3022 + "React Hook useEffect has a missing dependency: 'local'. " +
3023 + 'Either include it or remove the dependency array.',
3024 + // TODO: should we bail out instead?
3025 + suggestions: [
3026 + {
3027 + desc: 'Update the dependencies array to be: [local]',
3028 + output: normalizeIndent`
3029 + function MyComponent() {
3030 + const local = {};
3031 + useEffect(() => {
3032 + console.log(local);
3033 + }, [local]);
3034 + }
3035 + `,
3036 + },
3037 + ],
3038 + },
3039 + {
3040 + message:
3041 + 'React Hook useEffect has a complex expression in the dependency array. ' +
3042 + 'Extract it to a separate variable so it can be statically checked.',
3043 + suggestions: undefined,
3044 + },
3045 ],
3046 },
3047 {
2396 - code: `
3048 + code: normalizeIndent`
3049 function MyComponent() {
3050 const ref = useRef();
3051 const [state, setState] = useState();
@@ -2403,25 +3055,33 @@ const tests = {
3055 }, []);
3056 }
3057 `,
2406 - output: `
2407 - function MyComponent() {
2408 - const ref = useRef();
2409 - const [state, setState] = useState();
2410 - useEffect(() => {
2411 - ref.current = {};
2412 - setState(state + 1);
2413 - }, [state]);
2414 - }
2415 - `,
3058 errors: [
2417 - "React Hook useEffect has a missing dependency: 'state'. " +
2418 - 'Either include it or remove the dependency array. ' +
2419 - `You can also do a functional update 'setState(s => ...)' ` +
2420 - `if you only need 'state' in the 'setState' call.`,
3059 + {
3060 + message:
3061 + "React Hook useEffect has a missing dependency: 'state'. " +
3062 + 'Either include it or remove the dependency array. ' +
3063 + `You can also do a functional update 'setState(s => ...)' ` +
3064 + `if you only need 'state' in the 'setState' call.`,
3065 + suggestions: [
3066 + {
3067 + desc: 'Update the dependencies array to be: [state]',
3068 + output: normalizeIndent`
3069 + function MyComponent() {
3070 + const ref = useRef();
3071 + const [state, setState] = useState();
3072 + useEffect(() => {
3073 + ref.current = {};
3074 + setState(state + 1);
3075 + }, [state]);
3076 + }
3077 + `,
3078 + },
3079 + ],
3080 + },
3081 ],
3082 },
3083 {
2424 - code: `
3084 + code: normalizeIndent`
3085 function MyComponent() {
3086 const ref = useRef();
3087 const [state, setState] = useState();
@@ -2431,28 +3091,36 @@ const tests = {
3091 }, [ref]);
3092 }
3093 `,
2434 - // We don't ask to remove static deps but don't add them either.
2435 - // Don't suggest removing "ref" (it's fine either way)
2436 - // but *do* add "state". *Don't* add "setState" ourselves.
2437 - output: `
2438 - function MyComponent() {
2439 - const ref = useRef();
2440 - const [state, setState] = useState();
2441 - useEffect(() => {
2442 - ref.current = {};
2443 - setState(state + 1);
2444 - }, [ref, state]);
2445 - }
2446 - `,
3094 errors: [
2448 - "React Hook useEffect has a missing dependency: 'state'. " +
2449 - 'Either include it or remove the dependency array. ' +
2450 - `You can also do a functional update 'setState(s => ...)' ` +
2451 - `if you only need 'state' in the 'setState' call.`,
3095 + {
3096 + message:
3097 + "React Hook useEffect has a missing dependency: 'state'. " +
3098 + 'Either include it or remove the dependency array. ' +
3099 + `You can also do a functional update 'setState(s => ...)' ` +
3100 + `if you only need 'state' in the 'setState' call.`,
3101 + // We don't ask to remove static deps but don't add them either.
3102 + // Don't suggest removing "ref" (it's fine either way)
3103 + // but *do* add "state". *Don't* add "setState" ourselves.
3104 + suggestions: [
3105 + {
3106 + desc: 'Update the dependencies array to be: [ref, state]',
3107 + output: normalizeIndent`
3108 + function MyComponent() {
3109 + const ref = useRef();
3110 + const [state, setState] = useState();
3111 + useEffect(() => {
3112 + ref.current = {};
3113 + setState(state + 1);
3114 + }, [ref, state]);
3115 + }
3116 + `,
3117 + },
3118 + ],
3119 + },
3120 ],
3121 },
3122 {
2455 - code: `
3123 + code: normalizeIndent`
3124 function MyComponent(props) {
3125 const ref1 = useRef();
3126 const ref2 = useRef();
@@ -2464,25 +3132,34 @@ const tests = {
3132 }, []);
3133 }
3134 `,
2467 - output: `
2468 - function MyComponent(props) {
2469 - const ref1 = useRef();
2470 - const ref2 = useRef();
2471 - useEffect(() => {
2472 - ref1.current.focus();
2473 - console.log(ref2.current.textContent);
2474 - alert(props.someOtherRefs.current.innerHTML);
2475 - fetch(props.color);
2476 - }, [props.color, props.someOtherRefs]);
2477 - }
2478 - `,
3135 errors: [
2480 - "React Hook useEffect has missing dependencies: 'props.color' and 'props.someOtherRefs'. " +
2481 - 'Either include them or remove the dependency array.',
3136 + {
3137 + message:
3138 + "React Hook useEffect has missing dependencies: 'props.color' and 'props.someOtherRefs'. " +
3139 + 'Either include them or remove the dependency array.',
3140 + suggestions: [
3141 + {
3142 + desc:
3143 + 'Update the dependencies array to be: [props.color, props.someOtherRefs]',
3144 + output: normalizeIndent`
3145 + function MyComponent(props) {
3146 + const ref1 = useRef();
3147 + const ref2 = useRef();
3148 + useEffect(() => {
3149 + ref1.current.focus();
3150 + console.log(ref2.current.textContent);
3151 + alert(props.someOtherRefs.current.innerHTML);
3152 + fetch(props.color);
3153 + }, [props.color, props.someOtherRefs]);
3154 + }
3155 + `,
3156 + },
3157 + ],
3158 + },
3159 ],
3160 },
3161 {
2485 - code: `
3162 + code: normalizeIndent`
3163 function MyComponent(props) {
3164 const ref1 = useRef();
3165 const ref2 = useRef();
@@ -2494,27 +3171,36 @@ const tests = {
3171 }, [ref1.current, ref2.current, props.someOtherRefs, props.color]);
3172 }
3173 `,
2497 - output: `
2498 - function MyComponent(props) {
2499 - const ref1 = useRef();
2500 - const ref2 = useRef();
2501 - useEffect(() => {
2502 - ref1.current.focus();
2503 - console.log(ref2.current.textContent);
2504 - alert(props.someOtherRefs.current.innerHTML);
2505 - fetch(props.color);
2506 - }, [props.someOtherRefs, props.color]);
2507 - }
2508 - `,
3174 errors: [
2510 - "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
2511 - 'Either exclude them or remove the dependency array. ' +
2512 - "Mutable values like 'ref1.current' aren't valid dependencies " +
2513 - "because mutating them doesn't re-render the component.",
3175 + {
3176 + message:
3177 + "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
3178 + 'Either exclude them or remove the dependency array. ' +
3179 + "Mutable values like 'ref1.current' aren't valid dependencies " +
3180 + "because mutating them doesn't re-render the component.",
3181 + suggestions: [
3182 + {
3183 + desc:
3184 + 'Update the dependencies array to be: [props.someOtherRefs, props.color]',
3185 + output: normalizeIndent`
3186 + function MyComponent(props) {
3187 + const ref1 = useRef();
3188 + const ref2 = useRef();
3189 + useEffect(() => {
3190 + ref1.current.focus();
3191 + console.log(ref2.current.textContent);
3192 + alert(props.someOtherRefs.current.innerHTML);
3193 + fetch(props.color);
3194 + }, [props.someOtherRefs, props.color]);
3195 + }
3196 + `,
3197 + },
3198 + ],
3199 + },
3200 ],
3201 },
3202 {
2517 - code: `
3203 + code: normalizeIndent`
3204 function MyComponent() {
3205 const ref = useRef();
3206 useEffect(() => {
@@ -2522,23 +3208,31 @@ const tests = {
3208 }, [ref.current]);
3209 }
3210 `,
2525 - output: `
2526 - function MyComponent() {
2527 - const ref = useRef();
2528 - useEffect(() => {
2529 - console.log(ref.current);
2530 - }, []);
2531 - }
2532 - `,
3211 errors: [
2534 - "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
2535 - 'Either exclude it or remove the dependency array. ' +
2536 - "Mutable values like 'ref.current' aren't valid dependencies " +
2537 - "because mutating them doesn't re-render the component.",
3212 + {
3213 + message:
3214 + "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
3215 + 'Either exclude it or remove the dependency array. ' +
3216 + "Mutable values like 'ref.current' aren't valid dependencies " +
3217 + "because mutating them doesn't re-render the component.",
3218 + suggestions: [
3219 + {
3220 + desc: 'Update the dependencies array to be: []',
3221 + output: normalizeIndent`
3222 + function MyComponent() {
3223 + const ref = useRef();
3224 + useEffect(() => {
3225 + console.log(ref.current);
3226 + }, []);
3227 + }
3228 + `,
3229 + },
3230 + ],
3231 + },
3232 ],
3233 },
3234 {
2541 - code: `
3235 + code: normalizeIndent`
3236 function MyComponent({ activeTab }) {
3237 const ref1 = useRef();
3238 const ref2 = useRef();
@@ -2548,25 +3242,33 @@ const tests = {
3242 }, [ref1.current, ref2.current, activeTab]);
3243 }
3244 `,
2551 - output: `
2552 - function MyComponent({ activeTab }) {
2553 - const ref1 = useRef();
2554 - const ref2 = useRef();
2555 - useEffect(() => {
2556 - ref1.current.scrollTop = 0;
2557 - ref2.current.scrollTop = 0;
2558 - }, [activeTab]);
2559 - }
2560 - `,
3245 errors: [
2562 - "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
2563 - 'Either exclude them or remove the dependency array. ' +
2564 - "Mutable values like 'ref1.current' aren't valid dependencies " +
2565 - "because mutating them doesn't re-render the component.",
3246 + {
3247 + message:
3248 + "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
3249 + 'Either exclude them or remove the dependency array. ' +
3250 + "Mutable values like 'ref1.current' aren't valid dependencies " +
3251 + "because mutating them doesn't re-render the component.",
3252 + suggestions: [
3253 + {
3254 + desc: 'Update the dependencies array to be: [activeTab]',
3255 + output: normalizeIndent`
3256 + function MyComponent({ activeTab }) {
3257 + const ref1 = useRef();
3258 + const ref2 = useRef();
3259 + useEffect(() => {
3260 + ref1.current.scrollTop = 0;
3261 + ref2.current.scrollTop = 0;
3262 + }, [activeTab]);
3263 + }
3264 + `,
3265 + },
3266 + ],
3267 + },
3268 ],
3269 },
3270 {
2569 - code: `
3271 + code: normalizeIndent`
3272 function MyComponent({ activeTab, initY }) {
3273 const ref1 = useRef();
3274 const ref2 = useRef();
@@ -2576,25 +3278,33 @@ const tests = {
3278 }, [ref1.current, ref2.current, activeTab, initY]);
3279 }
3280 `,
2579 - output: `
2580 - function MyComponent({ activeTab, initY }) {
2581 - const ref1 = useRef();
2582 - const ref2 = useRef();
2583 - const fn = useCallback(() => {
2584 - ref1.current.scrollTop = initY;
2585 - ref2.current.scrollTop = initY;
2586 - }, [initY]);
2587 - }
2588 - `,
3281 errors: [
2590 - "React Hook useCallback has unnecessary dependencies: 'activeTab', 'ref1.current', and 'ref2.current'. " +
2591 - 'Either exclude them or remove the dependency array. ' +
2592 - "Mutable values like 'ref1.current' aren't valid dependencies " +
2593 - "because mutating them doesn't re-render the component.",
3282 + {
3283 + message:
3284 + "React Hook useCallback has unnecessary dependencies: 'activeTab', 'ref1.current', and 'ref2.current'. " +
3285 + 'Either exclude them or remove the dependency array. ' +
3286 + "Mutable values like 'ref1.current' aren't valid dependencies " +
3287 + "because mutating them doesn't re-render the component.",
3288 + suggestions: [
3289 + {
3290 + desc: 'Update the dependencies array to be: [initY]',
3291 + output: normalizeIndent`
3292 + function MyComponent({ activeTab, initY }) {
3293 + const ref1 = useRef();
3294 + const ref2 = useRef();
3295 + const fn = useCallback(() => {
3296 + ref1.current.scrollTop = initY;
3297 + ref2.current.scrollTop = initY;
3298 + }, [initY]);
3299 + }
3300 + `,
3301 + },
3302 + ],
3303 + },
3304 ],
3305 },
3306 {
2597 - code: `
3307 + code: normalizeIndent`
3308 function MyComponent() {
3309 const ref = useRef();
3310 useEffect(() => {
@@ -2602,23 +3312,31 @@ const tests = {
3312 }, [ref.current, ref]);
3313 }
3314 `,
2605 - output: `
2606 - function MyComponent() {
2607 - const ref = useRef();
2608 - useEffect(() => {
2609 - console.log(ref.current);
2610 - }, [ref]);
2611 - }
2612 - `,
3315 errors: [
2614 - "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
2615 - 'Either exclude it or remove the dependency array. ' +
2616 - "Mutable values like 'ref.current' aren't valid dependencies " +
2617 - "because mutating them doesn't re-render the component.",
3316 + {
3317 + message:
3318 + "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
3319 + 'Either exclude it or remove the dependency array. ' +
3320 + "Mutable values like 'ref.current' aren't valid dependencies " +
3321 + "because mutating them doesn't re-render the component.",
3322 + suggestions: [
3323 + {
3324 + desc: 'Update the dependencies array to be: [ref]',
3325 + output: normalizeIndent`
3326 + function MyComponent() {
3327 + const ref = useRef();
3328 + useEffect(() => {
3329 + console.log(ref.current);
3330 + }, [ref]);
3331 + }
3332 + `,
3333 + },
3334 + ],
3335 + },
3336 ],
3337 },
3338 {
2621 - code: `
3339 + code: normalizeIndent`
3340 const MyComponent = forwardRef((props, ref) => {
3341 useImperativeHandle(ref, () => ({
3342 focus() {
@@ -2627,22 +3345,30 @@ const tests = {
3345 }), [])
3346 });
3347 `,
2630 - output: `
2631 - const MyComponent = forwardRef((props, ref) => {
2632 - useImperativeHandle(ref, () => ({
2633 - focus() {
2634 - alert(props.hello);
2635 - }
2636 - }), [props.hello])
2637 - });
2638 - `,
3348 errors: [
2640 - "React Hook useImperativeHandle has a missing dependency: 'props.hello'. " +
2641 - 'Either include it or remove the dependency array.',
3349 + {
3350 + message:
3351 + "React Hook useImperativeHandle has a missing dependency: 'props.hello'. " +
3352 + 'Either include it or remove the dependency array.',
3353 + suggestions: [
3354 + {
3355 + desc: 'Update the dependencies array to be: [props.hello]',
3356 + output: normalizeIndent`
3357 + const MyComponent = forwardRef((props, ref) => {
3358 + useImperativeHandle(ref, () => ({
3359 + focus() {
3360 + alert(props.hello);
3361 + }
3362 + }), [props.hello])
3363 + });
3364 + `,
3365 + },
3366 + ],
3367 + },
3368 ],
3369 },
3370 {
2645 - code: `
3371 + code: normalizeIndent`
3372 function MyComponent(props) {
3373 useEffect(() => {
3374 if (props.onChange) {
@@ -2651,29 +3377,37 @@ const tests = {
3377 }, []);
3378 }
3379 `,
2654 - output: `
2655 - function MyComponent(props) {
2656 - useEffect(() => {
2657 - if (props.onChange) {
2658 - props.onChange();
2659 - }
2660 - }, [props]);
2661 - }
2662 - `,
3380 errors: [
2664 - "React Hook useEffect has a missing dependency: 'props'. " +
2665 - 'Either include it or remove the dependency array. ' +
2666 - `However, 'props' will change when *any* prop changes, so the ` +
2667 - `preferred fix is to destructure the 'props' object outside ` +
2668 - `of the useEffect call and refer to those specific ` +
2669 - `props inside useEffect.`,
3381 + {
3382 + message:
3383 + "React Hook useEffect has a missing dependency: 'props'. " +
3384 + 'Either include it or remove the dependency array. ' +
3385 + `However, 'props' will change when *any* prop changes, so the ` +
3386 + `preferred fix is to destructure the 'props' object outside ` +
3387 + `of the useEffect call and refer to those specific ` +
3388 + `props inside useEffect.`,
3389 + suggestions: [
3390 + {
3391 + desc: 'Update the dependencies array to be: [props]',
3392 + output: normalizeIndent`
3393 + function MyComponent(props) {
3394 + useEffect(() => {
3395 + if (props.onChange) {
3396 + props.onChange();
3397 + }
3398 + }, [props]);
3399 + }
3400 + `,
3401 + },
3402 + ],
3403 + },
3404 ],
3405 },
3406 {
2673 - code: `
3407 + code: normalizeIndent`
3408 function MyComponent(props) {
3409 useEffect(() => {
2676 - function play() {
3410 + function play() {
3411 props.onPlay();
3412 }
3413 function pause() {
@@ -2682,29 +3416,37 @@ const tests = {
3416 }, []);
3417 }
3418 `,
2685 - output: `
2686 - function MyComponent(props) {
2687 - useEffect(() => {
2688 - function play() {
2689 - props.onPlay();
2690 - }
2691 - function pause() {
2692 - props.onPause();
2693 - }
2694 - }, [props]);
2695 - }
2696 - `,
3419 errors: [
2698 - "React Hook useEffect has a missing dependency: 'props'. " +
2699 - 'Either include it or remove the dependency array. ' +
2700 - `However, 'props' will change when *any* prop changes, so the ` +
2701 - `preferred fix is to destructure the 'props' object outside ` +
2702 - `of the useEffect call and refer to those specific ` +
2703 - `props inside useEffect.`,
3420 + {
3421 + message:
3422 + "React Hook useEffect has a missing dependency: 'props'. " +
3423 + 'Either include it or remove the dependency array. ' +
3424 + `However, 'props' will change when *any* prop changes, so the ` +
3425 + `preferred fix is to destructure the 'props' object outside ` +
3426 + `of the useEffect call and refer to those specific ` +
3427 + `props inside useEffect.`,
3428 + suggestions: [
3429 + {
3430 + desc: 'Update the dependencies array to be: [props]',
3431 + output: normalizeIndent`
3432 + function MyComponent(props) {
3433 + useEffect(() => {
3434 + function play() {
3435 + props.onPlay();
3436 + }
3437 + function pause() {
3438 + props.onPause();
3439 + }
3440 + }, [props]);
3441 + }
3442 + `,
3443 + },
3444 + ],
3445 + },
3446 ],
3447 },
3448 {
2707 - code: `
3449 + code: normalizeIndent`
3450 function MyComponent(props) {
3451 useEffect(() => {
3452 if (props.foo.onChange) {
@@ -2713,22 +3455,30 @@ const tests = {
3455 }, []);
3456 }
3457 `,
2716 - output: `
2717 - function MyComponent(props) {
2718 - useEffect(() => {
2719 - if (props.foo.onChange) {
2720 - props.foo.onChange();
2721 - }
2722 - }, [props.foo]);
2723 - }
2724 - `,
3458 errors: [
2726 - "React Hook useEffect has a missing dependency: 'props.foo'. " +
2727 - 'Either include it or remove the dependency array.',
3459 + {
3460 + message:
3461 + "React Hook useEffect has a missing dependency: 'props.foo'. " +
3462 + 'Either include it or remove the dependency array.',
3463 + suggestions: [
3464 + {
3465 + desc: 'Update the dependencies array to be: [props.foo]',
3466 + output: normalizeIndent`
3467 + function MyComponent(props) {
3468 + useEffect(() => {
3469 + if (props.foo.onChange) {
3470 + props.foo.onChange();
3471 + }
3472 + }, [props.foo]);
3473 + }
3474 + `,
3475 + },
3476 + ],
3477 + },
3478 ],
3479 },
3480 {
2731 - code: `
3481 + code: normalizeIndent`
3482 function MyComponent(props) {
3483 useEffect(() => {
3484 props.onChange();
@@ -2738,27 +3488,35 @@ const tests = {
3488 }, []);
3489 }
3490 `,
2741 - output: `
2742 - function MyComponent(props) {
2743 - useEffect(() => {
2744 - props.onChange();
2745 - if (props.foo.onChange) {
2746 - props.foo.onChange();
2747 - }
2748 - }, [props]);
2749 - }
2750 - `,
3491 errors: [
2752 - "React Hook useEffect has a missing dependency: 'props'. " +
2753 - 'Either include it or remove the dependency array. ' +
2754 - `However, 'props' will change when *any* prop changes, so the ` +
2755 - `preferred fix is to destructure the 'props' object outside ` +
2756 - `of the useEffect call and refer to those specific ` +
2757 - `props inside useEffect.`,
3492 + {
3493 + message:
3494 + "React Hook useEffect has a missing dependency: 'props'. " +
3495 + 'Either include it or remove the dependency array. ' +
3496 + `However, 'props' will change when *any* prop changes, so the ` +
3497 + `preferred fix is to destructure the 'props' object outside ` +
3498 + `of the useEffect call and refer to those specific ` +
3499 + `props inside useEffect.`,
3500 + suggestions: [
3501 + {
3502 + desc: 'Update the dependencies array to be: [props]',
3503 + output: normalizeIndent`
3504 + function MyComponent(props) {
3505 + useEffect(() => {
3506 + props.onChange();
3507 + if (props.foo.onChange) {
3508 + props.foo.onChange();
3509 + }
3510 + }, [props]);
3511 + }
3512 + `,
3513 + },
3514 + ],
3515 + },
3516 ],
3517 },
3518 {
2761 - code: `
3519 + code: normalizeIndent`
3520 function MyComponent(props) {
3521 const [skillsCount] = useState();
3522 useEffect(() => {
@@ -2768,27 +3526,36 @@ const tests = {
3526 }, [skillsCount, props.isEditMode, props.toggleEditMode]);
3527 }
3528 `,
2771 - output: `
2772 - function MyComponent(props) {
2773 - const [skillsCount] = useState();
2774 - useEffect(() => {
2775 - if (skillsCount === 0 && !props.isEditMode) {
2776 - props.toggleEditMode();
2777 - }
2778 - }, [skillsCount, props.isEditMode, props.toggleEditMode, props]);
2779 - }
2780 - `,
3529 errors: [
2782 - "React Hook useEffect has a missing dependency: 'props'. " +
2783 - 'Either include it or remove the dependency array. ' +
2784 - `However, 'props' will change when *any* prop changes, so the ` +
2785 - `preferred fix is to destructure the 'props' object outside ` +
2786 - `of the useEffect call and refer to those specific ` +
2787 - `props inside useEffect.`,
3530 + {
3531 + message:
3532 + "React Hook useEffect has a missing dependency: 'props'. " +
3533 + 'Either include it or remove the dependency array. ' +
3534 + `However, 'props' will change when *any* prop changes, so the ` +
3535 + `preferred fix is to destructure the 'props' object outside ` +
3536 + `of the useEffect call and refer to those specific ` +
3537 + `props inside useEffect.`,
3538 + suggestions: [
3539 + {
3540 + desc:
3541 + 'Update the dependencies array to be: [skillsCount, props.isEditMode, props.toggleEditMode, props]',
3542 + output: normalizeIndent`
3543 + function MyComponent(props) {
3544 + const [skillsCount] = useState();
3545 + useEffect(() => {
3546 + if (skillsCount === 0 && !props.isEditMode) {
3547 + props.toggleEditMode();
3548 + }
3549 + }, [skillsCount, props.isEditMode, props.toggleEditMode, props]);
3550 + }
3551 + `,
3552 + },
3553 + ],
3554 + },
3555 ],
3556 },
3557 {
2791 - code: `
3558 + code: normalizeIndent`
3559 function MyComponent(props) {
3560 const [skillsCount] = useState();
3561 useEffect(() => {
@@ -2798,27 +3565,35 @@ const tests = {
3565 }, []);
3566 }
3567 `,
2801 - output: `
2802 - function MyComponent(props) {
2803 - const [skillsCount] = useState();
2804 - useEffect(() => {
2805 - if (skillsCount === 0 && !props.isEditMode) {
2806 - props.toggleEditMode();
2807 - }
2808 - }, [props, skillsCount]);
2809 - }
2810 - `,
3568 errors: [
2812 - "React Hook useEffect has missing dependencies: 'props' and 'skillsCount'. " +
2813 - 'Either include them or remove the dependency array. ' +
2814 - `However, 'props' will change when *any* prop changes, so the ` +
2815 - `preferred fix is to destructure the 'props' object outside ` +
2816 - `of the useEffect call and refer to those specific ` +
2817 - `props inside useEffect.`,
3569 + {
3570 + message:
3571 + "React Hook useEffect has missing dependencies: 'props' and 'skillsCount'. " +
3572 + 'Either include them or remove the dependency array. ' +
3573 + `However, 'props' will change when *any* prop changes, so the ` +
3574 + `preferred fix is to destructure the 'props' object outside ` +
3575 + `of the useEffect call and refer to those specific ` +
3576 + `props inside useEffect.`,
3577 + suggestions: [
3578 + {
3579 + desc: 'Update the dependencies array to be: [props, skillsCount]',
3580 + output: normalizeIndent`
3581 + function MyComponent(props) {
3582 + const [skillsCount] = useState();
3583 + useEffect(() => {
3584 + if (skillsCount === 0 && !props.isEditMode) {
3585 + props.toggleEditMode();
3586 + }
3587 + }, [props, skillsCount]);
3588 + }
3589 + `,
3590 + },
3591 + ],
3592 + },
3593 ],
3594 },
3595 {
2821 - code: `
3596 + code: normalizeIndent`
3597 function MyComponent(props) {
3598 useEffect(() => {
3599 externalCall(props);
@@ -2826,22 +3601,30 @@ const tests = {
3601 }, []);
3602 }
3603 `,
2829 - output: `
2830 - function MyComponent(props) {
2831 - useEffect(() => {
2832 - externalCall(props);
2833 - props.onChange();
2834 - }, [props]);
2835 - }
2836 - `,
3604 // Don't suggest to destructure props here since you can't.
3605 errors: [
2839 - "React Hook useEffect has a missing dependency: 'props'. " +
2840 - 'Either include it or remove the dependency array.',
3606 + {
3607 + message:
3608 + "React Hook useEffect has a missing dependency: 'props'. " +
3609 + 'Either include it or remove the dependency array.',
3610 + suggestions: [
3611 + {
3612 + desc: 'Update the dependencies array to be: [props]',
3613 + output: normalizeIndent`
3614 + function MyComponent(props) {
3615 + useEffect(() => {
3616 + externalCall(props);
3617 + props.onChange();
3618 + }, [props]);
3619 + }
3620 + `,
3621 + },
3622 + ],
3623 + },
3624 ],
3625 },
3626 {
2844 - code: `
3627 + code: normalizeIndent`
3628 function MyComponent(props) {
3629 useEffect(() => {
3630 props.onChange();
@@ -2849,22 +3632,30 @@ const tests = {
3632 }, []);
3633 }
3634 `,
2852 - output: `
2853 - function MyComponent(props) {
2854 - useEffect(() => {
2855 - props.onChange();
2856 - externalCall(props);
2857 - }, [props]);
2858 - }
2859 - `,
3635 // Don't suggest to destructure props here since you can't.
3636 errors: [
2862 - "React Hook useEffect has a missing dependency: 'props'. " +
2863 - 'Either include it or remove the dependency array.',
3637 + {
3638 + message:
3639 + "React Hook useEffect has a missing dependency: 'props'. " +
3640 + 'Either include it or remove the dependency array.',
3641 + suggestions: [
3642 + {
3643 + desc: 'Update the dependencies array to be: [props]',
3644 + output: normalizeIndent`
3645 + function MyComponent(props) {
3646 + useEffect(() => {
3647 + props.onChange();
3648 + externalCall(props);
3649 + }, [props]);
3650 + }
3651 + `,
3652 + },
3653 + ],
3654 + },
3655 ],
3656 },
3657 {
2867 - code: `
3658 + code: normalizeIndent`
3659 function MyComponent(props) {
3660 let value;
3661 let value2;
@@ -2888,54 +3679,48 @@ const tests = {
3679 `,
3680 // This is a separate warning unrelated to others.
3681 // We could've made a separate rule for it but it's rare enough to name it.
2891 - // No autofix suggestion because the intent isn't clear.
2892 - output: `
2893 - function MyComponent(props) {
2894 - let value;
2895 - let value2;
2896 - let value3;
2897 - let value4;
2898 - let asyncValue;
2899 - useEffect(() => {
2900 - if (value4) {
2901 - value = {};
2902 - }
2903 - value2 = 100;
2904 - value = 43;
2905 - value4 = true;
2906 - console.log(value2);
2907 - console.log(value3);
2908 - setTimeout(() => {
2909 - asyncValue = 100;
2910 - });
2911 - }, []);
2912 - }
2913 - `,
3682 + // No suggestions because the intent isn't clear.
3683 errors: [
2915 - // value2
2916 - `Assignments to the 'value2' variable from inside React Hook useEffect ` +
2917 - `will be lost after each render. To preserve the value over time, ` +
2918 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2919 - `Otherwise, you can move this variable directly inside useEffect.`,
2920 - // value
2921 - `Assignments to the 'value' variable from inside React Hook useEffect ` +
2922 - `will be lost after each render. To preserve the value over time, ` +
2923 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2924 - `Otherwise, you can move this variable directly inside useEffect.`,
2925 - // value4
2926 - `Assignments to the 'value4' variable from inside React Hook useEffect ` +
2927 - `will be lost after each render. To preserve the value over time, ` +
2928 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2929 - `Otherwise, you can move this variable directly inside useEffect.`,
2930 - // asyncValue
2931 - `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
2932 - `will be lost after each render. To preserve the value over time, ` +
2933 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2934 - `Otherwise, you can move this variable directly inside useEffect.`,
3684 + {
3685 + message:
3686 + // value2
3687 + `Assignments to the 'value2' variable from inside React Hook useEffect ` +
3688 + `will be lost after each render. To preserve the value over time, ` +
3689 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3690 + `Otherwise, you can move this variable directly inside useEffect.`,
3691 + suggestions: undefined,
3692 + },
3693 + {
3694 + message:
3695 + // value
3696 + `Assignments to the 'value' variable from inside React Hook useEffect ` +
3697 + `will be lost after each render. To preserve the value over time, ` +
3698 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3699 + `Otherwise, you can move this variable directly inside useEffect.`,
3700 + suggestions: undefined,
3701 + },
3702 + {
3703 + message:
3704 + // value4
3705 + `Assignments to the 'value4' variable from inside React Hook useEffect ` +
3706 + `will be lost after each render. To preserve the value over time, ` +
3707 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3708 + `Otherwise, you can move this variable directly inside useEffect.`,
3709 + suggestions: undefined,
3710 + },
3711 + {
3712 + message:
3713 + // asyncValue
3714 + `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
3715 + `will be lost after each render. To preserve the value over time, ` +
3716 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3717 + `Otherwise, you can move this variable directly inside useEffect.`,
3718 + suggestions: undefined,
3719 + },
3720 ],
3721 },
3722 {
2938 - code: `
3723 + code: normalizeIndent`
3724 function MyComponent(props) {
3725 let value;
3726 let value2;
@@ -2955,56 +3740,39 @@ const tests = {
3740 `,
3741 // This is a separate warning unrelated to others.
3742 // We could've made a separate rule for it but it's rare enough to name it.
2958 - // No autofix suggestion because the intent isn't clear.
2959 - output: `
2960 - function MyComponent(props) {
2961 - let value;
2962 - let value2;
2963 - let value3;
2964 - let asyncValue;
2965 - useEffect(() => {
2966 - value = {};
2967 - value2 = 100;
2968 - value = 43;
2969 - console.log(value2);
2970 - console.log(value3);
2971 - setTimeout(() => {
2972 - asyncValue = 100;
2973 - });
2974 - }, [value, value2, value3]);
2975 - }
2976 - `,
3743 + // No suggestions because the intent isn't clear.
3744 errors: [
2978 - // value
2979 - `Assignments to the 'value' variable from inside React Hook useEffect ` +
2980 - `will be lost after each render. To preserve the value over time, ` +
2981 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2982 - `Otherwise, you can move this variable directly inside useEffect.`,
2983 - // value2
2984 - `Assignments to the 'value2' variable from inside React Hook useEffect ` +
2985 - `will be lost after each render. To preserve the value over time, ` +
2986 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2987 - `Otherwise, you can move this variable directly inside useEffect.`,
2988 - // asyncValue
2989 - `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
2990 - `will be lost after each render. To preserve the value over time, ` +
2991 - `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2992 - `Otherwise, you can move this variable directly inside useEffect.`,
3745 + {
3746 + message:
3747 + // value
3748 + `Assignments to the 'value' variable from inside React Hook useEffect ` +
3749 + `will be lost after each render. To preserve the value over time, ` +
3750 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3751 + `Otherwise, you can move this variable directly inside useEffect.`,
3752 + suggestions: undefined,
3753 + },
3754 + {
3755 + message:
3756 + // value2
3757 + `Assignments to the 'value2' variable from inside React Hook useEffect ` +
3758 + `will be lost after each render. To preserve the value over time, ` +
3759 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3760 + `Otherwise, you can move this variable directly inside useEffect.`,
3761 + suggestions: undefined,
3762 + },
3763 + {
3764 + message:
3765 + // asyncValue
3766 + `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
3767 + `will be lost after each render. To preserve the value over time, ` +
3768 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
3769 + `Otherwise, you can move this variable directly inside useEffect.`,
3770 + suggestions: undefined,
3771 + },
3772 ],
3773 },
3774 {
2996 - code: `
2997 - function MyComponent() {
2998 - const myRef = useRef();
2999 - useEffect(() => {
3000 - const handleMove = () => {};
3001 - myRef.current.addEventListener('mousemove', handleMove);
3002 - return () => myRef.current.removeEventListener('mousemove', handleMove);
3003 - }, []);
3004 - return <div ref={myRef} />;
3005 - }
3006 - `,
3007 - output: `
3775 + code: normalizeIndent`
3776 function MyComponent() {
3777 const myRef = useRef();
3778 useEffect(() => {
@@ -3016,25 +3784,18 @@ const tests = {
3784 }
3785 `,
3786 errors: [
3019 - `The ref value 'myRef.current' will likely have changed by the time ` +
3020 - `this effect cleanup function runs. If this ref points to a node ` +
3021 - `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3022 - `and use that variable in the cleanup function.`,
3787 + {
3788 + message:
3789 + `The ref value 'myRef.current' will likely have changed by the time ` +
3790 + `this effect cleanup function runs. If this ref points to a node ` +
3791 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3792 + `and use that variable in the cleanup function.`,
3793 + suggestions: undefined,
3794 + },
3795 ],
3796 },
3797 {
3026 - code: `
3027 - function MyComponent() {
3028 - const myRef = useRef();
3029 - useEffect(() => {
3030 - const handleMove = () => {};
3031 - myRef.current.addEventListener('mousemove', handleMove);
3032 - return () => myRef.current.removeEventListener('mousemove', handleMove);
3033 - });
3034 - return <div ref={myRef} />;
3035 - }
3036 - `,
3037 - output: `
3798 + code: normalizeIndent`
3799 function MyComponent() {
3800 const myRef = useRef();
3801 useEffect(() => {
@@ -3046,23 +3807,18 @@ const tests = {
3807 }
3808 `,
3809 errors: [
3049 - `The ref value 'myRef.current' will likely have changed by the time ` +
3050 - `this effect cleanup function runs. If this ref points to a node ` +
3051 - `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3052 - `and use that variable in the cleanup function.`,
3810 + {
3811 + message:
3812 + `The ref value 'myRef.current' will likely have changed by the time ` +
3813 + `this effect cleanup function runs. If this ref points to a node ` +
3814 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3815 + `and use that variable in the cleanup function.`,
3816 + suggestions: undefined,
3817 + },
3818 ],
3819 },
3820 {
3056 - code: `
3057 - function useMyThing(myRef) {
3058 - useEffect(() => {
3059 - const handleMove = () => {};
3060 - myRef.current.addEventListener('mousemove', handleMove);
3061 - return () => myRef.current.removeEventListener('mousemove', handleMove);
3062 - }, [myRef]);
3063 - }
3064 - `,
3065 - output: `
3821 + code: normalizeIndent`
3822 function useMyThing(myRef) {
3823 useEffect(() => {
3824 const handleMove = () => {};
@@ -3072,29 +3828,18 @@ const tests = {
3828 }
3829 `,
3830 errors: [
3075 - `The ref value 'myRef.current' will likely have changed by the time ` +
3076 - `this effect cleanup function runs. If this ref points to a node ` +
3077 - `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3078 - `and use that variable in the cleanup function.`,
3831 + {
3832 + message:
3833 + `The ref value 'myRef.current' will likely have changed by the time ` +
3834 + `this effect cleanup function runs. If this ref points to a node ` +
3835 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3836 + `and use that variable in the cleanup function.`,
3837 + suggestions: undefined,
3838 + },
3839 ],
3840 },
3841 {
3082 - code: `
3083 - function useMyThing(myRef) {
3084 - useEffect(() => {
3085 - const handleMouse = () => {};
3086 - myRef.current.addEventListener('mousemove', handleMouse);
3087 - myRef.current.addEventListener('mousein', handleMouse);
3088 - return function() {
3089 - setTimeout(() => {
3090 - myRef.current.removeEventListener('mousemove', handleMouse);
3091 - myRef.current.removeEventListener('mousein', handleMouse);
3092 - });
3093 - }
3094 - }, [myRef]);
3095 - }
3096 - `,
3097 - output: `
3842 + code: normalizeIndent`
3843 function useMyThing(myRef) {
3844 useEffect(() => {
3845 const handleMouse = () => {};
@@ -3110,29 +3855,18 @@ const tests = {
3855 }
3856 `,
3857 errors: [
3113 - `The ref value 'myRef.current' will likely have changed by the time ` +
3114 - `this effect cleanup function runs. If this ref points to a node ` +
3115 - `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3116 - `and use that variable in the cleanup function.`,
3858 + {
3859 + message:
3860 + `The ref value 'myRef.current' will likely have changed by the time ` +
3861 + `this effect cleanup function runs. If this ref points to a node ` +
3862 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3863 + `and use that variable in the cleanup function.`,
3864 + suggestions: undefined,
3865 + },
3866 ],
3867 },
3868 {
3120 - code: `
3121 - function useMyThing(myRef, active) {
3122 - useEffect(() => {
3123 - const handleMove = () => {};
3124 - if (active) {
3125 - myRef.current.addEventListener('mousemove', handleMove);
3126 - return function() {
3127 - setTimeout(() => {
3128 - myRef.current.removeEventListener('mousemove', handleMove);
3129 - });
3130 - }
3131 - }
3132 - }, [myRef, active]);
3133 - }
3134 - `,
3135 - output: `
3869 + code: normalizeIndent`
3870 function useMyThing(myRef, active) {
3871 useEffect(() => {
3872 const handleMove = () => {};
@@ -3148,10 +3882,14 @@ const tests = {
3882 }
3883 `,
3884 errors: [
3151 - `The ref value 'myRef.current' will likely have changed by the time ` +
3152 - `this effect cleanup function runs. If this ref points to a node ` +
3153 - `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3154 - `and use that variable in the cleanup function.`,
3885 + {
3886 + message:
3887 + `The ref value 'myRef.current' will likely have changed by the time ` +
3888 + `this effect cleanup function runs. If this ref points to a node ` +
3889 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3890 + `and use that variable in the cleanup function.`,
3891 + suggestions: undefined,
3892 + },
3893 ],
3894 },
3895 {
@@ -3187,41 +3925,50 @@ const tests = {
3925 },
3926 {
3927 // Autofix ignores constant primitives (leaving the ones that are there).
3190 - code: `
3191 - function MyComponent() {
3192 - const local1 = 42;
3193 - const local2 = '42';
3194 - const local3 = null;
3195 - const local4 = {};
3196 - useEffect(() => {
3197 - console.log(local1);
3198 - console.log(local2);
3199 - console.log(local3);
3200 - console.log(local4);
3201 - }, [local1, local3]);
3202 - }
3203 - `,
3204 - output: `
3205 - function MyComponent() {
3206 - const local1 = 42;
3207 - const local2 = '42';
3208 - const local3 = null;
3209 - const local4 = {};
3210 - useEffect(() => {
3211 - console.log(local1);
3212 - console.log(local2);
3213 - console.log(local3);
3214 - console.log(local4);
3215 - }, [local1, local3, local4]);
3216 - }
3217 - `,
3928 + code: normalizeIndent`
3929 + function MyComponent() {
3930 + const local1 = 42;
3931 + const local2 = '42';
3932 + const local3 = null;
3933 + const local4 = {};
3934 + useEffect(() => {
3935 + console.log(local1);
3936 + console.log(local2);
3937 + console.log(local3);
3938 + console.log(local4);
3939 + }, [local1, local3]);
3940 + }
3941 + `,
3942 errors: [
3219 - "React Hook useEffect has a missing dependency: 'local4'. " +
3220 - 'Either include it or remove the dependency array.',
3943 + {
3944 + message:
3945 + "React Hook useEffect has a missing dependency: 'local4'. " +
3946 + 'Either include it or remove the dependency array.',
3947 + suggestions: [
3948 + {
3949 + desc:
3950 + 'Update the dependencies array to be: [local1, local3, local4]',
3951 + output: normalizeIndent`
3952 + function MyComponent() {
3953 + const local1 = 42;
3954 + const local2 = '42';
3955 + const local3 = null;
3956 + const local4 = {};
3957 + useEffect(() => {
3958 + console.log(local1);
3959 + console.log(local2);
3960 + console.log(local3);
3961 + console.log(local4);
3962 + }, [local1, local3, local4]);
3963 + }
3964 + `,
3965 + },
3966 + ],
3967 + },
3968 ],
3969 },
3970 {
3224 - code: `
3971 + code: normalizeIndent`
3972 function MyComponent() {
3973 useEffect(() => {
3974 window.scrollTo(0, 0);
@@ -3229,14 +3976,29 @@ const tests = {
3976 }
3977 `,
3978 errors: [
3232 - "React Hook useEffect has an unnecessary dependency: 'window'. " +
3233 - 'Either exclude it or remove the dependency array. ' +
3234 - "Outer scope values like 'window' aren't valid dependencies " +
3235 - "because mutating them doesn't re-render the component.",
3979 + {
3980 + message:
3981 + "React Hook useEffect has an unnecessary dependency: 'window'. " +
3982 + 'Either exclude it or remove the dependency array. ' +
3983 + "Outer scope values like 'window' aren't valid dependencies " +
3984 + "because mutating them doesn't re-render the component.",
3985 + suggestions: [
3986 + {
3987 + desc: 'Update the dependencies array to be: []',
3988 + output: normalizeIndent`
3989 + function MyComponent() {
3990 + useEffect(() => {
3991 + window.scrollTo(0, 0);
3992 + }, []);
3993 + }
3994 + `,
3995 + },
3996 + ],
3997 + },
3998 ],
3999 },
4000 {
3239 - code: `
4001 + code: normalizeIndent`
4002 import MutableStore from 'store';
4003
4004 function MyComponent() {
@@ -3245,24 +4007,32 @@ const tests = {
4007 }, [MutableStore.hello]);
4008 }
4009 `,
3248 - output: `
3249 - import MutableStore from 'store';
3250 -
3251 - function MyComponent() {
3252 - useEffect(() => {
3253 - console.log(MutableStore.hello);
3254 - }, []);
3255 - }
3256 - `,
4010 errors: [
3258 - "React Hook useEffect has an unnecessary dependency: 'MutableStore.hello'. " +
3259 - 'Either exclude it or remove the dependency array. ' +
3260 - "Outer scope values like 'MutableStore.hello' aren't valid dependencies " +
3261 - "because mutating them doesn't re-render the component.",
4011 + {
4012 + message:
4013 + "React Hook useEffect has an unnecessary dependency: 'MutableStore.hello'. " +
4014 + 'Either exclude it or remove the dependency array. ' +
4015 + "Outer scope values like 'MutableStore.hello' aren't valid dependencies " +
4016 + "because mutating them doesn't re-render the component.",
4017 + suggestions: [
4018 + {
4019 + desc: 'Update the dependencies array to be: []',
4020 + output: normalizeIndent`
4021 + import MutableStore from 'store';
4022 +
4023 + function MyComponent() {
4024 + useEffect(() => {
4025 + console.log(MutableStore.hello);
4026 + }, []);
4027 + }
4028 + `,
4029 + },
4030 + ],
4031 + },
4032 ],
4033 },
4034 {
3265 - code: `
4035 + code: normalizeIndent`
4036 import MutableStore from 'store';
4037 let z = {};
4038
@@ -3276,30 +4046,38 @@ const tests = {
4046 }
4047 }
4048 `,
3279 - output: `
3280 - import MutableStore from 'store';
3281 - let z = {};
3282 -
3283 - function MyComponent(props) {
3284 - let x = props.foo;
3285 - {
3286 - let y = props.bar;
3287 - useEffect(() => {
3288 - console.log(MutableStore.hello.world, props.foo, x, y, z, global.stuff);
3289 - }, [props.foo, x, y]);
3290 - }
3291 - }
3292 - `,
3293 - errors: [
3294 - 'React Hook useEffect has unnecessary dependencies: ' +
3295 - "'MutableStore.hello.world', 'global.stuff', and 'z'. " +
3296 - 'Either exclude them or remove the dependency array. ' +
3297 - "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
3298 - "because mutating them doesn't re-render the component.",
3299 - ],
3300 - },
3301 - {
3302 - code: `
4049 + errors: [
4050 + {
4051 + message:
4052 + 'React Hook useEffect has unnecessary dependencies: ' +
4053 + "'MutableStore.hello.world', 'global.stuff', and 'z'. " +
4054 + 'Either exclude them or remove the dependency array. ' +
4055 + "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
4056 + "because mutating them doesn't re-render the component.",
4057 + suggestions: [
4058 + {
4059 + desc: 'Update the dependencies array to be: [props.foo, x, y]',
4060 + output: normalizeIndent`
4061 + import MutableStore from 'store';
4062 + let z = {};
4063 +
4064 + function MyComponent(props) {
4065 + let x = props.foo;
4066 + {
4067 + let y = props.bar;
4068 + useEffect(() => {
4069 + console.log(MutableStore.hello.world, props.foo, x, y, z, global.stuff);
4070 + }, [props.foo, x, y]);
4071 + }
4072 + }
4073 + `,
4074 + },
4075 + ],
4076 + },
4077 + ],
4078 + },
4079 + {
4080 + code: normalizeIndent`
4081 import MutableStore from 'store';
4082 let z = {};
4083
@@ -3313,32 +4091,40 @@ const tests = {
4091 }
4092 }
4093 `,
3316 - // The output should contain the ones that are inside a component
3317 - // since there are legit reasons to over-specify them for effects.
3318 - output: `
3319 - import MutableStore from 'store';
3320 - let z = {};
3321 -
3322 - function MyComponent(props) {
3323 - let x = props.foo;
3324 - {
3325 - let y = props.bar;
3326 - useEffect(() => {
3327 - // nothing
3328 - }, [props.foo, x, y]);
3329 - }
3330 - }
3331 - `,
4094 errors: [
3333 - 'React Hook useEffect has unnecessary dependencies: ' +
3334 - "'MutableStore.hello.world', 'global.stuff', and 'z'. " +
3335 - 'Either exclude them or remove the dependency array. ' +
3336 - "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
3337 - "because mutating them doesn't re-render the component.",
4095 + {
4096 + message:
4097 + 'React Hook useEffect has unnecessary dependencies: ' +
4098 + "'MutableStore.hello.world', 'global.stuff', and 'z'. " +
4099 + 'Either exclude them or remove the dependency array. ' +
4100 + "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
4101 + "because mutating them doesn't re-render the component.",
4102 + // The output should contain the ones that are inside a component
4103 + // since there are legit reasons to over-specify them for effects.
4104 + suggestions: [
4105 + {
4106 + desc: 'Update the dependencies array to be: [props.foo, x, y]',
4107 + output: normalizeIndent`
4108 + import MutableStore from 'store';
4109 + let z = {};
4110 +
4111 + function MyComponent(props) {
4112 + let x = props.foo;
4113 + {
4114 + let y = props.bar;
4115 + useEffect(() => {
4116 + // nothing
4117 + }, [props.foo, x, y]);
4118 + }
4119 + }
4120 + `,
4121 + },
4122 + ],
4123 + },
4124 ],
4125 },
4126 {
3341 - code: `
4127 + code: normalizeIndent`
4128 import MutableStore from 'store';
4129 let z = {};
4130
@@ -3352,31 +4138,39 @@ const tests = {
4138 }
4139 }
4140 `,
3355 - output: `
3356 - import MutableStore from 'store';
3357 - let z = {};
3358 -
3359 - function MyComponent(props) {
3360 - let x = props.foo;
3361 - {
3362 - let y = props.bar;
3363 - const fn = useCallback(() => {
3364 - // nothing
3365 - }, []);
3366 - }
3367 - }
3368 - `,
4141 errors: [
3370 - 'React Hook useCallback has unnecessary dependencies: ' +
3371 - "'MutableStore.hello.world', 'global.stuff', 'props.foo', 'x', 'y', and 'z'. " +
3372 - 'Either exclude them or remove the dependency array. ' +
3373 - "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
3374 - "because mutating them doesn't re-render the component.",
4142 + {
4143 + message:
4144 + 'React Hook useCallback has unnecessary dependencies: ' +
4145 + "'MutableStore.hello.world', 'global.stuff', 'props.foo', 'x', 'y', and 'z'. " +
4146 + 'Either exclude them or remove the dependency array. ' +
4147 + "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
4148 + "because mutating them doesn't re-render the component.",
4149 + suggestions: [
4150 + {
4151 + desc: 'Update the dependencies array to be: []',
4152 + output: normalizeIndent`
4153 + import MutableStore from 'store';
4154 + let z = {};
4155 +
4156 + function MyComponent(props) {
4157 + let x = props.foo;
4158 + {
4159 + let y = props.bar;
4160 + const fn = useCallback(() => {
4161 + // nothing
4162 + }, []);
4163 + }
4164 + }
4165 + `,
4166 + },
4167 + ],
4168 + },
4169 ],
4170 },
4171 {
4172 // Every almost-static function is tainted by a dynamic value.
3379 - code: `
4173 + code: normalizeIndent`
4174 function MyComponent(props) {
4175 let [, setState] = useState();
4176 let [, dispatch] = React.useReducer();
@@ -3406,48 +4200,132 @@ const tests = {
4200 }, []);
4201 }
4202 `,
3409 - output: `
3410 - function MyComponent(props) {
3411 - let [, setState] = useState();
3412 - let [, dispatch] = React.useReducer();
3413 - let taint = props.foo;
3414 -
3415 - function handleNext1(value) {
3416 - let value2 = value * taint;
3417 - setState(value2);
3418 - console.log('hello');
3419 - }
3420 - const handleNext2 = (value) => {
3421 - setState(taint(value));
3422 - console.log('hello');
3423 - };
3424 - let handleNext3 = function(value) {
3425 - setTimeout(() => console.log(taint));
3426 - dispatch({ type: 'x', value });
3427 - };
3428 - useEffect(() => {
3429 - return Store.subscribe(handleNext1);
3430 - }, [handleNext1]);
3431 - useLayoutEffect(() => {
3432 - return Store.subscribe(handleNext2);
3433 - }, [handleNext2]);
3434 - useMemo(() => {
3435 - return Store.subscribe(handleNext3);
3436 - }, [handleNext3]);
3437 - }
3438 - `,
4203 errors: [
3440 - "React Hook useEffect has a missing dependency: 'handleNext1'. " +
3441 - 'Either include it or remove the dependency array.',
3442 - "React Hook useLayoutEffect has a missing dependency: 'handleNext2'. " +
3443 - 'Either include it or remove the dependency array.',
3444 - "React Hook useMemo has a missing dependency: 'handleNext3'. " +
3445 - 'Either include it or remove the dependency array.',
4204 + {
4205 + message:
4206 + "React Hook useEffect has a missing dependency: 'handleNext1'. " +
4207 + 'Either include it or remove the dependency array.',
4208 + suggestions: [
4209 + {
4210 + desc: 'Update the dependencies array to be: [handleNext1]',
4211 + output: normalizeIndent`
4212 + function MyComponent(props) {
4213 + let [, setState] = useState();
4214 + let [, dispatch] = React.useReducer();
4215 + let taint = props.foo;
4216 +
4217 + function handleNext1(value) {
4218 + let value2 = value * taint;
4219 + setState(value2);
4220 + console.log('hello');
4221 + }
4222 + const handleNext2 = (value) => {
4223 + setState(taint(value));
4224 + console.log('hello');
4225 + };
4226 + let handleNext3 = function(value) {
4227 + setTimeout(() => console.log(taint));
4228 + dispatch({ type: 'x', value });
4229 + };
4230 + useEffect(() => {
4231 + return Store.subscribe(handleNext1);
4232 + }, [handleNext1]);
4233 + useLayoutEffect(() => {
4234 + return Store.subscribe(handleNext2);
4235 + }, []);
4236 + useMemo(() => {
4237 + return Store.subscribe(handleNext3);
4238 + }, []);
4239 + }
4240 + `,
4241 + },
4242 + ],
4243 + },
4244 + {
4245 + message:
4246 + "React Hook useLayoutEffect has a missing dependency: 'handleNext2'. " +
4247 + 'Either include it or remove the dependency array.',
4248 + suggestions: [
4249 + {
4250 + desc: 'Update the dependencies array to be: [handleNext2]',
4251 + output: normalizeIndent`
4252 + function MyComponent(props) {
4253 + let [, setState] = useState();
4254 + let [, dispatch] = React.useReducer();
4255 + let taint = props.foo;
4256 +
4257 + function handleNext1(value) {
4258 + let value2 = value * taint;
4259 + setState(value2);
4260 + console.log('hello');
4261 + }
4262 + const handleNext2 = (value) => {
4263 + setState(taint(value));
4264 + console.log('hello');
4265 + };
4266 + let handleNext3 = function(value) {
4267 + setTimeout(() => console.log(taint));
4268 + dispatch({ type: 'x', value });
4269 + };
4270 + useEffect(() => {
4271 + return Store.subscribe(handleNext1);
4272 + }, []);
4273 + useLayoutEffect(() => {
4274 + return Store.subscribe(handleNext2);
4275 + }, [handleNext2]);
4276 + useMemo(() => {
4277 + return Store.subscribe(handleNext3);
4278 + }, []);
4279 + }
4280 + `,
4281 + },
4282 + ],
4283 + },
4284 + {
4285 + message:
4286 + "React Hook useMemo has a missing dependency: 'handleNext3'. " +
4287 + 'Either include it or remove the dependency array.',
4288 + suggestions: [
4289 + {
4290 + desc: 'Update the dependencies array to be: [handleNext3]',
4291 + output: normalizeIndent`
4292 + function MyComponent(props) {
4293 + let [, setState] = useState();
4294 + let [, dispatch] = React.useReducer();
4295 + let taint = props.foo;
4296 +
4297 + function handleNext1(value) {
4298 + let value2 = value * taint;
4299 + setState(value2);
4300 + console.log('hello');
4301 + }
4302 + const handleNext2 = (value) => {
4303 + setState(taint(value));
4304 + console.log('hello');
4305 + };
4306 + let handleNext3 = function(value) {
4307 + setTimeout(() => console.log(taint));
4308 + dispatch({ type: 'x', value });
4309 + };
4310 + useEffect(() => {
4311 + return Store.subscribe(handleNext1);
4312 + }, []);
4313 + useLayoutEffect(() => {
4314 + return Store.subscribe(handleNext2);
4315 + }, []);
4316 + useMemo(() => {
4317 + return Store.subscribe(handleNext3);
4318 + }, [handleNext3]);
4319 + }
4320 + `,
4321 + },
4322 + ],
4323 + },
4324 ],
4325 },
4326 {
4327 // Regression test
3450 - code: `
4328 + code: normalizeIndent`
4329 function MyComponent(props) {
4330 let [, setState] = useState();
4331 let [, dispatch] = React.useReducer();
@@ -3480,51 +4358,141 @@ const tests = {
4358 }, []);
4359 }
4360 `,
3483 - output: `
3484 - function MyComponent(props) {
3485 - let [, setState] = useState();
3486 - let [, dispatch] = React.useReducer();
3487 - let taint = props.foo;
4361 + errors: [
4362 + {
4363 + message:
4364 + "React Hook useEffect has a missing dependency: 'handleNext1'. " +
4365 + 'Either include it or remove the dependency array.',
4366 + suggestions: [
4367 + {
4368 + desc: 'Update the dependencies array to be: [handleNext1]',
4369 + output: normalizeIndent`
4370 + function MyComponent(props) {
4371 + let [, setState] = useState();
4372 + let [, dispatch] = React.useReducer();
4373 + let taint = props.foo;
4374
3489 - // Shouldn't affect anything
3490 - function handleChange() {}
4375 + // Shouldn't affect anything
4376 + function handleChange() {}
4377
3492 - function handleNext1(value) {
3493 - let value2 = value * taint;
3494 - setState(value2);
3495 - console.log('hello');
3496 - }
3497 - const handleNext2 = (value) => {
3498 - setState(taint(value));
3499 - console.log('hello');
3500 - };
3501 - let handleNext3 = function(value) {
3502 - console.log(taint);
3503 - dispatch({ type: 'x', value });
3504 - };
3505 - useEffect(() => {
3506 - return Store.subscribe(handleNext1);
3507 - }, [handleNext1]);
3508 - useLayoutEffect(() => {
3509 - return Store.subscribe(handleNext2);
3510 - }, [handleNext2]);
3511 - useMemo(() => {
3512 - return Store.subscribe(handleNext3);
3513 - }, [handleNext3]);
3514 - }
3515 - `,
3516 - errors: [
3517 - "React Hook useEffect has a missing dependency: 'handleNext1'. " +
3518 - 'Either include it or remove the dependency array.',
3519 - "React Hook useLayoutEffect has a missing dependency: 'handleNext2'. " +
3520 - 'Either include it or remove the dependency array.',
3521 - "React Hook useMemo has a missing dependency: 'handleNext3'. " +
3522 - 'Either include it or remove the dependency array.',
4378 + function handleNext1(value) {
4379 + let value2 = value * taint;
4380 + setState(value2);
4381 + console.log('hello');
4382 + }
4383 + const handleNext2 = (value) => {

This file is too large to show in full.

packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+53 -30
@@ -11,7 +11,6 @@
11
12 export default {
13 meta: {
14 - fixable: 'code',
14 schema: [
15 {
16 type: 'object',
@@ -94,7 +93,7 @@ export default {
93 reactiveHookName === 'useMemo' ||
94 reactiveHookName === 'useCallback'
95 ) {
97 - // TODO: Can this have an autofix?
96 + // TODO: Can this have a suggestion?
97 context.report({
98 node: node.parent.callee,
99 message:
@@ -558,12 +557,19 @@ export default {
557 `To fix this, pass [` +
558 suggestedDependencies.join(', ') +
559 `] as a second argument to the ${reactiveHookName} Hook.`,
561 - fix(fixer) {
562 - return fixer.insertTextAfter(
563 - node,
564 - `, [${suggestedDependencies.join(', ')}]`,
565 - );
566 - },
560 + suggest: [
561 + {
562 + desc: `Add dependencies array: [${suggestedDependencies.join(
563 + ', ',
564 + )}]`,
565 + fix(fixer) {
566 + return fixer.insertTextAfter(
567 + node,
568 + `, [${suggestedDependencies.join(', ')}]`,
569 + );
570 + },
571 + },
572 + ],
573 });
574 }
575 return;
@@ -702,27 +708,37 @@ export default {
708 ` Move it inside the ${reactiveHookName} callback. ` +
709 `Alternatively, wrap the '${fn.name.name}' definition into its own useCallback() Hook.`;
710 }
711 +
712 + let suggest;
713 + // Only handle the simple case: arrow functions.
714 + // Wrapping function declarations can mess up hoisting.
715 + if (suggestUseCallback && fn.type === 'Variable') {
716 + suggest = [
717 + {
718 + desc: `Wrap the '${
719 + fn.name.name
720 + }' definition into its own useCallback() Hook.`,
721 + fix(fixer) {
722 + return [
723 + // TODO: also add an import?
724 + fixer.insertTextBefore(fn.node.init, 'useCallback('),
725 + // TODO: ideally we'd gather deps here but it would require
726 + // restructuring the rule code. This will cause a new lint
727 + // error to appear immediately for useCallback. Note we're
728 + // not adding [] because would that changes semantics.
729 + fixer.insertTextAfter(fn.node.init, ')'),
730 + ];
731 + },
732 + },
733 + ];
734 + }
735 // TODO: What if the function needs to change on every render anyway?
736 // Should we suggest removing effect deps as an appropriate fix too?
737 context.report({
738 // TODO: Why not report this at the dependency site?
739 node: fn.node,
740 message,
711 - fix(fixer) {
712 - // Only handle the simple case: arrow functions.
713 - // Wrapping function declarations can mess up hoisting.
714 - if (suggestUseCallback && fn.type === 'Variable') {
715 - return [
716 - // TODO: also add an import?
717 - fixer.insertTextBefore(fn.node.init, 'useCallback('),
718 - // TODO: ideally we'd gather deps here but it would require
719 - // restructuring the rule code. This will cause a new lint
720 - // error to appear immediately for useCallback. Note we're
721 - // not adding [] because would that changes semantics.
722 - fixer.insertTextAfter(fn.node.init, ')'),
723 - ];
724 - }
725 - },
741 + suggest,
742 });
743 });
744 return;
@@ -1008,13 +1024,20 @@ export default {
1024 'omit',
1025 )) +
1026 extraWarning,
1011 - fix(fixer) {
1012 - // TODO: consider preserving the comments or formatting?
1013 - return fixer.replaceText(
1014 - declaredDependenciesNode,
1015 - `[${suggestedDependencies.join(', ')}]`,
1016 - );
1017 - },
1027 + suggest: [
1028 + {
1029 + desc: `Update the dependencies array to be: [${suggestedDependencies.join(
1030 + ', ',
1031 + )}]`,
1032 + fix(fixer) {
1033 + // TODO: consider preserving the comments or formatting?
1034 + return fixer.replaceText(
1035 + declaredDependenciesNode,
1036 + `[${suggestedDependencies.join(', ')}]`,
1037 + );
1038 + },
1039 + },
1040 + ],
1041 });
1042 }
1043 },