fix(linter/react): better detection for hooks in the rules_of_hooks. (#3306)

related to #3257
This commit is contained in:
rzvxa 2024-05-16 16:38:14 +00:00
parent 95944419ec
commit 0864cd0115
2 changed files with 27 additions and 8 deletions

View file

@ -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,

View file

@ -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
}
}