diff --git a/CHANGELOG.md b/CHANGELOG.md index 64b37befe623..c536c187fea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - The [`noUnmatchableAnbSelector`](https://biomejs.dev/linter/rules/no-unmatchable-anb-selector/) rule is now able to catch unmatchable `an+b` selectors like `0n+0` or `-0n+0`. Contributed by @Sec-ant. +- The [`useHookAtTopLevel`](https://biomejs.dev/linter/rules/use-hook-at-top-level/) rule now recognizes properties named as hooks like `foo.useFoo()`. Contributed by @ksnyder9801 + ### Parser ## v1.8.1 (2024-06-10) diff --git a/crates/biome_js_analyze/src/react/hooks.rs b/crates/biome_js_analyze/src/react/hooks.rs index 62c52163a898..660b93391ad2 100644 --- a/crates/biome_js_analyze/src/react/hooks.rs +++ b/crates/biome_js_analyze/src/react/hooks.rs @@ -111,15 +111,17 @@ impl From<(u8, u8, bool)> for ReactHookConfiguration { } fn get_untrimmed_callee_name(call: &JsCallExpression) -> Option> { - let name = call - .callee() - .ok()? - .as_js_identifier_expression()? - .name() - .ok()? - .value_token() - .ok()?; - Some(name) + let callee = call.callee().ok()?; + + if let Some(identifier) = callee.as_js_identifier_expression() { + return identifier.name().ok()?.value_token().ok(); + } + + if let Some(member_expression) = callee.as_js_static_member_expression() { + return member_expression.member().ok()?.value_token().ok(); + } + + None } /// Checks whether the given function name belongs to a React component, based diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js index e2be2e28b2fd..7c9e5380cdcd 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js @@ -6,4 +6,9 @@ function MyComponent() { if (a) { const { a } = useCustomHook(); } + + // This is invalid + if (a) { + const { a } = foo.bar.useCustomHook(); + } } \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js.snap index 5939e2a965eb..6d354ba7aa96 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/customHook.js.snap @@ -12,6 +12,11 @@ function MyComponent() { if (a) { const { a } = useCustomHook(); } + + // This is invalid + if (a) { + const { a } = foo.bar.useCustomHook(); + } } ``` @@ -26,7 +31,7 @@ customHook.js:7:23 lint/correctness/useHookAtTopLevel ━━━━━━━━ > 7 │ const { a } = useCustomHook(); │ ^^^^^^^^^^^^^ 8 │ } - 9 │ } + 9 │ i For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order. @@ -35,4 +40,21 @@ customHook.js:7:23 lint/correctness/useHookAtTopLevel ━━━━━━━━ ``` +``` +customHook.js:12:23 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This hook is being called conditionally, but all hooks must be called in the exact same order in every component render. + + 10 │ // This is invalid + 11 │ if (a) { + > 12 │ const { a } = foo.bar.useCustomHook(); + │ ^^^^^^^^^^^^^^^^^^^^^ + 13 │ } + 14 │ } + + i For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order. + + i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level + +```