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 066b9df
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 290 deletions.
75 changes: 61 additions & 14 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,26 +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,
};

Some(stable_config.contains(&stable))
let stable = StableReactHookConfiguration { hook_name, index };

Some(
stable_config.contains(&stable)
&& is_react_call_api(callee, model, ReactLibrary::React, &stable.hook_name),
)
})
.unwrap_or(false)
}
Expand All @@ -260,12 +269,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 +325,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 @@ -119,7 +119,7 @@ declare_rule! {
/// ```
///
/// ```js
/// import { useEffect } from "react";
/// import { useEffect, useState } from "react";
///
/// function component() {
/// const [name, setName] = useState();
Expand Down 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import React from "react";
import { useEffect, useCallback, useMemo, useLayoutEffect, useInsertionEffect, useImperativeHandle } from "react";
import {
useEffect,
useCallback,
useMemo,
useLayoutEffect,
useInsertionEffect,
useImperativeHandle,
useState,
useReducer,
useTransition,
} from "react";

function MyComponent1() {
let a = 1;
Expand Down
Loading

0 comments on commit 066b9df

Please sign in to comment.