mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(linter/react/rules-of-hooks): detect constructor callbacks (#25377)
## Summary - Report hooks called from anonymous functions passed to constructors, including `new Promise.then(...)` inside a component. - Keep the matching lower-case `notAComponent` case allowed, aligning with `eslint-plugin-react-hooks`. AI-assisted change; contributor review is requested before merge.
This commit is contained in:
1 parent
e3f6263178
commit
6846a9aa85
2 files changed
+28
-13
No files matched your search
@@ -865,7 +865,7 @@ fn is_reference_call_callee(nodes: &AstNodes<'_>, node_id: NodeId, span: Span) -
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the `node_id` is a callback argument (including JSX render props),
|
||||
/// Checks if the `node_id` is a callback argument (including constructor and JSX render props),
|
||||
/// And that function isn't a `React.memo` or `React.forwardRef`.
|
||||
/// Returns `true` if this node is a function argument/render prop and that isn't a React special function.
|
||||
/// Otherwise it would return `false`.
|
||||
@@ -877,8 +877,8 @@ fn is_non_react_func_arg(nodes: &AstNodes, node_id: NodeId) -> bool {
|
||||
AstKind::CallExpression(call) => {
|
||||
!(is_react_function_call(call, "forwardRef") || is_react_function_call(call, "memo"))
|
||||
}
|
||||
// Callback passed as JSX expression: <Foo>{() => { ... }}</Foo> or <Foo render={() => { ... }} />
|
||||
AstKind::JSXExpressionContainer(_) => true,
|
||||
// Callback passed as an argument to a constructor or JSX render prop.
|
||||
AstKind::NewExpression(_) | AstKind::JSXExpressionContainer(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -1714,6 +1714,14 @@ fn test() {
|
||||
return null;
|
||||
}
|
||||
",
|
||||
// This matches eslint-plugin-react-hooks: callbacks in non-components are not reported.
|
||||
r"
|
||||
function notAComponent() {
|
||||
return new Promise.then(() => {
|
||||
useState();
|
||||
});
|
||||
}
|
||||
",
|
||||
];
|
||||
|
||||
let pass_additional_effect_hooks = vec![(
|
||||
@@ -2459,16 +2467,14 @@ fn test() {
|
||||
}
|
||||
}
|
||||
",
|
||||
// TODO: This should error but doesn't.
|
||||
// Original rule also fails to raise this error.
|
||||
// errors: [genericError('useState')],
|
||||
// "
|
||||
// function notAComponent() {
|
||||
// return new Promise.then(() => {
|
||||
// useState();
|
||||
// });
|
||||
// }
|
||||
// " ,
|
||||
// Invalid because hooks cannot be called inside constructor callbacks.
|
||||
r"
|
||||
function Component() {
|
||||
return new Promise.then(() => {
|
||||
useState();
|
||||
});
|
||||
}
|
||||
",
|
||||
// https://github.com/oxc-project/oxc/issues/6651
|
||||
r"const MyComponent3 = makeComponent(function foo () { useHook(); });",
|
||||
// https://github.com/oxc-project/oxc/issues/17961
|
||||
|
||||
@@ -1119,6 +1119,15 @@ source: crates/oxc_linter/src/tester.rs
|
||||
╰────
|
||||
help: Move the Hook call before the condition, or call it unconditionally and branch inside the Hook/effect instead.
|
||||
|
||||
⚠ react-hooks(rules-of-hooks): React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
|
||||
╭─[rules_of_hooks.tsx:4:21]
|
||||
3 │ return new Promise.then(() => {
|
||||
4 │ useState();
|
||||
· ─────┬────
|
||||
· ╰── This Hook call is inside a nested callback.
|
||||
5 │ });
|
||||
╰────
|
||||
|
||||
⚠ react-hooks(rules-of-hooks): React Hook "useHook" is called in function "foo" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
|
||||
╭─[rules_of_hooks.tsx:1:54]
|
||||
1 │ const MyComponent3 = makeComponent(function foo () { useHook(); });
|
||||
|
||||
Reference in new issue
Block a user