@samitouri / QOS-React-2 / commits / eaf2d5c670

fix[eslint-plugin-react-hooks]: Fix error when callback argument is an identifier with an `as` expression (#31119)

Mark Skelton committed Nov 19, 2024 at 03:36 UTC eaf2d5c670c84124618977156d81946435922eb3
2 files changed +55 -25
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+31 -12
@@ -7088,18 +7088,7 @@ const tests = {
7088 errors: [
7089 {
7090 message:
7091 - "React Hook useEffect has a missing dependency: 'myEffect'. " +
7092 - 'Either include it or remove the dependency array.',
7093 - suggestions: [
7094 - {
7095 - desc: 'Update the dependencies array to be: [myEffect]',
7096 - output: normalizeIndent`
7097 - function MyComponent({myEffect}) {
7098 - useEffect(myEffect, [myEffect]);
7099 - }
7100 - `,
7101 - },
7102 - ],
7091 + 'React Hook useEffect received a function whose dependencies are unknown. Pass an inline function instead.',
7092 },
7093 ],
7094 },
@@ -7670,6 +7659,19 @@ const tests = {
7659 },
7660 ],
7661 },
7662 + {
7663 + code: normalizeIndent`
7664 + function useCustomCallback(callback, deps) {
7665 + return useCallback(callback, deps)
7666 + }
7667 + `,
7668 + errors: [
7669 + {
7670 + message:
7671 + 'React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead.',
7672 + },
7673 + ],
7674 + },
7675 ],
7676 };
7677
@@ -8193,6 +8195,19 @@ const testsTypescript = {
8195 },
8196 ],
8197 },
8198 + {
8199 + code: normalizeIndent`
8200 + function useCustomCallback(callback, deps) {
8201 + return useCallback(callback as any, deps)
8202 + }
8203 + `,
8204 + errors: [
8205 + {
8206 + message:
8207 + 'React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead.',
8208 + },
8209 + ],
8210 + },
8211 ],
8212 };
8213
@@ -8271,6 +8286,10 @@ if (!process.env.CI) {
8286 testsFlow.invalid = testsFlow.invalid.filter(predicate);
8287 testsTypescript.valid = testsTypescript.valid.filter(predicate);
8288 testsTypescript.invalid = testsTypescript.invalid.filter(predicate);
8289 + testsTypescriptEslintParserV4.valid =
8290 + testsTypescriptEslintParserV4.valid.filter(predicate);
8291 + testsTypescriptEslintParserV4.invalid =
8292 + testsTypescriptEslintParserV4.invalid.filter(predicate);
8293 }
8294
8295 describe('rules-of-hooks/exhaustive-deps', () => {
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+24 -13
@@ -652,6 +652,7 @@ export default {
652 const isTSAsArrayExpression =
653 declaredDependenciesNode.type === 'TSAsExpression' &&
654 declaredDependenciesNode.expression.type === 'ArrayExpression';
655 +
656 if (!isArrayExpression && !isTSAsArrayExpression) {
657 // If the declared dependencies are not an array expression then we
658 // can't verify that the user provided the correct dependencies. Tell
@@ -1196,7 +1197,7 @@ export default {
1197 // Not a React Hook call that needs deps.
1198 return;
1199 }
1199 - const callback = node.arguments[callbackIndex];
1200 + let callback = node.arguments[callbackIndex];
1201 const reactiveHook = node.callee;
1202 const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
1203 const maybeNode = node.arguments[callbackIndex + 1];
@@ -1241,6 +1242,13 @@ export default {
1242 return;
1243 }
1244
1245 + while (
1246 + callback.type === 'TSAsExpression' ||
1247 + callback.type === 'AsExpression'
1248 + ) {
1249 + callback = callback.expression;
1250 + }
1251 +
1252 switch (callback.type) {
1253 case 'FunctionExpression':
1254 case 'ArrowFunctionExpression':
@@ -1252,15 +1260,6 @@ export default {
1260 isEffect,
1261 );
1262 return; // Handled
1255 - case 'TSAsExpression':
1256 - visitFunctionWithDependencies(
1257 - callback.expression,
1258 - declaredDependenciesNode,
1259 - reactiveHook,
1260 - reactiveHookName,
1261 - isEffect,
1262 - );
1263 - return; // Handled
1263 case 'Identifier':
1264 if (!declaredDependenciesNode) {
1265 // No deps, no problems.
@@ -1291,6 +1290,13 @@ export default {
1290 if (!def || !def.node) {
1291 break; // Unhandled
1292 }
1293 + if (def.type === 'Parameter') {
1294 + reportProblem({
1295 + node: reactiveHook,
1296 + message: getUnknownDependenciesMessage(reactiveHookName),
1297 + });
1298 + return;
1299 + }
1300 if (def.type !== 'Variable' && def.type !== 'FunctionName') {
1301 // Parameter or an unusual pattern. Bail out.
1302 break; // Unhandled
@@ -1333,9 +1339,7 @@ export default {
1339 // useEffect(generateEffectBody(), []);
1340 reportProblem({
1341 node: reactiveHook,
1336 - message:
1337 - `React Hook ${reactiveHookName} received a function whose dependencies ` +
1338 - `are unknown. Pass an inline function instead.`,
1342 + message: getUnknownDependenciesMessage(reactiveHookName),
1343 });
1344 return; // Handled
1345 }
@@ -1912,3 +1916,10 @@ function isUseEffectEventIdentifier(node) {
1916 }
1917 return false;
1918 }
1919 +
1920 +function getUnknownDependenciesMessage(reactiveHookName) {
1921 + return (
1922 + `React Hook ${reactiveHookName} received a function whose dependencies ` +
1923 + `are unknown. Pass an inline function instead.`
1924 + );
1925 +}