[eslint] Disallow use within try/catch blocks (#34040)

Follow up to #34032. The linter now ensures that `use` cannot be used
within try/catch.
This commit is contained in:
lauren
2025-07-29 12:33:42 -04:00
committed by GitHub
parent 9be531cd37
commit 820af20971
2 changed files with 65 additions and 3 deletions
@@ -1324,6 +1324,34 @@ const allTests = {
`,
errors: [asyncComponentHookError('use')],
},
{
code: normalizeIndent`
function App({p1, p2}) {
try {
use(p1);
} catch (error) {
console.error(error);
}
use(p2);
return <div>App</div>;
}
`,
errors: [tryCatchUseError('use')],
},
{
code: normalizeIndent`
function App({p1, p2}) {
try {
doSomething();
} catch {
use(p1);
}
use(p2);
return <div>App</div>;
}
`,
errors: [tryCatchUseError('use')],
},
],
};
@@ -1383,7 +1411,7 @@ if (__EXPERIMENTAL__) {
const onEvent = useEffectEvent((text) => {
console.log(text);
});
useEffect(() => {
onEvent('Hello world');
});
@@ -1421,7 +1449,7 @@ if (__EXPERIMENTAL__) {
});
return <Child onClick={() => onClick()} />
}
// The useEffectEvent function shares an identifier name with the above
function MyLastComponent({theme}) {
const onClick = useEffectEvent(() => {
@@ -1573,6 +1601,12 @@ function asyncComponentHookError(fn) {
};
}
function tryCatchUseError(fn) {
return {
message: `React Hook "${fn}" cannot be called in a try/catch block.`,
};
}
// For easier local testing
if (!process.env.CI) {
let only = [];
@@ -7,7 +7,13 @@
/* eslint-disable no-for-of-loops/no-for-of-loops */
import type {Rule, Scope} from 'eslint';
import type {CallExpression, DoWhileStatement, Node} from 'estree';
import type {
CallExpression,
CatchClause,
DoWhileStatement,
Node,
TryStatement,
} from 'estree';
// @ts-expect-error untyped module
import CodePathAnalyzer from '../code-path-analysis/code-path-analyzer';
@@ -111,6 +117,18 @@ function isInsideDoWhileLoop(node: Node | undefined): node is DoWhileStatement {
return false;
}
function isInsideTryCatch(
node: Node | undefined,
): node is TryStatement | CatchClause {
while (node) {
if (node.type === 'TryStatement' || node.type === 'CatchClause') {
return true;
}
node = node.parent;
}
return false;
}
function isUseEffectEventIdentifier(node: Node): boolean {
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'useEffectEvent';
@@ -532,6 +550,16 @@ const rule = {
continue;
}
// Report an error if use() is called inside try/catch.
if (isUseIdentifier(hook) && isInsideTryCatch(hook)) {
context.report({
node: hook,
message: `React Hook "${getSourceCode().getText(
hook,
)}" cannot be called in a try/catch block.`,
});
}
// Report an error if a hook may be called more then once.
// `use(...)` can be called in loops.
if (