mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
fix(linter/react): rules_of_hooks add support for property hooks/components. (#3300)
related to #3257
This commit is contained in:
parent
0c09047111
commit
95944419ec
3 changed files with 94 additions and 13 deletions
|
|
@ -1135,6 +1135,12 @@ pub enum AssignmentTarget<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> AssignmentTarget<'a> {
|
||||||
|
pub fn get_identifier(&self) -> Option<&str> {
|
||||||
|
self.as_simple_assignment_target().and_then(|it| it.get_identifier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Macro for matching `AssignmentTarget`'s variants.
|
/// Macro for matching `AssignmentTarget`'s variants.
|
||||||
/// Includes `SimpleAssignmentTarget`'s and `AssignmentTargetPattern`'s variants.
|
/// Includes `SimpleAssignmentTarget`'s and `AssignmentTargetPattern`'s variants.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -1197,6 +1203,14 @@ macro_rules! match_simple_assignment_target {
|
||||||
pub use match_simple_assignment_target;
|
pub use match_simple_assignment_target;
|
||||||
|
|
||||||
impl<'a> SimpleAssignmentTarget<'a> {
|
impl<'a> SimpleAssignmentTarget<'a> {
|
||||||
|
pub fn get_identifier(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::AssignmentTargetIdentifier(ident) => Some(ident.name.as_str()),
|
||||||
|
match_member_expression!(Self) => self.to_member_expression().static_property_name(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_expression(&self) -> Option<&Expression<'a>> {
|
pub fn get_expression(&self) -> Option<&Expression<'a>> {
|
||||||
match self {
|
match self {
|
||||||
Self::TSAsExpression(expr) => Some(&expr.expression),
|
Self::TSAsExpression(expr) => Some(&expr.expression),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ use oxc_semantic::{
|
||||||
pg::neighbors_filtered_by_edge_weight,
|
pg::neighbors_filtered_by_edge_weight,
|
||||||
AstNodeId, AstNodes, BasicBlockElement, EdgeType, Register,
|
AstNodeId, AstNodes, BasicBlockElement, EdgeType, Register,
|
||||||
};
|
};
|
||||||
use oxc_span::Atom;
|
use oxc_span::{Atom, CompactStr};
|
||||||
|
use oxc_syntax::operator::AssignmentOperator;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::LintContext,
|
context::LintContext,
|
||||||
|
|
@ -195,7 +196,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.as_str()))
|
||||||
|| is_export_default(nodes, parent_func.id())
|
|| is_export_default(nodes, parent_func.id())
|
||||||
{
|
{
|
||||||
return ctx.diagnostic(diagnostics::function_error(
|
return ctx.diagnostic(diagnostics::function_error(
|
||||||
|
|
@ -368,7 +369,7 @@ fn is_somewhere_inside_component_or_hook(nodes: &AstNodes, node_id: AstNodeId) -
|
||||||
(
|
(
|
||||||
node.id(),
|
node.id(),
|
||||||
match node.kind() {
|
match node.kind() {
|
||||||
AstKind::Function(func) => func.id.as_ref().map(|it| it.name.as_str()),
|
AstKind::Function(func) => func.id.as_ref().map(|it| it.name.to_compact_str()),
|
||||||
AstKind::ArrowFunctionExpression(_) => {
|
AstKind::ArrowFunctionExpression(_) => {
|
||||||
get_declaration_identifier(nodes, node.id())
|
get_declaration_identifier(nodes, node.id())
|
||||||
}
|
}
|
||||||
|
|
@ -378,21 +379,37 @@ fn is_somewhere_inside_component_or_hook(nodes: &AstNodes, node_id: AstNodeId) -
|
||||||
})
|
})
|
||||||
.any(|(ix, id)| {
|
.any(|(ix, id)| {
|
||||||
id.is_some_and(|name| {
|
id.is_some_and(|name| {
|
||||||
is_react_component_or_hook_name(name) || is_memo_or_forward_ref_callback(nodes, ix)
|
is_react_component_or_hook_name(name.as_str())
|
||||||
|
|| is_memo_or_forward_ref_callback(nodes, ix)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_declaration_identifier<'a>(nodes: &'a AstNodes<'a>, node_id: AstNodeId) -> Option<&str> {
|
fn get_declaration_identifier<'a>(
|
||||||
nodes.ancestors(node_id).map(|id| nodes.get_node(id)).find_map(|node| {
|
nodes: &'a AstNodes<'a>,
|
||||||
if let AstKind::VariableDeclaration(decl) = node.kind() {
|
node_id: AstNodeId,
|
||||||
if decl.declarations.len() == 1 {
|
) -> Option<CompactStr> {
|
||||||
decl.declarations[0].id.get_identifier().map(Atom::as_str)
|
nodes.ancestors(node_id).map(|id| nodes.kind(id)).find_map(|kind| {
|
||||||
} else {
|
match kind {
|
||||||
None
|
// const useHook = () => {};
|
||||||
|
AstKind::VariableDeclaration(decl) if decl.declarations.len() == 1 => {
|
||||||
|
decl.declarations[0].id.get_identifier().map(Atom::to_compact_str)
|
||||||
}
|
}
|
||||||
} else {
|
// useHook = () => {};
|
||||||
None
|
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(Atom::to_compact_str)
|
||||||
|
}
|
||||||
|
// { useHook: () => {} }
|
||||||
|
// { useHook() {} }
|
||||||
|
AstKind::ObjectProperty(prop) => prop.key.name(),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -888,6 +905,24 @@ fn test() {
|
||||||
useHook();
|
useHook();
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
|
"
|
||||||
|
|
||||||
|
export const Component = () => {
|
||||||
|
return {
|
||||||
|
Target: () => {
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
something.value = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return <div></div>;
|
||||||
|
},
|
||||||
|
useTargetModule: (m) => {
|
||||||
|
useModule(m);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
",
|
||||||
];
|
];
|
||||||
|
|
||||||
let fail = vec![
|
let fail = vec![
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,38 @@ expression: rules_of_hooks
|
||||||
6 │ e = () => { useState(); };
|
6 │ e = () => { useState(); };
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
|
× 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:6:17]
|
||||||
|
5 │ let d = () => useState();
|
||||||
|
6 │ e = () => { useState(); };
|
||||||
|
· ─────────────────────
|
||||||
|
7 │ ({f: () => { useState(); }});
|
||||||
|
╰────
|
||||||
|
|
||||||
|
× 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:7:18]
|
||||||
|
6 │ e = () => { useState(); };
|
||||||
|
7 │ ({f: () => { useState(); }});
|
||||||
|
· ─────────────────────
|
||||||
|
8 │ ({g() { useState(); }});
|
||||||
|
╰────
|
||||||
|
|
||||||
|
× 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:8:16]
|
||||||
|
7 │ ({f: () => { useState(); }});
|
||||||
|
8 │ ({g() { useState(); }});
|
||||||
|
· ──────────────────
|
||||||
|
9 │ const {j = () => { useState(); }} = {};
|
||||||
|
╰────
|
||||||
|
|
||||||
|
× 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:9:24]
|
||||||
|
8 │ ({g() { useState(); }});
|
||||||
|
9 │ const {j = () => { useState(); }} = {};
|
||||||
|
· ─────────────────────
|
||||||
|
10 │ ({k = () => { useState(); }} = {});
|
||||||
|
╰────
|
||||||
|
|
||||||
× 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.
|
× 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:4:21]
|
╭─[rules_of_hooks.tsx:4:21]
|
||||||
3 │ if (a) return;
|
3 │ if (a) return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue