From 0e09c04c81cf7d5691bff332daf494aea7873811 Mon Sep 17 00:00:00 2001 From: Antony David Date: Thu, 31 Jul 2025 23:52:20 +0200 Subject: [PATCH 01/12] feat(lint): Add noAsyncClientComponent rule in Next domain --- .../migrate/eslint_any_rule_to_biome.rs | 12 + .../src/analyzer/linter/rules.rs | 289 ++++++++++-------- .../src/categories.rs | 1 + crates/biome_js_analyze/src/lint/nursery.rs | 3 +- .../lint/nursery/no_async_client_component.rs | 139 +++++++++ .../nursery/noAsyncClientComponent/invalid.js | 13 + .../noAsyncClientComponent/invalid.js.snap | 64 ++++ .../nursery/noAsyncClientComponent/valid.js | 20 ++ .../noAsyncClientComponent/valid.js.snap | 27 ++ crates/biome_rule_options/src/lib.rs | 1 + .../src/no_async_client_component.rs | 6 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 19 ++ .../@biomejs/biome/configuration_schema.json | 32 ++ 13 files changed, 491 insertions(+), 135 deletions(-) create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap create mode 100644 crates/biome_rule_options/src/no_async_client_component.rs diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index f9fd9b06b4c3..28498c8c33a6 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -61,6 +61,18 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "@next/no-async-client-component" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_async_client_component + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "@next/no-document-import-in-page" => { let group = rules.suspicious.get_or_insert_with(Default::default); let rule = group diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index ba29a254ffd3..655baafb6167 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -99,6 +99,7 @@ pub enum RuleName { NoAriaUnsupportedElements, NoArrayIndexKey, NoAssignInExpressions, + NoAsyncClientComponent, NoAsyncPromiseExecutor, NoAutofocus, NoAwaitInLoop, @@ -448,6 +449,7 @@ impl RuleName { Self::NoAriaUnsupportedElements => "noAriaUnsupportedElements", Self::NoArrayIndexKey => "noArrayIndexKey", Self::NoAssignInExpressions => "noAssignInExpressions", + Self::NoAsyncClientComponent => "noAsyncClientComponent", Self::NoAsyncPromiseExecutor => "noAsyncPromiseExecutor", Self::NoAutofocus => "noAutofocus", Self::NoAwaitInLoop => "noAwaitInLoop", @@ -801,6 +803,7 @@ impl RuleName { Self::NoAriaUnsupportedElements => RuleGroup::A11y, Self::NoArrayIndexKey => RuleGroup::Suspicious, Self::NoAssignInExpressions => RuleGroup::Suspicious, + Self::NoAsyncClientComponent => RuleGroup::Nursery, Self::NoAsyncPromiseExecutor => RuleGroup::Suspicious, Self::NoAutofocus => RuleGroup::A11y, Self::NoAwaitInLoop => RuleGroup::Nursery, @@ -1153,6 +1156,7 @@ impl std::str::FromStr for RuleName { "noAriaUnsupportedElements" => Ok(Self::NoAriaUnsupportedElements), "noArrayIndexKey" => Ok(Self::NoArrayIndexKey), "noAssignInExpressions" => Ok(Self::NoAssignInExpressions), + "noAsyncClientComponent" => Ok(Self::NoAsyncClientComponent), "noAsyncPromiseExecutor" => Ok(Self::NoAsyncPromiseExecutor), "noAutofocus" => Ok(Self::NoAutofocus), "noAwaitInLoop" => Ok(Self::NoAwaitInLoop), @@ -4302,10 +4306,11 @@ impl From for Correctness { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", default, deny_unknown_fields)] #[doc = r" A list of rules that belong to this group"] -pub struct Nursery { # [doc = r" It enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow await inside loops."] # [serde (skip_serializing_if = "Option::is_none")] pub no_await_in_loop : Option < RuleConfiguration < biome_rule_options :: no_await_in_loop :: NoAwaitInLoopOptions >> , # [doc = "Disallow bitwise operators."] # [serde (skip_serializing_if = "Option::is_none")] pub no_bitwise_operators : Option < RuleConfiguration < biome_rule_options :: no_bitwise_operators :: NoBitwiseOperatorsOptions >> , # [doc = "Disallow expressions where the operation doesn't affect the value"] # [serde (skip_serializing_if = "Option::is_none")] pub no_constant_binary_expression : Option < RuleConfiguration < biome_rule_options :: no_constant_binary_expression :: NoConstantBinaryExpressionOptions >> , # [doc = "Disallow destructuring props inside JSX components in Solid projects."] # [serde (skip_serializing_if = "Option::is_none")] pub no_destructured_props : Option < RuleConfiguration < biome_rule_options :: no_destructured_props :: NoDestructuredPropsOptions >> , # [doc = "Restrict the number of lines of code in a function."] # [serde (skip_serializing_if = "Option::is_none")] pub no_excessive_lines_per_function : Option < RuleConfiguration < biome_rule_options :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunctionOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow the use of __dirname and __filename in the global scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_global_dirname_filename : Option < RuleFixConfiguration < biome_rule_options :: no_global_dirname_filename :: NoGlobalDirnameFilenameOptions >> , # [doc = "Disallow shorthand type conversions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_implicit_coercion : Option < RuleFixConfiguration < biome_rule_options :: no_implicit_coercion :: NoImplicitCoercionOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow the use of the !important style."] # [serde (skip_serializing_if = "Option::is_none")] pub no_important_styles : Option < RuleFixConfiguration < biome_rule_options :: no_important_styles :: NoImportantStylesOptions >> , # [doc = "Reports usage of \"magic numbers\" — numbers used directly instead of being assigned to named constants."] # [serde (skip_serializing_if = "Option::is_none")] pub no_magic_numbers : Option < RuleConfiguration < biome_rule_options :: no_magic_numbers :: NoMagicNumbersOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallows defining React components inside other components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_nested_component_definitions : Option < RuleConfiguration < biome_rule_options :: no_nested_component_definitions :: NoNestedComponentDefinitionsOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow use event handlers on non-interactive elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_noninteractive_element_interactions : Option < RuleConfiguration < biome_rule_options :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractionsOptions >> , # [doc = "Disallow the use of process global."] # [serde (skip_serializing_if = "Option::is_none")] pub no_process_global : Option < RuleFixConfiguration < biome_rule_options :: no_process_global :: NoProcessGlobalOptions >> , # [doc = "Disallow the use if quickfix.biome inside editor settings file."] # [serde (skip_serializing_if = "Option::is_none")] pub no_quickfix_biome : Option < RuleFixConfiguration < biome_rule_options :: no_quickfix_biome :: NoQuickfixBiomeOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Disallow assigning to React component props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_prop_assign : Option < RuleConfiguration < biome_rule_options :: no_react_prop_assign :: NoReactPropAssignOptions >> , # [doc = "Disallow the use of configured elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_restricted_elements : Option < RuleConfiguration < biome_rule_options :: no_restricted_elements :: NoRestrictedElementsOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevents the use of the TypeScript directive @ts-ignore."] # [serde (skip_serializing_if = "Option::is_none")] pub no_ts_ignore : Option < RuleFixConfiguration < biome_rule_options :: no_ts_ignore :: NoTsIgnoreOptions >> , # [doc = "Disallow let or var variables that are read but never assigned."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unassigned_variables : Option < RuleConfiguration < biome_rule_options :: no_unassigned_variables :: NoUnassignedVariablesOptions >> , # [doc = "Disallow unknown at-rules."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_at_rule : Option < RuleConfiguration < biome_rule_options :: no_unknown_at_rule :: NoUnknownAtRuleOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Prevent duplicate polyfills from Polyfill.io."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unwanted_polyfillio : Option < RuleConfiguration < biome_rule_options :: no_unwanted_polyfillio :: NoUnwantedPolyfillioOptions >> , # [doc = "Disallow useless backreferences in regular expression literals that always match an empty string."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_backref_in_regex : Option < RuleConfiguration < biome_rule_options :: no_useless_backref_in_regex :: NoUselessBackrefInRegexOptions >> , # [doc = "Disallow unnecessary escapes in string literals."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_escape_in_string : Option < RuleFixConfiguration < biome_rule_options :: no_useless_escape_in_string :: NoUselessEscapeInStringOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforce that getters and setters for the same property are adjacent in class and object definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_adjacent_getter_setter : Option < RuleConfiguration < biome_rule_options :: use_adjacent_getter_setter :: UseAdjacentGetterSetterOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Require the consistent declaration of object literals. Defaults to explicit definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_object_definition : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_object_definition :: UseConsistentObjectDefinitionOptions >> , # [doc = "Use static Response methods instead of new Response() constructor when possible."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_response : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_response :: UseConsistentResponseOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Require that all exports are declared after all non-export statements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exports_last : Option < RuleConfiguration < biome_rule_options :: use_exports_last :: UseExportsLastOptions >> , # [doc = "Enforce using Solid's \\ component for mapping an array to JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_for_component : Option < RuleConfiguration < biome_rule_options :: use_for_component :: UseForComponentOptions >> , # [doc = "Ensure the preconnect attribute is used when using Google Fonts."] # [serde (skip_serializing_if = "Option::is_none")] pub use_google_font_preconnect : Option < RuleFixConfiguration < biome_rule_options :: use_google_font_preconnect :: UseGoogleFontPreconnectOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Prefer Array#{indexOf,lastIndexOf}() over Array#{findIndex,findLastIndex}() when looking for the index of an item."] # [serde (skip_serializing_if = "Option::is_none")] pub use_index_of : Option < RuleFixConfiguration < biome_rule_options :: use_index_of :: UseIndexOfOptions >> , # [doc = "Enforce consistent return values in iterable callbacks."] # [serde (skip_serializing_if = "Option::is_none")] pub use_iterable_callback_return : Option < RuleConfiguration < biome_rule_options :: use_iterable_callback_return :: UseIterableCallbackReturnOptions >> , # [doc = "Enforces the use of with { type: \"json\" } for JSON module imports."] # [serde (skip_serializing_if = "Option::is_none")] pub use_json_import_attribute : Option < RuleFixConfiguration < biome_rule_options :: use_json_import_attribute :: UseJsonImportAttributeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Enforce specifying the name of GraphQL operations."] # [serde (skip_serializing_if = "Option::is_none")] pub use_named_operation : Option < RuleFixConfiguration < biome_rule_options :: use_named_operation :: UseNamedOperationOptions >> , # [doc = "Validates that all enum values are capitalized."] # [serde (skip_serializing_if = "Option::is_none")] pub use_naming_convention : Option < RuleConfiguration < biome_rule_options :: use_naming_convention :: UseNamingConventionOptions >> , # [doc = "Enforce the use of numeric separators in numeric literals."] # [serde (skip_serializing_if = "Option::is_none")] pub use_numeric_separators : Option < RuleFixConfiguration < biome_rule_options :: use_numeric_separators :: UseNumericSeparatorsOptions >> , # [doc = "Prefer object spread over Object.assign() when constructing new objects."] # [serde (skip_serializing_if = "Option::is_none")] pub use_object_spread : Option < RuleFixConfiguration < biome_rule_options :: use_object_spread :: UseObjectSpreadOptions >> , # [doc = "Enforce the consistent use of the radix argument when using parseInt()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_parse_int_radix : Option < RuleFixConfiguration < biome_rule_options :: use_parse_int_radix :: UseParseIntRadixOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce marking members as readonly if they are never modified outside the constructor."] # [serde (skip_serializing_if = "Option::is_none")] pub use_readonly_class_properties : Option < RuleFixConfiguration < biome_rule_options :: use_readonly_class_properties :: UseReadonlyClassPropertiesOptions >> , # [doc = "Enforce JSDoc comment lines to start with a single asterisk, except for the first one."] # [serde (skip_serializing_if = "Option::is_none")] pub use_single_js_doc_asterisk : Option < RuleFixConfiguration < biome_rule_options :: use_single_js_doc_asterisk :: UseSingleJsDocAsteriskOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Require a description parameter for the Symbol()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_symbol_description : Option < RuleConfiguration < biome_rule_options :: use_symbol_description :: UseSymbolDescriptionOptions >> , # [doc = "Disallow overload signatures that can be unified into a single signature."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unified_type_signature : Option < RuleFixConfiguration < biome_rule_options :: use_unified_type_signature :: UseUnifiedTypeSignatureOptions >> , # [doc = "Prevent the usage of static string literal id attribute on elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_element_ids : Option < RuleConfiguration < biome_rule_options :: use_unique_element_ids :: UseUniqueElementIdsOptions >> } +pub struct Nursery { # [doc = r" It enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_async_client_component :: NoAsyncClientComponentOptions >> , # [doc = "Disallow await inside loops."] # [serde (skip_serializing_if = "Option::is_none")] pub no_await_in_loop : Option < RuleConfiguration < biome_rule_options :: no_await_in_loop :: NoAwaitInLoopOptions >> , # [doc = "Disallow bitwise operators."] # [serde (skip_serializing_if = "Option::is_none")] pub no_bitwise_operators : Option < RuleConfiguration < biome_rule_options :: no_bitwise_operators :: NoBitwiseOperatorsOptions >> , # [doc = "Disallow expressions where the operation doesn't affect the value"] # [serde (skip_serializing_if = "Option::is_none")] pub no_constant_binary_expression : Option < RuleConfiguration < biome_rule_options :: no_constant_binary_expression :: NoConstantBinaryExpressionOptions >> , # [doc = "Disallow destructuring props inside JSX components in Solid projects."] # [serde (skip_serializing_if = "Option::is_none")] pub no_destructured_props : Option < RuleConfiguration < biome_rule_options :: no_destructured_props :: NoDestructuredPropsOptions >> , # [doc = "Restrict the number of lines of code in a function."] # [serde (skip_serializing_if = "Option::is_none")] pub no_excessive_lines_per_function : Option < RuleConfiguration < biome_rule_options :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunctionOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow the use of __dirname and __filename in the global scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_global_dirname_filename : Option < RuleFixConfiguration < biome_rule_options :: no_global_dirname_filename :: NoGlobalDirnameFilenameOptions >> , # [doc = "Disallow shorthand type conversions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_implicit_coercion : Option < RuleFixConfiguration < biome_rule_options :: no_implicit_coercion :: NoImplicitCoercionOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow the use of the !important style."] # [serde (skip_serializing_if = "Option::is_none")] pub no_important_styles : Option < RuleFixConfiguration < biome_rule_options :: no_important_styles :: NoImportantStylesOptions >> , # [doc = "Reports usage of \"magic numbers\" — numbers used directly instead of being assigned to named constants."] # [serde (skip_serializing_if = "Option::is_none")] pub no_magic_numbers : Option < RuleConfiguration < biome_rule_options :: no_magic_numbers :: NoMagicNumbersOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallows defining React components inside other components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_nested_component_definitions : Option < RuleConfiguration < biome_rule_options :: no_nested_component_definitions :: NoNestedComponentDefinitionsOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow use event handlers on non-interactive elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_noninteractive_element_interactions : Option < RuleConfiguration < biome_rule_options :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractionsOptions >> , # [doc = "Disallow the use of process global."] # [serde (skip_serializing_if = "Option::is_none")] pub no_process_global : Option < RuleFixConfiguration < biome_rule_options :: no_process_global :: NoProcessGlobalOptions >> , # [doc = "Disallow the use if quickfix.biome inside editor settings file."] # [serde (skip_serializing_if = "Option::is_none")] pub no_quickfix_biome : Option < RuleFixConfiguration < biome_rule_options :: no_quickfix_biome :: NoQuickfixBiomeOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Disallow assigning to React component props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_prop_assign : Option < RuleConfiguration < biome_rule_options :: no_react_prop_assign :: NoReactPropAssignOptions >> , # [doc = "Disallow the use of configured elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_restricted_elements : Option < RuleConfiguration < biome_rule_options :: no_restricted_elements :: NoRestrictedElementsOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevents the use of the TypeScript directive @ts-ignore."] # [serde (skip_serializing_if = "Option::is_none")] pub no_ts_ignore : Option < RuleFixConfiguration < biome_rule_options :: no_ts_ignore :: NoTsIgnoreOptions >> , # [doc = "Disallow let or var variables that are read but never assigned."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unassigned_variables : Option < RuleConfiguration < biome_rule_options :: no_unassigned_variables :: NoUnassignedVariablesOptions >> , # [doc = "Disallow unknown at-rules."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_at_rule : Option < RuleConfiguration < biome_rule_options :: no_unknown_at_rule :: NoUnknownAtRuleOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Prevent duplicate polyfills from Polyfill.io."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unwanted_polyfillio : Option < RuleConfiguration < biome_rule_options :: no_unwanted_polyfillio :: NoUnwantedPolyfillioOptions >> , # [doc = "Disallow useless backreferences in regular expression literals that always match an empty string."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_backref_in_regex : Option < RuleConfiguration < biome_rule_options :: no_useless_backref_in_regex :: NoUselessBackrefInRegexOptions >> , # [doc = "Disallow unnecessary escapes in string literals."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_escape_in_string : Option < RuleFixConfiguration < biome_rule_options :: no_useless_escape_in_string :: NoUselessEscapeInStringOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforce that getters and setters for the same property are adjacent in class and object definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_adjacent_getter_setter : Option < RuleConfiguration < biome_rule_options :: use_adjacent_getter_setter :: UseAdjacentGetterSetterOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Require the consistent declaration of object literals. Defaults to explicit definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_object_definition : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_object_definition :: UseConsistentObjectDefinitionOptions >> , # [doc = "Use static Response methods instead of new Response() constructor when possible."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_response : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_response :: UseConsistentResponseOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Require that all exports are declared after all non-export statements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exports_last : Option < RuleConfiguration < biome_rule_options :: use_exports_last :: UseExportsLastOptions >> , # [doc = "Enforce using Solid's \\ component for mapping an array to JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_for_component : Option < RuleConfiguration < biome_rule_options :: use_for_component :: UseForComponentOptions >> , # [doc = "Ensure the preconnect attribute is used when using Google Fonts."] # [serde (skip_serializing_if = "Option::is_none")] pub use_google_font_preconnect : Option < RuleFixConfiguration < biome_rule_options :: use_google_font_preconnect :: UseGoogleFontPreconnectOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Prefer Array#{indexOf,lastIndexOf}() over Array#{findIndex,findLastIndex}() when looking for the index of an item."] # [serde (skip_serializing_if = "Option::is_none")] pub use_index_of : Option < RuleFixConfiguration < biome_rule_options :: use_index_of :: UseIndexOfOptions >> , # [doc = "Enforce consistent return values in iterable callbacks."] # [serde (skip_serializing_if = "Option::is_none")] pub use_iterable_callback_return : Option < RuleConfiguration < biome_rule_options :: use_iterable_callback_return :: UseIterableCallbackReturnOptions >> , # [doc = "Enforces the use of with { type: \"json\" } for JSON module imports."] # [serde (skip_serializing_if = "Option::is_none")] pub use_json_import_attribute : Option < RuleFixConfiguration < biome_rule_options :: use_json_import_attribute :: UseJsonImportAttributeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Enforce specifying the name of GraphQL operations."] # [serde (skip_serializing_if = "Option::is_none")] pub use_named_operation : Option < RuleFixConfiguration < biome_rule_options :: use_named_operation :: UseNamedOperationOptions >> , # [doc = "Validates that all enum values are capitalized."] # [serde (skip_serializing_if = "Option::is_none")] pub use_naming_convention : Option < RuleConfiguration < biome_rule_options :: use_naming_convention :: UseNamingConventionOptions >> , # [doc = "Enforce the use of numeric separators in numeric literals."] # [serde (skip_serializing_if = "Option::is_none")] pub use_numeric_separators : Option < RuleFixConfiguration < biome_rule_options :: use_numeric_separators :: UseNumericSeparatorsOptions >> , # [doc = "Prefer object spread over Object.assign() when constructing new objects."] # [serde (skip_serializing_if = "Option::is_none")] pub use_object_spread : Option < RuleFixConfiguration < biome_rule_options :: use_object_spread :: UseObjectSpreadOptions >> , # [doc = "Enforce the consistent use of the radix argument when using parseInt()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_parse_int_radix : Option < RuleFixConfiguration < biome_rule_options :: use_parse_int_radix :: UseParseIntRadixOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce marking members as readonly if they are never modified outside the constructor."] # [serde (skip_serializing_if = "Option::is_none")] pub use_readonly_class_properties : Option < RuleFixConfiguration < biome_rule_options :: use_readonly_class_properties :: UseReadonlyClassPropertiesOptions >> , # [doc = "Enforce JSDoc comment lines to start with a single asterisk, except for the first one."] # [serde (skip_serializing_if = "Option::is_none")] pub use_single_js_doc_asterisk : Option < RuleFixConfiguration < biome_rule_options :: use_single_js_doc_asterisk :: UseSingleJsDocAsteriskOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Require a description parameter for the Symbol()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_symbol_description : Option < RuleConfiguration < biome_rule_options :: use_symbol_description :: UseSymbolDescriptionOptions >> , # [doc = "Disallow overload signatures that can be unified into a single signature."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unified_type_signature : Option < RuleFixConfiguration < biome_rule_options :: use_unified_type_signature :: UseUnifiedTypeSignatureOptions >> , # [doc = "Prevent the usage of static string literal id attribute on elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_element_ids : Option < RuleConfiguration < biome_rule_options :: use_unique_element_ids :: UseUniqueElementIdsOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ + "noAsyncClientComponent", "noAwaitInLoop", "noBitwiseOperators", "noConstantBinaryExpression", @@ -4370,17 +4375,17 @@ impl Nursery { "useUniqueElementIds", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -4445,6 +4450,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62]), ]; } impl RuleGroupExt for Nursery { @@ -4456,630 +4462,640 @@ impl RuleGroupExt for Nursery { } fn get_enabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_await_in_loop.as_ref() { + if let Some(rule) = self.no_async_client_component.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_bitwise_operators.as_ref() { + if let Some(rule) = self.no_await_in_loop.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_constant_binary_expression.as_ref() { + if let Some(rule) = self.no_bitwise_operators.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_destructured_props.as_ref() { + if let Some(rule) = self.no_constant_binary_expression.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { + if let Some(rule) = self.no_destructured_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_floating_promises.as_ref() { + if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_global_dirname_filename.as_ref() { + if let Some(rule) = self.no_floating_promises.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_implicit_coercion.as_ref() { + if let Some(rule) = self.no_global_dirname_filename.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_import_cycles.as_ref() { + if let Some(rule) = self.no_implicit_coercion.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_important_styles.as_ref() { + if let Some(rule) = self.no_import_cycles.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_magic_numbers.as_ref() { + if let Some(rule) = self.no_important_styles.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_misused_promises.as_ref() { + if let Some(rule) = self.no_magic_numbers.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_nested_component_definitions.as_ref() { + if let Some(rule) = self.no_misused_promises.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() { + if let Some(rule) = self.no_nested_component_definitions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_noninteractive_element_interactions.as_ref() { + if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_process_global.as_ref() { + if let Some(rule) = self.no_noninteractive_element_interactions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_quickfix_biome.as_ref() { + if let Some(rule) = self.no_process_global.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_qwik_use_visible_task.as_ref() { + if let Some(rule) = self.no_quickfix_biome.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.no_react_prop_assign.as_ref() { + if let Some(rule) = self.no_qwik_use_visible_task.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.no_restricted_elements.as_ref() { + if let Some(rule) = self.no_react_prop_assign.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_secrets.as_ref() { + if let Some(rule) = self.no_restricted_elements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_shadow.as_ref() { + if let Some(rule) = self.no_secrets.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_ts_ignore.as_ref() { + if let Some(rule) = self.no_shadow.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_unassigned_variables.as_ref() { + if let Some(rule) = self.no_ts_ignore.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_unknown_at_rule.as_ref() { + if let Some(rule) = self.no_unassigned_variables.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() { + if let Some(rule) = self.no_unknown_at_rule.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_unresolved_imports.as_ref() { + if let Some(rule) = self.no_unnecessary_conditions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.no_unwanted_polyfillio.as_ref() { + if let Some(rule) = self.no_unresolved_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.no_useless_backref_in_regex.as_ref() { + if let Some(rule) = self.no_unwanted_polyfillio.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.no_useless_escape_in_string.as_ref() { + if let Some(rule) = self.no_useless_backref_in_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_useless_undefined.as_ref() { + if let Some(rule) = self.no_useless_escape_in_string.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() { + if let Some(rule) = self.no_useless_undefined.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() { + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_vue_reserved_props.as_ref() { + if let Some(rule) = self.no_vue_reserved_keys.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_adjacent_getter_setter.as_ref() { + if let Some(rule) = self.no_vue_reserved_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_anchor_href.as_ref() { + if let Some(rule) = self.use_adjacent_getter_setter.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_consistent_object_definition.as_ref() { + if let Some(rule) = self.use_anchor_href.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_consistent_response.as_ref() { + if let Some(rule) = self.use_consistent_object_definition.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_consistent_type_definitions.as_ref() { + if let Some(rule) = self.use_consistent_response.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() { + if let Some(rule) = self.use_consistent_type_definitions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_explicit_type.as_ref() { + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_exports_last.as_ref() { + if let Some(rule) = self.use_explicit_type.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_for_component.as_ref() { + if let Some(rule) = self.use_exports_last.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_google_font_preconnect.as_ref() { + if let Some(rule) = self.use_for_component.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_image_size.as_ref() { + if let Some(rule) = self.use_google_font_preconnect.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_index_of.as_ref() { + if let Some(rule) = self.use_image_size.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_iterable_callback_return.as_ref() { + if let Some(rule) = self.use_index_of.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_json_import_attribute.as_ref() { + if let Some(rule) = self.use_iterable_callback_return.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_max_params.as_ref() { + if let Some(rule) = self.use_json_import_attribute.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_named_operation.as_ref() { + if let Some(rule) = self.use_max_params.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_naming_convention.as_ref() { + if let Some(rule) = self.use_named_operation.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_numeric_separators.as_ref() { + if let Some(rule) = self.use_naming_convention.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_object_spread.as_ref() { + if let Some(rule) = self.use_numeric_separators.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_parse_int_radix.as_ref() { + if let Some(rule) = self.use_object_spread.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_qwik_classlist.as_ref() { + if let Some(rule) = self.use_parse_int_radix.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_react_function_components.as_ref() { + if let Some(rule) = self.use_qwik_classlist.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_readonly_class_properties.as_ref() { + if let Some(rule) = self.use_react_function_components.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_single_js_doc_asterisk.as_ref() { + if let Some(rule) = self.use_readonly_class_properties.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_single_js_doc_asterisk.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_symbol_description.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } - if let Some(rule) = self.use_unified_type_signature.as_ref() { + if let Some(rule) = self.use_symbol_description.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); } } - if let Some(rule) = self.use_unique_element_ids.as_ref() { + if let Some(rule) = self.use_unified_type_signature.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); } } + if let Some(rule) = self.use_unique_element_ids.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); + } + } index_set } fn get_disabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_await_in_loop.as_ref() { + if let Some(rule) = self.no_async_client_component.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_bitwise_operators.as_ref() { + if let Some(rule) = self.no_await_in_loop.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_constant_binary_expression.as_ref() { + if let Some(rule) = self.no_bitwise_operators.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_destructured_props.as_ref() { + if let Some(rule) = self.no_constant_binary_expression.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { + if let Some(rule) = self.no_destructured_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_floating_promises.as_ref() { + if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_global_dirname_filename.as_ref() { + if let Some(rule) = self.no_floating_promises.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_implicit_coercion.as_ref() { + if let Some(rule) = self.no_global_dirname_filename.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_import_cycles.as_ref() { + if let Some(rule) = self.no_implicit_coercion.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_important_styles.as_ref() { + if let Some(rule) = self.no_import_cycles.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_magic_numbers.as_ref() { + if let Some(rule) = self.no_important_styles.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_misused_promises.as_ref() { + if let Some(rule) = self.no_magic_numbers.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_nested_component_definitions.as_ref() { + if let Some(rule) = self.no_misused_promises.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() { + if let Some(rule) = self.no_nested_component_definitions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_noninteractive_element_interactions.as_ref() { + if let Some(rule) = self.no_non_null_asserted_optional_chain.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_process_global.as_ref() { + if let Some(rule) = self.no_noninteractive_element_interactions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_quickfix_biome.as_ref() { + if let Some(rule) = self.no_process_global.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_qwik_use_visible_task.as_ref() { + if let Some(rule) = self.no_quickfix_biome.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.no_react_prop_assign.as_ref() { + if let Some(rule) = self.no_qwik_use_visible_task.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.no_restricted_elements.as_ref() { + if let Some(rule) = self.no_react_prop_assign.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_secrets.as_ref() { + if let Some(rule) = self.no_restricted_elements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_shadow.as_ref() { + if let Some(rule) = self.no_secrets.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_ts_ignore.as_ref() { + if let Some(rule) = self.no_shadow.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_unassigned_variables.as_ref() { + if let Some(rule) = self.no_ts_ignore.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_unknown_at_rule.as_ref() { + if let Some(rule) = self.no_unassigned_variables.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() { + if let Some(rule) = self.no_unknown_at_rule.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_unresolved_imports.as_ref() { + if let Some(rule) = self.no_unnecessary_conditions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.no_unwanted_polyfillio.as_ref() { + if let Some(rule) = self.no_unresolved_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.no_useless_backref_in_regex.as_ref() { + if let Some(rule) = self.no_unwanted_polyfillio.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.no_useless_escape_in_string.as_ref() { + if let Some(rule) = self.no_useless_backref_in_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_useless_undefined.as_ref() { + if let Some(rule) = self.no_useless_escape_in_string.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() { + if let Some(rule) = self.no_useless_undefined.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() { + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_vue_reserved_props.as_ref() { + if let Some(rule) = self.no_vue_reserved_keys.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_adjacent_getter_setter.as_ref() { + if let Some(rule) = self.no_vue_reserved_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_anchor_href.as_ref() { + if let Some(rule) = self.use_adjacent_getter_setter.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_consistent_object_definition.as_ref() { + if let Some(rule) = self.use_anchor_href.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_consistent_response.as_ref() { + if let Some(rule) = self.use_consistent_object_definition.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_consistent_type_definitions.as_ref() { + if let Some(rule) = self.use_consistent_response.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() { + if let Some(rule) = self.use_consistent_type_definitions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_explicit_type.as_ref() { + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_exports_last.as_ref() { + if let Some(rule) = self.use_explicit_type.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_for_component.as_ref() { + if let Some(rule) = self.use_exports_last.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_google_font_preconnect.as_ref() { + if let Some(rule) = self.use_for_component.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_image_size.as_ref() { + if let Some(rule) = self.use_google_font_preconnect.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_index_of.as_ref() { + if let Some(rule) = self.use_image_size.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_iterable_callback_return.as_ref() { + if let Some(rule) = self.use_index_of.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_json_import_attribute.as_ref() { + if let Some(rule) = self.use_iterable_callback_return.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_max_params.as_ref() { + if let Some(rule) = self.use_json_import_attribute.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_named_operation.as_ref() { + if let Some(rule) = self.use_max_params.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_naming_convention.as_ref() { + if let Some(rule) = self.use_named_operation.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_numeric_separators.as_ref() { + if let Some(rule) = self.use_naming_convention.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_object_spread.as_ref() { + if let Some(rule) = self.use_numeric_separators.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_parse_int_radix.as_ref() { + if let Some(rule) = self.use_object_spread.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_qwik_classlist.as_ref() { + if let Some(rule) = self.use_parse_int_radix.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_react_function_components.as_ref() { + if let Some(rule) = self.use_qwik_classlist.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_readonly_class_properties.as_ref() { + if let Some(rule) = self.use_react_function_components.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_single_js_doc_asterisk.as_ref() { + if let Some(rule) = self.use_readonly_class_properties.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_single_js_doc_asterisk.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_symbol_description.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } - if let Some(rule) = self.use_unified_type_signature.as_ref() { + if let Some(rule) = self.use_symbol_description.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); } } - if let Some(rule) = self.use_unique_element_ids.as_ref() { + if let Some(rule) = self.use_unified_type_signature.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); } } + if let Some(rule) = self.use_unique_element_ids.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -5110,6 +5126,10 @@ impl RuleGroupExt for Nursery { rule_name: &str, ) -> Option<(RulePlainConfiguration, Option)> { match rule_name { + "noAsyncClientComponent" => self + .no_async_client_component + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noAwaitInLoop" => self .no_await_in_loop .as_ref() @@ -5366,6 +5386,7 @@ impl From for Nursery { fn from(value: GroupPlainConfiguration) -> Self { Self { recommended: None, + no_async_client_component: Some(value.into()), no_await_in_loop: Some(value.into()), no_bitwise_operators: Some(value.into()), no_constant_binary_expression: Some(value.into()), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 086c231b7229..08fb4aa2f5a7 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -146,6 +146,7 @@ define_categories! { "lint/correctness/useValidTypeof": "https://biomejs.dev/linter/rules/use-valid-typeof", "lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield", "lint/nursery/colorNoInvalidHex": "https://biomejs.dev/linter/rules/color-no-invalid-hex", + "lint/nursery/noAsyncClientComponent": "https://biomejs.dev/linter/rules/no-async-client-component", "lint/nursery/noAwaitInLoop": "https://biomejs.dev/linter/rules/no-await-in-loop", "lint/nursery/noBitwiseOperators": "https://biomejs.dev/linter/rules/no-bitwise-operators", "lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 32bbb4796f00..3a7131269e1c 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -3,6 +3,7 @@ //! Generated file, do not edit by hand, see `xtask/codegen` use biome_analyze::declare_lint_group; +pub mod no_async_client_component; pub mod no_await_in_loop; pub mod no_bitwise_operators; pub mod no_constant_binary_expression; @@ -60,4 +61,4 @@ pub mod use_sorted_classes; pub mod use_symbol_description; pub mod use_unified_type_signature; pub mod use_unique_element_ids; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_await_in_loop :: NoAwaitInLoop , self :: no_bitwise_operators :: NoBitwiseOperators , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunction , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_implicit_coercion :: NoImplicitCoercion , self :: no_import_cycles :: NoImportCycles , self :: no_magic_numbers :: NoMagicNumbers , self :: no_misused_promises :: NoMisusedPromises , self :: no_nested_component_definitions :: NoNestedComponentDefinitions , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_process_global :: NoProcessGlobal , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_prop_assign :: NoReactPropAssign , self :: no_restricted_elements :: NoRestrictedElements , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_ts_ignore :: NoTsIgnore , self :: no_unassigned_variables :: NoUnassignedVariables , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_backref_in_regex :: NoUselessBackrefInRegex , self :: no_useless_escape_in_string :: NoUselessEscapeInString , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_adjacent_getter_setter :: UseAdjacentGetterSetter , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_consistent_response :: UseConsistentResponse , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_for_component :: UseForComponent , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_image_size :: UseImageSize , self :: use_index_of :: UseIndexOf , self :: use_iterable_callback_return :: UseIterableCallbackReturn , self :: use_json_import_attribute :: UseJsonImportAttribute , self :: use_max_params :: UseMaxParams , self :: use_numeric_separators :: UseNumericSeparators , self :: use_object_spread :: UseObjectSpread , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_readonly_class_properties :: UseReadonlyClassProperties , self :: use_single_js_doc_asterisk :: UseSingleJsDocAsterisk , self :: use_sorted_classes :: UseSortedClasses , self :: use_symbol_description :: UseSymbolDescription , self :: use_unified_type_signature :: UseUnifiedTypeSignature , self :: use_unique_element_ids :: UseUniqueElementIds ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_async_client_component :: NoAsyncClientComponent , self :: no_await_in_loop :: NoAwaitInLoop , self :: no_bitwise_operators :: NoBitwiseOperators , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunction , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_implicit_coercion :: NoImplicitCoercion , self :: no_import_cycles :: NoImportCycles , self :: no_magic_numbers :: NoMagicNumbers , self :: no_misused_promises :: NoMisusedPromises , self :: no_nested_component_definitions :: NoNestedComponentDefinitions , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_process_global :: NoProcessGlobal , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_prop_assign :: NoReactPropAssign , self :: no_restricted_elements :: NoRestrictedElements , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_ts_ignore :: NoTsIgnore , self :: no_unassigned_variables :: NoUnassignedVariables , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_backref_in_regex :: NoUselessBackrefInRegex , self :: no_useless_escape_in_string :: NoUselessEscapeInString , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_adjacent_getter_setter :: UseAdjacentGetterSetter , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_consistent_response :: UseConsistentResponse , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_for_component :: UseForComponent , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_image_size :: UseImageSize , self :: use_index_of :: UseIndexOf , self :: use_iterable_callback_return :: UseIterableCallbackReturn , self :: use_json_import_attribute :: UseJsonImportAttribute , self :: use_max_params :: UseMaxParams , self :: use_numeric_separators :: UseNumericSeparators , self :: use_object_spread :: UseObjectSpread , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_readonly_class_properties :: UseReadonlyClassProperties , self :: use_single_js_doc_asterisk :: UseSingleJsDocAsterisk , self :: use_sorted_classes :: UseSortedClasses , self :: use_symbol_description :: UseSymbolDescription , self :: use_unified_type_signature :: UseUnifiedTypeSignature , self :: use_unique_element_ids :: UseUniqueElementIds ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs new file mode 100644 index 000000000000..d10092b07a3d --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs @@ -0,0 +1,139 @@ +use crate::react::components::{ + AnyPotentialReactComponentDeclaration, ReactComponentInfo, ReactComponentKind, +}; +use biome_analyze::{ + Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, +}; +use biome_console::markup; +use biome_diagnostics::Severity; +use biome_js_syntax::AnyJsRoot; +use biome_rowan::{AstNode, AstNodeList}; +use biome_rule_options::no_async_client_component::NoAsyncClientComponentOptions; + +declare_lint_rule! { + /// Prevent client components from being async functions. + /// + /// This rule prevents the use of async functions for client components in Next.js applications. + /// Client components marked with "use client" directive should not be async as this can cause + /// hydration mismatches, break component rendering lifecycle, and lead to unexpected behavior + /// with React's concurrent features. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// "use client"; + /// + /// export default async function MyComponent() { + /// return
Hello
; + /// } + /// ``` + /// + /// ### Valid + /// + /// ```js + /// "use client"; + /// + /// export default function MyComponent() { + /// return
Hello
; + /// } + /// ``` + /// + /// ```js + /// // No "use client" directive - server component can be async + /// export default async function ServerComponent() { + /// const data = await fetch('/api/data'); + /// return
{data}
; + /// } + /// ``` + /// + pub NoAsyncClientComponent { + version: "next", + name: "noAsyncClientComponent", + language: "js", + sources: &[RuleSource::EslintNext("no-async-client-component").same()], + recommended: false, + severity: Severity::Warning, + domains: &[RuleDomain::Next], + } +} + +impl Rule for NoAsyncClientComponent { + type Query = Ast; + type State = String; + type Signals = Option; + type Options = NoAsyncClientComponentOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let declaration = ctx.query(); + let component = ReactComponentInfo::from_declaration(declaration.syntax())?; + + // Only check function components + let ReactComponentKind::Function(_) = component.kind else { + return None; + }; + + // Check if we're in a module with "use client" directive + let root = ctx.root(); + let has_use_client = match root { + AnyJsRoot::JsModule(module) => module.directives().iter().any(|directive| { + directive + .inner_string_text() + .is_ok_and(|text| text.text() == "use client") + }), + AnyJsRoot::JsScript(script) => script.directives().iter().any(|directive| { + directive + .inner_string_text() + .is_ok_and(|text| text.text() == "use client") + }), + _ => false, + }; + + if !has_use_client { + return None; + } + + // Check if the component function is async + let is_async = match declaration { + AnyPotentialReactComponentDeclaration::JsFunctionDeclaration(func) => { + func.async_token().is_some() + } + AnyPotentialReactComponentDeclaration::JsFunctionExportDefaultDeclaration(func) => { + func.async_token().is_some() + } + _ => false, + }; + + if !is_async { + return None; + } + + let component_name = component.name.or(component.name_hint).map_or_else( + || "Component".to_string(), + |token| token.text_trimmed().to_string(), + ); + + Some(component_name) + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let declaration = ctx.query(); + + Some( + RuleDiagnostic::new( + rule_category!(), + declaration.range(), + markup! { + "Async client component "{state}" is not allowed." + }, + ) + .note(markup! { + "Client components with \"use client\" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle." + }) + .note(markup! { + "Consider using useEffect for async operations inside the component, or remove the \"use client\" directive if this should be a server component." + }), + ) + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js new file mode 100644 index 000000000000..55e80398770b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js @@ -0,0 +1,13 @@ +"use client"; + +export default async function MyComponent() { + return
Hello
; +} + +// Another case +"use client"; + +async function AnotherComponent() { + return World; +} +export default AnotherComponent; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap new file mode 100644 index 000000000000..039439ba2516 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap @@ -0,0 +1,64 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +"use client"; + +export default async function MyComponent() { + return
Hello
; +} + +// Another case +"use client"; + +async function AnotherComponent() { + return World; +} +export default AnotherComponent; +``` + +# Diagnostics +``` +invalid.js:3:16 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Async client component MyComponent is not allowed. + + 1 │ "use client"; + 2 │ + > 3 │ export default async function MyComponent() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 4 │ return
Hello
; + > 5 │ } + │ ^ + 6 │ + 7 │ // Another case + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.js:10:1 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Async client component AnotherComponent is not allowed. + + 8 │ "use client"; + 9 │ + > 10 │ async function AnotherComponent() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 11 │ return World; + > 12 │ } + │ ^ + 13 │ export default AnotherComponent; + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js new file mode 100644 index 000000000000..da2f242571e5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js @@ -0,0 +1,20 @@ +/* should not generate diagnostics */ + +// No "use client" directive - async function is allowed +export default async function MyComponent() { + return
Hello
; +} + +// "use client" but function name doesn't start with uppercase +"use client"; + +export default async function myFunction() { + return 'not a component'; +} + +// "use client" but not async +"use client"; + +export default function MyComponent() { + return
Hello
; +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap new file mode 100644 index 000000000000..d3f7fbd8caf0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap @@ -0,0 +1,27 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +/* should not generate diagnostics */ + +// No "use client" directive - async function is allowed +export default async function MyComponent() { + return
Hello
; +} + +// "use client" but function name doesn't start with uppercase +"use client"; + +export default async function myFunction() { + return 'not a component'; +} + +// "use client" but not async +"use client"; + +export default function MyComponent() { + return
Hello
; +} +``` diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 224bb9d79a8c..24a35ec361c5 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -12,6 +12,7 @@ pub mod no_aria_hidden_on_focusable; pub mod no_aria_unsupported_elements; pub mod no_array_index_key; pub mod no_assign_in_expressions; +pub mod no_async_client_component; pub mod no_async_promise_executor; pub mod no_autofocus; pub mod no_await_in_loop; diff --git a/crates/biome_rule_options/src/no_async_client_component.rs b/crates/biome_rule_options/src/no_async_client_component.rs new file mode 100644 index 000000000000..92df9c9477b6 --- /dev/null +++ b/crates/biome_rule_options/src/no_async_client_component.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoAsyncClientComponentOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 98a65a09f847..50187efbbcaa 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1551,6 +1551,10 @@ export interface Correctness { * A list of rules that belong to this group */ export interface Nursery { + /** + * Prevent client components from being async functions. + */ + noAsyncClientComponent?: RuleConfiguration_for_NoAsyncClientComponentOptions; /** * Disallow await inside loops. */ @@ -2888,6 +2892,9 @@ export type RuleFixConfiguration_for_UseValidTypeofOptions = export type RuleConfiguration_for_UseYieldOptions = | RulePlainConfiguration | RuleWithOptions_for_UseYieldOptions; +export type RuleConfiguration_for_NoAsyncClientComponentOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoAsyncClientComponentOptions; export type RuleConfiguration_for_NoAwaitInLoopOptions = | RulePlainConfiguration | RuleWithOptions_for_NoAwaitInLoopOptions; @@ -5080,6 +5087,16 @@ export interface RuleWithOptions_for_UseYieldOptions { */ options: UseYieldOptions; } +export interface RuleWithOptions_for_NoAsyncClientComponentOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoAsyncClientComponentOptions; +} export interface RuleWithOptions_for_NoAwaitInLoopOptions { /** * The severity of the emitted diagnostics by the rule @@ -7851,6 +7868,7 @@ export interface UseJsxKeyInIterableOptions { export interface UseValidForDirectionOptions {} export interface UseValidTypeofOptions {} export interface UseYieldOptions {} +export interface NoAsyncClientComponentOptions {} export interface NoAwaitInLoopOptions {} export interface NoBitwiseOperatorsOptions { /** @@ -8576,6 +8594,7 @@ export type Category = | "lint/correctness/useValidTypeof" | "lint/correctness/useYield" | "lint/nursery/colorNoInvalidHex" + | "lint/nursery/noAsyncClientComponent" | "lint/nursery/noAwaitInLoop" | "lint/nursery/noBitwiseOperators" | "lint/nursery/noColorInvalidHex" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 0f9c2eed6e98..a65f1d208857 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -2449,6 +2449,16 @@ "type": "object", "additionalProperties": false }, + "NoAsyncClientComponentConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoAsyncClientComponentOptions" } + ] + }, + "NoAsyncClientComponentOptions": { + "type": "object", + "additionalProperties": false + }, "NoAsyncPromiseExecutorConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -4774,6 +4784,13 @@ "description": "A list of rules that belong to this group", "type": "object", "properties": { + "noAsyncClientComponent": { + "description": "Prevent client components from being async functions.", + "anyOf": [ + { "$ref": "#/definitions/NoAsyncClientComponentConfiguration" }, + { "type": "null" } + ] + }, "noAwaitInLoop": { "description": "Disallow await inside loops.", "anyOf": [ @@ -5895,6 +5912,21 @@ }, "additionalProperties": false }, + "RuleWithNoAsyncClientComponentOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoAsyncClientComponentOptions" }] + } + }, + "additionalProperties": false + }, "RuleWithNoAsyncPromiseExecutorOptions": { "type": "object", "required": ["level"], From 411aa2a98883bbbcf7baa03960f2a7eafc33938a Mon Sep 17 00:00:00 2001 From: Antony David Date: Thu, 31 Jul 2025 23:52:33 +0200 Subject: [PATCH 02/12] chore: Add changeset --- .changeset/shaggy-keys-camp.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .changeset/shaggy-keys-camp.md diff --git a/.changeset/shaggy-keys-camp.md b/.changeset/shaggy-keys-camp.md new file mode 100644 index 000000000000..cbf24556146d --- /dev/null +++ b/.changeset/shaggy-keys-camp.md @@ -0,0 +1,21 @@ +--- +"@biomejs/biome": patch +--- + +Added the rule [`noAsyncClientComponent`](https://biomejs.dev/linter/rules/no-async-client-component/). + +This rule prevents the use of async functions for client components in Next.js applications. Client components marked with "use client" directive should not be async as this can cause hydration mismatches, break component rendering lifecycle, and lead to unexpected behavior with React's concurrent features. + +```jsx +"use client"; + +// Invalid - async client component +export default async function MyComponent() { + return
Hello
; +} + +// Valid - synchronous client component +export default function MyComponent() { + return
Hello
; +} +``` From 8cc81e780087ecbecacdfb5d5937b8762c2aea9e Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 08:11:05 +0200 Subject: [PATCH 03/12] fix: test files format --- .../nursery/noAsyncClientComponent/invalid.js | 5 +---- .../noAsyncClientComponent/invalid.js.snap | 21 +++++++++---------- .../nursery/noAsyncClientComponent/valid.js | 9 +------- .../noAsyncClientComponent/valid.js.snap | 8 +------ 4 files changed, 13 insertions(+), 30 deletions(-) diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js index 55e80398770b..681bf21325e0 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js @@ -4,10 +4,7 @@ export default async function MyComponent() { return
Hello
; } -// Another case -"use client"; - async function AnotherComponent() { return World; } -export default AnotherComponent; \ No newline at end of file +export default AnotherComponent; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap index 039439ba2516..22618b6462b4 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap @@ -10,13 +10,11 @@ export default async function MyComponent() { return
Hello
; } -// Another case -"use client"; - async function AnotherComponent() { return World; } export default AnotherComponent; + ``` # Diagnostics @@ -33,7 +31,7 @@ invalid.js:3:16 lint/nursery/noAsyncClientComponent ━━━━━━━━━ > 5 │ } │ ^ 6 │ - 7 │ // Another case + 7 │ async function AnotherComponent() { i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. @@ -43,18 +41,19 @@ invalid.js:3:16 lint/nursery/noAsyncClientComponent ━━━━━━━━━ ``` ``` -invalid.js:10:1 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:7:1 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Async client component AnotherComponent is not allowed. - 8 │ "use client"; - 9 │ - > 10 │ async function AnotherComponent() { + 5 │ } + 6 │ + > 7 │ async function AnotherComponent() { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 11 │ return World; - > 12 │ } + > 8 │ return World; + > 9 │ } │ ^ - 13 │ export default AnotherComponent; + 10 │ export default AnotherComponent; + 11 │ i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js index da2f242571e5..13320fc2a4be 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js @@ -1,20 +1,13 @@ /* should not generate diagnostics */ -// No "use client" directive - async function is allowed export default async function MyComponent() { return
Hello
; } -// "use client" but function name doesn't start with uppercase -"use client"; - export default async function myFunction() { return 'not a component'; } -// "use client" but not async -"use client"; - export default function MyComponent() { return
Hello
; -} \ No newline at end of file +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap index d3f7fbd8caf0..9215c3cf33de 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap @@ -6,22 +6,16 @@ expression: valid.js ```js /* should not generate diagnostics */ -// No "use client" directive - async function is allowed export default async function MyComponent() { return
Hello
; } -// "use client" but function name doesn't start with uppercase -"use client"; - export default async function myFunction() { return 'not a component'; } -// "use client" but not async -"use client"; - export default function MyComponent() { return
Hello
; } + ``` From 1feaadfd0b8389ee9a9fb4fe8b742d5f8734bb52 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 08:32:00 +0200 Subject: [PATCH 04/12] fix: use jsx for test files --- .../noAsyncClientComponent/invalid.js.snap | 63 ------------------- .../noAsyncClientComponent/invalid.jsx | 5 ++ .../noAsyncClientComponent/invalid.jsx.snap | 35 +++++++++++ .../{invalid.js => invalidExportDef.jsx} | 4 -- .../invalidExportDef.jsx.snap | 37 +++++++++++ .../nursery/noAsyncClientComponent/valid.js | 13 ---- .../nursery/noAsyncClientComponent/valid.jsx | 5 ++ .../noAsyncClientComponent/valid.jsx.snap | 13 ++++ .../noAsyncClientComponent/validNoAsync.jsx | 6 ++ .../validNoAsync.jsx.snap | 14 +++++ ...alid.js.snap => validNotComponent.js.snap} | 11 +--- .../validNotComponent.jsx | 6 ++ .../validNotComponent.jsx.snap | 14 +++++ 13 files changed, 137 insertions(+), 89 deletions(-) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap rename crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/{invalid.js => invalidExportDef.jsx} (60%) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx.snap rename crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/{valid.js.snap => validNotComponent.js.snap} (53%) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap deleted file mode 100644 index 22618b6462b4..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js.snap +++ /dev/null @@ -1,63 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: invalid.js ---- -# Input -```js -"use client"; - -export default async function MyComponent() { - return
Hello
; -} - -async function AnotherComponent() { - return World; -} -export default AnotherComponent; - -``` - -# Diagnostics -``` -invalid.js:3:16 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Async client component MyComponent is not allowed. - - 1 │ "use client"; - 2 │ - > 3 │ export default async function MyComponent() { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 4 │ return
Hello
; - > 5 │ } - │ ^ - 6 │ - 7 │ async function AnotherComponent() { - - i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. - - i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. - - -``` - -``` -invalid.js:7:1 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Async client component AnotherComponent is not allowed. - - 5 │ } - 6 │ - > 7 │ async function AnotherComponent() { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 8 │ return World; - > 9 │ } - │ ^ - 10 │ export default AnotherComponent; - 11 │ - - i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. - - i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx new file mode 100644 index 000000000000..2ab8d3abc8fa --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx @@ -0,0 +1,5 @@ +"use client"; + +export default async function MyComponent() { + return
Hello
; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap new file mode 100644 index 000000000000..bd6d2c1a242a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +"use client"; + +export default async function MyComponent() { + return
Hello
; +} + +``` + +# Diagnostics +``` +invalid.jsx:3:16 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Async client component MyComponent is not allowed. + + 1 │ "use client"; + 2 │ + > 3 │ export default async function MyComponent() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 4 │ return
Hello
; + > 5 │ } + │ ^ + 6 │ + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx similarity index 60% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js rename to crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx index 681bf21325e0..7fe37cc4959f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx @@ -1,9 +1,5 @@ "use client"; -export default async function MyComponent() { - return
Hello
; -} - async function AnotherComponent() { return World; } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap new file mode 100644 index 000000000000..d9dcde38459c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidExportDef.jsx +--- +# Input +```jsx +"use client"; + +async function AnotherComponent() { + return World; +} +export default AnotherComponent; + +``` + +# Diagnostics +``` +invalidExportDef.jsx:3:1 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Async client component AnotherComponent is not allowed. + + 1 │ "use client"; + 2 │ + > 3 │ async function AnotherComponent() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 4 │ return World; + > 5 │ } + │ ^ + 6 │ export default AnotherComponent; + 7 │ + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js deleted file mode 100644 index 13320fc2a4be..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js +++ /dev/null @@ -1,13 +0,0 @@ -/* should not generate diagnostics */ - -export default async function MyComponent() { - return
Hello
; -} - -export default async function myFunction() { - return 'not a component'; -} - -export default function MyComponent() { - return
Hello
; -} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx new file mode 100644 index 000000000000..2951d0c0df5f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx @@ -0,0 +1,5 @@ +/* should not generate diagnostics */ + +export default async function MyComponent() { + return
Hello
; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx.snap new file mode 100644 index 000000000000..10c988bb6bf9 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx.snap @@ -0,0 +1,13 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ + +export default async function MyComponent() { + return
Hello
; +} + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx new file mode 100644 index 000000000000..a3744447ad91 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx @@ -0,0 +1,6 @@ +/* should not generate diagnostics */ +"use client"; + +export default function MyComponent() { + return
Hello
; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx.snap new file mode 100644 index 000000000000..17a9558fdf7d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validNoAsync.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ +"use client"; + +export default function MyComponent() { + return
Hello
; +} + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.js.snap similarity index 53% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.js.snap index 9215c3cf33de..892ba2bc4b14 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.js.snap @@ -1,21 +1,14 @@ --- source: crates/biome_js_analyze/tests/spec_tests.rs -expression: valid.js +expression: validNotComponent.js --- # Input ```js /* should not generate diagnostics */ - -export default async function MyComponent() { - return
Hello
; -} +"use client"; export default async function myFunction() { return 'not a component'; } -export default function MyComponent() { - return
Hello
; -} - ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx new file mode 100644 index 000000000000..6bd220fa3136 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx @@ -0,0 +1,6 @@ +/* should not generate diagnostics */ +"use client"; + +export default async function myFunction() { + return 'not a component'; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx.snap new file mode 100644 index 000000000000..71f3c1602a03 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validNotComponent.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ +"use client"; + +export default async function myFunction() { + return 'not a component'; +} + +``` From 0ccaa3e1cd945d80704b2ee8e227f35130580d59 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 09:30:12 +0200 Subject: [PATCH 05/12] docs: use jsx code blocks --- .../src/lint/nursery/no_async_client_component.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs index d10092b07a3d..602e53c938b4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs @@ -22,7 +22,7 @@ declare_lint_rule! { /// /// ### Invalid /// - /// ```js,expect_diagnostic + /// ```jsx,expect_diagnostic /// "use client"; /// /// export default async function MyComponent() { @@ -32,7 +32,7 @@ declare_lint_rule! { /// /// ### Valid /// - /// ```js + /// ```jsx /// "use client"; /// /// export default function MyComponent() { @@ -40,7 +40,7 @@ declare_lint_rule! { /// } /// ``` /// - /// ```js + /// ```jsx /// // No "use client" directive - server component can be async /// export default async function ServerComponent() { /// const data = await fetch('/api/data'); From 0589bf6723132d13275e9a3641921e81319fafb7 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 19:43:10 +0200 Subject: [PATCH 06/12] refactor: string alloc and duplicate code --- .../lint/nursery/no_async_client_component.rs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs index 602e53c938b4..dd258422a381 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs @@ -7,7 +7,7 @@ use biome_analyze::{ use biome_console::markup; use biome_diagnostics::Severity; use biome_js_syntax::AnyJsRoot; -use biome_rowan::{AstNode, AstNodeList}; +use biome_rowan::{AstNode, TokenText}; use biome_rule_options::no_async_client_component::NoAsyncClientComponentOptions; declare_lint_rule! { @@ -61,7 +61,7 @@ declare_lint_rule! { impl Rule for NoAsyncClientComponent { type Query = Ast; - type State = String; + type State = Option; type Signals = Option; type Options = NoAsyncClientComponentOptions; @@ -77,16 +77,8 @@ impl Rule for NoAsyncClientComponent { // Check if we're in a module with "use client" directive let root = ctx.root(); let has_use_client = match root { - AnyJsRoot::JsModule(module) => module.directives().iter().any(|directive| { - directive - .inner_string_text() - .is_ok_and(|text| text.text() == "use client") - }), - AnyJsRoot::JsScript(script) => script.directives().iter().any(|directive| { - directive - .inner_string_text() - .is_ok_and(|text| text.text() == "use client") - }), + AnyJsRoot::JsModule(module) => has_use_client_directive(module.directives()), + AnyJsRoot::JsScript(script) => has_use_client_directive(script.directives()), _ => false, }; @@ -109,23 +101,24 @@ impl Rule for NoAsyncClientComponent { return None; } - let component_name = component.name.or(component.name_hint).map_or_else( - || "Component".to_string(), - |token| token.text_trimmed().to_string(), - ); + let component_name = component + .name + .or(component.name_hint) + .map(|token| token.token_text_trimmed()); Some(component_name) } fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let declaration = ctx.query(); + let component_name = state.as_ref().map_or("Component", |token| token.text()); Some( RuleDiagnostic::new( rule_category!(), declaration.range(), markup! { - "Async client component "{state}" is not allowed." + "Async client component "{component_name}" is not allowed." }, ) .note(markup! { @@ -137,3 +130,13 @@ impl Rule for NoAsyncClientComponent { ) } } + +fn has_use_client_directive( + directives: impl IntoIterator, +) -> bool { + directives.into_iter().any(|directive| { + directive + .inner_string_text() + .is_ok_and(|text| text.text() == "use client") + }) +} From 777f340da920a8e57eedbe5d562cd35ccd5766a7 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 21:26:10 +0200 Subject: [PATCH 07/12] refactor: rename rule to noNextAsyncClientComponent --- .changeset/shaggy-keys-camp.md | 2 +- .../migrate/eslint_any_rule_to_biome.rs | 2 +- .../src/analyzer/linter/rules.rs | 80 +++++++++---------- .../src/categories.rs | 2 +- crates/biome_js_analyze/src/lint/nursery.rs | 4 +- ...t.rs => no_next_async_client_component.rs} | 10 +-- .../invalid.jsx | 0 .../invalid.jsx.snap | 2 +- .../invalidExportDef.jsx | 0 .../invalidExportDef.jsx.snap | 2 +- .../valid.jsx | 0 .../valid.jsx.snap | 0 .../validNoAsync.jsx | 0 .../validNoAsync.jsx.snap | 0 .../validNotComponent.js.snap | 0 .../validNotComponent.jsx | 0 .../validNotComponent.jsx.snap | 0 crates/biome_rule_options/src/lib.rs | 2 +- .../src/no_async_client_component.rs | 2 +- .../src/no_next_async_client_component.rs | 6 ++ .../@biomejs/backend-jsonrpc/src/workspace.ts | 38 ++++----- .../@biomejs/biome/configuration_schema.json | 66 +++++++-------- 22 files changed, 113 insertions(+), 105 deletions(-) rename crates/biome_js_analyze/src/lint/nursery/{no_async_client_component.rs => no_next_async_client_component.rs} (94%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/invalid.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/invalid.jsx.snap (80%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/invalidExportDef.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/invalidExportDef.jsx.snap (83%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/valid.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/valid.jsx.snap (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/validNoAsync.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/validNoAsync.jsx.snap (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/validNotComponent.js.snap (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/validNotComponent.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noAsyncClientComponent => noNextAsyncClientComponent}/validNotComponent.jsx.snap (100%) create mode 100644 crates/biome_rule_options/src/no_next_async_client_component.rs diff --git a/.changeset/shaggy-keys-camp.md b/.changeset/shaggy-keys-camp.md index cbf24556146d..043a232f9aec 100644 --- a/.changeset/shaggy-keys-camp.md +++ b/.changeset/shaggy-keys-camp.md @@ -2,7 +2,7 @@ "@biomejs/biome": patch --- -Added the rule [`noAsyncClientComponent`](https://biomejs.dev/linter/rules/no-async-client-component/). +Added the rule [`noNextAsyncClientComponent`](https://biomejs.dev/linter/rules/no-next-async-client-component). This rule prevents the use of async functions for client components in Next.js applications. Client components marked with "use client" directive should not be async as this can cause hydration mismatches, break component rendering lifecycle, and lead to unexpected behavior with React's concurrent features. diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index 28498c8c33a6..89038b0c5f56 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -69,7 +69,7 @@ pub(crate) fn migrate_eslint_any_rule( let group = rules.nursery.get_or_insert_with(Default::default); let rule = group .unwrap_group_as_mut() - .no_async_client_component + .no_next_async_client_component .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 655baafb6167..d076cc9eb01f 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -99,7 +99,6 @@ pub enum RuleName { NoAriaUnsupportedElements, NoArrayIndexKey, NoAssignInExpressions, - NoAsyncClientComponent, NoAsyncPromiseExecutor, NoAutofocus, NoAwaitInLoop, @@ -213,6 +212,7 @@ pub enum RuleName { NoNegationElse, NoNestedComponentDefinitions, NoNestedTernary, + NoNextAsyncClientComponent, NoNodejsModules, NoNonNullAssertedOptionalChain, NoNonNullAssertion, @@ -449,7 +449,6 @@ impl RuleName { Self::NoAriaUnsupportedElements => "noAriaUnsupportedElements", Self::NoArrayIndexKey => "noArrayIndexKey", Self::NoAssignInExpressions => "noAssignInExpressions", - Self::NoAsyncClientComponent => "noAsyncClientComponent", Self::NoAsyncPromiseExecutor => "noAsyncPromiseExecutor", Self::NoAutofocus => "noAutofocus", Self::NoAwaitInLoop => "noAwaitInLoop", @@ -565,6 +564,7 @@ impl RuleName { Self::NoNegationElse => "noNegationElse", Self::NoNestedComponentDefinitions => "noNestedComponentDefinitions", Self::NoNestedTernary => "noNestedTernary", + Self::NoNextAsyncClientComponent => "noNextAsyncClientComponent", Self::NoNodejsModules => "noNodejsModules", Self::NoNonNullAssertedOptionalChain => "noNonNullAssertedOptionalChain", Self::NoNonNullAssertion => "noNonNullAssertion", @@ -803,7 +803,6 @@ impl RuleName { Self::NoAriaUnsupportedElements => RuleGroup::A11y, Self::NoArrayIndexKey => RuleGroup::Suspicious, Self::NoAssignInExpressions => RuleGroup::Suspicious, - Self::NoAsyncClientComponent => RuleGroup::Nursery, Self::NoAsyncPromiseExecutor => RuleGroup::Suspicious, Self::NoAutofocus => RuleGroup::A11y, Self::NoAwaitInLoop => RuleGroup::Nursery, @@ -917,6 +916,7 @@ impl RuleName { Self::NoNegationElse => RuleGroup::Style, Self::NoNestedComponentDefinitions => RuleGroup::Nursery, Self::NoNestedTernary => RuleGroup::Style, + Self::NoNextAsyncClientComponent => RuleGroup::Nursery, Self::NoNodejsModules => RuleGroup::Correctness, Self::NoNonNullAssertedOptionalChain => RuleGroup::Nursery, Self::NoNonNullAssertion => RuleGroup::Style, @@ -1156,7 +1156,6 @@ impl std::str::FromStr for RuleName { "noAriaUnsupportedElements" => Ok(Self::NoAriaUnsupportedElements), "noArrayIndexKey" => Ok(Self::NoArrayIndexKey), "noAssignInExpressions" => Ok(Self::NoAssignInExpressions), - "noAsyncClientComponent" => Ok(Self::NoAsyncClientComponent), "noAsyncPromiseExecutor" => Ok(Self::NoAsyncPromiseExecutor), "noAutofocus" => Ok(Self::NoAutofocus), "noAwaitInLoop" => Ok(Self::NoAwaitInLoop), @@ -1274,6 +1273,7 @@ impl std::str::FromStr for RuleName { "noNegationElse" => Ok(Self::NoNegationElse), "noNestedComponentDefinitions" => Ok(Self::NoNestedComponentDefinitions), "noNestedTernary" => Ok(Self::NoNestedTernary), + "noNextAsyncClientComponent" => Ok(Self::NoNextAsyncClientComponent), "noNodejsModules" => Ok(Self::NoNodejsModules), "noNonNullAssertedOptionalChain" => Ok(Self::NoNonNullAssertedOptionalChain), "noNonNullAssertion" => Ok(Self::NoNonNullAssertion), @@ -4306,11 +4306,10 @@ impl From for Correctness { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", default, deny_unknown_fields)] #[doc = r" A list of rules that belong to this group"] -pub struct Nursery { # [doc = r" It enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_async_client_component :: NoAsyncClientComponentOptions >> , # [doc = "Disallow await inside loops."] # [serde (skip_serializing_if = "Option::is_none")] pub no_await_in_loop : Option < RuleConfiguration < biome_rule_options :: no_await_in_loop :: NoAwaitInLoopOptions >> , # [doc = "Disallow bitwise operators."] # [serde (skip_serializing_if = "Option::is_none")] pub no_bitwise_operators : Option < RuleConfiguration < biome_rule_options :: no_bitwise_operators :: NoBitwiseOperatorsOptions >> , # [doc = "Disallow expressions where the operation doesn't affect the value"] # [serde (skip_serializing_if = "Option::is_none")] pub no_constant_binary_expression : Option < RuleConfiguration < biome_rule_options :: no_constant_binary_expression :: NoConstantBinaryExpressionOptions >> , # [doc = "Disallow destructuring props inside JSX components in Solid projects."] # [serde (skip_serializing_if = "Option::is_none")] pub no_destructured_props : Option < RuleConfiguration < biome_rule_options :: no_destructured_props :: NoDestructuredPropsOptions >> , # [doc = "Restrict the number of lines of code in a function."] # [serde (skip_serializing_if = "Option::is_none")] pub no_excessive_lines_per_function : Option < RuleConfiguration < biome_rule_options :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunctionOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow the use of __dirname and __filename in the global scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_global_dirname_filename : Option < RuleFixConfiguration < biome_rule_options :: no_global_dirname_filename :: NoGlobalDirnameFilenameOptions >> , # [doc = "Disallow shorthand type conversions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_implicit_coercion : Option < RuleFixConfiguration < biome_rule_options :: no_implicit_coercion :: NoImplicitCoercionOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow the use of the !important style."] # [serde (skip_serializing_if = "Option::is_none")] pub no_important_styles : Option < RuleFixConfiguration < biome_rule_options :: no_important_styles :: NoImportantStylesOptions >> , # [doc = "Reports usage of \"magic numbers\" — numbers used directly instead of being assigned to named constants."] # [serde (skip_serializing_if = "Option::is_none")] pub no_magic_numbers : Option < RuleConfiguration < biome_rule_options :: no_magic_numbers :: NoMagicNumbersOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallows defining React components inside other components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_nested_component_definitions : Option < RuleConfiguration < biome_rule_options :: no_nested_component_definitions :: NoNestedComponentDefinitionsOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow use event handlers on non-interactive elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_noninteractive_element_interactions : Option < RuleConfiguration < biome_rule_options :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractionsOptions >> , # [doc = "Disallow the use of process global."] # [serde (skip_serializing_if = "Option::is_none")] pub no_process_global : Option < RuleFixConfiguration < biome_rule_options :: no_process_global :: NoProcessGlobalOptions >> , # [doc = "Disallow the use if quickfix.biome inside editor settings file."] # [serde (skip_serializing_if = "Option::is_none")] pub no_quickfix_biome : Option < RuleFixConfiguration < biome_rule_options :: no_quickfix_biome :: NoQuickfixBiomeOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Disallow assigning to React component props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_prop_assign : Option < RuleConfiguration < biome_rule_options :: no_react_prop_assign :: NoReactPropAssignOptions >> , # [doc = "Disallow the use of configured elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_restricted_elements : Option < RuleConfiguration < biome_rule_options :: no_restricted_elements :: NoRestrictedElementsOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevents the use of the TypeScript directive @ts-ignore."] # [serde (skip_serializing_if = "Option::is_none")] pub no_ts_ignore : Option < RuleFixConfiguration < biome_rule_options :: no_ts_ignore :: NoTsIgnoreOptions >> , # [doc = "Disallow let or var variables that are read but never assigned."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unassigned_variables : Option < RuleConfiguration < biome_rule_options :: no_unassigned_variables :: NoUnassignedVariablesOptions >> , # [doc = "Disallow unknown at-rules."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_at_rule : Option < RuleConfiguration < biome_rule_options :: no_unknown_at_rule :: NoUnknownAtRuleOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Prevent duplicate polyfills from Polyfill.io."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unwanted_polyfillio : Option < RuleConfiguration < biome_rule_options :: no_unwanted_polyfillio :: NoUnwantedPolyfillioOptions >> , # [doc = "Disallow useless backreferences in regular expression literals that always match an empty string."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_backref_in_regex : Option < RuleConfiguration < biome_rule_options :: no_useless_backref_in_regex :: NoUselessBackrefInRegexOptions >> , # [doc = "Disallow unnecessary escapes in string literals."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_escape_in_string : Option < RuleFixConfiguration < biome_rule_options :: no_useless_escape_in_string :: NoUselessEscapeInStringOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforce that getters and setters for the same property are adjacent in class and object definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_adjacent_getter_setter : Option < RuleConfiguration < biome_rule_options :: use_adjacent_getter_setter :: UseAdjacentGetterSetterOptions >> , # [doc = "Enforces href attribute for \\
elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Require the consistent declaration of object literals. Defaults to explicit definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_object_definition : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_object_definition :: UseConsistentObjectDefinitionOptions >> , # [doc = "Use static Response methods instead of new Response() constructor when possible."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_response : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_response :: UseConsistentResponseOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Require that all exports are declared after all non-export statements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exports_last : Option < RuleConfiguration < biome_rule_options :: use_exports_last :: UseExportsLastOptions >> , # [doc = "Enforce using Solid's \\ component for mapping an array to JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_for_component : Option < RuleConfiguration < biome_rule_options :: use_for_component :: UseForComponentOptions >> , # [doc = "Ensure the preconnect attribute is used when using Google Fonts."] # [serde (skip_serializing_if = "Option::is_none")] pub use_google_font_preconnect : Option < RuleFixConfiguration < biome_rule_options :: use_google_font_preconnect :: UseGoogleFontPreconnectOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Prefer Array#{indexOf,lastIndexOf}() over Array#{findIndex,findLastIndex}() when looking for the index of an item."] # [serde (skip_serializing_if = "Option::is_none")] pub use_index_of : Option < RuleFixConfiguration < biome_rule_options :: use_index_of :: UseIndexOfOptions >> , # [doc = "Enforce consistent return values in iterable callbacks."] # [serde (skip_serializing_if = "Option::is_none")] pub use_iterable_callback_return : Option < RuleConfiguration < biome_rule_options :: use_iterable_callback_return :: UseIterableCallbackReturnOptions >> , # [doc = "Enforces the use of with { type: \"json\" } for JSON module imports."] # [serde (skip_serializing_if = "Option::is_none")] pub use_json_import_attribute : Option < RuleFixConfiguration < biome_rule_options :: use_json_import_attribute :: UseJsonImportAttributeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Enforce specifying the name of GraphQL operations."] # [serde (skip_serializing_if = "Option::is_none")] pub use_named_operation : Option < RuleFixConfiguration < biome_rule_options :: use_named_operation :: UseNamedOperationOptions >> , # [doc = "Validates that all enum values are capitalized."] # [serde (skip_serializing_if = "Option::is_none")] pub use_naming_convention : Option < RuleConfiguration < biome_rule_options :: use_naming_convention :: UseNamingConventionOptions >> , # [doc = "Enforce the use of numeric separators in numeric literals."] # [serde (skip_serializing_if = "Option::is_none")] pub use_numeric_separators : Option < RuleFixConfiguration < biome_rule_options :: use_numeric_separators :: UseNumericSeparatorsOptions >> , # [doc = "Prefer object spread over Object.assign() when constructing new objects."] # [serde (skip_serializing_if = "Option::is_none")] pub use_object_spread : Option < RuleFixConfiguration < biome_rule_options :: use_object_spread :: UseObjectSpreadOptions >> , # [doc = "Enforce the consistent use of the radix argument when using parseInt()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_parse_int_radix : Option < RuleFixConfiguration < biome_rule_options :: use_parse_int_radix :: UseParseIntRadixOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce marking members as readonly if they are never modified outside the constructor."] # [serde (skip_serializing_if = "Option::is_none")] pub use_readonly_class_properties : Option < RuleFixConfiguration < biome_rule_options :: use_readonly_class_properties :: UseReadonlyClassPropertiesOptions >> , # [doc = "Enforce JSDoc comment lines to start with a single asterisk, except for the first one."] # [serde (skip_serializing_if = "Option::is_none")] pub use_single_js_doc_asterisk : Option < RuleFixConfiguration < biome_rule_options :: use_single_js_doc_asterisk :: UseSingleJsDocAsteriskOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Require a description parameter for the Symbol()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_symbol_description : Option < RuleConfiguration < biome_rule_options :: use_symbol_description :: UseSymbolDescriptionOptions >> , # [doc = "Disallow overload signatures that can be unified into a single signature."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unified_type_signature : Option < RuleFixConfiguration < biome_rule_options :: use_unified_type_signature :: UseUnifiedTypeSignatureOptions >> , # [doc = "Prevent the usage of static string literal id attribute on elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_element_ids : Option < RuleConfiguration < biome_rule_options :: use_unique_element_ids :: UseUniqueElementIdsOptions >> } +pub struct Nursery { # [doc = r" It enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow await inside loops."] # [serde (skip_serializing_if = "Option::is_none")] pub no_await_in_loop : Option < RuleConfiguration < biome_rule_options :: no_await_in_loop :: NoAwaitInLoopOptions >> , # [doc = "Disallow bitwise operators."] # [serde (skip_serializing_if = "Option::is_none")] pub no_bitwise_operators : Option < RuleConfiguration < biome_rule_options :: no_bitwise_operators :: NoBitwiseOperatorsOptions >> , # [doc = "Disallow expressions where the operation doesn't affect the value"] # [serde (skip_serializing_if = "Option::is_none")] pub no_constant_binary_expression : Option < RuleConfiguration < biome_rule_options :: no_constant_binary_expression :: NoConstantBinaryExpressionOptions >> , # [doc = "Disallow destructuring props inside JSX components in Solid projects."] # [serde (skip_serializing_if = "Option::is_none")] pub no_destructured_props : Option < RuleConfiguration < biome_rule_options :: no_destructured_props :: NoDestructuredPropsOptions >> , # [doc = "Restrict the number of lines of code in a function."] # [serde (skip_serializing_if = "Option::is_none")] pub no_excessive_lines_per_function : Option < RuleConfiguration < biome_rule_options :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunctionOptions >> , # [doc = "Require Promise-like statements to be handled appropriately."] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow the use of __dirname and __filename in the global scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_global_dirname_filename : Option < RuleFixConfiguration < biome_rule_options :: no_global_dirname_filename :: NoGlobalDirnameFilenameOptions >> , # [doc = "Disallow shorthand type conversions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_implicit_coercion : Option < RuleFixConfiguration < biome_rule_options :: no_implicit_coercion :: NoImplicitCoercionOptions >> , # [doc = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallow the use of the !important style."] # [serde (skip_serializing_if = "Option::is_none")] pub no_important_styles : Option < RuleFixConfiguration < biome_rule_options :: no_important_styles :: NoImportantStylesOptions >> , # [doc = "Reports usage of \"magic numbers\" — numbers used directly instead of being assigned to named constants."] # [serde (skip_serializing_if = "Option::is_none")] pub no_magic_numbers : Option < RuleConfiguration < biome_rule_options :: no_magic_numbers :: NoMagicNumbersOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake."] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallows defining React components inside other components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_nested_component_definitions : Option < RuleConfiguration < biome_rule_options :: no_nested_component_definitions :: NoNestedComponentDefinitionsOptions >> , # [doc = "Prevent client components from being async functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow use event handlers on non-interactive elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_noninteractive_element_interactions : Option < RuleConfiguration < biome_rule_options :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractionsOptions >> , # [doc = "Disallow the use of process global."] # [serde (skip_serializing_if = "Option::is_none")] pub no_process_global : Option < RuleFixConfiguration < biome_rule_options :: no_process_global :: NoProcessGlobalOptions >> , # [doc = "Disallow the use if quickfix.biome inside editor settings file."] # [serde (skip_serializing_if = "Option::is_none")] pub no_quickfix_biome : Option < RuleFixConfiguration < biome_rule_options :: no_quickfix_biome :: NoQuickfixBiomeOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Disallow assigning to React component props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_prop_assign : Option < RuleConfiguration < biome_rule_options :: no_react_prop_assign :: NoReactPropAssignOptions >> , # [doc = "Disallow the use of configured elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_restricted_elements : Option < RuleConfiguration < biome_rule_options :: no_restricted_elements :: NoRestrictedElementsOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevents the use of the TypeScript directive @ts-ignore."] # [serde (skip_serializing_if = "Option::is_none")] pub no_ts_ignore : Option < RuleFixConfiguration < biome_rule_options :: no_ts_ignore :: NoTsIgnoreOptions >> , # [doc = "Disallow let or var variables that are read but never assigned."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unassigned_variables : Option < RuleConfiguration < biome_rule_options :: no_unassigned_variables :: NoUnassignedVariablesOptions >> , # [doc = "Disallow unknown at-rules."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_at_rule : Option < RuleConfiguration < biome_rule_options :: no_unknown_at_rule :: NoUnknownAtRuleOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Prevent duplicate polyfills from Polyfill.io."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unwanted_polyfillio : Option < RuleConfiguration < biome_rule_options :: no_unwanted_polyfillio :: NoUnwantedPolyfillioOptions >> , # [doc = "Disallow useless backreferences in regular expression literals that always match an empty string."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_backref_in_regex : Option < RuleConfiguration < biome_rule_options :: no_useless_backref_in_regex :: NoUselessBackrefInRegexOptions >> , # [doc = "Disallow unnecessary escapes in string literals."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_escape_in_string : Option < RuleFixConfiguration < biome_rule_options :: no_useless_escape_in_string :: NoUselessEscapeInStringOptions >> , # [doc = "Disallow the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforce that getters and setters for the same property are adjacent in class and object definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_adjacent_getter_setter : Option < RuleConfiguration < biome_rule_options :: use_adjacent_getter_setter :: UseAdjacentGetterSetterOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Require the consistent declaration of object literals. Defaults to explicit definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_object_definition : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_object_definition :: UseConsistentObjectDefinitionOptions >> , # [doc = "Use static Response methods instead of new Response() constructor when possible."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_response : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_response :: UseConsistentResponseOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Require that all exports are declared after all non-export statements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exports_last : Option < RuleConfiguration < biome_rule_options :: use_exports_last :: UseExportsLastOptions >> , # [doc = "Enforce using Solid's \\ component for mapping an array to JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_for_component : Option < RuleConfiguration < biome_rule_options :: use_for_component :: UseForComponentOptions >> , # [doc = "Ensure the preconnect attribute is used when using Google Fonts."] # [serde (skip_serializing_if = "Option::is_none")] pub use_google_font_preconnect : Option < RuleFixConfiguration < biome_rule_options :: use_google_font_preconnect :: UseGoogleFontPreconnectOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "Prefer Array#{indexOf,lastIndexOf}() over Array#{findIndex,findLastIndex}() when looking for the index of an item."] # [serde (skip_serializing_if = "Option::is_none")] pub use_index_of : Option < RuleFixConfiguration < biome_rule_options :: use_index_of :: UseIndexOfOptions >> , # [doc = "Enforce consistent return values in iterable callbacks."] # [serde (skip_serializing_if = "Option::is_none")] pub use_iterable_callback_return : Option < RuleConfiguration < biome_rule_options :: use_iterable_callback_return :: UseIterableCallbackReturnOptions >> , # [doc = "Enforces the use of with { type: \"json\" } for JSON module imports."] # [serde (skip_serializing_if = "Option::is_none")] pub use_json_import_attribute : Option < RuleFixConfiguration < biome_rule_options :: use_json_import_attribute :: UseJsonImportAttributeOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions."] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Enforce specifying the name of GraphQL operations."] # [serde (skip_serializing_if = "Option::is_none")] pub use_named_operation : Option < RuleFixConfiguration < biome_rule_options :: use_named_operation :: UseNamedOperationOptions >> , # [doc = "Validates that all enum values are capitalized."] # [serde (skip_serializing_if = "Option::is_none")] pub use_naming_convention : Option < RuleConfiguration < biome_rule_options :: use_naming_convention :: UseNamingConventionOptions >> , # [doc = "Enforce the use of numeric separators in numeric literals."] # [serde (skip_serializing_if = "Option::is_none")] pub use_numeric_separators : Option < RuleFixConfiguration < biome_rule_options :: use_numeric_separators :: UseNumericSeparatorsOptions >> , # [doc = "Prefer object spread over Object.assign() when constructing new objects."] # [serde (skip_serializing_if = "Option::is_none")] pub use_object_spread : Option < RuleFixConfiguration < biome_rule_options :: use_object_spread :: UseObjectSpreadOptions >> , # [doc = "Enforce the consistent use of the radix argument when using parseInt()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_parse_int_radix : Option < RuleFixConfiguration < biome_rule_options :: use_parse_int_radix :: UseParseIntRadixOptions >> , # [doc = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce marking members as readonly if they are never modified outside the constructor."] # [serde (skip_serializing_if = "Option::is_none")] pub use_readonly_class_properties : Option < RuleFixConfiguration < biome_rule_options :: use_readonly_class_properties :: UseReadonlyClassPropertiesOptions >> , # [doc = "Enforce JSDoc comment lines to start with a single asterisk, except for the first one."] # [serde (skip_serializing_if = "Option::is_none")] pub use_single_js_doc_asterisk : Option < RuleFixConfiguration < biome_rule_options :: use_single_js_doc_asterisk :: UseSingleJsDocAsteriskOptions >> , # [doc = "Enforce the sorting of CSS utility classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Require a description parameter for the Symbol()."] # [serde (skip_serializing_if = "Option::is_none")] pub use_symbol_description : Option < RuleConfiguration < biome_rule_options :: use_symbol_description :: UseSymbolDescriptionOptions >> , # [doc = "Disallow overload signatures that can be unified into a single signature."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unified_type_signature : Option < RuleFixConfiguration < biome_rule_options :: use_unified_type_signature :: UseUnifiedTypeSignatureOptions >> , # [doc = "Prevent the usage of static string literal id attribute on elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_element_ids : Option < RuleConfiguration < biome_rule_options :: use_unique_element_ids :: UseUniqueElementIdsOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ - "noAsyncClientComponent", "noAwaitInLoop", "noBitwiseOperators", "noConstantBinaryExpression", @@ -4324,6 +4323,7 @@ impl Nursery { "noMagicNumbers", "noMisusedPromises", "noNestedComponentDefinitions", + "noNextAsyncClientComponent", "noNonNullAssertedOptionalChain", "noNoninteractiveElementInteractions", "noProcessGlobal", @@ -4375,7 +4375,7 @@ impl Nursery { "useUniqueElementIds", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), @@ -4462,72 +4462,72 @@ impl RuleGroupExt for Nursery { } fn get_enabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_async_client_component.as_ref() { + if let Some(rule) = self.no_await_in_loop.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_await_in_loop.as_ref() { + if let Some(rule) = self.no_bitwise_operators.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_bitwise_operators.as_ref() { + if let Some(rule) = self.no_constant_binary_expression.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_constant_binary_expression.as_ref() { + if let Some(rule) = self.no_destructured_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_destructured_props.as_ref() { + if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { + if let Some(rule) = self.no_floating_promises.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_floating_promises.as_ref() { + if let Some(rule) = self.no_global_dirname_filename.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_global_dirname_filename.as_ref() { + if let Some(rule) = self.no_implicit_coercion.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_implicit_coercion.as_ref() { + if let Some(rule) = self.no_import_cycles.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_import_cycles.as_ref() { + if let Some(rule) = self.no_important_styles.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_important_styles.as_ref() { + if let Some(rule) = self.no_magic_numbers.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_magic_numbers.as_ref() { + if let Some(rule) = self.no_misused_promises.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_misused_promises.as_ref() { + if let Some(rule) = self.no_nested_component_definitions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_nested_component_definitions.as_ref() { + if let Some(rule) = self.no_next_async_client_component.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } @@ -4781,72 +4781,72 @@ impl RuleGroupExt for Nursery { } fn get_disabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_async_client_component.as_ref() { + if let Some(rule) = self.no_await_in_loop.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_await_in_loop.as_ref() { + if let Some(rule) = self.no_bitwise_operators.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_bitwise_operators.as_ref() { + if let Some(rule) = self.no_constant_binary_expression.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_constant_binary_expression.as_ref() { + if let Some(rule) = self.no_destructured_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_destructured_props.as_ref() { + if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_excessive_lines_per_function.as_ref() { + if let Some(rule) = self.no_floating_promises.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_floating_promises.as_ref() { + if let Some(rule) = self.no_global_dirname_filename.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_global_dirname_filename.as_ref() { + if let Some(rule) = self.no_implicit_coercion.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_implicit_coercion.as_ref() { + if let Some(rule) = self.no_import_cycles.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_import_cycles.as_ref() { + if let Some(rule) = self.no_important_styles.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_important_styles.as_ref() { + if let Some(rule) = self.no_magic_numbers.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_magic_numbers.as_ref() { + if let Some(rule) = self.no_misused_promises.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_misused_promises.as_ref() { + if let Some(rule) = self.no_nested_component_definitions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_nested_component_definitions.as_ref() { + if let Some(rule) = self.no_next_async_client_component.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } @@ -5126,10 +5126,6 @@ impl RuleGroupExt for Nursery { rule_name: &str, ) -> Option<(RulePlainConfiguration, Option)> { match rule_name { - "noAsyncClientComponent" => self - .no_async_client_component - .as_ref() - .map(|conf| (conf.level(), conf.get_options())), "noAwaitInLoop" => self .no_await_in_loop .as_ref() @@ -5182,6 +5178,10 @@ impl RuleGroupExt for Nursery { .no_nested_component_definitions .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noNextAsyncClientComponent" => self + .no_next_async_client_component + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noNonNullAssertedOptionalChain" => self .no_non_null_asserted_optional_chain .as_ref() @@ -5386,7 +5386,6 @@ impl From for Nursery { fn from(value: GroupPlainConfiguration) -> Self { Self { recommended: None, - no_async_client_component: Some(value.into()), no_await_in_loop: Some(value.into()), no_bitwise_operators: Some(value.into()), no_constant_binary_expression: Some(value.into()), @@ -5400,6 +5399,7 @@ impl From for Nursery { no_magic_numbers: Some(value.into()), no_misused_promises: Some(value.into()), no_nested_component_definitions: Some(value.into()), + no_next_async_client_component: Some(value.into()), no_non_null_asserted_optional_chain: Some(value.into()), no_noninteractive_element_interactions: Some(value.into()), no_process_global: Some(value.into()), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 08fb4aa2f5a7..58a26cb301a1 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -146,7 +146,7 @@ define_categories! { "lint/correctness/useValidTypeof": "https://biomejs.dev/linter/rules/use-valid-typeof", "lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield", "lint/nursery/colorNoInvalidHex": "https://biomejs.dev/linter/rules/color-no-invalid-hex", - "lint/nursery/noAsyncClientComponent": "https://biomejs.dev/linter/rules/no-async-client-component", + "lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component", "lint/nursery/noAwaitInLoop": "https://biomejs.dev/linter/rules/no-await-in-loop", "lint/nursery/noBitwiseOperators": "https://biomejs.dev/linter/rules/no-bitwise-operators", "lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 3a7131269e1c..ff1323652ec9 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -3,7 +3,6 @@ //! Generated file, do not edit by hand, see `xtask/codegen` use biome_analyze::declare_lint_group; -pub mod no_async_client_component; pub mod no_await_in_loop; pub mod no_bitwise_operators; pub mod no_constant_binary_expression; @@ -16,6 +15,7 @@ pub mod no_import_cycles; pub mod no_magic_numbers; pub mod no_misused_promises; pub mod no_nested_component_definitions; +pub mod no_next_async_client_component; pub mod no_non_null_asserted_optional_chain; pub mod no_noninteractive_element_interactions; pub mod no_process_global; @@ -61,4 +61,4 @@ pub mod use_sorted_classes; pub mod use_symbol_description; pub mod use_unified_type_signature; pub mod use_unique_element_ids; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_async_client_component :: NoAsyncClientComponent , self :: no_await_in_loop :: NoAwaitInLoop , self :: no_bitwise_operators :: NoBitwiseOperators , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunction , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_implicit_coercion :: NoImplicitCoercion , self :: no_import_cycles :: NoImportCycles , self :: no_magic_numbers :: NoMagicNumbers , self :: no_misused_promises :: NoMisusedPromises , self :: no_nested_component_definitions :: NoNestedComponentDefinitions , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_process_global :: NoProcessGlobal , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_prop_assign :: NoReactPropAssign , self :: no_restricted_elements :: NoRestrictedElements , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_ts_ignore :: NoTsIgnore , self :: no_unassigned_variables :: NoUnassignedVariables , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_backref_in_regex :: NoUselessBackrefInRegex , self :: no_useless_escape_in_string :: NoUselessEscapeInString , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_adjacent_getter_setter :: UseAdjacentGetterSetter , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_consistent_response :: UseConsistentResponse , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_for_component :: UseForComponent , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_image_size :: UseImageSize , self :: use_index_of :: UseIndexOf , self :: use_iterable_callback_return :: UseIterableCallbackReturn , self :: use_json_import_attribute :: UseJsonImportAttribute , self :: use_max_params :: UseMaxParams , self :: use_numeric_separators :: UseNumericSeparators , self :: use_object_spread :: UseObjectSpread , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_readonly_class_properties :: UseReadonlyClassProperties , self :: use_single_js_doc_asterisk :: UseSingleJsDocAsterisk , self :: use_sorted_classes :: UseSortedClasses , self :: use_symbol_description :: UseSymbolDescription , self :: use_unified_type_signature :: UseUnifiedTypeSignature , self :: use_unique_element_ids :: UseUniqueElementIds ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_await_in_loop :: NoAwaitInLoop , self :: no_bitwise_operators :: NoBitwiseOperators , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_excessive_lines_per_function :: NoExcessiveLinesPerFunction , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_implicit_coercion :: NoImplicitCoercion , self :: no_import_cycles :: NoImportCycles , self :: no_magic_numbers :: NoMagicNumbers , self :: no_misused_promises :: NoMisusedPromises , self :: no_nested_component_definitions :: NoNestedComponentDefinitions , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_process_global :: NoProcessGlobal , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_react_prop_assign :: NoReactPropAssign , self :: no_restricted_elements :: NoRestrictedElements , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_ts_ignore :: NoTsIgnore , self :: no_unassigned_variables :: NoUnassignedVariables , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_backref_in_regex :: NoUselessBackrefInRegex , self :: no_useless_escape_in_string :: NoUselessEscapeInString , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_adjacent_getter_setter :: UseAdjacentGetterSetter , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_consistent_response :: UseConsistentResponse , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_for_component :: UseForComponent , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_image_size :: UseImageSize , self :: use_index_of :: UseIndexOf , self :: use_iterable_callback_return :: UseIterableCallbackReturn , self :: use_json_import_attribute :: UseJsonImportAttribute , self :: use_max_params :: UseMaxParams , self :: use_numeric_separators :: UseNumericSeparators , self :: use_object_spread :: UseObjectSpread , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_readonly_class_properties :: UseReadonlyClassProperties , self :: use_single_js_doc_asterisk :: UseSingleJsDocAsterisk , self :: use_sorted_classes :: UseSortedClasses , self :: use_symbol_description :: UseSymbolDescription , self :: use_unified_type_signature :: UseUnifiedTypeSignature , self :: use_unique_element_ids :: UseUniqueElementIds ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs similarity index 94% rename from crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs rename to crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs index dd258422a381..943c54e2f011 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_async_client_component.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs @@ -8,7 +8,7 @@ use biome_console::markup; use biome_diagnostics::Severity; use biome_js_syntax::AnyJsRoot; use biome_rowan::{AstNode, TokenText}; -use biome_rule_options::no_async_client_component::NoAsyncClientComponentOptions; +use biome_rule_options::no_next_async_client_component::NoNextAsyncClientComponentOptions; declare_lint_rule! { /// Prevent client components from being async functions. @@ -48,9 +48,9 @@ declare_lint_rule! { /// } /// ``` /// - pub NoAsyncClientComponent { + pub NoNextAsyncClientComponent { version: "next", - name: "noAsyncClientComponent", + name: "noNextAsyncClientComponent", language: "js", sources: &[RuleSource::EslintNext("no-async-client-component").same()], recommended: false, @@ -59,11 +59,11 @@ declare_lint_rule! { } } -impl Rule for NoAsyncClientComponent { +impl Rule for NoNextAsyncClientComponent { type Query = Ast; type State = Option; type Signals = Option; - type Options = NoAsyncClientComponentOptions; + type Options = NoNextAsyncClientComponentOptions; fn run(ctx: &RuleContext) -> Self::Signals { let declaration = ctx.query(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap similarity index 80% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap index bd6d2c1a242a..1a3dcc959866 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap @@ -14,7 +14,7 @@ export default async function MyComponent() { # Diagnostics ``` -invalid.jsx:3:16 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:3:16 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Async client component MyComponent is not allowed. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap similarity index 83% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap index d9dcde38459c..f9784b96f438 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/invalidExportDef.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap @@ -15,7 +15,7 @@ export default AnotherComponent; # Diagnostics ``` -invalidExportDef.jsx:3:1 lint/nursery/noAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidExportDef.jsx:3:1 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Async client component AnotherComponent is not allowed. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/valid.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNoAsync.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.js.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.js.snap rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.js.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noAsyncClientComponent/validNotComponent.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx.snap diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 24a35ec361c5..2489e956212c 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -12,7 +12,6 @@ pub mod no_aria_hidden_on_focusable; pub mod no_aria_unsupported_elements; pub mod no_array_index_key; pub mod no_assign_in_expressions; -pub mod no_async_client_component; pub mod no_async_promise_executor; pub mod no_autofocus; pub mod no_await_in_loop; @@ -126,6 +125,7 @@ pub mod no_namespace_import; pub mod no_negation_else; pub mod no_nested_component_definitions; pub mod no_nested_ternary; +pub mod no_next_async_client_component; pub mod no_nodejs_modules; pub mod no_non_null_asserted_optional_chain; pub mod no_non_null_assertion; diff --git a/crates/biome_rule_options/src/no_async_client_component.rs b/crates/biome_rule_options/src/no_async_client_component.rs index 92df9c9477b6..67d41e833544 100644 --- a/crates/biome_rule_options/src/no_async_client_component.rs +++ b/crates/biome_rule_options/src/no_async_client_component.rs @@ -3,4 +3,4 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoAsyncClientComponentOptions {} +pub struct NoNextAsyncClientComponentOptions {} diff --git a/crates/biome_rule_options/src/no_next_async_client_component.rs b/crates/biome_rule_options/src/no_next_async_client_component.rs new file mode 100644 index 000000000000..67d41e833544 --- /dev/null +++ b/crates/biome_rule_options/src/no_next_async_client_component.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::Deserializable; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoNextAsyncClientComponentOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 50187efbbcaa..1211f13ff28b 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1551,10 +1551,6 @@ export interface Correctness { * A list of rules that belong to this group */ export interface Nursery { - /** - * Prevent client components from being async functions. - */ - noAsyncClientComponent?: RuleConfiguration_for_NoAsyncClientComponentOptions; /** * Disallow await inside loops. */ @@ -1607,6 +1603,10 @@ export interface Nursery { * Disallows defining React components inside other components. */ noNestedComponentDefinitions?: RuleConfiguration_for_NoNestedComponentDefinitionsOptions; + /** + * Prevent client components from being async functions. + */ + noNextAsyncClientComponent?: RuleConfiguration_for_NoNextAsyncClientComponentOptions; /** * Disallow non-null assertions after optional chaining expressions. */ @@ -2892,9 +2892,6 @@ export type RuleFixConfiguration_for_UseValidTypeofOptions = export type RuleConfiguration_for_UseYieldOptions = | RulePlainConfiguration | RuleWithOptions_for_UseYieldOptions; -export type RuleConfiguration_for_NoAsyncClientComponentOptions = - | RulePlainConfiguration - | RuleWithOptions_for_NoAsyncClientComponentOptions; export type RuleConfiguration_for_NoAwaitInLoopOptions = | RulePlainConfiguration | RuleWithOptions_for_NoAwaitInLoopOptions; @@ -2934,6 +2931,9 @@ export type RuleFixConfiguration_for_NoMisusedPromisesOptions = export type RuleConfiguration_for_NoNestedComponentDefinitionsOptions = | RulePlainConfiguration | RuleWithOptions_for_NoNestedComponentDefinitionsOptions; +export type RuleConfiguration_for_NoNextAsyncClientComponentOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoNextAsyncClientComponentOptions; export type RuleConfiguration_for_NoNonNullAssertedOptionalChainOptions = | RulePlainConfiguration | RuleWithOptions_for_NoNonNullAssertedOptionalChainOptions; @@ -5087,16 +5087,6 @@ export interface RuleWithOptions_for_UseYieldOptions { */ options: UseYieldOptions; } -export interface RuleWithOptions_for_NoAsyncClientComponentOptions { - /** - * The severity of the emitted diagnostics by the rule - */ - level: RulePlainConfiguration; - /** - * Rule's options - */ - options: NoAsyncClientComponentOptions; -} export interface RuleWithOptions_for_NoAwaitInLoopOptions { /** * The severity of the emitted diagnostics by the rule @@ -5247,6 +5237,16 @@ export interface RuleWithOptions_for_NoNestedComponentDefinitionsOptions { */ options: NoNestedComponentDefinitionsOptions; } +export interface RuleWithOptions_for_NoNextAsyncClientComponentOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoNextAsyncClientComponentOptions; +} export interface RuleWithOptions_for_NoNonNullAssertedOptionalChainOptions { /** * The severity of the emitted diagnostics by the rule @@ -7868,7 +7868,6 @@ export interface UseJsxKeyInIterableOptions { export interface UseValidForDirectionOptions {} export interface UseValidTypeofOptions {} export interface UseYieldOptions {} -export interface NoAsyncClientComponentOptions {} export interface NoAwaitInLoopOptions {} export interface NoBitwiseOperatorsOptions { /** @@ -7905,6 +7904,7 @@ export interface NoImportantStylesOptions {} export interface NoMagicNumbersOptions {} export interface NoMisusedPromisesOptions {} export interface NoNestedComponentDefinitionsOptions {} +export interface NoNextAsyncClientComponentOptions {} export interface NoNonNullAssertedOptionalChainOptions {} export interface NoNoninteractiveElementInteractionsOptions {} export interface NoProcessGlobalOptions {} @@ -8594,7 +8594,7 @@ export type Category = | "lint/correctness/useValidTypeof" | "lint/correctness/useYield" | "lint/nursery/colorNoInvalidHex" - | "lint/nursery/noAsyncClientComponent" + | "lint/nursery/noNextAsyncClientComponent" | "lint/nursery/noAwaitInLoop" | "lint/nursery/noBitwiseOperators" | "lint/nursery/noColorInvalidHex" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index a65f1d208857..169791e4b02b 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -2449,16 +2449,6 @@ "type": "object", "additionalProperties": false }, - "NoAsyncClientComponentConfiguration": { - "anyOf": [ - { "$ref": "#/definitions/RulePlainConfiguration" }, - { "$ref": "#/definitions/RuleWithNoAsyncClientComponentOptions" } - ] - }, - "NoAsyncClientComponentOptions": { - "type": "object", - "additionalProperties": false - }, "NoAsyncPromiseExecutorConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -3639,6 +3629,16 @@ "type": "object", "additionalProperties": false }, + "NoNextAsyncClientComponentConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoNextAsyncClientComponentOptions" } + ] + }, + "NoNextAsyncClientComponentOptions": { + "type": "object", + "additionalProperties": false + }, "NoNodejsModulesConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -4784,13 +4784,6 @@ "description": "A list of rules that belong to this group", "type": "object", "properties": { - "noAsyncClientComponent": { - "description": "Prevent client components from being async functions.", - "anyOf": [ - { "$ref": "#/definitions/NoAsyncClientComponentConfiguration" }, - { "type": "null" } - ] - }, "noAwaitInLoop": { "description": "Disallow await inside loops.", "anyOf": [ @@ -4886,6 +4879,13 @@ { "type": "null" } ] }, + "noNextAsyncClientComponent": { + "description": "Prevent client components from being async functions.", + "anyOf": [ + { "$ref": "#/definitions/NoNextAsyncClientComponentConfiguration" }, + { "type": "null" } + ] + }, "noNonNullAssertedOptionalChain": { "description": "Disallow non-null assertions after optional chaining expressions.", "anyOf": [ @@ -5912,21 +5912,6 @@ }, "additionalProperties": false }, - "RuleWithNoAsyncClientComponentOptions": { - "type": "object", - "required": ["level"], - "properties": { - "level": { - "description": "The severity of the emitted diagnostics by the rule", - "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] - }, - "options": { - "description": "Rule's options", - "allOf": [{ "$ref": "#/definitions/NoAsyncClientComponentOptions" }] - } - }, - "additionalProperties": false - }, "RuleWithNoAsyncPromiseExecutorOptions": { "type": "object", "required": ["level"], @@ -7808,6 +7793,23 @@ }, "additionalProperties": false }, + "RuleWithNoNextAsyncClientComponentOptions": { + "type": "object", + "required": ["level"], + "properties": { + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [ + { "$ref": "#/definitions/NoNextAsyncClientComponentOptions" } + ] + } + }, + "additionalProperties": false + }, "RuleWithNoNodejsModulesOptions": { "type": "object", "required": ["level"], From 998f24a8ed25ca1e1cb5e315a2f0b3beb7c68a34 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 21:44:05 +0200 Subject: [PATCH 08/12] chore: rm old option file --- crates/biome_rule_options/src/no_async_client_component.rs | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 crates/biome_rule_options/src/no_async_client_component.rs diff --git a/crates/biome_rule_options/src/no_async_client_component.rs b/crates/biome_rule_options/src/no_async_client_component.rs deleted file mode 100644 index 67d41e833544..000000000000 --- a/crates/biome_rule_options/src/no_async_client_component.rs +++ /dev/null @@ -1,6 +0,0 @@ -use biome_deserialize_macros::Deserializable; -use serde::{Deserialize, Serialize}; -#[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoNextAsyncClientComponentOptions {} From 305e84981db3151b71feaabaa7aac13124af7caf Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 1 Aug 2025 23:39:37 +0200 Subject: [PATCH 09/12] fix: diagnostic --- .../nursery/no_next_async_client_component.rs | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs index 943c54e2f011..2f133e5cf8b5 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs @@ -111,23 +111,34 @@ impl Rule for NoNextAsyncClientComponent { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let declaration = ctx.query(); - let component_name = state.as_ref().map_or("Component", |token| token.text()); - Some( - RuleDiagnostic::new( - rule_category!(), - declaration.range(), - markup! { - "Async client component "{component_name}" is not allowed." - }, - ) - .note(markup! { - "Client components with \"use client\" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle." - }) - .note(markup! { - "Consider using useEffect for async operations inside the component, or remove the \"use client\" directive if this should be a server component." - }), - ) + Some(match state { + Some(component_name) => { + let name_text = component_name.text(); + RuleDiagnostic::new( + rule_category!(), + declaration.range(), + markup! { + "Async client component "{name_text}" is not allowed." + }, + ) + } + None => { + RuleDiagnostic::new( + rule_category!(), + declaration.range(), + markup! { + "Async client component is not allowed." + }, + ) + } + } + .note(markup! { + "Client components with \"use client\" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle." + }) + .note(markup! { + "Consider using useEffect for async operations inside the component, or remove the \"use client\" directive if this should be a server component." + })) } } From 571469626bd85dacd1f862778a49379e67272d5f Mon Sep 17 00:00:00 2001 From: Antony David Date: Sat, 2 Aug 2025 07:56:55 +0200 Subject: [PATCH 10/12] fix: diag review --- .../src/lint/nursery/no_next_async_client_component.rs | 4 ++-- .../specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap | 2 +- .../noNextAsyncClientComponent/invalidExportDef.jsx.snap | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs index 2f133e5cf8b5..b21000f1d269 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs @@ -119,7 +119,7 @@ impl Rule for NoNextAsyncClientComponent { rule_category!(), declaration.range(), markup! { - "Async client component "{name_text}" is not allowed." + "The component "{name_text}" is an async client component, which is not allowed." }, ) } @@ -128,7 +128,7 @@ impl Rule for NoNextAsyncClientComponent { rule_category!(), declaration.range(), markup! { - "Async client component is not allowed." + "Async client component are not allowed." }, ) } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap index 1a3dcc959866..1eb5fd279a91 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap @@ -16,7 +16,7 @@ export default async function MyComponent() { ``` invalid.jsx:3:16 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Async client component MyComponent is not allowed. + ! The component MyComponent is an async client component, which is not allowed. 1 │ "use client"; 2 │ diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap index f9784b96f438..81f875639cff 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap @@ -17,7 +17,7 @@ export default AnotherComponent; ``` invalidExportDef.jsx:3:1 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Async client component AnotherComponent is not allowed. + ! The component AnotherComponent is an async client component, which is not allowed. 1 │ "use client"; 2 │ From daede665301f514fd3e08afcee5860fec2c20ee7 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 8 Aug 2025 15:30:38 +0200 Subject: [PATCH 11/12] feat(lint): handle missing async component cases --- .../nursery/no_next_async_client_component.rs | 46 +++- .../noNextAsyncClientComponent/invalid.jsx | 46 ++++ .../invalid.jsx.snap | 239 ++++++++++++++++++ .../invalidExportDef.jsx | 6 - .../invalidExportDef.jsx.snap | 37 --- .../noNextAsyncClientComponent/valid.jsx | 81 +++++- .../noNextAsyncClientComponent/valid.jsx.snap | 81 +++++- .../validNoAsync.jsx | 6 - .../validNoAsync.jsx.snap | 14 - .../validNotComponent.js.snap | 14 - .../validNotComponent.jsx | 6 - .../validNotComponent.jsx.snap | 14 - 12 files changed, 490 insertions(+), 100 deletions(-) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.js.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx.snap diff --git a/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs index b21000f1d269..67296eb94e2f 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_next_async_client_component.rs @@ -6,7 +6,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_diagnostics::Severity; -use biome_js_syntax::AnyJsRoot; +use biome_js_syntax::{AnyJsExpression, AnyJsRoot}; use biome_rowan::{AstNode, TokenText}; use biome_rule_options::no_next_async_client_component::NoNextAsyncClientComponentOptions; @@ -94,6 +94,50 @@ impl Rule for NoNextAsyncClientComponent { AnyPotentialReactComponentDeclaration::JsFunctionExportDefaultDeclaration(func) => { func.async_token().is_some() } + AnyPotentialReactComponentDeclaration::JsVariableDeclarator(declarator) => declarator + .initializer() + .and_then(|init| init.expression().ok()) + .and_then(|expr| match expr { + AnyJsExpression::JsArrowFunctionExpression(arrow) => arrow.async_token(), + AnyJsExpression::JsFunctionExpression(func) => func.async_token(), + _ => None, + }) + .is_some(), + AnyPotentialReactComponentDeclaration::JsAssignmentExpression(assignment) => assignment + .right() + .ok() + .and_then(|expr| match expr { + AnyJsExpression::JsArrowFunctionExpression(arrow) => arrow.async_token(), + AnyJsExpression::JsFunctionExpression(func) => func.async_token(), + _ => None, + }) + .is_some(), + AnyPotentialReactComponentDeclaration::JsExportDefaultExpressionClause(export) => { + export + .expression() + .ok() + .and_then(|expr| match expr { + AnyJsExpression::JsArrowFunctionExpression(arrow) => arrow.async_token(), + AnyJsExpression::JsFunctionExpression(func) => func.async_token(), + _ => None, + }) + .is_some() + } + AnyPotentialReactComponentDeclaration::JsMethodObjectMember(method) => { + method.async_token().is_some() + } + AnyPotentialReactComponentDeclaration::JsPropertyObjectMember(prop) => prop + .value() + .ok() + .and_then(|expr| match expr { + AnyJsExpression::JsArrowFunctionExpression(arrow) => arrow.async_token(), + AnyJsExpression::JsFunctionExpression(func) => func.async_token(), + _ => None, + }) + .is_some(), + AnyPotentialReactComponentDeclaration::JsMethodClassMember(method) => { + method.async_token().is_some() + } _ => false, }; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx index 2ab8d3abc8fa..4c1aa89946c8 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx @@ -3,3 +3,49 @@ export default async function MyComponent() { return
Hello
; } + +async function MyComponent2() { + return
Hello
; +} + +const MyComponent3 = async () => { + return
Hello
; +}; + +const MyComponent4 = async function() { + return
Hello
; +}; + +let MyComponent5; +MyComponent5 = async () => { + return
Hello
; +}; + +let MyComponent6; +MyComponent6 = async function() { + return
Hello
; +}; + +const components = { + async MyComponent() { + return
Hello
; + } +}; + +const components2 = { + MyComponent: async () => { + return
Hello
; + } +}; + +const components3 = { + MyComponent: async function() { + return
Hello
; + } +}; + +class ComponentClass { + async MyComponent() { + return
Hello
; + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap index 1eb5fd279a91..c6fbf24d6339 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalid.jsx.snap @@ -10,6 +10,52 @@ export default async function MyComponent() { return
Hello
; } +async function MyComponent2() { + return
Hello
; +} + +const MyComponent3 = async () => { + return
Hello
; +}; + +const MyComponent4 = async function() { + return
Hello
; +}; + +let MyComponent5; +MyComponent5 = async () => { + return
Hello
; +}; + +let MyComponent6; +MyComponent6 = async function() { + return
Hello
; +}; + +const components = { + async MyComponent() { + return
Hello
; + } +}; + +const components2 = { + MyComponent: async () => { + return
Hello
; + } +}; + +const components3 = { + MyComponent: async function() { + return
Hello
; + } +}; + +class ComponentClass { + async MyComponent() { + return
Hello
; + } +} + ``` # Diagnostics @@ -26,6 +72,199 @@ invalid.jsx:3:16 lint/nursery/noNextAsyncClientComponent ━━━━━━━ > 5 │ } │ ^ 6 │ + 7 │ async function MyComponent2() { + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:7:1 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent2 is an async client component, which is not allowed. + + 5 │ } + 6 │ + > 7 │ async function MyComponent2() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 8 │ return
Hello
; + > 9 │ } + │ ^ + 10 │ + 11 │ const MyComponent3 = async () => { + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:11:7 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent3 is an async client component, which is not allowed. + + 9 │ } + 10 │ + > 11 │ const MyComponent3 = async () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 12 │ return
Hello
; + > 13 │ }; + │ ^ + 14 │ + 15 │ const MyComponent4 = async function() { + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:15:7 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent4 is an async client component, which is not allowed. + + 13 │ }; + 14 │ + > 15 │ const MyComponent4 = async function() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 16 │ return
Hello
; + > 17 │ }; + │ ^ + 18 │ + 19 │ let MyComponent5; + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:20:1 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent5 is an async client component, which is not allowed. + + 19 │ let MyComponent5; + > 20 │ MyComponent5 = async () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 21 │ return
Hello
; + > 22 │ }; + │ ^ + 23 │ + 24 │ let MyComponent6; + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:25:1 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent6 is an async client component, which is not allowed. + + 24 │ let MyComponent6; + > 25 │ MyComponent6 = async function() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 26 │ return
Hello
; + > 27 │ }; + │ ^ + 28 │ + 29 │ const components = { + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:30:3 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent is an async client component, which is not allowed. + + 29 │ const components = { + > 30 │ async MyComponent() { + │ ^^^^^^^^^^^^^^^^^^^^^ + > 31 │ return
Hello
; + > 32 │ } + │ ^ + 33 │ }; + 34 │ + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:36:3 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent is an async client component, which is not allowed. + + 35 │ const components2 = { + > 36 │ MyComponent: async () => { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 37 │ return
Hello
; + > 38 │ } + │ ^ + 39 │ }; + 40 │ + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:42:3 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent is an async client component, which is not allowed. + + 41 │ const components3 = { + > 42 │ MyComponent: async function() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 43 │ return
Hello
; + > 44 │ } + │ ^ + 45 │ }; + 46 │ + + i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. + + i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. + + +``` + +``` +invalid.jsx:48:3 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The component MyComponent is an async client component, which is not allowed. + + 47 │ class ComponentClass { + > 48 │ async MyComponent() { + │ ^^^^^^^^^^^^^^^^^^^^^ + > 49 │ return
Hello
; + > 50 │ } + │ ^ + 51 │ } + 52 │ i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx deleted file mode 100644 index 7fe37cc4959f..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx +++ /dev/null @@ -1,6 +0,0 @@ -"use client"; - -async function AnotherComponent() { - return World; -} -export default AnotherComponent; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap deleted file mode 100644 index 81f875639cff..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/invalidExportDef.jsx.snap +++ /dev/null @@ -1,37 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: invalidExportDef.jsx ---- -# Input -```jsx -"use client"; - -async function AnotherComponent() { - return World; -} -export default AnotherComponent; - -``` - -# Diagnostics -``` -invalidExportDef.jsx:3:1 lint/nursery/noNextAsyncClientComponent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! The component AnotherComponent is an async client component, which is not allowed. - - 1 │ "use client"; - 2 │ - > 3 │ async function AnotherComponent() { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 4 │ return World; - > 5 │ } - │ ^ - 6 │ export default AnotherComponent; - 7 │ - - i Client components with "use client" directive should not be async functions as this can cause hydration mismatches and break React's rendering lifecycle. - - i Consider using useEffect for async operations inside the component, or remove the "use client" directive if this should be a server component. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx index 2951d0c0df5f..33ec42a6f7df 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx @@ -1,5 +1,84 @@ /* should not generate diagnostics */ -export default async function MyComponent() { +export default async function ServerComponent() { return
Hello
; } + +const ServerComponent2 = async () => { + return
Hello
; +}; + +let ServerComponent3; +ServerComponent3 = async function() { + return
Hello
; +}; + +const serverComponents = { + async ServerComponent() { + return
Hello
; + } +}; + +const serverComponents2 = { + ServerComponent: async () => { + return
Hello
; + } +}; + +class ServerComponentClass { + async ServerComponent() { + return
Hello
; + } +} + +"use client"; + +export default function ClientComponent() { + return
Hello
; +} + +function ClientComponent2() { + return
Hello
; +} + +const ClientComponent3 = () => { + return
Hello
; +}; + +const ClientComponent4 = function() { + return
Hello
; +}; + +let ClientComponent5; +ClientComponent5 = () => { + return
Hello
; +}; + +let ClientComponent6; +ClientComponent6 = function() { + return
Hello
; +}; + +const components = { + MyComponent() { + return
Hello
; + } +}; + +const components2 = { + MyComponent: () => { + return
Hello
; + } +}; + +const components3 = { + MyComponent: function() { + return
Hello
; + } +}; + +class ComponentClass { + MyComponent() { + return
Hello
; + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap index 10c988bb6bf9..298a0f4f1d5d 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap @@ -6,8 +6,87 @@ expression: valid.jsx ```jsx /* should not generate diagnostics */ -export default async function MyComponent() { +export default async function ServerComponent() { return
Hello
; } +const ServerComponent2 = async () => { + return
Hello
; +}; + +let ServerComponent3; +ServerComponent3 = async function() { + return
Hello
; +}; + +const serverComponents = { + async ServerComponent() { + return
Hello
; + } +}; + +const serverComponents2 = { + ServerComponent: async () => { + return
Hello
; + } +}; + +class ServerComponentClass { + async ServerComponent() { + return
Hello
; + } +} + +"use client"; + +export default function ClientComponent() { + return
Hello
; +} + +function ClientComponent2() { + return
Hello
; +} + +const ClientComponent3 = () => { + return
Hello
; +}; + +const ClientComponent4 = function() { + return
Hello
; +}; + +let ClientComponent5; +ClientComponent5 = () => { + return
Hello
; +}; + +let ClientComponent6; +ClientComponent6 = function() { + return
Hello
; +}; + +const components = { + MyComponent() { + return
Hello
; + } +}; + +const components2 = { + MyComponent: () => { + return
Hello
; + } +}; + +const components3 = { + MyComponent: function() { + return
Hello
; + } +}; + +class ComponentClass { + MyComponent() { + return
Hello
; + } +} + ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx deleted file mode 100644 index a3744447ad91..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx +++ /dev/null @@ -1,6 +0,0 @@ -/* should not generate diagnostics */ -"use client"; - -export default function MyComponent() { - return
Hello
; -} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx.snap deleted file mode 100644 index 17a9558fdf7d..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNoAsync.jsx.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: validNoAsync.jsx ---- -# Input -```jsx -/* should not generate diagnostics */ -"use client"; - -export default function MyComponent() { - return
Hello
; -} - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.js.snap deleted file mode 100644 index 892ba2bc4b14..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.js.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: validNotComponent.js ---- -# Input -```js -/* should not generate diagnostics */ -"use client"; - -export default async function myFunction() { - return 'not a component'; -} - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx deleted file mode 100644 index 6bd220fa3136..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx +++ /dev/null @@ -1,6 +0,0 @@ -/* should not generate diagnostics */ -"use client"; - -export default async function myFunction() { - return 'not a component'; -} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx.snap deleted file mode 100644 index 71f3c1602a03..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validNotComponent.jsx.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: validNotComponent.jsx ---- -# Input -```jsx -/* should not generate diagnostics */ -"use client"; - -export default async function myFunction() { - return 'not a component'; -} - -``` From f99813821ed214b8cf1382fd464534e28de40243 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 8 Aug 2025 15:47:31 +0200 Subject: [PATCH 12/12] test: add valid cases with use client --- .../noNextAsyncClientComponent/valid.jsx | 53 ----------------- .../noNextAsyncClientComponent/valid.jsx.snap | 53 ----------------- .../validClient.jsx | 51 ++++++++++++++++ .../validClient.jsx.snap | 59 +++++++++++++++++++ 4 files changed, 110 insertions(+), 106 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx index 33ec42a6f7df..453865f48037 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx @@ -1,5 +1,4 @@ /* should not generate diagnostics */ - export default async function ServerComponent() { return
Hello
; } @@ -30,55 +29,3 @@ class ServerComponentClass { return
Hello
; } } - -"use client"; - -export default function ClientComponent() { - return
Hello
; -} - -function ClientComponent2() { - return
Hello
; -} - -const ClientComponent3 = () => { - return
Hello
; -}; - -const ClientComponent4 = function() { - return
Hello
; -}; - -let ClientComponent5; -ClientComponent5 = () => { - return
Hello
; -}; - -let ClientComponent6; -ClientComponent6 = function() { - return
Hello
; -}; - -const components = { - MyComponent() { - return
Hello
; - } -}; - -const components2 = { - MyComponent: () => { - return
Hello
; - } -}; - -const components3 = { - MyComponent: function() { - return
Hello
; - } -}; - -class ComponentClass { - MyComponent() { - return
Hello
; - } -} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap index 298a0f4f1d5d..9e24e9482bad 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/valid.jsx.snap @@ -5,7 +5,6 @@ expression: valid.jsx # Input ```jsx /* should not generate diagnostics */ - export default async function ServerComponent() { return
Hello
; } @@ -37,56 +36,4 @@ class ServerComponentClass { } } -"use client"; - -export default function ClientComponent() { - return
Hello
; -} - -function ClientComponent2() { - return
Hello
; -} - -const ClientComponent3 = () => { - return
Hello
; -}; - -const ClientComponent4 = function() { - return
Hello
; -}; - -let ClientComponent5; -ClientComponent5 = () => { - return
Hello
; -}; - -let ClientComponent6; -ClientComponent6 = function() { - return
Hello
; -}; - -const components = { - MyComponent() { - return
Hello
; - } -}; - -const components2 = { - MyComponent: () => { - return
Hello
; - } -}; - -const components3 = { - MyComponent: function() { - return
Hello
; - } -}; - -class ComponentClass { - MyComponent() { - return
Hello
; - } -} - ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx new file mode 100644 index 000000000000..28b8d81607df --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx @@ -0,0 +1,51 @@ +/* should not generate diagnostics */ +"use client"; +export default function ClientComponent() { + return
Hello
; +} + +function ClientComponent2() { + return
Hello
; +} + +const ClientComponent3 = () => { + return
Hello
; +}; + +const ClientComponent4 = function() { + return
Hello
; +}; + +let ClientComponent5; +ClientComponent5 = () => { + return
Hello
; +}; + +let ClientComponent6; +ClientComponent6 = function() { + return
Hello
; +}; + +const components = { + MyComponent() { + return
Hello
; + } +}; + +const components2 = { + MyComponent: () => { + return
Hello
; + } +}; + +const components3 = { + MyComponent: function() { + return
Hello
; + } +}; + +class ComponentClass { + MyComponent() { + return
Hello
; + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx.snap new file mode 100644 index 000000000000..1586fc323889 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noNextAsyncClientComponent/validClient.jsx.snap @@ -0,0 +1,59 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validClient.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ +"use client"; +export default function ClientComponent() { + return
Hello
; +} + +function ClientComponent2() { + return
Hello
; +} + +const ClientComponent3 = () => { + return
Hello
; +}; + +const ClientComponent4 = function() { + return
Hello
; +}; + +let ClientComponent5; +ClientComponent5 = () => { + return
Hello
; +}; + +let ClientComponent6; +ClientComponent6 = function() { + return
Hello
; +}; + +const components = { + MyComponent() { + return
Hello
; + } +}; + +const components2 = { + MyComponent: () => { + return
Hello
; + } +}; + +const components3 = { + MyComponent: function() { + return
Hello
; + } +}; + +class ComponentClass { + MyComponent() { + return
Hello
; + } +} + +```