Skip to content

Commit eaf2d5c

Browse files
authored
fix[eslint-plugin-react-hooks]: Fix error when callback argument is an identifier with an as expression (#31119)
1 parent c866d75 commit eaf2d5c

File tree

2 files changed

+55
-25
lines changed

2 files changed

+55
-25
lines changed

packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js

+31-12
Original file line numberDiff line numberDiff line change
@@ -7088,18 +7088,7 @@ const tests = {
70887088
errors: [
70897089
{
70907090
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.',
71037092
},
71047093
],
71057094
},
@@ -7670,6 +7659,19 @@ const tests = {
76707659
},
76717660
],
76727661
},
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+
},
76737675
],
76747676
};
76757677

@@ -8193,6 +8195,19 @@ const testsTypescript = {
81938195
},
81948196
],
81958197
},
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+
},
81968211
],
81978212
};
81988213

@@ -8271,6 +8286,10 @@ if (!process.env.CI) {
82718286
testsFlow.invalid = testsFlow.invalid.filter(predicate);
82728287
testsTypescript.valid = testsTypescript.valid.filter(predicate);
82738288
testsTypescript.invalid = testsTypescript.invalid.filter(predicate);
8289+
testsTypescriptEslintParserV4.valid =
8290+
testsTypescriptEslintParserV4.valid.filter(predicate);
8291+
testsTypescriptEslintParserV4.invalid =
8292+
testsTypescriptEslintParserV4.invalid.filter(predicate);
82748293
}
82758294

82768295
describe('rules-of-hooks/exhaustive-deps', () => {

packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js

+24-13
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ export default {
652652
const isTSAsArrayExpression =
653653
declaredDependenciesNode.type === 'TSAsExpression' &&
654654
declaredDependenciesNode.expression.type === 'ArrayExpression';
655+
655656
if (!isArrayExpression && !isTSAsArrayExpression) {
656657
// If the declared dependencies are not an array expression then we
657658
// can't verify that the user provided the correct dependencies. Tell
@@ -1196,7 +1197,7 @@ export default {
11961197
// Not a React Hook call that needs deps.
11971198
return;
11981199
}
1199-
const callback = node.arguments[callbackIndex];
1200+
let callback = node.arguments[callbackIndex];
12001201
const reactiveHook = node.callee;
12011202
const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
12021203
const maybeNode = node.arguments[callbackIndex + 1];
@@ -1241,6 +1242,13 @@ export default {
12411242
return;
12421243
}
12431244

1245+
while (
1246+
callback.type === 'TSAsExpression' ||
1247+
callback.type === 'AsExpression'
1248+
) {
1249+
callback = callback.expression;
1250+
}
1251+
12441252
switch (callback.type) {
12451253
case 'FunctionExpression':
12461254
case 'ArrowFunctionExpression':
@@ -1252,15 +1260,6 @@ export default {
12521260
isEffect,
12531261
);
12541262
return; // Handled
1255-
case 'TSAsExpression':
1256-
visitFunctionWithDependencies(
1257-
callback.expression,
1258-
declaredDependenciesNode,
1259-
reactiveHook,
1260-
reactiveHookName,
1261-
isEffect,
1262-
);
1263-
return; // Handled
12641263
case 'Identifier':
12651264
if (!declaredDependenciesNode) {
12661265
// No deps, no problems.
@@ -1291,6 +1290,13 @@ export default {
12911290
if (!def || !def.node) {
12921291
break; // Unhandled
12931292
}
1293+
if (def.type === 'Parameter') {
1294+
reportProblem({
1295+
node: reactiveHook,
1296+
message: getUnknownDependenciesMessage(reactiveHookName),
1297+
});
1298+
return;
1299+
}
12941300
if (def.type !== 'Variable' && def.type !== 'FunctionName') {
12951301
// Parameter or an unusual pattern. Bail out.
12961302
break; // Unhandled
@@ -1333,9 +1339,7 @@ export default {
13331339
// useEffect(generateEffectBody(), []);
13341340
reportProblem({
13351341
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),
13391343
});
13401344
return; // Handled
13411345
}
@@ -1912,3 +1916,10 @@ function isUseEffectEventIdentifier(node) {
19121916
}
19131917
return false;
19141918
}
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+
}

0 commit comments

Comments
 (0)