Skip to content

Commit

Permalink
fix(lint/useExhaustiveDeps): handle hook source (biomejs#578)
Browse files Browse the repository at this point in the history
  • Loading branch information
XiNiHa committed Dec 1, 2023
1 parent b71b4d0 commit dccd790
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 14 deletions.
74 changes: 61 additions & 13 deletions crates/biome_js_analyze/src/react/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ impl StableReactHookConfiguration {
/// ```
pub fn is_binding_react_stable(
binding: &AnyJsIdentifierBinding,
model: &SemanticModel,
stable_config: &FxHashSet<StableReactHookConfiguration>,
) -> bool {
fn array_binding_declarator_index(
Expand All @@ -232,25 +233,34 @@ pub fn is_binding_react_stable(
array_binding_declarator_index(binding)
.or_else(|| assignment_declarator(binding))
.and_then(|(declarator, index)| {
let hook_name = declarator
let callee = declarator
.initializer()?
.expression()
.ok()?
.as_js_call_expression()?
.callee()
.ok()?
.as_js_identifier_expression()?
.name()
.ok()?
.value_token()
.ok()?
.token_text_trimmed();
.ok()?;

let stable = StableReactHookConfiguration {
hook_name: hook_name.to_string(),
index,
let hook_name = match &callee {
AnyJsExpression::JsIdentifierExpression(expr) => expr
.name()
.ok()?
.value_token()
.ok()?
.text_trimmed()
.to_string(),
AnyJsExpression::JsStaticMemberExpression(expr) => {
expr.member().ok()?.text().to_string()
}
_ => return None,
};

if !is_react_call_api(callee, model, ReactLibrary::React, &hook_name) {
return None;
}

let stable = StableReactHookConfiguration { hook_name, index };

Some(stable_config.contains(&stable))
})
.unwrap_or(false)
Expand All @@ -260,12 +270,46 @@ pub fn is_binding_react_stable(
mod test {
use super::*;
use biome_js_parser::JsParserOptions;
use biome_js_semantic::{semantic_model, SemanticModelOptions};
use biome_js_syntax::JsFileSource;

#[test]
pub fn ok_react_stable_captures() {
let r = biome_js_parser::parse(
"const ref = useRef();",
r#"
import { useRef } from "react";
const ref = useRef();
"#,
JsFileSource::js_module(),
JsParserOptions::default(),
);
let node = r
.syntax()
.descendants()
.filter(|x| x.text_trimmed() == "ref")
.last()
.unwrap();
let set_name = AnyJsIdentifierBinding::cast(node).unwrap();

let config = FxHashSet::from_iter([
StableReactHookConfiguration::new("useRef", None),
StableReactHookConfiguration::new("useState", Some(1)),
]);

assert!(is_binding_react_stable(
&set_name,
&semantic_model(&r.ok().unwrap(), SemanticModelOptions::default()),
&config
));
}

#[test]
pub fn ok_react_stable_captures_with_default_import() {
let r = biome_js_parser::parse(
r#"
import * as React from "react";
const ref = React.useRef();
"#,
JsFileSource::js_module(),
JsParserOptions::default(),
);
Expand All @@ -282,6 +326,10 @@ mod test {
StableReactHookConfiguration::new("useState", Some(1)),
]);

assert!(is_binding_react_stable(&set_name, &config));
assert!(is_binding_react_stable(
&set_name,
&semantic_model(&r.ok().unwrap(), SemanticModelOptions::default()),
&config
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,8 @@ fn capture_needs_to_be_in_the_dependency_list(
}

// ... they are assign to stable returns of another React function
let not_stable = !is_binding_react_stable(&binding.tree(), &options.stable_config);
let not_stable =
!is_binding_react_stable(&binding.tree(), model, &options.stable_config);
not_stable.then_some(capture)
}

Expand Down

0 comments on commit dccd790

Please sign in to comment.