diff --git a/CHANGELOG.md b/CHANGELOG.md index 667e814896ad..a273ccc3bddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -319,6 +319,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @lutaok +- Make [useExhaustiveDependencies](https://biomejs.dev/linter/rules/use-exhaustive-dependencies/) report duplicate dependencies. Contributed by @tunamaguro #### Bug fixes diff --git a/crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs b/crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs index e4af2ba9532e..b00ddf9de95a 100644 --- a/crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs +++ b/crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs @@ -782,6 +782,23 @@ impl Rule for UseExhaustiveDependencies { }) }); + // Find duplicated deps from specified ones + { + let mut dep_list: BTreeMap = BTreeMap::new(); + for dep in correct_deps.iter() { + let expression_name = dep.to_string(); + if dep_list.contains_key(&expression_name) { + signals.push(Fix::RemoveDependency { + function_name_range: result.function_name_range, + component_function: component_function.clone(), + dependencies: vec![dep.clone()], + }); + continue; + } + dep_list.insert(expression_name, dep.clone()); + } + } + // Find correctly specified dependencies with an unstable identity, // since they would trigger re-evaluation on every render. let unstable_deps = correct_deps.into_iter().filter_map(|dep| { diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/duplicateDependencies.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/duplicateDependencies.js new file mode 100644 index 000000000000..c79cc61c54db --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/duplicateDependencies.js @@ -0,0 +1,14 @@ +import { useCallback } from "react"; + +function Component1({ a }) { + const handle = useCallback(() => { + console.log(a); + }, [a, a]); +} + +function Component2() { + const [local,SetLocal] = useState(0); + const handle = useCallback(() => { + console.log(local); + }, [local, local]); +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/duplicateDependencies.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/duplicateDependencies.js.snap new file mode 100644 index 000000000000..ca5f68829282 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/duplicateDependencies.js.snap @@ -0,0 +1,68 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: duplicateDependencies.js +--- +# Input +```jsx +import { useCallback } from "react"; + +function Component1({ a }) { + const handle = useCallback(() => { + console.log(a); + }, [a, a]); +} + +function Component2() { + const [local,SetLocal] = useState(0); + const handle = useCallback(() => { + console.log(local); + }, [local, local]); +} +``` + +# Diagnostics +``` +duplicateDependencies.js:4:20 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This hook specifies more dependencies than necessary: a + + 3 │ function Component1({ a }) { + > 4 │ const handle = useCallback(() => { + │ ^^^^^^^^^^^ + 5 │ console.log(a); + 6 │ }, [a, a]); + + i Outer scope values aren't valid dependencies because mutating them doesn't re-render the component. + + 4 │ const handle = useCallback(() => { + 5 │ console.log(a); + > 6 │ }, [a, a]); + │ ^ + 7 │ } + 8 │ + + +``` + +``` +duplicateDependencies.js:11:20 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This hook specifies more dependencies than necessary: local + + 9 │ function Component2() { + 10 │ const [local,SetLocal] = useState(0); + > 11 │ const handle = useCallback(() => { + │ ^^^^^^^^^^^ + 12 │ console.log(local); + 13 │ }, [local, local]); + + i This dependency can be removed from the list. + + 11 │ const handle = useCallback(() => { + 12 │ console.log(local); + > 13 │ }, [local, local]); + │ ^^^^^ + 14 │ } + + +```