fix(linter): rules-of-hooks fix false positive with default export (#7570)

closes #7555
This commit is contained in:
camc314 2024-12-02 03:09:30 +00:00
parent cb1f8e6e03
commit 4e3044e225
2 changed files with 25 additions and 32 deletions

View file

@ -172,13 +172,12 @@ impl Rule for RulesOfHooks {
}) => { }) => {
let ident = get_declaration_identifier(nodes, parent_func.id()); let ident = get_declaration_identifier(nodes, parent_func.id());
// Hooks cannot be called inside of export default functions or used in a function // Hooks cannot be used in a function declaration outside of a react component or hook.
// declaration outside of a react component or hook.
// For example these are invalid: // For example these are invalid:
// const notAComponent = () => { // const notAComponent = () => {
// return () => { // return () => {
// useState(); // useState();
// } // }
// } // }
// -------------- // --------------
// export default () => { // export default () => {
@ -192,9 +191,7 @@ impl Rule for RulesOfHooks {
// useState(0); // useState(0);
// } // }
// } // }
if ident.is_some_and(|name| !is_react_component_or_hook_name(&name)) if ident.is_some_and(|name| !is_react_component_or_hook_name(&name)) {
|| is_export_default(nodes, parent_func.id())
{
return ctx.diagnostic(diagnostics::function_error( return ctx.diagnostic(diagnostics::function_error(
*span, *span,
hook_name, hook_name,
@ -400,14 +397,6 @@ fn get_declaration_identifier<'a>(
}) })
} }
fn is_export_default<'a>(nodes: &'a AstNodes<'a>, node_id: NodeId) -> bool {
nodes
.ancestor_ids(node_id)
.map(|id| nodes.get_node(id))
.nth(1)
.is_some_and(|node| matches!(node.kind(), AstKind::ExportDefaultDeclaration(_)))
}
/// # Panics /// # Panics
/// `node_id` should always point to a valid `Function`. /// `node_id` should always point to a valid `Function`.
fn is_memo_or_forward_ref_callback(nodes: &AstNodes, node_id: NodeId) -> bool { fn is_memo_or_forward_ref_callback(nodes: &AstNodes, node_id: NodeId) -> bool {
@ -916,6 +905,16 @@ fn test() {
}); });
}); });
", ",
"export default function App() {
const [state, setState] = useState(0);
useEffect(() => {
console.log('Effect called');
}, []);
return <div>{state}</div>;
}
"
]; ];
let fail = vec![ let fail = vec![

View file

@ -668,24 +668,18 @@ source: crates/oxc_linter/src/tester.rs
5 │ 5 │
╰──── ╰────
⚠ eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called in function "Anonymous" 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". ⚠ eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
╭─[rules_of_hooks.tsx:2:28] ╭─[rules_of_hooks.tsx:4:21]
1 │ 3 │ if (isVal) {
2 │ ╭─▶ export default () => { 4 │ useState(0);
3 │ │ if (isVal) { · ───────────
4 │ │ useState(0); 5 │ }
5 │ │ }
6 │ ╰─▶ }
7 │
╰──── ╰────
⚠ eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called in function "Anonymous" 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". ⚠ eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
╭─[rules_of_hooks.tsx:2:28] ╭─[rules_of_hooks.tsx:4:21]
1 │ 3 │ if (isVal) {
2 │ ╭─▶ export default function() { 4 │ useState(0);
3 │ │ if (isVal) { · ───────────
4 │ │ useState(0); 5 │ }
5 │ │ }
6 │ ╰─▶ }
7 │
╰──── ╰────