mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
fix(linter/react): better detection for hooks in the rules_of_hooks. (#3306)
related to #3257
This commit is contained in:
parent
95944419ec
commit
0864cd0115
2 changed files with 27 additions and 8 deletions
|
|
@ -15,7 +15,7 @@ use oxc_syntax::operator::AssignmentOperator;
|
|||
use crate::{
|
||||
context::LintContext,
|
||||
rule::Rule,
|
||||
utils::{is_react_component_or_hook_name, is_react_hook},
|
||||
utils::{is_react_component_or_hook_name, is_react_function_call, is_react_hook},
|
||||
AstNode,
|
||||
};
|
||||
|
||||
|
|
@ -120,6 +120,8 @@ impl Rule for RulesOfHooks {
|
|||
let semantic = ctx.semantic();
|
||||
let nodes = semantic.nodes();
|
||||
|
||||
let is_use = is_react_function_call(call, "use");
|
||||
|
||||
let Some(parent_func) = parent_func(nodes, node) else {
|
||||
return ctx.diagnostic(diagnostics::top_level_hook(span, hook_name));
|
||||
};
|
||||
|
|
@ -136,8 +138,6 @@ impl Rule for RulesOfHooks {
|
|||
return ctx.diagnostic(diagnostics::class_component(span, hook_name));
|
||||
}
|
||||
|
||||
let is_use = hook_name == "use";
|
||||
|
||||
match parent_func.kind() {
|
||||
// We are in a named function that isn't a hook or component, which is illegal
|
||||
AstKind::Function(Function { id: Some(id), .. })
|
||||
|
|
@ -356,8 +356,7 @@ fn is_non_react_func_arg(nodes: &AstNodes, node_id: AstNodeId) -> bool {
|
|||
return false;
|
||||
};
|
||||
|
||||
// TODO make it better, might have false positives.
|
||||
call.callee_name().is_some_and(|name| !matches!(name, "forwardRef" | "memo"))
|
||||
!(is_react_function_call(call, "forwardRef") || is_react_function_call(call, "memo"))
|
||||
}
|
||||
|
||||
fn is_somewhere_inside_component_or_hook(nodes: &AstNodes, node_id: AstNodeId) -> bool {
|
||||
|
|
@ -618,6 +617,7 @@ fn test() {
|
|||
use_hook();
|
||||
// also valid because it's not matching the PascalCase namespace
|
||||
jest.useFakeTimer()
|
||||
AFFiNE.plugins.use('oauth');
|
||||
",
|
||||
// Regression test for some internal code.
|
||||
// This shows how the "callback rule" is more relaxed,
|
||||
|
|
|
|||
|
|
@ -301,14 +301,16 @@ pub fn is_react_hook(expr: &Expression) -> bool {
|
|||
#[allow(unsafe_code)]
|
||||
let expr = unsafe { expr.as_member_expression().unwrap_unchecked() };
|
||||
let MemberExpression::StaticMemberExpression(static_expr) = expr else { return false };
|
||||
|
||||
let is_valid_property = is_react_hook_name(&static_expr.property.name);
|
||||
let is_valid_namespace = match &static_expr.object {
|
||||
Expression::Identifier(ident) => {
|
||||
// TODO: test PascalCase
|
||||
ident.name.chars().next().is_some_and(char::is_uppercase)
|
||||
}
|
||||
Expression::ThisExpression(_) | Expression::Super(_) => false,
|
||||
_ => true,
|
||||
_ => false,
|
||||
};
|
||||
is_valid_namespace && expr.static_property_name().is_some_and(is_react_hook_name)
|
||||
is_valid_namespace && is_valid_property
|
||||
}
|
||||
Expression::Identifier(ident) => is_react_hook_name(ident.name.as_str()),
|
||||
_ => false,
|
||||
|
|
@ -326,3 +328,20 @@ pub fn is_react_component_name(name: &str) -> bool {
|
|||
pub fn is_react_component_or_hook_name(name: &str) -> bool {
|
||||
is_react_component_name(name) || is_react_hook_name(name)
|
||||
}
|
||||
|
||||
pub fn is_react_function_call(call: &CallExpression, expected_call: &str) -> bool {
|
||||
let Some(subject) = call.callee_name() else { return false };
|
||||
|
||||
if subject != expected_call {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(member) = call.callee.as_member_expression() {
|
||||
matches! {
|
||||
member.object().get_identifier_reference(),
|
||||
Some(ident) if ident.name.as_str() == PRAGMA
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue