From 244fc0270a8c48be55a3487c693e067343d4a3ba Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Tue, 4 Nov 2025 10:33:23 +0545 Subject: [PATCH 01/24] chores(lint): fixed merge conflicts --- .../src/analyzer/linter/rules.rs | 103 +++++++++--------- .../src/categories.rs | 1 + crates/biome_js_analyze/src/lint/nursery.rs | 3 +- .../no_leaked_conditional_rendering.rs | 90 +++++++++++++++ .../src/suppressions.tests.rs | 6 +- .../noLeakedConditionalRendering/invalid.js | 3 + .../noLeakedConditionalRendering/valid.js | 2 + crates/biome_rule_options/src/lib.rs | 1 + .../src/no_leaked_conditional_rendering.rs | 6 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 19 ++++ .../@biomejs/biome/configuration_schema.json | 36 ++++++ 11 files changed, 214 insertions(+), 56 deletions(-) create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js create mode 100644 crates/biome_rule_options/src/no_leaked_conditional_rendering.rs diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index dc0f853d46a3..73553549620f 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -206,6 +206,7 @@ pub enum RuleName { NoJsxLiterals, NoLabelVar, NoLabelWithoutControl, + NoLeakedConditionalRendering, NoMagicNumbers, NoMisleadingCharacterClass, NoMisleadingInstantiator, @@ -578,6 +579,7 @@ impl RuleName { Self::NoJsxLiterals => "noJsxLiterals", Self::NoLabelVar => "noLabelVar", Self::NoLabelWithoutControl => "noLabelWithoutControl", + Self::NoLeakedConditionalRendering => "noLeakedConditionalRendering", Self::NoMagicNumbers => "noMagicNumbers", Self::NoMisleadingCharacterClass => "noMisleadingCharacterClass", Self::NoMisleadingInstantiator => "noMisleadingInstantiator", @@ -950,6 +952,7 @@ impl RuleName { Self::NoJsxLiterals => RuleGroup::Nursery, Self::NoLabelVar => RuleGroup::Suspicious, Self::NoLabelWithoutControl => RuleGroup::A11y, + Self::NoLeakedConditionalRendering => RuleGroup::Nursery, Self::NoMagicNumbers => RuleGroup::Style, Self::NoMisleadingCharacterClass => RuleGroup::Suspicious, Self::NoMisleadingInstantiator => RuleGroup::Suspicious, @@ -1327,6 +1330,7 @@ impl std::str::FromStr for RuleName { "noJsxLiterals" => Ok(Self::NoJsxLiterals), "noLabelVar" => Ok(Self::NoLabelVar), "noLabelWithoutControl" => Ok(Self::NoLabelWithoutControl), + "noLeakedConditionalRendering" => Ok(Self::NoLeakedConditionalRendering), "noMagicNumbers" => Ok(Self::NoMagicNumbers), "noMisleadingCharacterClass" => Ok(Self::NoMisleadingCharacterClass), "noMisleadingInstantiator" => Ok(Self::NoMisleadingInstantiator), @@ -4712,7 +4716,7 @@ 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" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [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 = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --."] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [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 = "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 function parameters that are only used in recursive calls."] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [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 = "Disallow unknown DOM properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [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 = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [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 duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [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 consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [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 = "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 = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [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 = "Enforce specific order of Vue compiler macros."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } +pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [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 = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --."] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Succinct description of the rule."] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_conditional_rendering : Option < RuleConfiguration < biome_rule_options :: no_leaked_conditional_rendering :: NoLeakedConditionalRenderingOptions >> , # [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 = "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 function parameters that are only used in recursive calls."] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [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 = "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 = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [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 duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [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 consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [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 = "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 = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [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 = "Enforce specific order of Vue compiler macros."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4724,6 +4728,7 @@ impl Nursery { "noImportCycles", "noIncrementDecrement", "noJsxLiterals", + "noLeakedConditionalRendering", "noMisusedPromises", "noNextAsyncClientComponent", "noParametersOnlyUsedInRecursion", @@ -4784,7 +4789,6 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), ]; } impl RuleGroupExt for Nursery { @@ -4831,7 +4835,7 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } - if let Some(rule) = self.no_jsx_literals.as_ref() + if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); @@ -4861,105 +4865,100 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_unknown_attribute.as_ref() - && rule.is_enabled() - { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); - } if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } if let Some(rule) = self.use_max_params.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } if let Some(rule) = self.use_vue_define_macros_order.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_enabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } index_set } @@ -5000,7 +4999,7 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } - if let Some(rule) = self.no_jsx_literals.as_ref() + if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); @@ -5030,105 +5029,100 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_unknown_attribute.as_ref() - && rule.is_disabled() - { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); - } if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } if let Some(rule) = self.use_max_params.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } if let Some(rule) = self.use_vue_define_macros_order.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_disabled() { - index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } index_set } @@ -5192,6 +5186,10 @@ impl RuleGroupExt for Nursery { .no_jsx_literals .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noLeakedConditionalRendering" => self + .no_leaked_conditional_rendering + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noMisusedPromises" => self .no_misused_promises .as_ref() @@ -5308,6 +5306,7 @@ impl From for Nursery { no_import_cycles: Some(value.into()), no_increment_decrement: Some(value.into()), no_jsx_literals: Some(value.into()), + no_leaked_conditional_rendering: Some(value.into()), no_misused_promises: Some(value.into()), no_next_async_client_component: Some(value.into()), no_parameters_only_used_in_recursion: Some(value.into()), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index ec2bfbb15ddd..08b6df7fccf2 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -174,6 +174,7 @@ define_categories! { "lint/nursery/noImportCycles": "https://biomejs.dev/linter/rules/no-import-cycles", "lint/nursery/noIncrementDecrement": "https://biomejs.dev/linter/rules/no-increment-decrement", "lint/nursery/noJsxLiterals": "https://biomejs.dev/linter/rules/no-jsx-literals", + "lint/nursery/noLeakedConditionalRendering": "https://biomejs.dev/linter/rules/no-leaked-conditional-rendering", "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", "lint/nursery/noMisusedPromises": "https://biomejs.dev/linter/rules/no-misused-promises", "lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index ffcf4a707b56..76c2d1772616 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -10,6 +10,7 @@ pub mod no_floating_promises; pub mod no_import_cycles; pub mod no_increment_decrement; pub mod no_jsx_literals; +pub mod no_leaked_conditional_rendering; pub mod no_misused_promises; pub mod no_next_async_client_component; pub mod no_parameters_only_used_in_recursion; @@ -34,4 +35,4 @@ pub mod use_qwik_valid_lexical_scope; pub mod use_sorted_classes; pub mod use_vue_define_macros_order; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_conditional_rendering :: NoLeakedConditionalRendering , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs new file mode 100644 index 000000000000..c16bc7d1dbfe --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs @@ -0,0 +1,90 @@ +use biome_analyze::{Ast, Rule, RuleDiagnostic, context::RuleContext, declare_lint_rule}; +use biome_console::markup; +use biome_js_syntax::{ + AnyJsExpression, JsIdentifierBinding, JsLogicalExpression, JsxExpressionChild, + JsxTagExpression, jsx_ext::AnyJsxElement, +}; +use biome_rowan::AstNode; +use biome_rule_options::no_leaked_conditional_rendering::NoLeakedConditionalRenderingOptions; + +declare_lint_rule! { + /// Succinct description of the rule. + /// + /// Put context and details about the rule. + /// As a starting point, you can take the description of the corresponding _ESLint_ rule (if any). + /// + /// Try to stay consistent with the descriptions of implemented rules. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// var a = 1; + /// a = 2; + /// ``` + /// + /// ### Valid + /// + /// ```js + /// // var a = 1; + /// ``` + /// + pub NoLeakedConditionalRendering { + version: "next", + name: "noLeakedConditionalRendering", + language: "js", + recommended: false, + } +} + +impl Rule for NoLeakedConditionalRendering { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = NoLeakedConditionalRenderingOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let jsx_expression = ctx.query(); + if !jsx_expression.to_trimmed_text().contains("&&") { + return None; + } + match jsx_expression.expression()? { + AnyJsExpression::JsLogicalExpression(exp) => { + let left = exp.left().ok()?; + let right = exp.right().ok()?; + + match left { + AnyJsExpression::JsIdentifierExpression(ident) => { + let ident = ident.name().ok()?; + if ident.to_trimmed_text() == "NaN" {} + } + _ => return None, + } + } + _ => return None, + } + + Some(()) + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + // + // Read our guidelines to write great diagnostics: + // https://docs.rs/biome_analyze/latest/biome_analyze/#what-a-rule-should-say-to-the-user + // + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Variable is read here." + }, + ) + .note(markup! { + "This note will give you more information." + }), + ) + } +} diff --git a/crates/biome_js_analyze/src/suppressions.tests.rs b/crates/biome_js_analyze/src/suppressions.tests.rs index 2fc7237c2e67..24f232a1237d 100644 --- a/crates/biome_js_analyze/src/suppressions.tests.rs +++ b/crates/biome_js_analyze/src/suppressions.tests.rs @@ -7,16 +7,16 @@ use biome_js_syntax::{JsFileSource, TextRange, TextSize}; use biome_package::{Dependencies, PackageJson}; use std::slice; -#[ignore] #[test] fn quick_test() { - const SOURCE: &str = r#"f({ prop: () => {} })"#; + const SOURCE: &str = r#" + const b = <>{!(NaN) && }; "#; let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default()); let mut error_ranges: Vec = Vec::new(); let options = AnalyzerOptions::default(); - let rule_filter = RuleFilter::Rule("nursery", "useExplicitType"); + let rule_filter = RuleFilter::Rule("nursery", "noLeakedConditionalRendering"); let dependencies = Dependencies(Box::new([("buffer".into(), "latest".into())])); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js new file mode 100644 index 000000000000..d58e4390e601 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js @@ -0,0 +1,3 @@ +var a = 1; +a = 2; +a = 3; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js new file mode 100644 index 000000000000..f299f876959a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js @@ -0,0 +1,2 @@ +/* should not generate diagnostics */ +// var a = 1; \ No newline at end of file diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 4695d8ccc6dc..7e0239818693 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -119,6 +119,7 @@ pub mod no_irregular_whitespace; pub mod no_jsx_literals; pub mod no_label_var; pub mod no_label_without_control; +pub mod no_leaked_conditional_rendering; pub mod no_magic_numbers; pub mod no_misleading_character_class; pub mod no_misleading_instantiator; diff --git a/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs b/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs new file mode 100644 index 000000000000..1df39e16122c --- /dev/null +++ b/crates/biome_rule_options/src/no_leaked_conditional_rendering.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 NoLeakedConditionalRenderingOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index bb7ae96540d4..ce83edd8720f 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1692,6 +1692,10 @@ export interface Nursery { * Disallow string literals inside JSX elements. */ noJsxLiterals?: RuleConfiguration_for_NoJsxLiteralsOptions; + /** + * Succinct description of the rule. + */ + noLeakedConditionalRendering?: RuleConfiguration_for_NoLeakedConditionalRenderingOptions; /** * Disallow Promises to be used in places where they are almost certainly a mistake. */ @@ -3082,6 +3086,9 @@ export type RuleConfiguration_for_NoIncrementDecrementOptions = export type RuleConfiguration_for_NoJsxLiteralsOptions = | RulePlainConfiguration | RuleWithOptions_for_NoJsxLiteralsOptions; +export type RuleConfiguration_for_NoLeakedConditionalRenderingOptions = + | RulePlainConfiguration + | RuleWithOptions_for_NoLeakedConditionalRenderingOptions; export type RuleFixConfiguration_for_NoMisusedPromisesOptions = | RulePlainConfiguration | RuleWithFixOptions_for_NoMisusedPromisesOptions; @@ -5568,6 +5575,16 @@ export interface RuleWithOptions_for_NoJsxLiteralsOptions { */ options: NoJsxLiteralsOptions; } +export interface RuleWithOptions_for_NoLeakedConditionalRenderingOptions { + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoLeakedConditionalRenderingOptions; +} export interface RuleWithFixOptions_for_NoMisusedPromisesOptions { /** * The kind of the code actions emitted by the rule @@ -8336,6 +8353,7 @@ export interface NoJsxLiteralsOptions { */ noStrings?: boolean; } +export interface NoLeakedConditionalRenderingOptions {} export interface NoMisusedPromisesOptions {} export interface NoNextAsyncClientComponentOptions {} export interface NoParametersOnlyUsedInRecursionOptions {} @@ -9115,6 +9133,7 @@ export type Category = | "lint/nursery/noImportCycles" | "lint/nursery/noIncrementDecrement" | "lint/nursery/noJsxLiterals" + | "lint/nursery/noLeakedConditionalRendering" | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noMisusedPromises" | "lint/nursery/noNextAsyncClientComponent" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 6d5bba373965..976bbabe69a6 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -3755,6 +3755,16 @@ }, "additionalProperties": false }, + "NoLeakedConditionalRenderingConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoLeakedConditionalRenderingOptions" } + ] + }, + "NoLeakedConditionalRenderingOptions": { + "type": "object", + "additionalProperties": false + }, "NoMagicNumbersConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -5176,6 +5186,15 @@ { "type": "null" } ] }, + "noLeakedConditionalRendering": { + "description": "Succinct description of the rule.", + "anyOf": [ + { + "$ref": "#/definitions/NoLeakedConditionalRenderingConfiguration" + }, + { "type": "null" } + ] + }, "noMisusedPromises": { "description": "Disallow Promises to be used in places where they are almost certainly a mistake.", "anyOf": [ @@ -7909,6 +7928,23 @@ }, "additionalProperties": false }, + "RuleWithNoLeakedConditionalRenderingOptions": { + "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/NoLeakedConditionalRenderingOptions" } + ] + } + }, + "additionalProperties": false + }, "RuleWithNoMagicNumbersOptions": { "type": "object", "required": ["level"], From d1689a69bfb85b85a6f59ad93cb6ee17f8551602 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Tue, 18 Nov 2025 23:47:54 +0545 Subject: [PATCH 02/24] feat(lint): ported `no-leaked-conditional-rendering` rule from `eslint-react` --- .../src/analyzer/linter/rules.rs | 124 ++++--- crates/biome_js_analyze/src/lint/nursery.rs | 2 +- .../no_leaked_conditional_rendering.rs | 203 +++++++++-- .../src/suppressions.tests.rs | 6 +- crates/biome_js_analyze/tests/quick_test.rs | 16 +- .../coerce/invalid.jsx | 88 +++++ .../coerce/invalid.jsx.snap | 325 ++++++++++++++++++ .../coerce/invalid.options.json | 16 + .../coerce/valid.jsx | 39 +++ .../coerce/valid.jsx.snap | 47 +++ .../coerce/valid.options.json | 16 + .../noLeakedConditionalRendering/invalid.js | 3 - .../noLeakedConditionalRendering/invalid.jsx | 37 ++ .../invalid.jsx.snap | 177 ++++++++++ .../ternary/invalid.jsx | 50 +++ .../ternary/invalid.jsx.snap | 269 +++++++++++++++ .../ternary/invalid.options.json | 16 + .../ternary/valid.jsx | 5 + .../ternary/valid.jsx.snap | 13 + .../ternary/valid.options.json | 16 + .../noLeakedConditionalRendering/valid.js | 2 - .../noLeakedConditionalRendering/valid.jsx | 64 ++++ .../valid.jsx.snap | 72 ++++ .../src/no_leaked_conditional_rendering.rs | 12 +- .../@biomejs/backend-jsonrpc/src/workspace.ts | 4 +- .../@biomejs/biome/configuration_schema.json | 6 + 26 files changed, 1520 insertions(+), 108 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx.snap diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 73553549620f..4301193ff422 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -4716,7 +4716,7 @@ 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" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [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 = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --."] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Succinct description of the rule."] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_conditional_rendering : Option < RuleConfiguration < biome_rule_options :: no_leaked_conditional_rendering :: NoLeakedConditionalRenderingOptions >> , # [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 = "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 function parameters that are only used in recursive calls."] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [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 = "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 = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [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 duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [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 consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [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 = "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 = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [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 = "Enforce specific order of Vue compiler macros."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } +pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports."] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\"."] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources."] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [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 = "Prevent import cycles."] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --."] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements."] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Succinct description of the rule."] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_conditional_rendering : Option < RuleConfiguration < biome_rule_options :: no_leaked_conditional_rendering :: NoLeakedConditionalRenderingOptions >> , # [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 = "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 function parameters that are only used in recursive calls."] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop."] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [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 = "Disallow unknown DOM properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [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 = "Disallow expression statements that are neither a function call nor an assignment."] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [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 duplicate keys in Vue component data, methods, computed properties, and other options."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [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 consistent arrow function bodies."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date."] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [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 = "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 = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [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 = "Enforce specific order of Vue compiler macros."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce multi-word component names in Vue components."] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4789,6 +4789,8 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), ]; } impl RuleGroupExt for Nursery { @@ -4835,131 +4837,141 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } - if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() + if let Some(rule) = self.no_jsx_literals.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } - if let Some(rule) = self.no_misused_promises.as_ref() + if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } - if let Some(rule) = self.no_next_async_client_component.as_ref() + if let Some(rule) = self.no_misused_promises.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() + if let Some(rule) = self.no_next_async_client_component.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() + if let Some(rule) = self.no_shadow.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } - if let Some(rule) = self.no_unresolved_imports.as_ref() + if let Some(rule) = self.no_unknown_attribute.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } - if let Some(rule) = self.no_unused_expressions.as_ref() + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } - if let Some(rule) = self.no_useless_catch_binding.as_ref() + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } - if let Some(rule) = self.no_useless_undefined.as_ref() + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } - if let Some(rule) = self.no_vue_reserved_props.as_ref() + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.use_consistent_arrow_return.as_ref() + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } - if let Some(rule) = self.use_deprecated_date.as_ref() + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } - if let Some(rule) = self.use_explicit_type.as_ref() + if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } - if let Some(rule) = self.use_qwik_method_usage.as_ref() + if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + if let Some(rule) = self.use_max_params.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } - if let Some(rule) = self.use_vue_define_macros_order.as_ref() + if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } + if let Some(rule) = self.use_vue_define_macros_order.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); + } + if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); + } index_set } fn get_disabled_rules(&self) -> FxHashSet> { @@ -4999,131 +5011,141 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } - if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() + if let Some(rule) = self.no_jsx_literals.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } - if let Some(rule) = self.no_misused_promises.as_ref() + if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } - if let Some(rule) = self.no_next_async_client_component.as_ref() + if let Some(rule) = self.no_misused_promises.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() + if let Some(rule) = self.no_next_async_client_component.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_unnecessary_conditions.as_ref() + if let Some(rule) = self.no_shadow.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } - if let Some(rule) = self.no_unresolved_imports.as_ref() + if let Some(rule) = self.no_unknown_attribute.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } - if let Some(rule) = self.no_unused_expressions.as_ref() + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } - if let Some(rule) = self.no_useless_catch_binding.as_ref() + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } - if let Some(rule) = self.no_useless_undefined.as_ref() + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } - if let Some(rule) = self.no_vue_reserved_keys.as_ref() + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } - if let Some(rule) = self.no_vue_reserved_props.as_ref() + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.use_consistent_arrow_return.as_ref() + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } - if let Some(rule) = self.use_deprecated_date.as_ref() + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } - if let Some(rule) = self.use_explicit_type.as_ref() + if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } - if let Some(rule) = self.use_qwik_method_usage.as_ref() + if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + if let Some(rule) = self.use_max_params.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } - if let Some(rule) = self.use_vue_define_macros_order.as_ref() + if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } + if let Some(rule) = self.use_vue_define_macros_order.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); + } + if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 76c2d1772616..dfcad8cd1610 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -35,4 +35,4 @@ pub mod use_qwik_valid_lexical_scope; pub mod use_sorted_classes; pub mod use_vue_define_macros_order; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_conditional_rendering :: NoLeakedConditionalRendering , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_conditional_rendering :: NoLeakedConditionalRendering , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs index c16bc7d1dbfe..61f2ac7b90d5 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs @@ -1,12 +1,15 @@ -use biome_analyze::{Ast, Rule, RuleDiagnostic, context::RuleContext, declare_lint_rule}; +use biome_analyze::{Rule, RuleDiagnostic, context::RuleContext, declare_lint_rule}; use biome_console::markup; +use biome_js_semantic::{Binding, SemanticModel}; use biome_js_syntax::{ - AnyJsExpression, JsIdentifierBinding, JsLogicalExpression, JsxExpressionChild, - JsxTagExpression, jsx_ext::AnyJsxElement, + AnyJsExpression, JsConditionalExpression, JsLogicalExpression, JsLogicalOperator, JsSyntaxNode, + binding_ext::AnyJsBindingDeclaration, }; -use biome_rowan::AstNode; +use biome_rowan::{AstNode, SyntaxResult, declare_node_union}; use biome_rule_options::no_leaked_conditional_rendering::NoLeakedConditionalRenderingOptions; +use crate::services::semantic::Semantic; + declare_lint_rule! { /// Succinct description of the rule. /// @@ -38,53 +41,177 @@ declare_lint_rule! { } } +const COERCE_STRATEGY: &str = "coerce"; +const TERNARY_STRATEGY: &str = "ternary"; +const TERNARY_INVALID_ALTERNATE_VALUES: &[&str] = &["null", "undefined", "false"]; + +const DEFAULT_VALID_STRATEGIES: &[&str] = &[TERNARY_STRATEGY, COERCE_STRATEGY]; + +pub enum NoLeakedConditionalRenderingState { + NoPotentialLeakedRender, +} + +fn get_variable_from_context( + model: &SemanticModel, + node: &JsSyntaxNode, + name: &str, +) -> Option { + let scope = model.scope(node); + + // Search through scope hierarchy + for scope in scope.ancestors() { + if let Some(binding) = scope.get_binding(name) { + return Some(binding); + } + } + + None +} + +declare_node_union! { + pub Query = JsLogicalExpression | JsConditionalExpression +} + impl Rule for NoLeakedConditionalRendering { - type Query = Ast; - type State = (); + type Query = Semantic; + type State = NoLeakedConditionalRenderingState; type Signals = Option; type Options = NoLeakedConditionalRenderingOptions; fn run(ctx: &RuleContext) -> Self::Signals { - let jsx_expression = ctx.query(); - if !jsx_expression.to_trimmed_text().contains("&&") { - return None; - } - match jsx_expression.expression()? { - AnyJsExpression::JsLogicalExpression(exp) => { + let query = ctx.query(); + let model = ctx.model(); + + let options = ctx.options(); + let valid_strategies: Vec> = + if let Some(strategies) = options.valid_strategies.clone() { + strategies.to_vec() + } else { + DEFAULT_VALID_STRATEGIES + .iter() + .map(|&str| str.into()) + .collect() + }; + match query { + Query::JsLogicalExpression(exp) => { + let op = exp.operator().ok()?; let left = exp.left().ok()?; - let right = exp.right().ok()?; - match left { - AnyJsExpression::JsIdentifierExpression(ident) => { - let ident = ident.name().ok()?; - if ident.to_trimmed_text() == "NaN" {} + if op != JsLogicalOperator::LogicalAnd { + return None; + } + + let is_coerce_valid_left_side = matches!( + left, + AnyJsExpression::JsUnaryExpression(_) + | AnyJsExpression::JsCallExpression(_) + | AnyJsExpression::JsBinaryExpression(_) + ); + + if valid_strategies + .iter() + .any(|s| s.as_ref() == COERCE_STRATEGY) + { + if is_coerce_valid_left_side + || get_is_coerce_valid_nested_logical_expression(exp.left()) + { + return None; + } + let left_node = left.syntax(); + + if let AnyJsExpression::JsIdentifierExpression(ident) = &left { + let name = ident.name().ok()?; + + let binding = get_variable_from_context( + model, + left_node, + name.to_trimmed_text().trim(), + )?; + + let declaration = binding.tree().declaration()?; + + if let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = + declaration + { + let initializer = declarator.initializer()?; + let initializer = initializer.expression().ok()?; + + if let AnyJsExpression::AnyJsLiteralExpression(literal) = initializer { + let literal = literal.value_token().ok()?; + + if matches!(literal.text_trimmed(), "true" | "false") { + return None; + } + } + } } - _ => return None, } + + let is_literal = matches!(left, AnyJsExpression::AnyJsLiteralExpression(_)); + if is_literal && left.to_trimmed_text().is_empty() { + return None; + } + + return Some(NoLeakedConditionalRenderingState::NoPotentialLeakedRender); } - _ => return None, - } + Query::JsConditionalExpression(expr) => { + if valid_strategies + .iter() + .any(|s| s.as_ref() == TERNARY_STRATEGY) + { + return None; + } + let alternate = expr.alternate().ok()?; + let is_problematic_alternate = TERNARY_INVALID_ALTERNATE_VALUES + .iter() + .any(|&s| alternate.to_trimmed_text() == s); - Some(()) + let is_jsx_element_alt = matches!(alternate, AnyJsExpression::JsxTagExpression(_)); + + if !is_problematic_alternate || is_jsx_element_alt { + return None; + } + + return Some(NoLeakedConditionalRenderingState::NoPotentialLeakedRender); + } + } } - fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { - // - // Read our guidelines to write great diagnostics: - // https://docs.rs/biome_analyze/latest/biome_analyze/#what-a-rule-should-say-to-the-user - // + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); - Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - markup! { - "Variable is read here." - }, - ) - .note(markup! { - "This note will give you more information." - }), - ) + + match state { + NoLeakedConditionalRenderingState::NoPotentialLeakedRender { + } => Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Potential leaked value that might cause unintentionally rendered values or rendering crashes" + }, + ) + .note(markup! { + "This note will give you more information." + }), + ), + } + } +} + +fn get_is_coerce_valid_nested_logical_expression(node: SyntaxResult) -> bool { + match node { + Ok(AnyJsExpression::JsLogicalExpression(expr)) => { + get_is_coerce_valid_nested_logical_expression(expr.left()) + && get_is_coerce_valid_nested_logical_expression(expr.right()) + } + Ok(AnyJsExpression::JsParenthesizedExpression(expr)) => { + get_is_coerce_valid_nested_logical_expression(expr.expression()) + } + Ok( + AnyJsExpression::JsUnaryExpression(_) + | AnyJsExpression::JsCallExpression(_) + | AnyJsExpression::JsBinaryExpression(_), + ) => true, + _ => false, } } diff --git a/crates/biome_js_analyze/src/suppressions.tests.rs b/crates/biome_js_analyze/src/suppressions.tests.rs index 24f232a1237d..35a5a67685c2 100644 --- a/crates/biome_js_analyze/src/suppressions.tests.rs +++ b/crates/biome_js_analyze/src/suppressions.tests.rs @@ -10,7 +10,11 @@ use std::slice; #[test] fn quick_test() { const SOURCE: &str = r#" - const b = <>{!(NaN) && }; "#; + const isOpen = true; + const Component = () => { + return 0} /> + } + "#; let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default()); diff --git a/crates/biome_js_analyze/tests/quick_test.rs b/crates/biome_js_analyze/tests/quick_test.rs index adbb7a946590..8dd31a5201a1 100644 --- a/crates/biome_js_analyze/tests/quick_test.rs +++ b/crates/biome_js_analyze/tests/quick_test.rs @@ -25,17 +25,13 @@ fn project_layout_with_top_level_dependencies(dependencies: Dependencies) -> Arc } // use this test check if your snippet produces the diagnostics you wish, without using a snapshot -#[ignore] #[test] fn quick_test() { - const FILENAME: &str = "dummyFile.ts"; - const SOURCE: &str = r#"import * as postcssModules from "postcss-modules" - -type PostcssOptions = Parameters[0] - -export function f(options: PostcssOptions) { - console.log(options) -} + const FILENAME: &str = "App.tsx"; + const SOURCE: &str = r#" + const Component = ({ elements }) => { + return
{elements.length && }
+ } "#; let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default()); @@ -52,7 +48,7 @@ export function f(options: PostcssOptions) { .with_configuration( AnalyzerConfiguration::default().with_jsx_runtime(JsxRuntime::ReactClassic), ); - let rule_filter = RuleFilter::Rule("correctness", "noUnusedImports"); + let rule_filter = RuleFilter::Rule("nursery", "noLeakedConditionalRendering"); let dependencies = Dependencies(Box::new([("buffer".into(), "latest".into())])); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx new file mode 100644 index 000000000000..4d9d3e9e29e7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx @@ -0,0 +1,88 @@ +// Invalid cases with validStrategies: ['coerce'] + +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +const Component7 = ({ connection, hasError, hasErrorUpdate }) => { + return
{connection && (hasError || hasErrorUpdate)}
; +}; + +// Ternary isn't valid if strategy is only "coerce" +const Component8 = ({ count, title }) => { + return
{count ? title : null}
; +}; + +const Component9 = ({ count, title }) => { + return
{!count ? title : null}
; +}; + +const Component10 = ({ count, somethingElse, title }) => { + return
{count && somethingElse ? title : null}
; +}; + +const Component11 = ({ items, somethingElse, title }) => { + return
{items.length > 0 && somethingElse && title}
; +}; + +const MyComponent1 = () => { + const items = []; + const breakpoint = { phones: true }; + + return
{items.length > 0 && breakpoint.phones && }
; +}; + +const MyComponent2 = () => { + return
{maybeObject && (isFoo ? : )}
; +}; + +const MyComponent3 = () => { + return ; +}; + +const MyComponent4 = () => { + return ; +}; + +const MyComponent5 = () => { + return ( + <> + {someCondition && ( +
+

hello

+
+ )} + + ); +}; + +const MyComponent6 = () => { + return <>{someCondition && }; +}; + +const isOpen = 0; +const Component12 = () => { + return 0} />; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap new file mode 100644 index 000000000000..c338c1986343 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap @@ -0,0 +1,325 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +// Invalid cases with validStrategies: ['coerce'] + +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +const Component7 = ({ connection, hasError, hasErrorUpdate }) => { + return
{connection && (hasError || hasErrorUpdate)}
; +}; + +// Ternary isn't valid if strategy is only "coerce" +const Component8 = ({ count, title }) => { + return
{count ? title : null}
; +}; + +const Component9 = ({ count, title }) => { + return
{!count ? title : null}
; +}; + +const Component10 = ({ count, somethingElse, title }) => { + return
{count && somethingElse ? title : null}
; +}; + +const Component11 = ({ items, somethingElse, title }) => { + return
{items.length > 0 && somethingElse && title}
; +}; + +const MyComponent1 = () => { + const items = []; + const breakpoint = { phones: true }; + + return
{items.length > 0 && breakpoint.phones && }
; +}; + +const MyComponent2 = () => { + return
{maybeObject && (isFoo ? : )}
; +}; + +const MyComponent3 = () => { + return ; +}; + +const MyComponent4 = () => { + return ; +}; + +const MyComponent5 = () => { + return ( + <> + {someCondition && ( +
+

hello

+
+ )} + + ); +}; + +const MyComponent6 = () => { + return <>{someCondition && }; +}; + +const isOpen = 0; +const Component12 = () => { + return 0} />; +}; + +``` + +# Diagnostics +``` +invalid.jsx:4:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 3 │ const Component1 = ({ count, title }) => { + > 4 │ return
{count && title}
; + │ ^^^^^^^^^^^^^^ + 5 │ }; + 6 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:8:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 7 │ const Component2 = ({ count }) => { + > 8 │ return
{count && There are {count} results}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ }; + 10 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:12:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 11 │ const Component3 = ({ elements }) => { + > 12 │ return
{elements.length && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │ }; + 14 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:17:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 15 │ const Component4 = ({ nestedCollection }) => { + 16 │ return ( + > 17 │
{nestedCollection.elements.length && }
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ ); + 19 │ }; + + i This note will give you more information. + + +``` + +``` +invalid.jsx:22:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 21 │ const Component5 = ({ elements }) => { + > 22 │ return
{elements[0] && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 23 │ }; + 24 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:26:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 25 │ const Component6 = ({ numberA, numberB }) => { + > 26 │ return
{(numberA || numberB) && {numberA + numberB}}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ }; + 28 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:30:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 29 │ const Component7 = ({ connection, hasError, hasErrorUpdate }) => { + > 30 │ return
{connection && (hasError || hasErrorUpdate)}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 31 │ }; + 32 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:35:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 33 │ // Ternary isn't valid if strategy is only "coerce" + 34 │ const Component8 = ({ count, title }) => { + > 35 │ return
{count ? title : null}
; + │ ^^^^^^^^^^^^^^^^^^^^ + 36 │ }; + 37 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:39:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 38 │ const Component9 = ({ count, title }) => { + > 39 │ return
{!count ? title : null}
; + │ ^^^^^^^^^^^^^^^^^^^^^ + 40 │ }; + 41 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:43:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 42 │ const Component10 = ({ count, somethingElse, title }) => { + > 43 │ return
{count && somethingElse ? title : null}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 44 │ }; + 45 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:43:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 42 │ const Component10 = ({ count, somethingElse, title }) => { + > 43 │ return
{count && somethingElse ? title : null}
; + │ ^^^^^^^^^^^^^^^^^^^^^^ + 44 │ }; + 45 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:47:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 46 │ const Component11 = ({ items, somethingElse, title }) => { + > 47 │ return
{items.length > 0 && somethingElse && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 48 │ }; + 49 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:54:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 52 │ const breakpoint = { phones: true }; + 53 │ + > 54 │ return
{items.length > 0 && breakpoint.phones && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 55 │ }; + 56 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:87:24 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 85 │ const isOpen = 0; + 86 │ const Component12 = () => { + > 87 │ return 0} />; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 88 │ }; + 89 │ + + i This note will give you more information. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json new file mode 100644 index 000000000000..b0f8c4ca6e84 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noLeakedConditionalRendering": { + "level": "error", + "options": { + "validStrategies": ["coerce"] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx new file mode 100644 index 000000000000..b5b5268aad91 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx @@ -0,0 +1,39 @@ +/* should not generate diagnostics */ + +const Component1 = ({ elements, count }) => { + return
{!!count && }
; +}; + +const Component2 = ({ elements, count }) => { + return ( +
+
{direction ? (direction === 'down' ? '▼' : '▲') : ''}
+
{containerName.length > 0 ? 'Loading several stuff' : 'Loading'}
+
+ ); +}; + +const Component3 = ({ direction }) => { + return ( +
+
{!!direction && direction === 'down' && '▼'}
+
{direction === 'down' && !!direction && '▼'}
+
{direction === 'down' || (!!direction && '▼')}
+
{(!display || display === DISPLAY.WELCOME) && foo}
+
+ ); +}; + +const Component4 = ({ elements, count }) => { + return
{count ? : }
; +}; + +const isOpen1 = true; +const Component5 = () => { + return 0} />; +}; + +const isOpen2 = false; +const Component6 = () => { + return 0} />; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx.snap new file mode 100644 index 000000000000..4b342b15f906 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx.snap @@ -0,0 +1,47 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ + +const Component1 = ({ elements, count }) => { + return
{!!count && }
; +}; + +const Component2 = ({ elements, count }) => { + return ( +
+
{direction ? (direction === 'down' ? '▼' : '▲') : ''}
+
{containerName.length > 0 ? 'Loading several stuff' : 'Loading'}
+
+ ); +}; + +const Component3 = ({ direction }) => { + return ( +
+
{!!direction && direction === 'down' && '▼'}
+
{direction === 'down' && !!direction && '▼'}
+
{direction === 'down' || (!!direction && '▼')}
+
{(!display || display === DISPLAY.WELCOME) && foo}
+
+ ); +}; + +const Component4 = ({ elements, count }) => { + return
{count ? : }
; +}; + +const isOpen1 = true; +const Component5 = () => { + return 0} />; +}; + +const isOpen2 = false; +const Component6 = () => { + return 0} />; +}; + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json new file mode 100644 index 000000000000..b0f8c4ca6e84 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noLeakedConditionalRendering": { + "level": "error", + "options": { + "validStrategies": ["coerce"] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js deleted file mode 100644 index d58e4390e601..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.js +++ /dev/null @@ -1,3 +0,0 @@ -var a = 1; -a = 2; -a = 3; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx new file mode 100644 index 000000000000..30dbf5c1e507 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx @@ -0,0 +1,37 @@ +// Invalid cases with default options (both 'coerce' and 'ternary' strategies) + +const Example1 = () => { + return ( + <> + {0 && } + {'' && } + {NaN && } + + ); +}; + +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap new file mode 100644 index 000000000000..d5654491c603 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap @@ -0,0 +1,177 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +// Invalid cases with default options (both 'coerce' and 'ternary' strategies) + +const Example1 = () => { + return ( + <> + {0 && } + {'' && } + {NaN && } + + ); +}; + +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +``` + +# Diagnostics +``` +invalid.jsx:6:5 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 4 │ return ( + 5 │ <> + > 6 │ {0 && } + │ ^^^^^^^^^^^^^^^^^^ + 7 │ {'' && } + 8 │ {NaN && } + + i This note will give you more information. + + +``` + +``` +invalid.jsx:7:5 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 5 │ <> + 6 │ {0 && } + > 7 │ {'' && } + │ ^^^^^^^^^^^^^^^^^^^ + 8 │ {NaN && } + 9 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:14:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 13 │ const Component1 = ({ count, title }) => { + > 14 │ return
{count && title}
; + │ ^^^^^^^^^^^^^^ + 15 │ }; + 16 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:18:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 17 │ const Component2 = ({ count }) => { + > 18 │ return
{count && There are {count} results}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 19 │ }; + 20 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:22:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 21 │ const Component3 = ({ elements }) => { + > 22 │ return
{elements.length && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 23 │ }; + 24 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:27:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 25 │ const Component4 = ({ nestedCollection }) => { + 26 │ return ( + > 27 │
{nestedCollection.elements.length && }
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 28 │ ); + 29 │ }; + + i This note will give you more information. + + +``` + +``` +invalid.jsx:32:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 31 │ const Component5 = ({ elements }) => { + > 32 │ return
{elements[0] && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 33 │ }; + 34 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:36:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 35 │ const Component6 = ({ numberA, numberB }) => { + > 36 │ return
{(numberA || numberB) && {numberA + numberB}}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 37 │ }; + 38 │ + + i This note will give you more information. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx new file mode 100644 index 000000000000..8181daf7d040 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx @@ -0,0 +1,50 @@ +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +// Boolean coerce isn't valid if strategy is only "ternary" +const Component7 = ({ someCondition, title }) => { + return
{!someCondition && title}
; +}; + +const Component8 = ({ count, title }) => { + return
{!!count && title}
; +}; + +const Component9 = ({ count, title }) => { + return
{count > 0 && title}
; +}; + +const Component10 = ({ count, title }) => { + return
{0 != count && title}
; +}; + +const Component11 = ({ count, total, title }) => { + return
{count < total && title}
; +}; + +const Component12 = ({ count, title, somethingElse }) => { + return
{!!(count && somethingElse) && title}
; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap new file mode 100644 index 000000000000..7bb24d358c9c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap @@ -0,0 +1,269 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +// Boolean coerce isn't valid if strategy is only "ternary" +const Component7 = ({ someCondition, title }) => { + return
{!someCondition && title}
; +}; + +const Component8 = ({ count, title }) => { + return
{!!count && title}
; +}; + +const Component9 = ({ count, title }) => { + return
{count > 0 && title}
; +}; + +const Component10 = ({ count, title }) => { + return
{0 != count && title}
; +}; + +const Component11 = ({ count, total, title }) => { + return
{count < total && title}
; +}; + +const Component12 = ({ count, title, somethingElse }) => { + return
{!!(count && somethingElse) && title}
; +}; + +``` + +# Diagnostics +``` +invalid.jsx:2:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 1 │ const Component1 = ({ count, title }) => { + > 2 │ return
{count && title}
; + │ ^^^^^^^^^^^^^^ + 3 │ }; + 4 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:6:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 5 │ const Component2 = ({ count }) => { + > 6 │ return
{count && There are {count} results}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ }; + 8 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:10:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 9 │ const Component3 = ({ elements }) => { + > 10 │ return
{elements.length && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ }; + 12 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:15:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 13 │ const Component4 = ({ nestedCollection }) => { + 14 │ return ( + > 15 │
{nestedCollection.elements.length && }
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │ ); + 17 │ }; + + i This note will give you more information. + + +``` + +``` +invalid.jsx:20:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 19 │ const Component5 = ({ elements }) => { + > 20 │ return
{elements[0] && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 21 │ }; + 22 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:24:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 23 │ const Component6 = ({ numberA, numberB }) => { + > 24 │ return
{(numberA || numberB) && {numberA + numberB}}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 25 │ }; + 26 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:29:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 27 │ // Boolean coerce isn't valid if strategy is only "ternary" + 28 │ const Component7 = ({ someCondition, title }) => { + > 29 │ return
{!someCondition && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 30 │ }; + 31 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:33:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 32 │ const Component8 = ({ count, title }) => { + > 33 │ return
{!!count && title}
; + │ ^^^^^^^^^^^^^^^^ + 34 │ }; + 35 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:37:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 36 │ const Component9 = ({ count, title }) => { + > 37 │ return
{count > 0 && title}
; + │ ^^^^^^^^^^^^^^^^^^ + 38 │ }; + 39 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:41:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 40 │ const Component10 = ({ count, title }) => { + > 41 │ return
{0 != count && title}
; + │ ^^^^^^^^^^^^^^^^^^^ + 42 │ }; + 43 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:45:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 44 │ const Component11 = ({ count, total, title }) => { + > 45 │ return
{count < total && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^ + 46 │ }; + 47 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:49:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 48 │ const Component12 = ({ count, title, somethingElse }) => { + > 49 │ return
{!!(count && somethingElse) && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 50 │ }; + 51 │ + + i This note will give you more information. + + +``` + +``` +invalid.jsx:49:18 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintentionally rendered values or rendering crashes + + 48 │ const Component12 = ({ count, title, somethingElse }) => { + > 49 │ return
{!!(count && somethingElse) && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^ + 50 │ }; + 51 │ + + i This note will give you more information. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json new file mode 100644 index 000000000000..57b512b82a5d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noLeakedConditionalRendering": { + "level": "error", + "options": { + "validStrategies": ["ternary"] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx new file mode 100644 index 000000000000..3cf7566c70bd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx @@ -0,0 +1,5 @@ +/* should not generate diagnostics */ + +const Component1 = ({ elements, count }) => { + return
{count ? : null}
; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx.snap new file mode 100644 index 000000000000..78158067ef10 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/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 */ + +const Component1 = ({ elements, count }) => { + return
{count ? : null}
; +}; + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json new file mode 100644 index 000000000000..57b512b82a5d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noLeakedConditionalRendering": { + "level": "error", + "options": { + "validStrategies": ["ternary"] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js deleted file mode 100644 index f299f876959a..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.js +++ /dev/null @@ -1,2 +0,0 @@ -/* should not generate diagnostics */ -// var a = 1; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx new file mode 100644 index 000000000000..7829f7a27d3f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx @@ -0,0 +1,64 @@ +// /* should not generate diagnostics */ + +const Component1 = () => { + return
{customTitle || defaultTitle}
; +}; + +const Component2 = ({ elements }) => { + return
{elements}
; +}; + +const Component3 = ({ elements }) => { + return
There are {elements.length} elements
; +}; + +const Component4 = ({ elements, count }) => { + return
{!count && 'No results found'}
; +}; + +const Component5 = ({ elements }) => { + return
{!!elements.length && }
; +}; + +const Component6 = ({ elements }) => { + return
{Boolean(elements.length) && }
; +}; + +const Component7 = ({ elements }) => { + return
{elements.length > 0 && }
; +}; + +const Component8 = ({ elements }) => { + return
{elements.length ? : null}
; +}; + +const Component9 = ({ elements, count }) => { + return
{count ? : null}
; +}; + +const Component10 = ({ elements, count }) => { + return
{count ? : }
; +}; + +const Component11 = ({ elements, count }) => { + return ( +
+
{direction ? (direction === 'down' ? '▼' : '▲') : ''}
+
{containerName.length > 0 ? 'Loading several stuff' : 'Loading'}
+
+ ); +}; + +const Component12 = ({ elements, count }) => { + return
{direction ? (direction === 'down' ? '▼' : '▲') : ''}
; +}; + +const isOpen1 = true; +const Component13 = () => { + return 0} />; +}; + +const isOpen2 = false; +const Component14 = () => { + return 0} />; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx.snap new file mode 100644 index 000000000000..95586daeea00 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx.snap @@ -0,0 +1,72 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```jsx +// /* should not generate diagnostics */ + +const Component1 = () => { + return
{customTitle || defaultTitle}
; +}; + +const Component2 = ({ elements }) => { + return
{elements}
; +}; + +const Component3 = ({ elements }) => { + return
There are {elements.length} elements
; +}; + +const Component4 = ({ elements, count }) => { + return
{!count && 'No results found'}
; +}; + +const Component5 = ({ elements }) => { + return
{!!elements.length && }
; +}; + +const Component6 = ({ elements }) => { + return
{Boolean(elements.length) && }
; +}; + +const Component7 = ({ elements }) => { + return
{elements.length > 0 && }
; +}; + +const Component8 = ({ elements }) => { + return
{elements.length ? : null}
; +}; + +const Component9 = ({ elements, count }) => { + return
{count ? : null}
; +}; + +const Component10 = ({ elements, count }) => { + return
{count ? : }
; +}; + +const Component11 = ({ elements, count }) => { + return ( +
+
{direction ? (direction === 'down' ? '▼' : '▲') : ''}
+
{containerName.length > 0 ? 'Loading several stuff' : 'Loading'}
+
+ ); +}; + +const Component12 = ({ elements, count }) => { + return
{direction ? (direction === 'down' ? '▼' : '▲') : ''}
; +}; + +const isOpen1 = true; +const Component13 = () => { + return 0} />; +}; + +const isOpen2 = false; +const Component14 = () => { + return 0} />; +}; + +``` diff --git a/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs b/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs index 1df39e16122c..93833d059759 100644 --- a/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs +++ b/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs @@ -3,4 +3,14 @@ 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 NoLeakedConditionalRenderingOptions {} +pub struct NoLeakedConditionalRenderingOptions { + #[serde(skip_serializing_if = "Option::<_>::is_none")] + pub valid_strategies: Option]>>, +} +impl biome_deserialize::Merge for NoLeakedConditionalRenderingOptions { + fn merge_with(&mut self, other: Self) { + if let Some(valid_strategies) = other.valid_strategies { + self.valid_strategies = Some(valid_strategies); + } + } +} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 71d5b309fe49..b0b0659a9967 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -8353,7 +8353,9 @@ export interface NoJsxLiteralsOptions { */ noStrings?: boolean; } -export interface NoLeakedConditionalRenderingOptions {} +export interface NoLeakedConditionalRenderingOptions { + validStrategies?: string[]; +} export interface NoMisusedPromisesOptions {} export interface NoNextAsyncClientComponentOptions {} export interface NoParametersOnlyUsedInRecursionOptions {} diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 976bbabe69a6..9e7bcba7cd8b 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -3763,6 +3763,12 @@ }, "NoLeakedConditionalRenderingOptions": { "type": "object", + "properties": { + "validStrategies": { + "type": ["array", "null"], + "items": { "type": "string" } + } + }, "additionalProperties": false }, "NoMagicNumbersConfiguration": { From 3e3606b56e5996fc78c36225bbddf9a92a9000e0 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Wed, 19 Nov 2025 20:56:53 +0545 Subject: [PATCH 03/24] chores: renamed no-leaked-conditional-rendering to no-leaked-render --- crates/biome_js_analyze/src/lint/nursery.rs | 4 +- ...ional_rendering.rs => no_leaked_render.rs} | 143 +++++++-- .../src/suppressions.tests.rs | 10 +- crates/biome_js_analyze/tests/quick_test.rs | 16 +- .../invalid.jsx.snap | 177 ----------- .../ternary/invalid.jsx.snap | 269 ---------------- .../coerce/invalid.jsx | 7 - .../coerce/invalid.jsx.snap | 112 ++++--- .../coerce/invalid.options.json | 2 +- .../coerce/valid.jsx | 0 .../coerce/valid.jsx.snap | 0 .../coerce/valid.options.json | 2 +- .../invalid.jsx | 0 .../nursery/noLeakedRender/invalid.jsx.snap | 193 ++++++++++++ .../ternary/invalid.jsx | 0 .../noLeakedRender/ternary/invalid.jsx.snap | 295 ++++++++++++++++++ .../ternary/invalid.options.json | 2 +- .../ternary/valid.jsx | 0 .../ternary/valid.jsx.snap | 0 .../ternary/valid.options.json | 2 +- .../valid.jsx | 0 .../valid.jsx.snap | 0 22 files changed, 688 insertions(+), 546 deletions(-) rename crates/biome_js_analyze/src/lint/nursery/{no_leaked_conditional_rendering.rs => no_leaked_render.rs} (57%) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/coerce/invalid.jsx (92%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/coerce/invalid.jsx.snap (51%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/coerce/invalid.options.json (87%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/coerce/valid.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/coerce/valid.jsx.snap (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/coerce/valid.options.json (87%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/invalid.jsx (100%) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/ternary/invalid.jsx (100%) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx.snap rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/ternary/invalid.options.json (87%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/ternary/valid.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/ternary/valid.jsx.snap (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/ternary/valid.options.json (87%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/valid.jsx (100%) rename crates/biome_js_analyze/tests/specs/nursery/{noLeakedConditionalRendering => noLeakedRender}/valid.jsx.snap (100%) diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 529610f5dbd5..affe3a3ae9b7 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -11,7 +11,7 @@ pub mod no_for_in; pub mod no_import_cycles; pub mod no_increment_decrement; pub mod no_jsx_literals; -pub mod no_leaked_conditional_rendering; +pub mod no_leaked_render; pub mod no_misused_promises; pub mod no_next_async_client_component; pub mod no_parameters_only_used_in_recursion; @@ -40,4 +40,4 @@ pub mod use_sorted_classes; pub mod use_spread; pub mod use_vue_define_macros_order; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs similarity index 57% rename from crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs rename to crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index 61f2ac7b90d5..58a40a840ded 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_conditional_rendering.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -6,36 +6,94 @@ use biome_js_syntax::{ binding_ext::AnyJsBindingDeclaration, }; use biome_rowan::{AstNode, SyntaxResult, declare_node_union}; -use biome_rule_options::no_leaked_conditional_rendering::NoLeakedConditionalRenderingOptions; +use biome_rule_options::no_leaked_render::NoLeakedRenderOptions; use crate::services::semantic::Semantic; declare_lint_rule! { - /// Succinct description of the rule. + /// Prevent problematic leaked values from being rendered. /// - /// Put context and details about the rule. - /// As a starting point, you can take the description of the corresponding _ESLint_ rule (if any). + /// This rule prevents values that might cause unintentionally rendered values + /// or rendering crashes in React JSX. When using conditional rendering with the + /// logical AND operator (`&&`), if the left-hand side evaluates to a falsy value like + /// `0`, `NaN`, or any empty string, these values will be rendered instead of rendering nothing. + /// + /// Similarly, when using ternary operators with problematic alternate values like `null`, + /// `undefined`, or `false`, it can cause rendering issues or crashes. /// - /// Try to stay consistent with the descriptions of implemented rules. /// /// ## Examples /// /// ### Invalid /// - /// ```js,expect_diagnostic - /// var a = 1; - /// a = 2; + /// ```jsx,expect_diagnostic + /// const Component = () => { + /// const count = 0; + /// return
{count && Count: {count}}
; + /// } + /// ``` + /// + /// ```jsx,expect_diagnostic + /// const Component = () => { + /// const items = []; + /// return
{items.length && }
; + /// } + /// ``` + /// + /// ```jsx,expect_diagnostic + /// const Component = () => { + /// const user = null; + /// return
{user && }
; + /// } + /// ``` + /// + /// ```jsx,expect_diagnostic + /// const Component = () => { + /// const condition = false; + /// return
{condition ? : null}
; + /// } /// ``` /// /// ### Valid /// - /// ```js - /// // var a = 1; + /// ```jsx + /// const Component = () => { + /// const count = 0; + /// return
{count > 0 && Count: {count}}
; + /// } + /// ``` + /// + /// ```jsx + /// const Component = () => { + /// const items = []; + /// return
{!!items.length && }
; + /// } /// ``` /// - pub NoLeakedConditionalRendering { + /// ```jsx + /// const Component = () => { + /// const user = null; + /// return
{user ? : null}
; + /// } + /// ``` + /// + /// ```jsx + /// const Component = () => { + /// const condition = false; + /// return
{condition ? : }
; + /// } + /// ``` + /// + /// ```jsx + /// const Component = () => { + /// const isReady = true; + /// return
{isReady && }
; + /// } + /// ``` + + pub NoLeakedRender{ version: "next", - name: "noLeakedConditionalRendering", + name: "noLeakedRender", language: "js", recommended: false, } @@ -47,7 +105,7 @@ const TERNARY_INVALID_ALTERNATE_VALUES: &[&str] = &["null", "undefined", "false" const DEFAULT_VALID_STRATEGIES: &[&str] = &[TERNARY_STRATEGY, COERCE_STRATEGY]; -pub enum NoLeakedConditionalRenderingState { +pub enum NoLeakedRenderState { NoPotentialLeakedRender, } @@ -72,11 +130,11 @@ declare_node_union! { pub Query = JsLogicalExpression | JsConditionalExpression } -impl Rule for NoLeakedConditionalRendering { +impl Rule for NoLeakedRender { type Query = Semantic; - type State = NoLeakedConditionalRenderingState; + type State = NoLeakedRenderState; type Signals = Option; - type Options = NoLeakedConditionalRenderingOptions; + type Options = NoLeakedRenderOptions; fn run(ctx: &RuleContext) -> Self::Signals { let query = ctx.query(); @@ -152,7 +210,7 @@ impl Rule for NoLeakedConditionalRendering { return None; } - return Some(NoLeakedConditionalRenderingState::NoPotentialLeakedRender); + return Some(NoLeakedRenderState::NoPotentialLeakedRender); } Query::JsConditionalExpression(expr) => { if valid_strategies @@ -172,28 +230,49 @@ impl Rule for NoLeakedConditionalRendering { return None; } - return Some(NoLeakedConditionalRenderingState::NoPotentialLeakedRender); + return Some(NoLeakedRenderState::NoPotentialLeakedRender); } } } - fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { let node = ctx.query(); - match state { - NoLeakedConditionalRenderingState::NoPotentialLeakedRender { - } => Some( - RuleDiagnostic::new( - rule_category!(), - node.range(), - markup! { - "Potential leaked value that might cause unintentionally rendered values or rendering crashes" - }, + match node { + Query::JsLogicalExpression(_) => { + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Potential leaked value that might cause unintended rendering." + }, + ) + .note(markup! { + "JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output." + }) + .note(markup! { + "Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression." + }) + ) + } + Query::JsConditionalExpression(_) => { + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Potential leaked value that might cause unintended rendering." + }, + ) + .note(markup! { + "This happens When you use ternary operators in JSX with alternate values like null,undefined, or false" + }) + .note(markup! { + "Replace with a safe alternate value like an empty string or another JSX element" + }) ) - .note(markup! { - "This note will give you more information." - }), - ), + } } } } diff --git a/crates/biome_js_analyze/src/suppressions.tests.rs b/crates/biome_js_analyze/src/suppressions.tests.rs index 35a5a67685c2..2fc7237c2e67 100644 --- a/crates/biome_js_analyze/src/suppressions.tests.rs +++ b/crates/biome_js_analyze/src/suppressions.tests.rs @@ -7,20 +7,16 @@ use biome_js_syntax::{JsFileSource, TextRange, TextSize}; use biome_package::{Dependencies, PackageJson}; use std::slice; +#[ignore] #[test] fn quick_test() { - const SOURCE: &str = r#" - const isOpen = true; - const Component = () => { - return 0} /> - } - "#; + const SOURCE: &str = r#"f({ prop: () => {} })"#; let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default()); let mut error_ranges: Vec = Vec::new(); let options = AnalyzerOptions::default(); - let rule_filter = RuleFilter::Rule("nursery", "noLeakedConditionalRendering"); + let rule_filter = RuleFilter::Rule("nursery", "useExplicitType"); let dependencies = Dependencies(Box::new([("buffer".into(), "latest".into())])); diff --git a/crates/biome_js_analyze/tests/quick_test.rs b/crates/biome_js_analyze/tests/quick_test.rs index 8dd31a5201a1..adbb7a946590 100644 --- a/crates/biome_js_analyze/tests/quick_test.rs +++ b/crates/biome_js_analyze/tests/quick_test.rs @@ -25,13 +25,17 @@ fn project_layout_with_top_level_dependencies(dependencies: Dependencies) -> Arc } // use this test check if your snippet produces the diagnostics you wish, without using a snapshot +#[ignore] #[test] fn quick_test() { - const FILENAME: &str = "App.tsx"; - const SOURCE: &str = r#" - const Component = ({ elements }) => { - return
{elements.length && }
- } + const FILENAME: &str = "dummyFile.ts"; + const SOURCE: &str = r#"import * as postcssModules from "postcss-modules" + +type PostcssOptions = Parameters[0] + +export function f(options: PostcssOptions) { + console.log(options) +} "#; let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default()); @@ -48,7 +52,7 @@ fn quick_test() { .with_configuration( AnalyzerConfiguration::default().with_jsx_runtime(JsxRuntime::ReactClassic), ); - let rule_filter = RuleFilter::Rule("nursery", "noLeakedConditionalRendering"); + let rule_filter = RuleFilter::Rule("correctness", "noUnusedImports"); let dependencies = Dependencies(Box::new([("buffer".into(), "latest".into())])); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap deleted file mode 100644 index d5654491c603..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx.snap +++ /dev/null @@ -1,177 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: invalid.jsx ---- -# Input -```jsx -// Invalid cases with default options (both 'coerce' and 'ternary' strategies) - -const Example1 = () => { - return ( - <> - {0 && } - {'' && } - {NaN && } - - ); -}; - -const Component1 = ({ count, title }) => { - return
{count && title}
; -}; - -const Component2 = ({ count }) => { - return
{count && There are {count} results}
; -}; - -const Component3 = ({ elements }) => { - return
{elements.length && }
; -}; - -const Component4 = ({ nestedCollection }) => { - return ( -
{nestedCollection.elements.length && }
- ); -}; - -const Component5 = ({ elements }) => { - return
{elements[0] && }
; -}; - -const Component6 = ({ numberA, numberB }) => { - return
{(numberA || numberB) && {numberA + numberB}}
; -}; - -``` - -# Diagnostics -``` -invalid.jsx:6:5 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 4 │ return ( - 5 │ <> - > 6 │ {0 && } - │ ^^^^^^^^^^^^^^^^^^ - 7 │ {'' && } - 8 │ {NaN && } - - i This note will give you more information. - - -``` - -``` -invalid.jsx:7:5 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 5 │ <> - 6 │ {0 && } - > 7 │ {'' && } - │ ^^^^^^^^^^^^^^^^^^^ - 8 │ {NaN && } - 9 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:14:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 13 │ const Component1 = ({ count, title }) => { - > 14 │ return
{count && title}
; - │ ^^^^^^^^^^^^^^ - 15 │ }; - 16 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:18:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 17 │ const Component2 = ({ count }) => { - > 18 │ return
{count && There are {count} results}
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 19 │ }; - 20 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:22:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 21 │ const Component3 = ({ elements }) => { - > 22 │ return
{elements.length && }
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 23 │ }; - 24 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:27:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 25 │ const Component4 = ({ nestedCollection }) => { - 26 │ return ( - > 27 │
{nestedCollection.elements.length && }
- │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 28 │ ); - 29 │ }; - - i This note will give you more information. - - -``` - -``` -invalid.jsx:32:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 31 │ const Component5 = ({ elements }) => { - > 32 │ return
{elements[0] && }
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 33 │ }; - 34 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:36:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 35 │ const Component6 = ({ numberA, numberB }) => { - > 36 │ return
{(numberA || numberB) && {numberA + numberB}}
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 37 │ }; - 38 │ - - i This note will give you more information. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap deleted file mode 100644 index 7bb24d358c9c..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx.snap +++ /dev/null @@ -1,269 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: invalid.jsx ---- -# Input -```jsx -const Component1 = ({ count, title }) => { - return
{count && title}
; -}; - -const Component2 = ({ count }) => { - return
{count && There are {count} results}
; -}; - -const Component3 = ({ elements }) => { - return
{elements.length && }
; -}; - -const Component4 = ({ nestedCollection }) => { - return ( -
{nestedCollection.elements.length && }
- ); -}; - -const Component5 = ({ elements }) => { - return
{elements[0] && }
; -}; - -const Component6 = ({ numberA, numberB }) => { - return
{(numberA || numberB) && {numberA + numberB}}
; -}; - -// Boolean coerce isn't valid if strategy is only "ternary" -const Component7 = ({ someCondition, title }) => { - return
{!someCondition && title}
; -}; - -const Component8 = ({ count, title }) => { - return
{!!count && title}
; -}; - -const Component9 = ({ count, title }) => { - return
{count > 0 && title}
; -}; - -const Component10 = ({ count, title }) => { - return
{0 != count && title}
; -}; - -const Component11 = ({ count, total, title }) => { - return
{count < total && title}
; -}; - -const Component12 = ({ count, title, somethingElse }) => { - return
{!!(count && somethingElse) && title}
; -}; - -``` - -# Diagnostics -``` -invalid.jsx:2:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 1 │ const Component1 = ({ count, title }) => { - > 2 │ return
{count && title}
; - │ ^^^^^^^^^^^^^^ - 3 │ }; - 4 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:6:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 5 │ const Component2 = ({ count }) => { - > 6 │ return
{count && There are {count} results}
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 7 │ }; - 8 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:10:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 9 │ const Component3 = ({ elements }) => { - > 10 │ return
{elements.length && }
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 11 │ }; - 12 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:15:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 13 │ const Component4 = ({ nestedCollection }) => { - 14 │ return ( - > 15 │
{nestedCollection.elements.length && }
- │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 16 │ ); - 17 │ }; - - i This note will give you more information. - - -``` - -``` -invalid.jsx:20:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 19 │ const Component5 = ({ elements }) => { - > 20 │ return
{elements[0] && }
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 21 │ }; - 22 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:24:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 23 │ const Component6 = ({ numberA, numberB }) => { - > 24 │ return
{(numberA || numberB) && {numberA + numberB}}
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 25 │ }; - 26 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:29:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 27 │ // Boolean coerce isn't valid if strategy is only "ternary" - 28 │ const Component7 = ({ someCondition, title }) => { - > 29 │ return
{!someCondition && title}
; - │ ^^^^^^^^^^^^^^^^^^^^^^^ - 30 │ }; - 31 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:33:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 32 │ const Component8 = ({ count, title }) => { - > 33 │ return
{!!count && title}
; - │ ^^^^^^^^^^^^^^^^ - 34 │ }; - 35 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:37:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 36 │ const Component9 = ({ count, title }) => { - > 37 │ return
{count > 0 && title}
; - │ ^^^^^^^^^^^^^^^^^^ - 38 │ }; - 39 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:41:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 40 │ const Component10 = ({ count, title }) => { - > 41 │ return
{0 != count && title}
; - │ ^^^^^^^^^^^^^^^^^^^ - 42 │ }; - 43 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:45:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 44 │ const Component11 = ({ count, total, title }) => { - > 45 │ return
{count < total && title}
; - │ ^^^^^^^^^^^^^^^^^^^^^^ - 46 │ }; - 47 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:49:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 48 │ const Component12 = ({ count, title, somethingElse }) => { - > 49 │ return
{!!(count && somethingElse) && title}
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 50 │ }; - 51 │ - - i This note will give you more information. - - -``` - -``` -invalid.jsx:49:18 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintentionally rendered values or rendering crashes - - 48 │ const Component12 = ({ count, title, somethingElse }) => { - > 49 │ return
{!!(count && somethingElse) && title}
; - │ ^^^^^^^^^^^^^^^^^^^^^^ - 50 │ }; - 51 │ - - i This note will give you more information. - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx similarity index 92% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx index 4d9d3e9e29e7..97e042650853 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx @@ -47,13 +47,6 @@ const Component11 = ({ items, somethingElse, title }) => { return
{items.length > 0 && somethingElse && title}
; }; -const MyComponent1 = () => { - const items = []; - const breakpoint = { phones: true }; - - return
{items.length > 0 && breakpoint.phones && }
; -}; - const MyComponent2 = () => { return
{maybeObject && (isFoo ? : )}
; }; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap similarity index 51% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap index c338c1986343..825b36367fb0 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap @@ -97,9 +97,9 @@ const Component12 = () => { # Diagnostics ``` -invalid.jsx:4:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:4:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 3 │ const Component1 = ({ count, title }) => { > 4 │ return
{count && title}
; @@ -107,15 +107,17 @@ invalid.jsx:4:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━ 5 │ }; 6 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:8:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:8:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 7 │ const Component2 = ({ count }) => { > 8 │ return
{count && There are {count} results}
; @@ -123,15 +125,17 @@ invalid.jsx:8:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━ 9 │ }; 10 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:12:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:12:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 11 │ const Component3 = ({ elements }) => { > 12 │ return
{elements.length && }
; @@ -139,15 +143,17 @@ invalid.jsx:12:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 13 │ }; 14 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:17:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:17:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 15 │ const Component4 = ({ nestedCollection }) => { 16 │ return ( @@ -156,15 +162,17 @@ invalid.jsx:17:9 lint/nursery/noLeakedConditionalRendering ━━━━━━━ 18 │ ); 19 │ }; - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:22:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:22:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 21 │ const Component5 = ({ elements }) => { > 22 │ return
{elements[0] && }
; @@ -172,15 +180,17 @@ invalid.jsx:22:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 23 │ }; 24 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:26:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:26:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 25 │ const Component6 = ({ numberA, numberB }) => { > 26 │ return
{(numberA || numberB) && {numberA + numberB}}
; @@ -188,15 +198,17 @@ invalid.jsx:26:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 27 │ }; 28 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:30:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:30:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 29 │ const Component7 = ({ connection, hasError, hasErrorUpdate }) => { > 30 │ return
{connection && (hasError || hasErrorUpdate)}
; @@ -204,15 +216,17 @@ invalid.jsx:30:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 31 │ }; 32 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:35:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:35:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 33 │ // Ternary isn't valid if strategy is only "coerce" 34 │ const Component8 = ({ count, title }) => { @@ -221,15 +235,17 @@ invalid.jsx:35:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 36 │ }; 37 │ - i This note will give you more information. + i This happens When you use ternary operators in JSX with alternate values like null,undefined, or false + + i Replace with a safe alternate value like an empty string or another JSX element ``` ``` -invalid.jsx:39:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:39:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 38 │ const Component9 = ({ count, title }) => { > 39 │ return
{!count ? title : null}
; @@ -237,15 +253,17 @@ invalid.jsx:39:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 40 │ }; 41 │ - i This note will give you more information. + i This happens When you use ternary operators in JSX with alternate values like null,undefined, or false + + i Replace with a safe alternate value like an empty string or another JSX element ``` ``` -invalid.jsx:43:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:43:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 42 │ const Component10 = ({ count, somethingElse, title }) => { > 43 │ return
{count && somethingElse ? title : null}
; @@ -253,15 +271,17 @@ invalid.jsx:43:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 44 │ }; 45 │ - i This note will give you more information. + i This happens When you use ternary operators in JSX with alternate values like null,undefined, or false + + i Replace with a safe alternate value like an empty string or another JSX element ``` ``` -invalid.jsx:43:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:43:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 42 │ const Component10 = ({ count, somethingElse, title }) => { > 43 │ return
{count && somethingElse ? title : null}
; @@ -269,15 +289,17 @@ invalid.jsx:43:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 44 │ }; 45 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:47:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:47:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 46 │ const Component11 = ({ items, somethingElse, title }) => { > 47 │ return
{items.length > 0 && somethingElse && title}
; @@ -285,15 +307,17 @@ invalid.jsx:47:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 48 │ }; 49 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:54:15 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:54:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 52 │ const breakpoint = { phones: true }; 53 │ @@ -302,15 +326,17 @@ invalid.jsx:54:15 lint/nursery/noLeakedConditionalRendering ━━━━━━ 55 │ }; 56 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` ``` -invalid.jsx:87:24 lint/nursery/noLeakedConditionalRendering ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:87:24 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - i Potential leaked value that might cause unintentionally rendered values or rendering crashes + i Potential leaked value that might cause unintended rendering. 85 │ const isOpen = 0; 86 │ const Component12 = () => { @@ -319,7 +345,9 @@ invalid.jsx:87:24 lint/nursery/noLeakedConditionalRendering ━━━━━━ 88 │ }; 89 │ - i This note will give you more information. + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.options.json similarity index 87% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.options.json index b0f8c4ca6e84..c90517c45271 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/invalid.options.json +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.options.json @@ -4,7 +4,7 @@ "enabled": true, "rules": { "nursery": { - "noLeakedConditionalRendering": { + "noLeakedRender": { "level": "error", "options": { "validStrategies": ["coerce"] diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.jsx.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.options.json similarity index 87% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.options.json index b0f8c4ca6e84..c90517c45271 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/coerce/valid.options.json +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/valid.options.json @@ -4,7 +4,7 @@ "enabled": true, "rules": { "nursery": { - "noLeakedConditionalRendering": { + "noLeakedRender": { "level": "error", "options": { "validStrategies": ["coerce"] diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/invalid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap new file mode 100644 index 000000000000..e85a5ea38513 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap @@ -0,0 +1,193 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +// Invalid cases with default options (both 'coerce' and 'ternary' strategies) + +const Example1 = () => { + return ( + <> + {0 && } + {'' && } + {NaN && } + + ); +}; + +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +``` + +# Diagnostics +``` +invalid.jsx:6:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 4 │ return ( + 5 │ <> + > 6 │ {0 && } + │ ^^^^^^^^^^^^^^^^^^ + 7 │ {'' && } + 8 │ {NaN && } + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:7:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 5 │ <> + 6 │ {0 && } + > 7 │ {'' && } + │ ^^^^^^^^^^^^^^^^^^^ + 8 │ {NaN && } + 9 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:14:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 13 │ const Component1 = ({ count, title }) => { + > 14 │ return
{count && title}
; + │ ^^^^^^^^^^^^^^ + 15 │ }; + 16 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:18:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 17 │ const Component2 = ({ count }) => { + > 18 │ return
{count && There are {count} results}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 19 │ }; + 20 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:22:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 21 │ const Component3 = ({ elements }) => { + > 22 │ return
{elements.length && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 23 │ }; + 24 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:27:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 25 │ const Component4 = ({ nestedCollection }) => { + 26 │ return ( + > 27 │
{nestedCollection.elements.length && }
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 28 │ ); + 29 │ }; + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:32:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 31 │ const Component5 = ({ elements }) => { + > 32 │ return
{elements[0] && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 33 │ }; + 34 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:36:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 35 │ const Component6 = ({ numberA, numberB }) => { + > 36 │ return
{(numberA || numberB) && {numberA + numberB}}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 37 │ }; + 38 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx.snap new file mode 100644 index 000000000000..0df6f29d2e3b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.jsx.snap @@ -0,0 +1,295 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +const Component1 = ({ count, title }) => { + return
{count && title}
; +}; + +const Component2 = ({ count }) => { + return
{count && There are {count} results}
; +}; + +const Component3 = ({ elements }) => { + return
{elements.length && }
; +}; + +const Component4 = ({ nestedCollection }) => { + return ( +
{nestedCollection.elements.length && }
+ ); +}; + +const Component5 = ({ elements }) => { + return
{elements[0] && }
; +}; + +const Component6 = ({ numberA, numberB }) => { + return
{(numberA || numberB) && {numberA + numberB}}
; +}; + +// Boolean coerce isn't valid if strategy is only "ternary" +const Component7 = ({ someCondition, title }) => { + return
{!someCondition && title}
; +}; + +const Component8 = ({ count, title }) => { + return
{!!count && title}
; +}; + +const Component9 = ({ count, title }) => { + return
{count > 0 && title}
; +}; + +const Component10 = ({ count, title }) => { + return
{0 != count && title}
; +}; + +const Component11 = ({ count, total, title }) => { + return
{count < total && title}
; +}; + +const Component12 = ({ count, title, somethingElse }) => { + return
{!!(count && somethingElse) && title}
; +}; + +``` + +# Diagnostics +``` +invalid.jsx:2:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 1 │ const Component1 = ({ count, title }) => { + > 2 │ return
{count && title}
; + │ ^^^^^^^^^^^^^^ + 3 │ }; + 4 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:6:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 5 │ const Component2 = ({ count }) => { + > 6 │ return
{count && There are {count} results}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ }; + 8 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:10:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 9 │ const Component3 = ({ elements }) => { + > 10 │ return
{elements.length && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ }; + 12 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:15:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 13 │ const Component4 = ({ nestedCollection }) => { + 14 │ return ( + > 15 │
{nestedCollection.elements.length && }
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │ ); + 17 │ }; + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:20:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 19 │ const Component5 = ({ elements }) => { + > 20 │ return
{elements[0] && }
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 21 │ }; + 22 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:24:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 23 │ const Component6 = ({ numberA, numberB }) => { + > 24 │ return
{(numberA || numberB) && {numberA + numberB}}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 25 │ }; + 26 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:29:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 27 │ // Boolean coerce isn't valid if strategy is only "ternary" + 28 │ const Component7 = ({ someCondition, title }) => { + > 29 │ return
{!someCondition && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 30 │ }; + 31 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:33:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 32 │ const Component8 = ({ count, title }) => { + > 33 │ return
{!!count && title}
; + │ ^^^^^^^^^^^^^^^^ + 34 │ }; + 35 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:37:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 36 │ const Component9 = ({ count, title }) => { + > 37 │ return
{count > 0 && title}
; + │ ^^^^^^^^^^^^^^^^^^ + 38 │ }; + 39 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:41:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 40 │ const Component10 = ({ count, title }) => { + > 41 │ return
{0 != count && title}
; + │ ^^^^^^^^^^^^^^^^^^^ + 42 │ }; + 43 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:45:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 44 │ const Component11 = ({ count, total, title }) => { + > 45 │ return
{count < total && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^ + 46 │ }; + 47 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:49:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 48 │ const Component12 = ({ count, title, somethingElse }) => { + > 49 │ return
{!!(count && somethingElse) && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 50 │ }; + 51 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:49:18 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 48 │ const Component12 = ({ count, title, somethingElse }) => { + > 49 │ return
{!!(count && somethingElse) && title}
; + │ ^^^^^^^^^^^^^^^^^^^^^^ + 50 │ }; + 51 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.options.json similarity index 87% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.options.json index 57b512b82a5d..67be5f45c49f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/invalid.options.json +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/invalid.options.json @@ -4,7 +4,7 @@ "enabled": true, "rules": { "nursery": { - "noLeakedConditionalRendering": { + "noLeakedRender": { "level": "error", "options": { "validStrategies": ["ternary"] diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.jsx.snap diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.options.json similarity index 87% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.options.json index 57b512b82a5d..67be5f45c49f 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/ternary/valid.options.json +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/ternary/valid.options.json @@ -4,7 +4,7 @@ "enabled": true, "rules": { "nursery": { - "noLeakedConditionalRendering": { + "noLeakedRender": { "level": "error", "options": { "validStrategies": ["ternary"] diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap similarity index 100% rename from crates/biome_js_analyze/tests/specs/nursery/noLeakedConditionalRendering/valid.jsx.snap rename to crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap From a244fc44ea3c85c283be1c35092dd7eedecf3ccd Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Wed, 19 Nov 2025 21:53:04 +0545 Subject: [PATCH 04/24] chores: generated new configs and changeset --- .changeset/calm-shrimps-study.md | 5 + .../src/analyzer/linter/rules.rs | 364 +-- .../src/categories.rs | 2 +- .../src/lint/nursery/no_leaked_render.rs | 17 +- .../noLeakedRender/coerce/invalid.jsx.snap | 38 +- .../specs/nursery/noLeakedRender/invalid.jsx | 16 + .../nursery/noLeakedRender/invalid.jsx.snap | 16 + crates/biome_rule_options/src/lib.rs | 2 +- ...ional_rendering.rs => no_leaked_render.rs} | 4 +- .../@biomejs/backend-jsonrpc/src/workspace.ts | 2684 +++++++++-------- .../@biomejs/biome/configuration_schema.json | 36 +- 11 files changed, 1659 insertions(+), 1525 deletions(-) create mode 100644 .changeset/calm-shrimps-study.md rename crates/biome_rule_options/src/{no_leaked_conditional_rendering.rs => no_leaked_render.rs} (83%) diff --git a/.changeset/calm-shrimps-study.md b/.changeset/calm-shrimps-study.md new file mode 100644 index 000000000000..fdb533c6cde0 --- /dev/null +++ b/.changeset/calm-shrimps-study.md @@ -0,0 +1,5 @@ +--- +'@biomejs/biome': patch +--- + +Fixed [#7659](https://github.com/biomejs/biome/issues/7659): Added the new rule [`jsx-no-leaked-render`](https://biomejs.dev/linter/rules/no-leaked-render) from ESLint diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index e07b5709be33..12f0907c9b0f 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -207,7 +207,7 @@ pub enum RuleName { NoJsxLiterals, NoLabelVar, NoLabelWithoutControl, - NoLeakedConditionalRendering, + NoLeakedRender, NoMagicNumbers, NoMisleadingCharacterClass, NoMisleadingInstantiator, @@ -595,7 +595,7 @@ impl RuleName { Self::NoJsxLiterals => "noJsxLiterals", Self::NoLabelVar => "noLabelVar", Self::NoLabelWithoutControl => "noLabelWithoutControl", - Self::NoLeakedConditionalRendering => "noLeakedConditionalRendering", + Self::NoLeakedRender => "noLeakedRender", Self::NoMagicNumbers => "noMagicNumbers", Self::NoMisleadingCharacterClass => "noMisleadingCharacterClass", Self::NoMisleadingInstantiator => "noMisleadingInstantiator", @@ -983,7 +983,7 @@ impl RuleName { Self::NoJsxLiterals => RuleGroup::Nursery, Self::NoLabelVar => RuleGroup::Suspicious, Self::NoLabelWithoutControl => RuleGroup::A11y, - Self::NoLeakedConditionalRendering => RuleGroup::Nursery, + Self::NoLeakedRender => RuleGroup::Nursery, Self::NoMagicNumbers => RuleGroup::Style, Self::NoMisleadingCharacterClass => RuleGroup::Suspicious, Self::NoMisleadingInstantiator => RuleGroup::Suspicious, @@ -1376,7 +1376,7 @@ impl std::str::FromStr for RuleName { "noJsxLiterals" => Ok(Self::NoJsxLiterals), "noLabelVar" => Ok(Self::NoLabelVar), "noLabelWithoutControl" => Ok(Self::NoLabelWithoutControl), - "noLeakedConditionalRendering" => Ok(Self::NoLeakedConditionalRendering), + "noLeakedRender" => Ok(Self::NoLeakedRender), "noMagicNumbers" => Ok(Self::NoMagicNumbers), "noMisleadingCharacterClass" => Ok(Self::NoMisleadingCharacterClass), "noMisleadingInstantiator" => Ok(Self::NoMisleadingInstantiator), @@ -4776,7 +4776,7 @@ 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" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements.\nSee https://biomejs.dev/linter/rules/no-continue"] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports.\nSee https://biomejs.dev/linter/rules/no-deprecated-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee https://biomejs.dev/linter/rules/no-duplicate-dependencies"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources.\nSee https://biomejs.dev/linter/rules/no-empty-source"] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require Promise-like statements to be handled appropriately.\nSee https://biomejs.dev/linter/rules/no-floating-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow iterating using a for-in loop.\nSee https://biomejs.dev/linter/rules/no-for-in"] # [serde (skip_serializing_if = "Option::is_none")] pub no_for_in : Option < RuleConfiguration < biome_rule_options :: no_for_in :: NoForInOptions >> , # [doc = "Prevent import cycles.\nSee https://biomejs.dev/linter/rules/no-import-cycles"] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --.\nSee https://biomejs.dev/linter/rules/no-increment-decrement"] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements.\nSee https://biomejs.dev/linter/rules/no-jsx-literals"] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake.\nSee https://biomejs.dev/linter/rules/no-misused-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Prevent client components from being async functions.\nSee https://biomejs.dev/linter/rules/no-next-async-client-component"] # [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 function parameters that are only used in recursive calls.\nSee https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion"] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop.\nSee https://biomejs.dev/linter/rules/no-react-forward-ref"] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee https://biomejs.dev/linter/rules/no-shadow"] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevent the usage of synchronous scripts.\nSee https://biomejs.dev/linter/rules/no-sync-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub no_sync_scripts : Option < RuleConfiguration < biome_rule_options :: no_sync_scripts :: NoSyncScriptsOptions >> , # [doc = "Disallow unknown DOM properties.\nSee https://biomejs.dev/linter/rules/no-unknown-attribute"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant.\nSee https://biomejs.dev/linter/rules/no-unnecessary-conditions"] # [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.\nSee https://biomejs.dev/linter/rules/no-unresolved-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment.\nSee https://biomejs.dev/linter/rules/no-unused-expressions"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings.\nSee https://biomejs.dev/linter/rules/no-useless-catch-binding"] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined.\nSee https://biomejs.dev/linter/rules/no-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.\nSee https://biomejs.dev/linter/rules/no-vue-data-object-declaration"] # [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 duplicate keys in Vue component data, methods, computed properties, and other options.\nSee https://biomejs.dev/linter/rules/no-vue-duplicate-keys"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-keys"] # [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.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Disallow using v-if and v-for directives on the same element.\nSee https://biomejs.dev/linter/rules/no-vue-v-if-with-v-for"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_v_if_with_v_for : Option < RuleConfiguration < biome_rule_options :: no_vue_v_if_with_v_for :: NoVueVIfWithVForOptions >> , # [doc = "Require Array#sort and Array#toSorted calls to always provide a compareFunction.\nSee https://biomejs.dev/linter/rules/use-array-sort-compare"] # [serde (skip_serializing_if = "Option::is_none")] pub use_array_sort_compare : Option < RuleConfiguration < biome_rule_options :: use_array_sort_compare :: UseArraySortCompareOptions >> , # [doc = "Enforce consistent arrow function bodies.\nSee https://biomejs.dev/linter/rules/use-consistent-arrow-return"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_graphql_descriptions : Option < RuleConfiguration < biome_rule_options :: use_consistent_graphql_descriptions :: UseConsistentGraphqlDescriptionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date.\nSee https://biomejs.dev/linter/rules/use-deprecated-date"] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive.\nSee https://biomejs.dev/linter/rules/use-exhaustive-switch-cases"] # [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.\nSee https://biomejs.dev/linter/rules/use-explicit-type"] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.\nSee https://biomejs.dev/linter/rules/use-find"] # [serde (skip_serializing_if = "Option::is_none")] pub use_find : Option < RuleConfiguration < biome_rule_options :: use_find :: UseFindOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions.\nSee https://biomejs.dev/linter/rules/use-max-params"] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce the sorting of CSS utility classes.\nSee https://biomejs.dev/linter/rules/use-sorted-classes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce the use of the spread operator over .apply().\nSee https://biomejs.dev/linter/rules/use-spread"] # [serde (skip_serializing_if = "Option::is_none")] pub use_spread : Option < RuleFixConfiguration < biome_rule_options :: use_spread :: UseSpreadOptions >> , # [doc = "Enforce unique operation names across a GraphQL document.\nSee https://biomejs.dev/linter/rules/use-unique-graphql-operation-name"] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_graphql_operation_name : Option < RuleConfiguration < biome_rule_options :: use_unique_graphql_operation_name :: UseUniqueGraphqlOperationNameOptions >> , # [doc = "Enforce specific order of Vue compiler macros.\nSee https://biomejs.dev/linter/rules/use-vue-define-macros-order"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce hyphenated (kebab-case) attribute names in Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-hyphenated-attributes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_hyphenated_attributes : Option < RuleFixConfiguration < biome_rule_options :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributesOptions >> , # [doc = "Enforce multi-word component names in Vue components.\nSee https://biomejs.dev/linter/rules/use-vue-multi-word-component-names"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> , # [doc = "Forbids v-bind directives with missing arguments or invalid modifiers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-bind"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_bind : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_bind :: UseVueValidVBindOptions >> , # [doc = "Enforce valid usage of v-else.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else :: UseVueValidVElseOptions >> , # [doc = "Enforce valid v-else-if directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else_if :: UseVueValidVElseIfOptions >> , # [doc = "Enforce valid v-html directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-html"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_html : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_html :: UseVueValidVHtmlOptions >> , # [doc = "Enforces valid v-if usage for Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_if :: UseVueValidVIfOptions >> , # [doc = "Enforce valid v-on directives with proper arguments, modifiers, and handlers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-on"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_on : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_on :: UseVueValidVOnOptions >> } +pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements.\nSee https://biomejs.dev/linter/rules/no-continue"] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports.\nSee https://biomejs.dev/linter/rules/no-deprecated-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee https://biomejs.dev/linter/rules/no-duplicate-dependencies"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow empty sources.\nSee https://biomejs.dev/linter/rules/no-empty-source"] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require Promise-like statements to be handled appropriately.\nSee https://biomejs.dev/linter/rules/no-floating-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow iterating using a for-in loop.\nSee https://biomejs.dev/linter/rules/no-for-in"] # [serde (skip_serializing_if = "Option::is_none")] pub no_for_in : Option < RuleConfiguration < biome_rule_options :: no_for_in :: NoForInOptions >> , # [doc = "Prevent import cycles.\nSee https://biomejs.dev/linter/rules/no-import-cycles"] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --.\nSee https://biomejs.dev/linter/rules/no-increment-decrement"] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements.\nSee https://biomejs.dev/linter/rules/no-jsx-literals"] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Prevent problematic leaked values from being rendered.\nSee https://biomejs.dev/linter/rules/no-leaked-render"] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_render : Option < RuleConfiguration < biome_rule_options :: no_leaked_render :: NoLeakedRenderOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake.\nSee https://biomejs.dev/linter/rules/no-misused-promises"] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Prevent client components from being async functions.\nSee https://biomejs.dev/linter/rules/no-next-async-client-component"] # [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 function parameters that are only used in recursive calls.\nSee https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion"] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop.\nSee https://biomejs.dev/linter/rules/no-react-forward-ref"] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee https://biomejs.dev/linter/rules/no-shadow"] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevent the usage of synchronous scripts.\nSee https://biomejs.dev/linter/rules/no-sync-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub no_sync_scripts : Option < RuleConfiguration < biome_rule_options :: no_sync_scripts :: NoSyncScriptsOptions >> , # [doc = "Disallow unknown DOM properties.\nSee https://biomejs.dev/linter/rules/no-unknown-attribute"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant.\nSee https://biomejs.dev/linter/rules/no-unnecessary-conditions"] # [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.\nSee https://biomejs.dev/linter/rules/no-unresolved-imports"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment.\nSee https://biomejs.dev/linter/rules/no-unused-expressions"] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings.\nSee https://biomejs.dev/linter/rules/no-useless-catch-binding"] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined.\nSee https://biomejs.dev/linter/rules/no-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.\nSee https://biomejs.dev/linter/rules/no-vue-data-object-declaration"] # [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 duplicate keys in Vue component data, methods, computed properties, and other options.\nSee https://biomejs.dev/linter/rules/no-vue-duplicate-keys"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-keys"] # [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.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Disallow using v-if and v-for directives on the same element.\nSee https://biomejs.dev/linter/rules/no-vue-v-if-with-v-for"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_v_if_with_v_for : Option < RuleConfiguration < biome_rule_options :: no_vue_v_if_with_v_for :: NoVueVIfWithVForOptions >> , # [doc = "Require Array#sort and Array#toSorted calls to always provide a compareFunction.\nSee https://biomejs.dev/linter/rules/use-array-sort-compare"] # [serde (skip_serializing_if = "Option::is_none")] pub use_array_sort_compare : Option < RuleConfiguration < biome_rule_options :: use_array_sort_compare :: UseArraySortCompareOptions >> , # [doc = "Enforce consistent arrow function bodies.\nSee https://biomejs.dev/linter/rules/use-consistent-arrow-return"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions"] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_graphql_descriptions : Option < RuleConfiguration < biome_rule_options :: use_consistent_graphql_descriptions :: UseConsistentGraphqlDescriptionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date.\nSee https://biomejs.dev/linter/rules/use-deprecated-date"] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require switch-case statements to be exhaustive.\nSee https://biomejs.dev/linter/rules/use-exhaustive-switch-cases"] # [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.\nSee https://biomejs.dev/linter/rules/use-explicit-type"] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.\nSee https://biomejs.dev/linter/rules/use-find"] # [serde (skip_serializing_if = "Option::is_none")] pub use_find : Option < RuleConfiguration < biome_rule_options :: use_find :: UseFindOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions.\nSee https://biomejs.dev/linter/rules/use-max-params"] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope"] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce the sorting of CSS utility classes.\nSee https://biomejs.dev/linter/rules/use-sorted-classes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce the use of the spread operator over .apply().\nSee https://biomejs.dev/linter/rules/use-spread"] # [serde (skip_serializing_if = "Option::is_none")] pub use_spread : Option < RuleFixConfiguration < biome_rule_options :: use_spread :: UseSpreadOptions >> , # [doc = "Enforce unique operation names across a GraphQL document.\nSee https://biomejs.dev/linter/rules/use-unique-graphql-operation-name"] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_graphql_operation_name : Option < RuleConfiguration < biome_rule_options :: use_unique_graphql_operation_name :: UseUniqueGraphqlOperationNameOptions >> , # [doc = "Enforce specific order of Vue compiler macros.\nSee https://biomejs.dev/linter/rules/use-vue-define-macros-order"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce hyphenated (kebab-case) attribute names in Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-hyphenated-attributes"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_hyphenated_attributes : Option < RuleFixConfiguration < biome_rule_options :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributesOptions >> , # [doc = "Enforce multi-word component names in Vue components.\nSee https://biomejs.dev/linter/rules/use-vue-multi-word-component-names"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> , # [doc = "Forbids v-bind directives with missing arguments or invalid modifiers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-bind"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_bind : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_bind :: UseVueValidVBindOptions >> , # [doc = "Enforce valid usage of v-else.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else :: UseVueValidVElseOptions >> , # [doc = "Enforce valid v-else-if directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-else-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else_if :: UseVueValidVElseIfOptions >> , # [doc = "Enforce valid v-html directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-html"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_html : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_html :: UseVueValidVHtmlOptions >> , # [doc = "Enforces valid v-if usage for Vue templates.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-if"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_if :: UseVueValidVIfOptions >> , # [doc = "Enforce valid v-on directives with proper arguments, modifiers, and handlers.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-on"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_on : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_on :: UseVueValidVOnOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4789,7 +4789,7 @@ impl Nursery { "noImportCycles", "noIncrementDecrement", "noJsxLiterals", - "noLeakedConditionalRendering", + "noLeakedRender", "noMisusedPromises", "noNextAsyncClientComponent", "noParametersOnlyUsedInRecursion", @@ -4831,7 +4831,7 @@ impl Nursery { "useVueValidVOn", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = - &[RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])]; + &[RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), @@ -4867,8 +4867,6 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), -<<<<<<< HEAD -======= RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), @@ -4883,7 +4881,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), ]; } impl RuleGroupExt for Nursery { @@ -4935,293 +4933,211 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } -<<<<<<< HEAD - if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() -======= if let Some(rule) = self.no_jsx_literals.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } - if let Some(rule) = self.no_misused_promises.as_ref() + if let Some(rule) = self.no_leaked_render.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_next_async_client_component.as_ref() + if let Some(rule) = self.no_misused_promises.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } - if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() + if let Some(rule) = self.no_next_async_client_component.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unknown_attribute.as_ref() -======= - if let Some(rule) = self.no_sync_scripts.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_shadow.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unnecessary_conditions.as_ref() -======= - if let Some(rule) = self.no_unknown_attribute.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_sync_scripts.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unresolved_imports.as_ref() -======= - if let Some(rule) = self.no_unnecessary_conditions.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unknown_attribute.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unused_expressions.as_ref() -======= - if let Some(rule) = self.no_unresolved_imports.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } -<<<<<<< HEAD - if let Some(rule) = self.no_useless_catch_binding.as_ref() -======= - if let Some(rule) = self.no_unused_expressions.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } -<<<<<<< HEAD - if let Some(rule) = self.no_useless_undefined.as_ref() -======= - if let Some(rule) = self.no_useless_catch_binding.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() -======= - if let Some(rule) = self.no_useless_undefined.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() -======= - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_reserved_keys.as_ref() -======= - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_reserved_props.as_ref() -======= - if let Some(rule) = self.no_vue_reserved_keys.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } -<<<<<<< HEAD - if let Some(rule) = self.use_consistent_arrow_return.as_ref() -======= - if let Some(rule) = self.no_vue_reserved_props.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } -<<<<<<< HEAD - if let Some(rule) = self.use_deprecated_date.as_ref() -======= - if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } -<<<<<<< HEAD - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() -======= - if let Some(rule) = self.use_array_sort_compare.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } -<<<<<<< HEAD - if let Some(rule) = self.use_explicit_type.as_ref() -======= - if let Some(rule) = self.use_consistent_arrow_return.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_array_sort_compare.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } -<<<<<<< HEAD - if let Some(rule) = self.use_max_params.as_ref() -======= - if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } -<<<<<<< HEAD - if let Some(rule) = self.use_qwik_method_usage.as_ref() -======= - if let Some(rule) = self.use_deprecated_date.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } -<<<<<<< HEAD - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() -======= - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } -<<<<<<< HEAD - if let Some(rule) = self.use_sorted_classes.as_ref() -======= - if let Some(rule) = self.use_explicit_type.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } -<<<<<<< HEAD - if let Some(rule) = self.use_vue_define_macros_order.as_ref() -======= - if let Some(rule) = self.use_find.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } -<<<<<<< HEAD - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() -======= - if let Some(rule) = self.use_max_params.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_find.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } -<<<<<<< HEAD -======= - if let Some(rule) = self.use_qwik_method_usage.as_ref() + if let Some(rule) = self.use_max_params.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } - if let Some(rule) = self.use_spread.as_ref() + if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } - if let Some(rule) = self.use_unique_graphql_operation_name.as_ref() + if let Some(rule) = self.use_spread.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } - if let Some(rule) = self.use_vue_define_macros_order.as_ref() + if let Some(rule) = self.use_unique_graphql_operation_name.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } - if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref() + if let Some(rule) = self.use_vue_define_macros_order.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } - if let Some(rule) = self.use_vue_valid_v_bind.as_ref() + if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } - if let Some(rule) = self.use_vue_valid_v_else.as_ref() + if let Some(rule) = self.use_vue_valid_v_bind.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } - if let Some(rule) = self.use_vue_valid_v_else_if.as_ref() + if let Some(rule) = self.use_vue_valid_v_else.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } - if let Some(rule) = self.use_vue_valid_v_html.as_ref() + if let Some(rule) = self.use_vue_valid_v_else_if.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } - if let Some(rule) = self.use_vue_valid_v_if.as_ref() + if let Some(rule) = self.use_vue_valid_v_html.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } - if let Some(rule) = self.use_vue_valid_v_on.as_ref() + if let Some(rule) = self.use_vue_valid_v_if.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_vue_valid_v_on.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); + } index_set } fn get_disabled_rules(&self) -> FxHashSet> { @@ -5266,293 +5182,211 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } -<<<<<<< HEAD - if let Some(rule) = self.no_leaked_conditional_rendering.as_ref() -======= if let Some(rule) = self.no_jsx_literals.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } - if let Some(rule) = self.no_misused_promises.as_ref() + if let Some(rule) = self.no_leaked_render.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_next_async_client_component.as_ref() + if let Some(rule) = self.no_misused_promises.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } - if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() + if let Some(rule) = self.no_next_async_client_component.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } - if let Some(rule) = self.no_react_forward_ref.as_ref() + if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } - if let Some(rule) = self.no_shadow.as_ref() + if let Some(rule) = self.no_react_forward_ref.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unknown_attribute.as_ref() -======= - if let Some(rule) = self.no_sync_scripts.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_shadow.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unnecessary_conditions.as_ref() -======= - if let Some(rule) = self.no_unknown_attribute.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_sync_scripts.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unresolved_imports.as_ref() -======= - if let Some(rule) = self.no_unnecessary_conditions.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unknown_attribute.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } -<<<<<<< HEAD - if let Some(rule) = self.no_unused_expressions.as_ref() -======= - if let Some(rule) = self.no_unresolved_imports.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unnecessary_conditions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } -<<<<<<< HEAD - if let Some(rule) = self.no_useless_catch_binding.as_ref() -======= - if let Some(rule) = self.no_unused_expressions.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unresolved_imports.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } -<<<<<<< HEAD - if let Some(rule) = self.no_useless_undefined.as_ref() -======= - if let Some(rule) = self.no_useless_catch_binding.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_unused_expressions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() -======= - if let Some(rule) = self.no_useless_undefined.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_useless_catch_binding.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() -======= - if let Some(rule) = self.no_vue_data_object_declaration.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_useless_undefined.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_reserved_keys.as_ref() -======= - if let Some(rule) = self.no_vue_duplicate_keys.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_data_object_declaration.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } -<<<<<<< HEAD - if let Some(rule) = self.no_vue_reserved_props.as_ref() -======= - if let Some(rule) = self.no_vue_reserved_keys.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_duplicate_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } -<<<<<<< HEAD - if let Some(rule) = self.use_consistent_arrow_return.as_ref() -======= - if let Some(rule) = self.no_vue_reserved_props.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_reserved_keys.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } -<<<<<<< HEAD - if let Some(rule) = self.use_deprecated_date.as_ref() -======= - if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_reserved_props.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } -<<<<<<< HEAD - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() -======= - if let Some(rule) = self.use_array_sort_compare.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } -<<<<<<< HEAD - if let Some(rule) = self.use_explicit_type.as_ref() -======= - if let Some(rule) = self.use_consistent_arrow_return.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_array_sort_compare.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } -<<<<<<< HEAD - if let Some(rule) = self.use_max_params.as_ref() -======= - if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_consistent_arrow_return.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } -<<<<<<< HEAD - if let Some(rule) = self.use_qwik_method_usage.as_ref() -======= - if let Some(rule) = self.use_deprecated_date.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } -<<<<<<< HEAD - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() -======= - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_deprecated_date.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } -<<<<<<< HEAD - if let Some(rule) = self.use_sorted_classes.as_ref() -======= - if let Some(rule) = self.use_explicit_type.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } -<<<<<<< HEAD - if let Some(rule) = self.use_vue_define_macros_order.as_ref() -======= - if let Some(rule) = self.use_find.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_explicit_type.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } -<<<<<<< HEAD - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() -======= - if let Some(rule) = self.use_max_params.as_ref() ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_find.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } -<<<<<<< HEAD -======= - if let Some(rule) = self.use_qwik_method_usage.as_ref() + if let Some(rule) = self.use_max_params.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } - if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() + if let Some(rule) = self.use_qwik_method_usage.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } - if let Some(rule) = self.use_spread.as_ref() + if let Some(rule) = self.use_sorted_classes.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } - if let Some(rule) = self.use_unique_graphql_operation_name.as_ref() + if let Some(rule) = self.use_spread.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } - if let Some(rule) = self.use_vue_define_macros_order.as_ref() + if let Some(rule) = self.use_unique_graphql_operation_name.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } - if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref() + if let Some(rule) = self.use_vue_define_macros_order.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } - if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() + if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } - if let Some(rule) = self.use_vue_valid_v_bind.as_ref() + if let Some(rule) = self.use_vue_multi_word_component_names.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } - if let Some(rule) = self.use_vue_valid_v_else.as_ref() + if let Some(rule) = self.use_vue_valid_v_bind.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } - if let Some(rule) = self.use_vue_valid_v_else_if.as_ref() + if let Some(rule) = self.use_vue_valid_v_else.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } - if let Some(rule) = self.use_vue_valid_v_html.as_ref() + if let Some(rule) = self.use_vue_valid_v_else_if.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } - if let Some(rule) = self.use_vue_valid_v_if.as_ref() + if let Some(rule) = self.use_vue_valid_v_html.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } - if let Some(rule) = self.use_vue_valid_v_on.as_ref() + if let Some(rule) = self.use_vue_valid_v_if.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } ->>>>>>> 748e948166f9ec5685458dd633fe0060781d886a + if let Some(rule) = self.use_vue_valid_v_on.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -5619,8 +5453,8 @@ impl RuleGroupExt for Nursery { .no_jsx_literals .as_ref() .map(|conf| (conf.level(), conf.get_options())), - "noLeakedConditionalRendering" => self - .no_leaked_conditional_rendering + "noLeakedRender" => self + .no_leaked_render .as_ref() .map(|conf| (conf.level(), conf.get_options())), "noMisusedPromises" => self @@ -5796,7 +5630,7 @@ impl From for Nursery { no_import_cycles: Some(value.into()), no_increment_decrement: Some(value.into()), no_jsx_literals: Some(value.into()), - no_leaked_conditional_rendering: Some(value.into()), + no_leaked_render: Some(value.into()), no_misused_promises: Some(value.into()), no_next_async_client_component: Some(value.into()), no_parameters_only_used_in_recursion: Some(value.into()), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index ef8bdc28d65f..ea3e4261dda7 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -175,7 +175,7 @@ define_categories! { "lint/nursery/noImportCycles": "https://biomejs.dev/linter/rules/no-import-cycles", "lint/nursery/noIncrementDecrement": "https://biomejs.dev/linter/rules/no-increment-decrement", "lint/nursery/noJsxLiterals": "https://biomejs.dev/linter/rules/no-jsx-literals", - "lint/nursery/noLeakedConditionalRendering": "https://biomejs.dev/linter/rules/no-leaked-conditional-rendering", + "lint/nursery/noLeakedRender": "https://biomejs.dev/linter/rules/no-leaked-render", "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", "lint/nursery/noMisusedPromises": "https://biomejs.dev/linter/rules/no-misused-promises", "lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component", diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index 58a40a840ded..18df812e78df 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -153,23 +153,22 @@ impl Rule for NoLeakedRender { match query { Query::JsLogicalExpression(exp) => { let op = exp.operator().ok()?; - let left = exp.left().ok()?; if op != JsLogicalOperator::LogicalAnd { return None; } - - let is_coerce_valid_left_side = matches!( - left, - AnyJsExpression::JsUnaryExpression(_) - | AnyJsExpression::JsCallExpression(_) - | AnyJsExpression::JsBinaryExpression(_) - ); + let left = exp.left().ok()?; if valid_strategies .iter() .any(|s| s.as_ref() == COERCE_STRATEGY) { + let is_coerce_valid_left_side = matches!( + left, + AnyJsExpression::JsUnaryExpression(_) + | AnyJsExpression::JsCallExpression(_) + | AnyJsExpression::JsBinaryExpression(_) + ); if is_coerce_valid_left_side || get_is_coerce_valid_nested_logical_expression(exp.left()) { @@ -224,7 +223,7 @@ impl Rule for NoLeakedRender { .iter() .any(|&s| alternate.to_trimmed_text() == s); - let is_jsx_element_alt = matches!(alternate, AnyJsExpression::JsxTagExpression(_)); + let is_jsx_element_alt = matches!(alternate, AnyJsExpression::sxTagExpression(_)); if !is_problematic_alternate || is_jsx_element_alt { return None; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap index 825b36367fb0..12413fdfbc75 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/coerce/invalid.jsx.snap @@ -53,13 +53,6 @@ const Component11 = ({ items, somethingElse, title }) => { return
{items.length > 0 && somethingElse && title}
; }; -const MyComponent1 = () => { - const items = []; - const breakpoint = { phones: true }; - - return
{items.length > 0 && breakpoint.phones && }
; -}; - const MyComponent2 = () => { return
{maybeObject && (isFoo ? : )}
; }; @@ -315,35 +308,16 @@ invalid.jsx:47:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:54:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential leaked value that might cause unintended rendering. - - 52 │ const breakpoint = { phones: true }; - 53 │ - > 54 │ return
{items.length > 0 && breakpoint.phones && }
; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 55 │ }; - 56 │ - - i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. - - i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. - - -``` - -``` -invalid.jsx:87:24 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:80:24 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 85 │ const isOpen = 0; - 86 │ const Component12 = () => { - > 87 │ return 0} />; + 78 │ const isOpen = 0; + 79 │ const Component12 = () => { + > 80 │ return 0} />; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 88 │ }; - 89 │ + 81 │ }; + 82 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, '').React will render that value, causing unexpected UI output. diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx index 30dbf5c1e507..e76919143e88 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx @@ -35,3 +35,19 @@ const Component5 = ({ elements }) => { const Component6 = ({ numberA, numberB }) => { return
{(numberA || numberB) && {numberA + numberB}}
; }; + +const MyComponent1 = () => { + return ( + <> + {someCondition && ( +
+

hello

+
+ )} + + ); +}; + +const MyComponent2 = () => { + return <>{someCondition && }; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap index e85a5ea38513..226b3584bc03 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap @@ -42,6 +42,22 @@ const Component6 = ({ numberA, numberB }) => { return
{(numberA || numberB) && {numberA + numberB}}
; }; +const MyComponent1 = () => { + return ( + <> + {someCondition && ( +
+

hello

+
+ )} + + ); +}; + +const MyComponent2 = () => { + return <>{someCondition && }; +}; + ``` # Diagnostics diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 1589071579d0..48f3cf836844 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -120,7 +120,7 @@ pub mod no_irregular_whitespace; pub mod no_jsx_literals; pub mod no_label_var; pub mod no_label_without_control; -pub mod no_leaked_conditional_rendering; +pub mod no_leaked_render; pub mod no_magic_numbers; pub mod no_misleading_character_class; pub mod no_misleading_instantiator; diff --git a/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs b/crates/biome_rule_options/src/no_leaked_render.rs similarity index 83% rename from crates/biome_rule_options/src/no_leaked_conditional_rendering.rs rename to crates/biome_rule_options/src/no_leaked_render.rs index 93833d059759..1ac8a6a4ef9b 100644 --- a/crates/biome_rule_options/src/no_leaked_conditional_rendering.rs +++ b/crates/biome_rule_options/src/no_leaked_render.rs @@ -3,11 +3,11 @@ 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 NoLeakedConditionalRenderingOptions { +pub struct NoLeakedRenderOptions { #[serde(skip_serializing_if = "Option::<_>::is_none")] pub valid_strategies: Option]>>, } -impl biome_deserialize::Merge for NoLeakedConditionalRenderingOptions { +impl biome_deserialize::Merge for NoLeakedRenderOptions { fn merge_with(&mut self, other: Self) { if let Some(valid_strategies) = other.valid_strategies { self.valid_strategies = Some(valid_strategies); diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 3f606a1d9cbc..c89f3cc1b2d2 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1,5 +1,5 @@ // Generated file, do not edit by hand, see `xtask/codegen` -import type { Transport } from './transport'; +import type { Transport } from "./transport"; export interface SupportsFeatureParams { features: FeatureName; path: BiomePath; @@ -8,17 +8,23 @@ export interface SupportsFeatureParams { export type FeatureName = FeatureKind[]; export type BiomePath = string; export type ProjectKey = number; -export type FeatureKind = 'format' | 'lint' | 'search' | 'assist' | 'debug' | 'htmlFullSupport'; +export type FeatureKind = + | "format" + | "lint" + | "search" + | "assist" + | "debug" + | "htmlFullSupport"; export interface FileFeaturesResult { featuresSupported: FeaturesSupported; } export type FeaturesSupported = { [K in FeatureKind]?: SupportKind }; export type SupportKind = - | 'supported' - | 'ignored' - | 'protected' - | 'featureNotEnabled' - | 'fileNotSupported'; + | "supported" + | "ignored" + | "protected" + | "featureNotEnabled" + | "fileNotSupported"; export interface UpdateSettingsParams { configuration: Configuration; projectKey: ProjectKey; @@ -86,7 +92,7 @@ export interface Configuration { plugins?: Plugins; /** * Indicates whether this configuration file is at the root of a Biome -project. By default, this is `true`. +project. By default, this is `true`. */ root?: Bool; /** @@ -106,7 +112,7 @@ export interface AssistConfiguration { enabled?: Bool; /** * A list of glob patterns. Biome will include files/folders that will -match these patterns. +match these patterns. */ includes?: NormalizedGlob[]; } @@ -145,7 +151,7 @@ export interface FilesConfiguration { instead: Set of file and folder names that should be unconditionally ignored by -Biome's scanner. +Biome's scanner. */ experimentalScannerIgnores?: string[]; /** @@ -154,12 +160,12 @@ Biome's scanner. ignoreUnknown?: Bool; /** * A list of glob patterns. Biome will handle only those files/folders that will -match these patterns. +match these patterns. */ includes?: NormalizedGlob[]; /** * The maximum allowed size for source code files in bytes. Files above -this limit will be ignored for performance reasons. Defaults to 1 MiB +this limit will be ignored for performance reasons. Defaults to 1 MiB */ maxSize?: MaxSize; } @@ -186,17 +192,17 @@ When set to `auto`, object literals are formatted on multiple lines if the first and array literals are formatted on a single line if it fits in the line. When set to `always`, these literals are formatted on multiple lines, regardless of length of the list. When set to `never`, these literals are formatted on a single line if it fits in the line. -When formatting `package.json`, Biome will use `always` unless configured otherwise. Defaults to "auto". +When formatting `package.json`, Biome will use `always` unless configured otherwise. Defaults to "auto". */ expand?: Expand; /** * Whether formatting should be allowed to proceed if a given file -has syntax errors +has syntax errors */ formatWithErrors?: Bool; /** * A list of glob patterns. The formatter will include files/folders that will -match these patterns. +match these patterns. */ includes?: NormalizedGlob[]; /** @@ -219,7 +225,7 @@ match these patterns. * Use any `.editorconfig` files to configure the formatter. Configuration in `biome.json` will override `.editorconfig` configuration. -Default: `true`. +Default: `true`. */ useEditorconfig?: Bool; } @@ -291,7 +297,7 @@ export interface JsConfiguration { /** * A list of global bindings that should be ignored by the analyzers -If defined here, they should not emit diagnostics. +If defined here, they should not emit diagnostics. */ globals?: string[]; /** @@ -339,7 +345,7 @@ export interface LinterConfiguration { enabled?: Bool; /** * A list of glob patterns. The analyzer will handle only those files/folders that will -match these patterns. +match these patterns. */ includes?: NormalizedGlob[]; /** @@ -372,12 +378,12 @@ folder where `biome.json` was found. If Biome can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, Biome won't use the VCS integration, and a diagnostic -will be emitted +will be emitted */ root?: string; /** * Whether Biome should use the VCS ignore file. When [true], Biome will ignore the files -specified in the ignore file. +specified in the ignore file. */ useIgnoreFile?: Bool; } @@ -457,20 +463,20 @@ export interface CssParserConfiguration { tailwindDirectives?: Bool; } export type MaxSize = number; -export type AttributePosition = 'auto' | 'multiline'; +export type AttributePosition = "auto" | "multiline"; /** * Put the `>` of a multi-line HTML or JSX element at the end of the last line instead of being alone on the next line (does not apply to self closing elements). */ export type BracketSameLine = boolean; export type BracketSpacing = boolean; -export type Expand = 'auto' | 'always' | 'never'; -export type IndentStyle = 'tab' | 'space'; +export type Expand = "auto" | "always" | "never"; +export type IndentStyle = "tab" | "space"; export type IndentWidth = number; -export type LineEnding = 'lf' | 'crlf' | 'cr' | 'auto'; +export type LineEnding = "lf" | "crlf" | "cr" | "auto"; /** * Validated value for the `line_width` formatter options -The allowed range of values is 1..=320 +The allowed range of values is 1..=320 */ export type LineWidth = number; /** @@ -669,7 +675,7 @@ When set to `auto`, object literals are formatted on multiple lines if the first and array literals are formatted on a single line if it fits in the line. When set to `always`, these literals are formatted on multiple lines, regardless of length of the list. When set to `never`, these literals are formatted on a single line if it fits in the line. -When formatting `package.json`, Biome will use `always` unless configured otherwise. Defaults to "auto". +When formatting `package.json`, Biome will use `always` unless configured otherwise. Defaults to "auto". */ expand?: Expand; /** @@ -716,7 +722,7 @@ When formatting `package.json`, Biome will use `always` unless configured otherw /** * Indicates the type of runtime or transformation used for interpreting JSX. */ -export type JsxRuntime = 'transparent' | 'reactClassic'; +export type JsxRuntime = "transparent" | "reactClassic"; /** * Linter options specific to the JavaScript linter */ @@ -732,19 +738,19 @@ export interface JsLinterConfiguration { export interface JsParserConfiguration { /** * Enables parsing of Grit metavariables. -Defaults to `false`. +Defaults to `false`. */ gritMetavariables?: Bool; /** * When enabled, files like `.js`/`.mjs`/`.cjs` may contain JSX syntax. -Defaults to `true`. +Defaults to `true`. */ jsxEverywhere?: Bool; /** * It enables the experimental and unsafe parsing of parameter decorators -These decorators belong to an old proposal, and they are subject to change. +These decorators belong to an old proposal, and they are subject to change. */ unsafeParameterDecoratorsEnabled?: Bool; } @@ -772,7 +778,7 @@ When set to `auto`, object literals are formatted on multiple lines if the first and array literals are formatted on a single line if it fits in the line. When set to `always`, these literals are formatted on multiple lines, regardless of length of the list. When set to `never`, these literals are formatted on a single line if it fits in the line. -When formatting `package.json`, Biome will use `always` unless configured otherwise. Defaults to "auto". +When formatting `package.json`, Biome will use `always` unless configured otherwise. Defaults to "auto". */ expand?: Expand; /** @@ -864,7 +870,7 @@ export interface OverridePattern { html?: HtmlConfiguration; /** * A list of glob patterns. Biome will include files/folders that will -match these patterns. +match these patterns. */ includes?: OverrideGlobs; /** @@ -885,14 +891,14 @@ match these patterns. plugins?: Plugins; } export type PluginConfiguration = string; -export type VcsClientKind = 'git'; +export type VcsClientKind = "git"; /** * A list of rules that belong to this group */ export interface Source { /** * Provides a code action to sort the imports and exports in the file using a built-in or custom order. -See https://biomejs.dev/assist/actions/organize-imports +See https://biomejs.dev/assist/actions/organize-imports */ organizeImports?: OrganizeImportsConfiguration; /** @@ -901,31 +907,31 @@ See https://biomejs.dev/assist/actions/organize-imports recommended?: boolean; /** * Enforce attribute sorting in JSX elements. -See https://biomejs.dev/assist/actions/use-sorted-attributes +See https://biomejs.dev/assist/actions/use-sorted-attributes */ useSortedAttributes?: UseSortedAttributesConfiguration; /** * Sort the keys of a JSON object in natural order. -See https://biomejs.dev/assist/actions/use-sorted-keys +See https://biomejs.dev/assist/actions/use-sorted-keys */ useSortedKeys?: UseSortedKeysConfiguration; /** * Enforce ordering of CSS properties and nested rules. -See https://biomejs.dev/assist/actions/use-sorted-properties +See https://biomejs.dev/assist/actions/use-sorted-properties */ useSortedProperties?: UseSortedPropertiesConfiguration; } -export type QuoteStyle = 'double' | 'single'; +export type QuoteStyle = "double" | "single"; /** * Whether to indent the content of `