diff --git a/.changeset/huge-cycles-dance.md b/.changeset/huge-cycles-dance.md new file mode 100644 index 000000000000..c167c3984d72 --- /dev/null +++ b/.changeset/huge-cycles-dance.md @@ -0,0 +1,36 @@ +--- +"@biomejs/biome": minor +--- + +Implements [#1984](https://github.com/biomejs/biome/issues/1984). Updated [`useHookAtTopLevel`](https://biomejs.dev/linter/rules/use-hook-at-top-level/) to better catch invalid hook usage. + +This rule is now capable of finding invalid hook usage in more locations. A diagnostic will now be generated if: +- A hook is used at the module level (top of the file, outside any function). +- A hook is used within a function or method which is not a hook or component, unless it is a function expression (such as arrow functions commonly used in tests). + +**Invalid:** + +```js +// Invalid: hooks cannot be called at the module level. +useHook(); +``` + +```js +// Invalid: hooks must be called from another hook or component. +function notAHook() { + useHook(); +} +``` + +**Valid:** + +```js +// Valid: hooks may be called from function expressions, such as in tests. +test("my hook", () => { + renderHook(() => useHook()); + + renderHook(function() { + return useHook(); + }); +}); +``` diff --git a/crates/biome_cli/tests/snapshots/main_commands_explain/explain_valid_rule_multiple_domains.snap b/crates/biome_cli/tests/snapshots/main_commands_explain/explain_valid_rule_multiple_domains.snap index 97ce910a073f..8c31cb8a2aef 100644 --- a/crates/biome_cli/tests/snapshots/main_commands_explain/explain_valid_rule_multiple_domains.snap +++ b/crates/biome_cli/tests/snapshots/main_commands_explain/explain_valid_rule_multiple_domains.snap @@ -56,6 +56,12 @@ Invalid } ``` + ```js,expect_diagnostic + function notAHook() { + useEffect(); + } + ``` + Valid ```js @@ -64,5 +70,11 @@ Valid } ``` + ```js + test("the hook", () => { + renderHook(() => useHook()); + }); + ``` + ``` diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/should_not_choke_on_recursive_function_call.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/should_not_choke_on_recursive_function_call.snap index 04168e98a3d5..4eca7b088f2c 100644 --- a/crates/biome_cli/tests/snapshots/main_commands_lint/should_not_choke_on_recursive_function_call.snap +++ b/crates/biome_cli/tests/snapshots/main_commands_lint/should_not_choke_on_recursive_function_call.snap @@ -78,7 +78,7 @@ src/hooks/useHook.ts:2:5 lint/correctness/useHookAtTopLevel ━━━━━━ i This means recursive calls are not allowed, because they require a condition in order to terminate. - i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/src/lint/correctness/use_hook_at_top_level.rs b/crates/biome_js_analyze/src/lint/correctness/use_hook_at_top_level.rs index 8f4a73af41ec..ce36148d9a28 100644 --- a/crates/biome_js_analyze/src/lint/correctness/use_hook_at_top_level.rs +++ b/crates/biome_js_analyze/src/lint/correctness/use_hook_at_top_level.rs @@ -53,6 +53,12 @@ declare_lint_rule! { /// } /// ``` /// + /// ```js,expect_diagnostic + /// function notAHook() { + /// useEffect(); + /// } + /// ``` + /// /// ### Valid /// /// ```js @@ -61,6 +67,12 @@ declare_lint_rule! { /// } /// ``` /// + /// ```js + /// test("the hook", () => { + /// renderHook(() => useHook()); + /// }); + /// ``` + /// pub UseHookAtTopLevel { version: "1.0.0", name: "useHookAtTopLevel", @@ -88,6 +100,16 @@ impl AnyJsFunctionOrMethod { false } + fn is_function_expression(&self) -> bool { + matches!( + self, + Self::AnyJsFunction( + AnyJsFunction::JsArrowFunctionExpression(_) + | AnyJsFunction::JsFunctionExpression(_) + ) + ) + } + fn name(&self) -> Option { match self { Self::AnyJsFunction(function) => function @@ -119,6 +141,8 @@ pub enum SuggestionKind { EarlyReturn(TextRange), Nested, Recursive, + TopLevel, + ComponentOrHook, } /// Verifies whether the call expression is at the top level of the component, @@ -245,6 +269,13 @@ fn is_nested_function_inside_component_or_hook(function: &AnyJsFunctionOrMethod) }) } +fn is_top_level_call(call: &JsCallExpression) -> bool { + !call + .syntax() + .ancestors() + .any(|node| AnyJsFunctionOrMethod::can_cast(node.kind())) +} + /// Model for tracking which function calls are preceded by an early return. /// /// The keys in the model are call sites and each value is the text range of an @@ -444,6 +475,14 @@ impl Rule for UseHookAtTopLevel { return None; } + if is_top_level_call(call) { + return Some(Suggestion { + hook_name_range: get_hook_name_range()?, + path: vec![call.syntax().text_range_with_trivia()], + kind: SuggestionKind::TopLevel, + }); + } + let model = ctx.semantic_model(); let early_returns = ctx.early_returns_model(); @@ -521,6 +560,16 @@ impl Rule for UseHookAtTopLevel { } } + if enclosing_function_if_call_is_at_top_level(call).is_some_and(|function| { + !function.is_react_component_or_hook() && !function.is_function_expression() + }) { + return Some(Suggestion { + hook_name_range: get_hook_name_range()?, + path: vec![call.syntax().text_range_with_trivia()], + kind: SuggestionKind::ComponentOrHook, + }); + } + None } @@ -537,6 +586,13 @@ impl Rule for UseHookAtTopLevel { "unconditionally from the top-level component." }, SuggestionKind::Recursive => markup! { "This hook is being called recursively." }, + SuggestionKind::TopLevel => markup! { + "This hook is being called at the module level, but all hooks must be called from " + "within a hook or component." + }, + SuggestionKind::ComponentOrHook => markup! { + "This hook is being called from within a function or method that is not a hook or component." + }, _ if path.len() <= 1 => markup! { "This hook is being called conditionally, but all hooks must be called in the " "exact same order in every component render." @@ -562,13 +618,18 @@ impl Rule for UseHookAtTopLevel { diag = diag.detail( *range, markup! { "Hooks should not be called after an early return." }, - ) + ); } - let mut diag = diag.note(markup! { - "For React to preserve state between calls, hooks needs to be called " - "unconditionally and always in the same order." - }); + diag = match kind { + SuggestionKind::TopLevel | SuggestionKind::ComponentOrHook => diag.note(markup! { + "Move the hook call into the top level of a hook or component in order to use it." + }), + _ => diag.note(markup! { + "For React to preserve state between calls, hooks needs to be called " + "unconditionally and always in the same order." + }), + }; if matches!(kind, SuggestionKind::Recursive) { diag = diag.note(markup! { @@ -577,8 +638,8 @@ impl Rule for UseHookAtTopLevel { }); } - let diag = diag.note(markup! { - "See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level" + diag = diag.note(markup! { + "See https://react.dev/reference/rules/rules-of-hooks" }); Some(diag) } 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 e8d1740811a0..a5e6952b1b54 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 @@ -1,7 +1,6 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs expression: customHook.js -snapshot_kind: text --- # Input ```js @@ -36,7 +35,7 @@ customHook.js:7:23 lint/correctness/useHookAtTopLevel ━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -55,7 +54,7 @@ customHook.js:12:23 lint/correctness/useHookAtTopLevel ━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js new file mode 100644 index 000000000000..6152d7b79204 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js @@ -0,0 +1,48 @@ +// Invalid as hooks cannot be used at module level. +useHook(); + +// Invalid as hooks cannot be called in non-component functions. +function notAComponent() { + useHook(); +} + +// Valid as hook is called in a component (by naming convention). +function AComponent() { + useHook(); +} + +// Invalid as hooks cannot be called in non-hook functions. +function notUseMyHook() { + useHook(); +} +const SomeObject = { + notHook() { + useHook(); + }, +}; +class SomeClass { + notHook() { + useHook(); + } +} + +// Valid as hook is called in a hook (by naming convention). +function useMyHook() { + useHook(); +} + +// Valid as hooks can be called within function expressions (for better or worse). +test("the hook", () => { + useHook(); +}); +test("the hook", function () { + useHook(); +}); +test("the hook", function named() { + useHook(); +}); + +// Valid as hooks can be called within nested function expressions. +test("more hook", () => { + renderHook(() => useHook()); +}); diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js.snap new file mode 100644 index 000000000000..af72aac26833 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/hookLocations.js.snap @@ -0,0 +1,151 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: hookLocations.js +--- +# Input +```js +// Invalid as hooks cannot be used at module level. +useHook(); + +// Invalid as hooks cannot be called in non-component functions. +function notAComponent() { + useHook(); +} + +// Valid as hook is called in a component (by naming convention). +function AComponent() { + useHook(); +} + +// Invalid as hooks cannot be called in non-hook functions. +function notUseMyHook() { + useHook(); +} +const SomeObject = { + notHook() { + useHook(); + }, +}; +class SomeClass { + notHook() { + useHook(); + } +} + +// Valid as hook is called in a hook (by naming convention). +function useMyHook() { + useHook(); +} + +// Valid as hooks can be called within function expressions (for better or worse). +test("the hook", () => { + useHook(); +}); +test("the hook", function () { + useHook(); +}); +test("the hook", function named() { + useHook(); +}); + +// Valid as hooks can be called within nested function expressions. +test("more hook", () => { + renderHook(() => useHook()); +}); + +``` + +# Diagnostics +``` +hookLocations.js:2:1 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × This hook is being called at the module level, but all hooks must be called from within a hook or component. + + 1 │ // Invalid as hooks cannot be used at module level. + > 2 │ useHook(); + │ ^^^^^^^ + 3 │ + 4 │ // Invalid as hooks cannot be called in non-component functions. + + i Move the hook call into the top level of a hook or component in order to use it. + + i See https://react.dev/reference/rules/rules-of-hooks + + +``` + +``` +hookLocations.js:6:2 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × This hook is being called from within a function or method that is not a hook or component. + + 4 │ // Invalid as hooks cannot be called in non-component functions. + 5 │ function notAComponent() { + > 6 │ useHook(); + │ ^^^^^^^ + 7 │ } + 8 │ + + i Move the hook call into the top level of a hook or component in order to use it. + + i See https://react.dev/reference/rules/rules-of-hooks + + +``` + +``` +hookLocations.js:16:2 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × This hook is being called from within a function or method that is not a hook or component. + + 14 │ // Invalid as hooks cannot be called in non-hook functions. + 15 │ function notUseMyHook() { + > 16 │ useHook(); + │ ^^^^^^^ + 17 │ } + 18 │ const SomeObject = { + + i Move the hook call into the top level of a hook or component in order to use it. + + i See https://react.dev/reference/rules/rules-of-hooks + + +``` + +``` +hookLocations.js:20:3 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × This hook is being called from within a function or method that is not a hook or component. + + 18 │ const SomeObject = { + 19 │ notHook() { + > 20 │ useHook(); + │ ^^^^^^^ + 21 │ }, + 22 │ }; + + i Move the hook call into the top level of a hook or component in order to use it. + + i See https://react.dev/reference/rules/rules-of-hooks + + +``` + +``` +hookLocations.js:25:3 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × This hook is being called from within a function or method that is not a hook or component. + + 23 │ class SomeClass { + 24 │ notHook() { + > 25 │ useHook(); + │ ^^^^^^^ + 26 │ } + 27 │ } + + i Move the hook call into the top level of a hook or component in order to use it. + + i See https://react.dev/reference/rules/rules-of-hooks + + +``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.js.snap index f876c827e257..52bced582d21 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: invalid.js --- # Input @@ -215,7 +214,7 @@ invalid.js:4:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -234,7 +233,7 @@ invalid.js:9:13 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -252,7 +251,7 @@ invalid.js:14:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -270,7 +269,7 @@ invalid.js:18:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -288,7 +287,7 @@ invalid.js:22:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -306,7 +305,7 @@ invalid.js:26:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -324,7 +323,7 @@ invalid.js:30:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -343,7 +342,7 @@ invalid.js:33:10 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -362,7 +361,7 @@ invalid.js:35:17 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -402,7 +401,7 @@ invalid.js:40:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -421,7 +420,7 @@ invalid.js:55:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -440,7 +439,7 @@ invalid.js:61:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -459,7 +458,7 @@ invalid.js:67:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -478,7 +477,7 @@ invalid.js:77:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -497,7 +496,7 @@ invalid.js:83:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -516,7 +515,7 @@ invalid.js:86:42 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -534,7 +533,7 @@ invalid.js:90:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -553,7 +552,7 @@ invalid.js:91:10 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -583,7 +582,7 @@ invalid.js:97:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -612,7 +611,7 @@ invalid.js:105:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -640,7 +639,7 @@ invalid.js:114:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -680,7 +679,7 @@ invalid.js:119:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -699,7 +698,7 @@ invalid.js:132:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -718,7 +717,7 @@ invalid.js:139:9 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -736,7 +735,7 @@ invalid.js:144:21 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -755,7 +754,7 @@ invalid.js:147:68 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -774,7 +773,7 @@ invalid.js:152:13 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -792,7 +791,7 @@ invalid.js:156:19 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -811,7 +810,7 @@ invalid.js:163:13 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -830,7 +829,7 @@ invalid.js:173:20 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -849,7 +848,7 @@ invalid.js:181:20 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -880,7 +879,7 @@ invalid.js:187:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━ i This means recursive calls are not allowed, because they require a condition in order to terminate. - i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -911,7 +910,7 @@ invalid.js:191:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━ i This means recursive calls are not allowed, because they require a condition in order to terminate. - i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.jsx.snap index 715a39a2e0b7..3d15f26e86c5 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.jsx.snap @@ -1,7 +1,6 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs expression: invalid.jsx -snapshot_kind: text --- # Input ```jsx @@ -33,7 +32,7 @@ invalid.jsx:4:39 lint/correctness/useHookAtTopLevel ━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.ts.snap index 62a2e67c5eb8..b88b5bdd8f84 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.ts.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalid.ts.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: invalid.ts --- # Input @@ -32,7 +31,7 @@ invalid.ts:7:7 lint/correctness/useHookAtTopLevel ━━━━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidCompositeHook.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidCompositeHook.js.snap index 9e2165f77dd7..fd11bbe5b2b6 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidCompositeHook.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidCompositeHook.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 152 expression: invalidCompositeHook.js --- # Input @@ -34,7 +33,7 @@ invalidCompositeHook.js:9:16 lint/correctness/useHookAtTopLevel ━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidWrapped.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidWrapped.js.snap index 27db60e66561..728badc99ab4 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidWrapped.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/invalidWrapped.js.snap @@ -1,6 +1,5 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 134 expression: invalidWrapped.js --- # Input @@ -38,7 +37,7 @@ invalidWrapped.js:4:37 lint/correctness/useHookAtTopLevel ━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` @@ -57,7 +56,7 @@ invalidWrapped.js:12:33 lint/correctness/useHookAtTopLevel ━━━━━━━ 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 + i See https://react.dev/reference/rules/rules-of-hooks ``` diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js index 22295390bf9b..b234f74d71d9 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js @@ -65,37 +65,28 @@ const obj = { } } -// Hook called indirectly -function helper() { - useEffect(); -} - -function Component2({ a }) { - helper(); -} - -const Component3 = () => { +const Component2 = () => { useEffect(); }; -export function Component4() { +export function Component3() { useEffect(); }; -export default function Component5() { +export default function Component4() { useEffect(); }; -const Component6 = () => { +const Component5 = () => { return useState(); }; -const Component7 = () => { +const Component6 = () => { const value = useRef().value; const [_val, _setter] = useState(useMemo('hello')); } -function Component8() { +function Component7() { const a = () => { return; }; diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js.snap index 53992b74e7c6..6bde6ba3b4c3 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/valid.js.snap @@ -71,37 +71,28 @@ const obj = { } } -// Hook called indirectly -function helper() { - useEffect(); -} - -function Component2({ a }) { - helper(); -} - -const Component3 = () => { +const Component2 = () => { useEffect(); }; -export function Component4() { +export function Component3() { useEffect(); }; -export default function Component5() { +export default function Component4() { useEffect(); }; -const Component6 = () => { +const Component5 = () => { return useState(); }; -const Component7 = () => { +const Component6 = () => { const value = useRef().value; const [_val, _setter] = useState(useMemo('hello')); } -function Component8() { +function Component7() { const a = () => { return; };