mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
fix(linter): fix false positives in rules-of-hooks (#7606)
closes https://github.com/oxc-project/oxc/issues/7572 should remove all false positives from this rule
This commit is contained in:
parent
40792b4440
commit
e80214c74f
2 changed files with 71 additions and 42 deletions
|
|
@ -362,9 +362,8 @@ fn is_somewhere_inside_component_or_hook(nodes: &AstNodes, node_id: NodeId) -> b
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.any(|(id, ident)| {
|
.any(|(id, ident)| {
|
||||||
ident.is_some_and(|name| {
|
ident.is_some_and(|name| is_react_component_or_hook_name(&name))
|
||||||
is_react_component_or_hook_name(&name) || is_memo_or_forward_ref_callback(nodes, id)
|
|| is_memo_or_forward_ref_callback(nodes, id)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,29 +371,44 @@ fn get_declaration_identifier<'a>(
|
||||||
nodes: &'a AstNodes<'a>,
|
nodes: &'a AstNodes<'a>,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
) -> Option<Cow<'a, str>> {
|
) -> Option<Cow<'a, str>> {
|
||||||
nodes.ancestor_ids(node_id).map(|id| nodes.kind(id)).find_map(|kind| {
|
let node = nodes.get_node(node_id);
|
||||||
match kind {
|
|
||||||
// const useHook = () => {};
|
match node.kind() {
|
||||||
AstKind::VariableDeclaration(decl) if decl.declarations.len() == 1 => {
|
AstKind::Function(Function { id: Some(id), .. }) => {
|
||||||
decl.declarations[0].id.get_identifier().map(|id| Cow::Borrowed(id.as_str()))
|
// function useHook() {}
|
||||||
}
|
// const whatever = function useHook() {};
|
||||||
// useHook = () => {};
|
//
|
||||||
AstKind::AssignmentExpression(expr)
|
// Function declaration or function expression names win over any
|
||||||
if matches!(expr.operator, AssignmentOperator::Assign) =>
|
// assignment statements or other renames.
|
||||||
{
|
Some(Cow::Borrowed(id.name.as_str()))
|
||||||
expr.left.get_identifier().map(std::convert::Into::into)
|
|
||||||
}
|
|
||||||
// const {useHook = () => {}} = {};
|
|
||||||
// ({useHook = () => {}} = {});
|
|
||||||
AstKind::AssignmentPattern(patt) => {
|
|
||||||
patt.left.get_identifier().map(|id| Cow::Borrowed(id.as_str()))
|
|
||||||
}
|
|
||||||
// { useHook: () => {} }
|
|
||||||
// { useHook() {} }
|
|
||||||
AstKind::ObjectProperty(prop) => prop.key.name(),
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
})
|
AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => {
|
||||||
|
let parent =
|
||||||
|
nodes.ancestor_ids(node_id).skip(1).map(|node| nodes.get_node(node)).next()?;
|
||||||
|
|
||||||
|
match parent.kind() {
|
||||||
|
AstKind::VariableDeclarator(decl) => {
|
||||||
|
decl.id.get_identifier().map(|id| Cow::Borrowed(id.as_str()))
|
||||||
|
}
|
||||||
|
// useHook = () => {};
|
||||||
|
AstKind::AssignmentExpression(expr)
|
||||||
|
if matches!(expr.operator, AssignmentOperator::Assign) =>
|
||||||
|
{
|
||||||
|
expr.left.get_identifier().map(std::convert::Into::into)
|
||||||
|
}
|
||||||
|
// const {useHook = () => {}} = {};
|
||||||
|
// ({useHook = () => {}} = {});
|
||||||
|
AstKind::AssignmentPattern(patt) => {
|
||||||
|
patt.left.get_identifier().map(|id| Cow::Borrowed(id.as_str()))
|
||||||
|
}
|
||||||
|
// { useHook: () => {} }
|
||||||
|
// { useHook() {} }
|
||||||
|
AstKind::ObjectProperty(prop) => prop.key.name(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
|
@ -914,7 +928,22 @@ fn test() {
|
||||||
|
|
||||||
return <div>{state}</div>;
|
return <div>{state}</div>;
|
||||||
}
|
}
|
||||||
"
|
// https://github.com/toeverything/AFFiNE/blob/0ec1995addbb09fb5d4af765d84cc914b2905150/packages/frontend/core/src/hooks/use-query.ts#L46
|
||||||
|
",
|
||||||
|
"const createUseQuery =
|
||||||
|
(immutable: boolean): useQueryFn =>
|
||||||
|
(options, config) => {
|
||||||
|
const configWithSuspense: SWRConfiguration = useMemo(
|
||||||
|
() => ({
|
||||||
|
suspense: true,
|
||||||
|
...config,
|
||||||
|
}),
|
||||||
|
[config],
|
||||||
|
);
|
||||||
|
|
||||||
|
const useSWRFn = immutable ? useSWRImutable : useSWR;
|
||||||
|
return useSWRFn(options ? () => ['cloud', options.query.id, options.variables] : null, options ? () => fetcher(options) : null, configWithSuspense);
|
||||||
|
};"
|
||||||
];
|
];
|
||||||
|
|
||||||
let fail = vec![
|
let fail = vec![
|
||||||
|
|
@ -1500,13 +1529,22 @@ fn test() {
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
// errors: [functionError('use', 'notAComponent')],
|
// errors: [functionError('use', 'notAComponent')],
|
||||||
"
|
// React doesn't report on this https://github.com/facebook/react/blob/9daabc0bf97805be23f6131be4d84d063a3ff446/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js#L520-L530
|
||||||
export const notAComponent = () => {
|
// Even so, i think this is valid
|
||||||
return () => {
|
// e.g:
|
||||||
useState();
|
// ```
|
||||||
}
|
// const useMyHook = notAComponent();
|
||||||
}
|
// function Foo () {
|
||||||
",
|
// useMyHook();
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// "
|
||||||
|
// export const notAComponent = () => {
|
||||||
|
// return () => {
|
||||||
|
// useState();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ",
|
||||||
// errors: [functionError('use', 'notAComponent')],
|
// errors: [functionError('use', 'notAComponent')],
|
||||||
"
|
"
|
||||||
const notAComponent = () => {
|
const notAComponent = () => {
|
||||||
|
|
|
||||||
|
|
@ -650,15 +650,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
3 │ use();
|
3 │ use();
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
⚠ 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".
|
|
||||||
╭─[rules_of_hooks.tsx:3:24]
|
|
||||||
2 │ export const notAComponent = () => {
|
|
||||||
3 │ ╭─▶ return () => {
|
|
||||||
4 │ │ useState();
|
|
||||||
5 │ ╰─▶ }
|
|
||||||
6 │ }
|
|
||||||
╰────
|
|
||||||
|
|
||||||
⚠ 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 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".
|
||||||
╭─[rules_of_hooks.tsx:2:35]
|
╭─[rules_of_hooks.tsx:2:35]
|
||||||
1 │
|
1 │
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue