Skip to content

Commit

Permalink
fix(lint/useExhaustiveDeps): handle hook source (#578) (#996)
Browse files Browse the repository at this point in the history
  • Loading branch information
XiNiHa authored Dec 4, 2023
1 parent 610394f commit 6739a51
Show file tree
Hide file tree
Showing 7 changed files with 499 additions and 293 deletions.
67 changes: 51 additions & 16 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,22 @@ 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,
};

Some(stable_config.contains(&stable))
Some(stable_config.iter().any(|config| {
is_react_call_api(
callee.clone(),
model,
ReactLibrary::React,
&config.hook_name,
) && index == config.index
}))
})
.unwrap_or(false)
}
Expand All @@ -260,12 +257,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 +313,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 @@ -120,7 +120,7 @@ declare_rule! {
/// ```
///
/// ```js
/// import { useEffect } from "react";
/// import { useEffect, useState } from "react";
///
/// function component() {
/// const [name, setName] = useState();
Expand Down Expand Up @@ -472,7 +472,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,16 @@
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";
import { useRef } from "preact/hooks"

function MyComponent1() {
let a = 1;
Expand Down Expand Up @@ -123,3 +134,22 @@ function MyComponent13() {
console.log(a);
}, []);
}

// imports from other libraries
function MyComponent14() {
const ref = useRef();
useEffect(() => {
console.log(ref.current);
}, []);
}

// local overrides
function MyComponent15() {
const useRef = () => {
return { current: 1 }
}
const ref = useRef();
useEffect(() => {
console.log(ref.current);
}, []);
}
Loading

0 comments on commit 6739a51

Please sign in to comment.