diff --git a/crates/biome_js_analyze/src/react/hooks.rs b/crates/biome_js_analyze/src/react/hooks.rs index bba7e3e329e0..a3c505424245 100644 --- a/crates/biome_js_analyze/src/react/hooks.rs +++ b/crates/biome_js_analyze/src/react/hooks.rs @@ -209,6 +209,7 @@ impl StableReactHookConfiguration { /// ``` pub fn is_binding_react_stable( binding: &AnyJsIdentifierBinding, + model: &SemanticModel, stable_config: &FxHashSet, ) -> bool { fn array_binding_declarator_index( @@ -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) } @@ -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(), ); @@ -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 + )); } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs index 515a3017d7e5..5121618d7cfe 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs @@ -120,7 +120,7 @@ declare_rule! { /// ``` /// /// ```js - /// import { useEffect } from "react"; + /// import { useEffect, useState } from "react"; /// /// function component() { /// const [name, setName] = useState(); @@ -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) } diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js index 4d48fb914dfc..cabbf29b47ac 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js @@ -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; @@ -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); + }, []); +} diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap index 01aa7d17f2d9..972516db330c 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap @@ -5,7 +5,18 @@ expression: missingDependenciesInvalid.js # Input ```js 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; @@ -130,29 +141,48 @@ function MyComponent13() { }, []); } +// 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); + }, []); +} + ``` # Diagnostics ``` -missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:18:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 5 │ let a = 1; - 6 │ const b = a + 1; - > 7 │ useEffect(() => { - │ ^^^^^^^^^ - 8 │ console.log(a, b); - 9 │ }, []); + 16 │ let a = 1; + 17 │ const b = a + 1; + > 18 │ useEffect(() => { + │ ^^^^^^^^^ + 19 │ console.log(a, b); + 20 │ }, []); i This dependency is not specified in the hook dependency list. - 6 │ const b = a + 1; - 7 │ useEffect(() => { - > 8 │ console.log(a, b); + 17 │ const b = a + 1; + 18 │ useEffect(() => { + > 19 │ console.log(a, b); │ ^ - 9 │ }, []); - 10 │ } + 20 │ }, []); + 21 │ } i Either include it or remove the dependency array @@ -160,25 +190,25 @@ missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━ ``` ``` -missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:18:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 5 │ let a = 1; - 6 │ const b = a + 1; - > 7 │ useEffect(() => { - │ ^^^^^^^^^ - 8 │ console.log(a, b); - 9 │ }, []); + 16 │ let a = 1; + 17 │ const b = a + 1; + > 18 │ useEffect(() => { + │ ^^^^^^^^^ + 19 │ console.log(a, b); + 20 │ }, []); i This dependency is not specified in the hook dependency list. - 6 │ const b = a + 1; - 7 │ useEffect(() => { - > 8 │ console.log(a, b); + 17 │ const b = a + 1; + 18 │ useEffect(() => { + > 19 │ console.log(a, b); │ ^ - 9 │ }, []); - 10 │ } + 20 │ }, []); + 21 │ } i Either include it or remove the dependency array @@ -186,25 +216,25 @@ missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━ ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 28 │ console.log(memoizedCallback); - 29 │ console.log(memoizedValue); - > 30 │ console.log(deferredValue); + 39 │ console.log(memoizedCallback); + 40 │ console.log(memoizedValue); + > 41 │ console.log(deferredValue); │ ^^^^^^^^^^^^^ - 31 │ - 32 │ console.log(isPending); + 42 │ + 43 │ console.log(isPending); i Either include it or remove the dependency array @@ -212,25 +242,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 26 │ dispatch(1); - 27 │ - > 28 │ console.log(memoizedCallback); + 37 │ dispatch(1); + 38 │ + > 39 │ console.log(memoizedCallback); │ ^^^^^^^^^^^^^^^^ - 29 │ console.log(memoizedValue); - 30 │ console.log(deferredValue); + 40 │ console.log(memoizedValue); + 41 │ console.log(deferredValue); i Either include it or remove the dependency array @@ -238,25 +268,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 23 │ setName(1); - 24 │ - > 25 │ console.log(state); + 34 │ setName(1); + 35 │ + > 36 │ console.log(state); │ ^^^^^ - 26 │ dispatch(1); - 27 │ + 37 │ dispatch(1); + 38 │ i Either include it or remove the dependency array @@ -264,25 +294,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 20 │ const [isPending, startTransition] = useTransition(); - 21 │ useEffect(() => { - > 22 │ console.log(name); + 31 │ const [isPending, startTransition] = useTransition(); + 32 │ useEffect(() => { + > 33 │ console.log(name); │ ^^^^ - 23 │ setName(1); - 24 │ + 34 │ setName(1); + 35 │ i Either include it or remove the dependency array @@ -290,25 +320,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 30 │ console.log(deferredValue); - 31 │ - > 32 │ console.log(isPending); + 41 │ console.log(deferredValue); + 42 │ + > 43 │ console.log(isPending); │ ^^^^^^^^^ - 33 │ startTransition(); - 34 │ }, []); + 44 │ startTransition(); + 45 │ }, []); i Either include it or remove the dependency array @@ -316,24 +346,24 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 28 │ console.log(memoizedCallback); - > 29 │ console.log(memoizedValue); + 39 │ console.log(memoizedCallback); + > 40 │ console.log(memoizedValue); │ ^^^^^^^^^^^^^ - 30 │ console.log(deferredValue); - 31 │ + 41 │ console.log(deferredValue); + 42 │ i Either include it or remove the dependency array @@ -341,25 +371,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:41:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:52:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 39 │ function MyComponent3() { - 40 │ let a = 1; - > 41 │ useEffect(() => console.log(a), []); + 50 │ function MyComponent3() { + 51 │ let a = 1; + > 52 │ useEffect(() => console.log(a), []); │ ^^^^^^^^^ - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); i This dependency is not specified in the hook dependency list. - 39 │ function MyComponent3() { - 40 │ let a = 1; - > 41 │ useEffect(() => console.log(a), []); + 50 │ function MyComponent3() { + 51 │ let a = 1; + > 52 │ useEffect(() => console.log(a), []); │ ^ - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); i Either include it or remove the dependency array @@ -367,25 +397,25 @@ missingDependenciesInvalid.js:41:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:42:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:53:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 40 │ let a = 1; - 41 │ useEffect(() => console.log(a), []); - > 42 │ useCallback(() => console.log(a), []); + 51 │ let a = 1; + 52 │ useEffect(() => console.log(a), []); + > 53 │ useCallback(() => console.log(a), []); │ ^^^^^^^^^^^ - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); i This dependency is not specified in the hook dependency list. - 40 │ let a = 1; - 41 │ useEffect(() => console.log(a), []); - > 42 │ useCallback(() => console.log(a), []); + 51 │ let a = 1; + 52 │ useEffect(() => console.log(a), []); + > 53 │ useCallback(() => console.log(a), []); │ ^ - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); i Either include it or remove the dependency array @@ -393,25 +423,25 @@ missingDependenciesInvalid.js:42:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:43:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:54:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 41 │ useEffect(() => console.log(a), []); - 42 │ useCallback(() => console.log(a), []); - > 43 │ useMemo(() => console.log(a), []); + 52 │ useEffect(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + > 54 │ useMemo(() => console.log(a), []); │ ^^^^^^^ - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); i This dependency is not specified in the hook dependency list. - 41 │ useEffect(() => console.log(a), []); - 42 │ useCallback(() => console.log(a), []); - > 43 │ useMemo(() => console.log(a), []); + 52 │ useEffect(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + > 54 │ useMemo(() => console.log(a), []); │ ^ - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); i Either include it or remove the dependency array @@ -419,25 +449,25 @@ missingDependenciesInvalid.js:43:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:44:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:55:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); - > 44 │ useImperativeHandle(ref, () => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + > 55 │ useImperativeHandle(ref, () => console.log(a), []); │ ^^^^^^^^^^^^^^^^^^^ - 45 │ useLayoutEffect(() => console.log(a), []); - 46 │ useInsertionEffect(() => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + 57 │ useInsertionEffect(() => console.log(a), []); i This dependency is not specified in the hook dependency list. - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); - > 44 │ useImperativeHandle(ref, () => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + > 55 │ useImperativeHandle(ref, () => console.log(a), []); │ ^ - 45 │ useLayoutEffect(() => console.log(a), []); - 46 │ useInsertionEffect(() => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + 57 │ useInsertionEffect(() => console.log(a), []); i Either include it or remove the dependency array @@ -445,25 +475,25 @@ missingDependenciesInvalid.js:44:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:45:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:56:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); - > 45 │ useLayoutEffect(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + > 56 │ useLayoutEffect(() => console.log(a), []); │ ^^^^^^^^^^^^^^^ - 46 │ useInsertionEffect(() => console.log(a), []); - 47 │ } + 57 │ useInsertionEffect(() => console.log(a), []); + 58 │ } i This dependency is not specified in the hook dependency list. - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); - > 45 │ useLayoutEffect(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + > 56 │ useLayoutEffect(() => console.log(a), []); │ ^ - 46 │ useInsertionEffect(() => console.log(a), []); - 47 │ } + 57 │ useInsertionEffect(() => console.log(a), []); + 58 │ } i Either include it or remove the dependency array @@ -471,25 +501,25 @@ missingDependenciesInvalid.js:45:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:46:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:57:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); - > 46 │ useInsertionEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + > 57 │ useInsertionEffect(() => console.log(a), []); │ ^^^^^^^^^^^^^^^^^^ - 47 │ } - 48 │ + 58 │ } + 59 │ i This dependency is not specified in the hook dependency list. - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); - > 46 │ useInsertionEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + > 57 │ useInsertionEffect(() => console.log(a), []); │ ^ - 47 │ } - 48 │ + 58 │ } + 59 │ i Either include it or remove the dependency array @@ -497,25 +527,25 @@ missingDependenciesInvalid.js:46:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:53:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:64:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 51 │ function MyComponent4() { - 52 │ let a = 1; - > 53 │ useEffect(() => { + 62 │ function MyComponent4() { + 63 │ let a = 1; + > 64 │ useEffect(() => { │ ^^^^^^^^^ - 54 │ return () => console.log(a) - 55 │ }, []); + 65 │ return () => console.log(a) + 66 │ }, []); i This dependency is not specified in the hook dependency list. - 52 │ let a = 1; - 53 │ useEffect(() => { - > 54 │ return () => console.log(a) + 63 │ let a = 1; + 64 │ useEffect(() => { + > 65 │ return () => console.log(a) │ ^ - 55 │ }, []); - 56 │ } + 66 │ }, []); + 67 │ } i Either include it or remove the dependency array @@ -523,34 +553,34 @@ missingDependenciesInvalid.js:53:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:62:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:73:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 60 │ function MyComponent5() { - 61 │ let a = 1; - > 62 │ useEffect(() => { + 71 │ function MyComponent5() { + 72 │ let a = 1; + > 73 │ useEffect(() => { │ ^^^^^^^^^ - 63 │ console.log(a); - 64 │ return () => console.log(a); + 74 │ console.log(a); + 75 │ return () => console.log(a); i This dependency is not specified in the hook dependency list. - 61 │ let a = 1; - 62 │ useEffect(() => { - > 63 │ console.log(a); + 72 │ let a = 1; + 73 │ useEffect(() => { + > 74 │ console.log(a); │ ^ - 64 │ return () => console.log(a); - 65 │ }, []); + 75 │ return () => console.log(a); + 76 │ }, []); i This dependency is not specified in the hook dependency list. - 62 │ useEffect(() => { - 63 │ console.log(a); - > 64 │ return () => console.log(a); + 73 │ useEffect(() => { + 74 │ console.log(a); + > 75 │ return () => console.log(a); │ ^ - 65 │ }, []); - 66 │ } + 76 │ }, []); + 77 │ } i Either include them or remove the dependency array @@ -558,25 +588,25 @@ missingDependenciesInvalid.js:62:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:72:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:83:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 70 │ function MyComponent6() { - 71 │ let someObj = getObj(); - > 72 │ useEffect(() => { + 81 │ function MyComponent6() { + 82 │ let someObj = getObj(); + > 83 │ useEffect(() => { │ ^^^^^^^^^ - 73 │ console.log(someObj.name) - 74 │ }, []); + 84 │ console.log(someObj.name) + 85 │ }, []); i This dependency is not specified in the hook dependency list. - 71 │ let someObj = getObj(); - 72 │ useEffect(() => { - > 73 │ console.log(someObj.name) + 82 │ let someObj = getObj(); + 83 │ useEffect(() => { + > 84 │ console.log(someObj.name) │ ^^^^^^^^^^^^ - 74 │ }, []); - 75 │ } + 85 │ }, []); + 86 │ } i Either include it or remove the dependency array @@ -584,24 +614,24 @@ missingDependenciesInvalid.js:72:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:78:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:89:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 77 │ const MyComponent7 = React.memo(function ({ a }) { - > 78 │ useEffect(() => { + 88 │ const MyComponent7 = React.memo(function ({ a }) { + > 89 │ useEffect(() => { │ ^^^^^^^^^ - 79 │ console.log(a); - 80 │ }, []); + 90 │ console.log(a); + 91 │ }, []); i This dependency is not specified in the hook dependency list. - 77 │ const MyComponent7 = React.memo(function ({ a }) { - 78 │ useEffect(() => { - > 79 │ console.log(a); + 88 │ const MyComponent7 = React.memo(function ({ a }) { + 89 │ useEffect(() => { + > 90 │ console.log(a); │ ^ - 80 │ }, []); - 81 │ }); + 91 │ }, []); + 92 │ }); i Either include it or remove the dependency array @@ -609,24 +639,24 @@ missingDependenciesInvalid.js:78:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:84:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:95:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 83 │ const MyComponent8 = React.memo(({ a }) => { - > 84 │ useEffect(() => { + 94 │ const MyComponent8 = React.memo(({ a }) => { + > 95 │ useEffect(() => { │ ^^^^^^^^^ - 85 │ console.log(a); - 86 │ }, []); + 96 │ console.log(a); + 97 │ }, []); i This dependency is not specified in the hook dependency list. - 83 │ const MyComponent8 = React.memo(({ a }) => { - 84 │ useEffect(() => { - > 85 │ console.log(a); + 94 │ const MyComponent8 = React.memo(({ a }) => { + 95 │ useEffect(() => { + > 96 │ console.log(a); │ ^ - 86 │ }, []); - 87 │ }); + 97 │ }, []); + 98 │ }); i Either include it or remove the dependency array @@ -634,25 +664,25 @@ missingDependenciesInvalid.js:84:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:92:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:103:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 90 │ export function MyComponent9() { - 91 │ let a = 1; - > 92 │ useEffect(() => { - │ ^^^^^^^^^ - 93 │ console.log(a); - 94 │ }, []); + 101 │ export function MyComponent9() { + 102 │ let a = 1; + > 103 │ useEffect(() => { + │ ^^^^^^^^^ + 104 │ console.log(a); + 105 │ }, []); i This dependency is not specified in the hook dependency list. - 91 │ let a = 1; - 92 │ useEffect(() => { - > 93 │ console.log(a); - │ ^ - 94 │ }, []); - 95 │ } + 102 │ let a = 1; + 103 │ useEffect(() => { + > 104 │ console.log(a); + │ ^ + 105 │ }, []); + 106 │ } i Either include it or remove the dependency array @@ -660,25 +690,25 @@ missingDependenciesInvalid.js:92:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:99:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:110:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 97 │ export default function MyComponent10() { - 98 │ let a = 1; - > 99 │ useEffect(() => { + 108 │ export default function MyComponent10() { + 109 │ let a = 1; + > 110 │ useEffect(() => { │ ^^^^^^^^^ - 100 │ console.log(a); - 101 │ }, []); + 111 │ console.log(a); + 112 │ }, []); i This dependency is not specified in the hook dependency list. - 98 │ let a = 1; - 99 │ useEffect(() => { - > 100 │ console.log(a); + 109 │ let a = 1; + 110 │ useEffect(() => { + > 111 │ console.log(a); │ ^ - 101 │ }, []); - 102 │ } + 112 │ }, []); + 113 │ } i Either include it or remove the dependency array @@ -686,25 +716,25 @@ missingDependenciesInvalid.js:99:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:107:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:118:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 105 │ function MyComponent11() { - 106 │ let a = 1; - > 107 │ useEffect(function inner() { + 116 │ function MyComponent11() { + 117 │ let a = 1; + > 118 │ useEffect(function inner() { │ ^^^^^^^^^ - 108 │ console.log(a); - 109 │ }, []); + 119 │ console.log(a); + 120 │ }, []); i This dependency is not specified in the hook dependency list. - 106 │ let a = 1; - 107 │ useEffect(function inner() { - > 108 │ console.log(a); + 117 │ let a = 1; + 118 │ useEffect(function inner() { + > 119 │ console.log(a); │ ^ - 109 │ }, []); - 110 │ } + 120 │ }, []); + 121 │ } i Either include it or remove the dependency array @@ -712,25 +742,25 @@ missingDependenciesInvalid.js:107:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:114:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:125:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 112 │ function MyComponent12() { - 113 │ let a = 1; - > 114 │ useEffect(async function inner() { + 123 │ function MyComponent12() { + 124 │ let a = 1; + > 125 │ useEffect(async function inner() { │ ^^^^^^^^^ - 115 │ console.log(a); - 116 │ }, []); + 126 │ console.log(a); + 127 │ }, []); i This dependency is not specified in the hook dependency list. - 113 │ let a = 1; - 114 │ useEffect(async function inner() { - > 115 │ console.log(a); + 124 │ let a = 1; + 125 │ useEffect(async function inner() { + > 126 │ console.log(a); │ ^ - 116 │ }, []); - 117 │ } + 127 │ }, []); + 128 │ } i Either include it or remove the dependency array @@ -738,25 +768,77 @@ missingDependenciesInvalid.js:114:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:122:9 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:133:9 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 120 │ function MyComponent13() { - 121 │ let a = 1; - > 122 │ React.useEffect(() => { + 131 │ function MyComponent13() { + 132 │ let a = 1; + > 133 │ React.useEffect(() => { │ ^^^^^^^^^ - 123 │ console.log(a); - 124 │ }, []); + 134 │ console.log(a); + 135 │ }, []); i This dependency is not specified in the hook dependency list. - 121 │ let a = 1; - 122 │ React.useEffect(() => { - > 123 │ console.log(a); + 132 │ let a = 1; + 133 │ React.useEffect(() => { + > 134 │ console.log(a); │ ^ - 124 │ }, []); - 125 │ } + 135 │ }, []); + 136 │ } + + i Either include it or remove the dependency array + + +``` + +``` +missingDependenciesInvalid.js:141:2 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ + + ! This hook does not specify all of its dependencies. + + 139 │ function MyComponent14() { + 140 │ const ref = useRef(); + > 141 │ useEffect(() => { + │ ^^^^^^^^^ + 142 │ console.log(ref.current); + 143 │ }, []); + + i This dependency is not specified in the hook dependency list. + + 140 │ const ref = useRef(); + 141 │ useEffect(() => { + > 142 │ console.log(ref.current); + │ ^^^^^^^^^^^ + 143 │ }, []); + 144 │ } + + i Either include it or remove the dependency array + + +``` + +``` +missingDependenciesInvalid.js:152:2 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ + + ! This hook does not specify all of its dependencies. + + 150 │ } + 151 │ const ref = useRef(); + > 152 │ useEffect(() => { + │ ^^^^^^^^^ + 153 │ console.log(ref.current); + 154 │ }, []); + + i This dependency is not specified in the hook dependency list. + + 151 │ const ref = useRef(); + 152 │ useEffect(() => { + > 153 │ console.log(ref.current); + │ ^^^^^^^^^^^ + 154 │ }, []); + 155 │ } i Either include it or remove the dependency array diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js index 5699230c7baa..26d9d740b749 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js @@ -1,7 +1,19 @@ /* should not generate diagnostics */ import React from "react"; -import { useEffect, useSyncExternalStore, useMemo } from "react"; +import { + useEffect, + useSyncExternalStore, + useRef, + useState, + useContext, + useReducer, + useCallback, + useMemo, + useTransition, + useId, +} from "react"; +import { useRef as uR } from "react" import doSomething from 'a'; // No captures @@ -194,3 +206,20 @@ function MyComponent19() { console.log(a); }); } + +// Namespaced imports +// https://github.com/biomejs/biome/issues/578 +function MyComponent20() { + const ref = React.useRef() + React.useEffect(() => { + console.log(ref.current) + }, []) +} + +// Aliased imports +function MyComponent21() { + const ref = uR() + useEffect(() => { + console.log(ref.current) + }, []) +} diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap index 7dfb89268f8e..fa0957fbb235 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap @@ -7,7 +7,19 @@ expression: valid.js /* should not generate diagnostics */ import React from "react"; -import { useEffect, useSyncExternalStore, useMemo } from "react"; +import { + useEffect, + useSyncExternalStore, + useRef, + useState, + useContext, + useReducer, + useCallback, + useMemo, + useTransition, + useId, +} from "react"; +import { useRef as uR } from "react" import doSomething from 'a'; // No captures @@ -201,6 +213,23 @@ function MyComponent19() { }); } +// Namespaced imports +// https://github.com/biomejs/biome/issues/578 +function MyComponent20() { + const ref = React.useRef() + React.useEffect(() => { + console.log(ref.current) + }, []) +} + +// Aliased imports +function MyComponent21() { + const ref = uR() + useEffect(() => { + console.log(ref.current) + }, []) +} + ```