diff --git a/.changeset/pink-coats-jog.md b/.changeset/pink-coats-jog.md new file mode 100644 index 000000000000..21be2b6eb837 --- /dev/null +++ b/.changeset/pink-coats-jog.md @@ -0,0 +1,12 @@ +--- +"@biomejs/biome": minor +--- + +Added the new nursery rule `noUselessCatchBinding`. This rule disallows unnecessary catch bindings. + +```diff +try { + // Do something +- } catch (unused) {} ++ } catch {} +``` diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 1988b07bc0f4..87b2db94f8e7 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -293,6 +293,7 @@ pub enum RuleName { NoUnusedVariables, NoUnwantedPolyfillio, NoUselessCatch, + NoUselessCatchBinding, NoUselessConstructor, NoUselessContinue, NoUselessElse, @@ -650,6 +651,7 @@ impl RuleName { Self::NoUnusedVariables => "noUnusedVariables", Self::NoUnwantedPolyfillio => "noUnwantedPolyfillio", Self::NoUselessCatch => "noUselessCatch", + Self::NoUselessCatchBinding => "noUselessCatchBinding", Self::NoUselessConstructor => "noUselessConstructor", Self::NoUselessContinue => "noUselessContinue", Self::NoUselessElse => "noUselessElse", @@ -1003,6 +1005,7 @@ impl RuleName { Self::NoUnusedVariables => RuleGroup::Correctness, Self::NoUnwantedPolyfillio => RuleGroup::Performance, Self::NoUselessCatch => RuleGroup::Complexity, + Self::NoUselessCatchBinding => RuleGroup::Nursery, Self::NoUselessConstructor => RuleGroup::Complexity, Self::NoUselessContinue => RuleGroup::Complexity, Self::NoUselessElse => RuleGroup::Style, @@ -1365,6 +1368,7 @@ impl std::str::FromStr for RuleName { "noUnusedVariables" => Ok(Self::NoUnusedVariables), "noUnwantedPolyfillio" => Ok(Self::NoUnwantedPolyfillio), "noUselessCatch" => Ok(Self::NoUselessCatch), + "noUselessCatchBinding" => Ok(Self::NoUselessCatchBinding), "noUselessConstructor" => Ok(Self::NoUselessConstructor), "noUselessContinue" => Ok(Self::NoUselessContinue), "noUselessElse" => Ok(Self::NoUselessElse), @@ -4594,7 +4598,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" It enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [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 = "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 non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "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 the use of useless undefined."] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props."] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "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 = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce 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 >> } +pub struct Nursery { # [doc = r" It enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "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 = "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 non-null assertions after optional chaining expressions."] # [serde (skip_serializing_if = "Option::is_none")] pub no_non_null_asserted_optional_chain : Option < RuleConfiguration < biome_rule_options :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChainOptions >> , # [doc = "Disallow useVisibleTask$() functions in Qwik components."] # [serde (skip_serializing_if = "Option::is_none")] pub no_qwik_use_visible_task : Option < RuleConfiguration < biome_rule_options :: no_qwik_use_visible_task :: NoQwikUseVisibleTaskOptions >> , # [doc = "Disallow usage of sensitive data such as API keys and tokens."] # [serde (skip_serializing_if = "Option::is_none")] pub no_secrets : Option < RuleConfiguration < biome_rule_options :: no_secrets :: NoSecretsOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope."] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "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 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 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 = "Enforces href attribute for \\ elements."] # [serde (skip_serializing_if = "Option::is_none")] pub use_anchor_href : Option < RuleConfiguration < biome_rule_options :: use_anchor_href :: UseAnchorHrefOptions >> , # [doc = "Enforce type definitions to consistently use either interface or type."] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_type_definitions : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_type_definitions :: UseConsistentTypeDefinitionsOptions >> , # [doc = "Require switch-case statements to be exhaustive."] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters."] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforces that \\ elements have both width and height attributes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_image_size : Option < RuleConfiguration < biome_rule_options :: use_image_size :: UseImageSizeOptions >> , # [doc = "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 = "Prefer using the class prop as a classlist over the classnames helper."] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_classlist : Option < RuleConfiguration < biome_rule_options :: use_qwik_classlist :: UseQwikClasslistOptions >> , # [doc = "Enforce that components are defined as functions and never as classes."] # [serde (skip_serializing_if = "Option::is_none")] pub use_react_function_components : Option < RuleConfiguration < biome_rule_options :: use_react_function_components :: UseReactFunctionComponentsOptions >> , # [doc = "Enforce 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 >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4608,6 +4612,7 @@ impl Nursery { "noShadow", "noUnnecessaryConditions", "noUnresolvedImports", + "noUselessCatchBinding", "noUselessUndefined", "noVueDataObjectDeclaration", "noVueReservedKeys", @@ -4648,6 +4653,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), ]; } impl RuleGroupExt for Nursery { @@ -4709,71 +4715,76 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_useless_undefined.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[10])); } - if let Some(rule) = self.no_vue_data_object_declaration.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[11])); } - 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[12])); } - if let Some(rule) = self.no_vue_reserved_props.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[13])); } - if let Some(rule) = self.use_anchor_href.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[14])); } - if let Some(rule) = self.use_consistent_type_definitions.as_ref() + if let Some(rule) = self.use_anchor_href.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_consistent_type_definitions.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } - if let Some(rule) = self.use_explicit_type.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[17])); } - if let Some(rule) = self.use_image_size.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[18])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.use_image_size.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } - if let Some(rule) = self.use_qwik_classlist.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[20])); } - if let Some(rule) = self.use_react_function_components.as_ref() + if let Some(rule) = self.use_qwik_classlist.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_react_function_components.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } + if let Some(rule) = self.use_sorted_classes.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + } index_set } fn get_disabled_rules(&self) -> FxHashSet> { @@ -4828,71 +4839,76 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } - if let Some(rule) = self.no_useless_undefined.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[10])); } - if let Some(rule) = self.no_vue_data_object_declaration.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[11])); } - 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[12])); } - if let Some(rule) = self.no_vue_reserved_props.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[13])); } - if let Some(rule) = self.use_anchor_href.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[14])); } - if let Some(rule) = self.use_consistent_type_definitions.as_ref() + if let Some(rule) = self.use_anchor_href.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_consistent_type_definitions.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } - if let Some(rule) = self.use_explicit_type.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[17])); } - if let Some(rule) = self.use_image_size.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[18])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.use_image_size.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } - if let Some(rule) = self.use_qwik_classlist.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[20])); } - if let Some(rule) = self.use_react_function_components.as_ref() + if let Some(rule) = self.use_qwik_classlist.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_react_function_components.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } + if let Some(rule) = self.use_sorted_classes.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -4963,6 +4979,10 @@ impl RuleGroupExt for Nursery { .no_unresolved_imports .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noUselessCatchBinding" => self + .no_useless_catch_binding + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noUselessUndefined" => self .no_useless_undefined .as_ref() @@ -5033,6 +5053,7 @@ impl From for Nursery { no_shadow: Some(value.into()), no_unnecessary_conditions: Some(value.into()), no_unresolved_imports: Some(value.into()), + no_useless_catch_binding: Some(value.into()), no_useless_undefined: Some(value.into()), no_vue_data_object_declaration: Some(value.into()), no_vue_reserved_keys: Some(value.into()), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index f8745843c7aa..051a23f8668e 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -149,25 +149,25 @@ define_categories! { "lint/correctness/noVoidElementsWithChildren": "https://biomejs.dev/linter/rules/no-void-elements-with-children", "lint/correctness/noVoidTypeReturn": "https://biomejs.dev/linter/rules/no-void-type-return", "lint/correctness/useExhaustiveDependencies": "https://biomejs.dev/linter/rules/use-exhaustive-dependencies", + "lint/correctness/useGraphqlNamedOperations": "https://biomejs.dev/linter/rules/use-graphql-named-operations", "lint/correctness/useHookAtTopLevel": "https://biomejs.dev/linter/rules/use-hook-at-top-level", "lint/correctness/useImportExtensions": "https://biomejs.dev/linter/rules/use-import-extensions", "lint/correctness/useIsNan": "https://biomejs.dev/linter/rules/use-is-nan", "lint/correctness/useJsonImportAttributes": "https://biomejs.dev/linter/rules/use-json-import-attributes", "lint/correctness/useJsxKeyInIterable": "https://biomejs.dev/linter/rules/use-jsx-key-in-iterable", - "lint/correctness/useGraphqlNamedOperations": "https://biomejs.dev/linter/rules/use-graphql-named-operations", "lint/correctness/useParseIntRadix": "https://biomejs.dev/linter/rules/use-parse-int-radix", "lint/correctness/useSingleJsDocAsterisk": "https://biomejs.dev/linter/rules/use-single-js-doc-asterisk", "lint/correctness/useUniqueElementIds": "https://biomejs.dev/linter/rules/use-unique-element-ids", "lint/correctness/useValidForDirection": "https://biomejs.dev/linter/rules/use-valid-for-direction", "lint/correctness/useValidTypeof": "https://biomejs.dev/linter/rules/use-valid-typeof", "lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield", - "lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component", "lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex", "lint/nursery/noFloatingPromises": "https://biomejs.dev/linter/rules/no-floating-promises", "lint/nursery/noImplicitCoercion": "https://biomejs.dev/linter/rules/no-implicit-coercion", "lint/nursery/noImportCycles": "https://biomejs.dev/linter/rules/no-import-cycles", "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", "lint/nursery/noNonNullAssertedOptionalChain": "https://biomejs.dev/linter/rules/no-non-null-asserted-optional-chain", "lint/nursery/noQwikUseVisibleTask": "https://biomejs.dev/linter/rules/no-qwik-use-visible-task", "lint/nursery/noSecrets": "https://biomejs.dev/linter/rules/no-secrets", @@ -176,6 +176,7 @@ define_categories! { "lint/nursery/noUnresolvedImports": "https://biomejs.dev/linter/rules/no-unresolved-imports", "lint/nursery/noUnwantedPolyfillio": "https://biomejs.dev/linter/rules/no-unwanted-polyfillio", "lint/nursery/noUselessBackrefInRegex": "https://biomejs.dev/linter/rules/no-useless-backref-in-regex", + "lint/nursery/noUselessCatchBinding": "https://biomejs.dev/linter/rules/no-useless-catch-binding", "lint/nursery/noUselessUndefined": "https://biomejs.dev/linter/rules/no-useless-undefined", "lint/nursery/noVueDataObjectDeclaration": "https://biomejs.dev/linter/rules/no-vue-data-object-declaration", "lint/nursery/noVueReservedKeys": "https://biomejs.dev/linter/rules/no-vue-reserved-keys", @@ -359,20 +360,20 @@ define_categories! { "lint/suspicious/noUnknownAtRules": "https://biomejs.dev/linter/rules/no-unknown-at-rules", "lint/suspicious/noUnsafeDeclarationMerging": "https://biomejs.dev/linter/rules/no-unsafe-declaration-merging", "lint/suspicious/noUnsafeNegation": "https://biomejs.dev/linter/rules/no-unsafe-negation", - "lint/suspicious/noUselessRegexBackrefs": "https://biomejs.dev/linter/rules/no-useless-regex-backrefs", "lint/suspicious/noUselessEscapeInString": "https://biomejs.dev/linter/rules/no-useless-escape-in-string", + "lint/suspicious/noUselessRegexBackrefs": "https://biomejs.dev/linter/rules/no-useless-regex-backrefs", "lint/suspicious/noVar": "https://biomejs.dev/linter/rules/no-var", "lint/suspicious/noWith": "https://biomejs.dev/linter/rules/no-with", "lint/suspicious/useAdjacentOverloadSignatures": "https://biomejs.dev/linter/rules/use-adjacent-overload-signatures", "lint/suspicious/useAwait": "https://biomejs.dev/linter/rules/use-await", "lint/suspicious/useBiomeIgnoreFolder": "https://biomejs.dev/linter/rules/use-biome-ignore-folder", - "lint/suspicious/useIterableCallbackReturn": "https://biomejs.dev/linter/rules/use-iterable-callback-return", "lint/suspicious/useDefaultSwitchClauseLast": "https://biomejs.dev/linter/rules/use-default-switch-clause-last", "lint/suspicious/useErrorMessage": "https://biomejs.dev/linter/rules/use-error-message", "lint/suspicious/useGetterReturn": "https://biomejs.dev/linter/rules/use-getter-return", "lint/suspicious/useGoogleFontDisplay": "https://biomejs.dev/linter/rules/use-google-font-display", "lint/suspicious/useGuardForIn": "https://biomejs.dev/linter/rules/use-guard-for-in", "lint/suspicious/useIsArray": "https://biomejs.dev/linter/rules/use-is-array", + "lint/suspicious/useIterableCallbackReturn": "https://biomejs.dev/linter/rules/use-iterable-callback-return", "lint/suspicious/useNamespaceKeyword": "https://biomejs.dev/linter/rules/use-namespace-keyword", "lint/suspicious/useNumberToFixedDigitsArgument": "https://biomejs.dev/linter/rules/use-number-to-fixed-digits-argument", "lint/suspicious/useStaticResponseMethods": "https://biomejs.dev/linter/rules/use-static-response-methods", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 3ebaf8df98dc..245502f07ce4 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -13,6 +13,7 @@ pub mod no_secrets; pub mod no_shadow; pub mod no_unnecessary_conditions; pub mod no_unresolved_imports; +pub mod no_useless_catch_binding; pub mod no_useless_undefined; pub mod no_vue_data_object_declaration; pub mod no_vue_reserved_keys; @@ -26,4 +27,4 @@ pub mod use_max_params; pub mod use_qwik_classlist; pub mod use_react_function_components; pub mod use_sorted_classes; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_image_size :: UseImageSize , self :: use_max_params :: UseMaxParams , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_sorted_classes :: UseSortedClasses ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_floating_promises :: NoFloatingPromises , self :: no_import_cycles :: NoImportCycles , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_non_null_asserted_optional_chain :: NoNonNullAssertedOptionalChain , self :: no_qwik_use_visible_task :: NoQwikUseVisibleTask , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_anchor_href :: UseAnchorHref , self :: use_consistent_type_definitions :: UseConsistentTypeDefinitions , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_image_size :: UseImageSize , self :: use_max_params :: UseMaxParams , self :: use_qwik_classlist :: UseQwikClasslist , self :: use_react_function_components :: UseReactFunctionComponents , self :: use_sorted_classes :: UseSortedClasses ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/no_useless_catch_binding.rs b/crates/biome_js_analyze/src/lint/nursery/no_useless_catch_binding.rs new file mode 100644 index 000000000000..b273f8ca9c60 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_useless_catch_binding.rs @@ -0,0 +1,150 @@ +use crate::JsRuleAction; +use crate::lint::correctness::no_unused_variables::is_unused; +use crate::services::semantic::Semantic; +use biome_analyze::{FixKind, Rule, RuleDiagnostic, context::RuleContext, declare_lint_rule}; +use biome_console::markup; +use biome_js_syntax::{ + JsCatchClause, JsCatchClauseFields, JsCatchDeclaration, binding_ext::AnyJsIdentifierBinding, +}; +use biome_rowan::{AstNode, BatchMutationExt, trim_leading_trivia_pieces}; +use biome_rule_options::no_useless_catch_binding::NoUselessCatchBindingOptions; + +declare_lint_rule! { + /// Disallow unused catch bindings. + /// + /// This rule disallows unnecessary catch bindings in accordance with ECMAScript 2019. + /// See also: the ECMAScript 2019 “optional catch binding” feature in the language specification. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// try { + /// // Do something + /// } catch (unused) {} + /// ``` + /// + /// ```js,expect_diagnostic + /// try { + /// // Do something + /// } catch ({ unused }) {} + /// ``` + /// + /// ```js,expect_diagnostic + /// try { + /// // Do something + /// } catch ({ unused1, unused2 }) {} + /// ``` + /// + /// ### Valid + /// + /// ```js + /// try { + /// // Do something + /// } catch (used) { + /// console.error(used); + /// } + /// ``` + /// + /// ```js + /// try { + /// // Do something + /// } catch ({ used }) { + /// console.error(used); + /// } + /// ``` + /// + /// ```js + /// try { + /// // Do something + /// } catch ({ used, unused }) { + /// console.error(used); + /// } + /// ``` + /// + /// ```js + /// try { + /// // Do something + /// } catch {} + /// ``` + /// + pub NoUselessCatchBinding { + version: "next", + name: "noUselessCatchBinding", + language: "js", + recommended: false, + fix_kind: FixKind::Unsafe, + } +} + +impl Rule for NoUselessCatchBinding { + type Query = Semantic; + type State = (); + type Signals = Option; + type Options = NoUselessCatchBindingOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let model = ctx.model(); + let catch_declaration = ctx.query(); + let catch_binding = catch_declaration.binding().ok()?; + + let all_unused = catch_binding + .syntax() + .descendants() + .filter_map(AnyJsIdentifierBinding::cast) + .all(|ident| is_unused(model, &ident)); + if all_unused { + return Some(()); + } + None + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + let catch_declaration = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + catch_declaration.range(), + markup! { + "This ""catch binding"" is unused." + }, + ) + .note(markup! { + "Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it." + }), + ) + } + + fn action(ctx: &RuleContext, _state: &Self::State) -> Option { + let mut mutation = ctx.root().begin(); + let node = ctx.query(); + + let catch_clause = node.syntax().parent()?; + let JsCatchClauseFields { + catch_token, + declaration, + .. + } = JsCatchClause::cast(catch_clause)?.as_fields(); + + let catch_token = catch_token.ok()?; + let declaration = declaration?; + let catch_token_replacement = + if let Some(trivia) = declaration.syntax().last_trailing_trivia() { + catch_token + .clone() + .append_trivia_pieces(trim_leading_trivia_pieces(trivia.pieces())) + } else { + catch_token.clone() + }; + mutation.remove_node(declaration); + mutation.replace_token_discard_trivia(catch_token, catch_token_replacement); + + Some(JsRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + ctx.metadata().applicability(), + markup! { "Remove the catch binding." }.to_owned(), + mutation, + )) + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.js new file mode 100644 index 000000000000..f0f93de2a0b0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.js @@ -0,0 +1,28 @@ +// should generate diagnostics + +try { /* ... */ } catch (unused) { } + +try { /* ... */ } catch (_unused) { } + + +try { /* ... */ } catch ({ unused }) { } + +try { /* ... */ } catch ({ _unused }) { } + +try { /* ... */ } catch ({ unused, _unused }) { } + + +try { /* ... */ } catch (/* leading inner */ unused /* trailing inner */) { } + +try { /* ... */ } catch /* leading outer */ (unused) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + +try { /* ... */ } catch ({ used: alias }) { } + +try { /* ... */ } catch ({ nested: { unused } }) { } + +try { /* ... */ } catch ({ ...rest }) { } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.js.snap new file mode 100644 index 000000000000..d405087d212b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.js.snap @@ -0,0 +1,295 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: invalid.js +--- +# Input +```js +// should generate diagnostics + +try { /* ... */ } catch (unused) { } + +try { /* ... */ } catch (_unused) { } + + +try { /* ... */ } catch ({ unused }) { } + +try { /* ... */ } catch ({ _unused }) { } + +try { /* ... */ } catch ({ unused, _unused }) { } + + +try { /* ... */ } catch (/* leading inner */ unused /* trailing inner */) { } + +try { /* ... */ } catch /* leading outer */ (unused) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + +try { /* ... */ } catch ({ used: alias }) { } + +try { /* ... */ } catch ({ nested: { unused } }) { } + +try { /* ... */ } catch ({ ...rest }) { } + +``` + +# Diagnostics +``` +invalid.js:3:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 1 │ // should generate diagnostics + 2 │ + > 3 │ try { /* ... */ } catch (unused) { } + │ ^^^^^^^^ + 4 │ + 5 │ try { /* ... */ } catch (_unused) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 3 │ try·{·/*·...·*/·}·catch·(unused)·{·} + │ --------- + +``` + +``` +invalid.js:5:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 3 │ try { /* ... */ } catch (unused) { } + 4 │ + > 5 │ try { /* ... */ } catch (_unused) { } + │ ^^^^^^^^^ + 6 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 5 │ try·{·/*·...·*/·}·catch·(_unused)·{·} + │ ---------- + +``` + +``` +invalid.js:8:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + > 8 │ try { /* ... */ } catch ({ unused }) { } + │ ^^^^^^^^^^^^ + 9 │ + 10 │ try { /* ... */ } catch ({ _unused }) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 8 │ try·{·/*·...·*/·}·catch·({·unused·})·{·} + │ ------------- + +``` + +``` +invalid.js:10:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 8 │ try { /* ... */ } catch ({ unused }) { } + 9 │ + > 10 │ try { /* ... */ } catch ({ _unused }) { } + │ ^^^^^^^^^^^^^ + 11 │ + 12 │ try { /* ... */ } catch ({ unused, _unused }) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 10 │ try·{·/*·...·*/·}·catch·({·_unused·})·{·} + │ -------------- + +``` + +``` +invalid.js:12:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 10 │ try { /* ... */ } catch ({ _unused }) { } + 11 │ + > 12 │ try { /* ... */ } catch ({ unused, _unused }) { } + │ ^^^^^^^^^^^^^^^^^^^^^ + 13 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 12 │ try·{·/*·...·*/·}·catch·({·unused,·_unused·})·{·} + │ ---------------------- + +``` + +``` +invalid.js:15:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + > 15 │ try { /* ... */ } catch (/* leading inner */ unused /* trailing inner */) { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │ + 17 │ try { /* ... */ } catch /* leading outer */ (unused) /* trailing outer */ { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 15 │ try·{·/*·...·*/·}·catch·(/*·leading·inner·*/·unused·/*·trailing·inner·*/)·{·} + │ -------------------------------------------------- + +``` + +``` +invalid.js:17:45 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 15 │ try { /* ... */ } catch (/* leading inner */ unused /* trailing inner */) { } + 16 │ + > 17 │ try { /* ... */ } catch /* leading outer */ (unused) /* trailing outer */ { } + │ ^^^^^^^^ + 18 │ + 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 15 15 │ try { /* ... */ } catch (/* leading inner */ unused /* trailing inner */) { } + 16 16 │ + 17 │ - try·{·/*·...·*/·}·catch·/*·leading·outer·*/·(unused)·/*·trailing·outer·*/·{·} + 17 │ + try·{·/*·...·*/·}·catch·/*·leading·outer·*/·/*·trailing·outer·*/·{·} + 18 18 │ + 19 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + + +``` + +``` +invalid.js:19:45 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 17 │ try { /* ... */ } catch /* leading outer */ (unused) /* trailing outer */ { } + 18 │ + > 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 20 │ + 21 │ try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 17 17 │ try { /* ... */ } catch /* leading outer */ (unused) /* trailing outer */ { } + 18 18 │ + 19 │ - try·{·/*·...·*/·}·catch·/*·leading·outer·*/·(/*·leading·inner·*/·unused·/*·trailing·inner·*/)·/*·trailing·outer·*/·{·} + 19 │ + try·{·/*·...·*/·}·catch·/*·leading·outer·*/·/*·trailing·outer·*/·{·} + 20 20 │ + 21 21 │ try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + +``` + +``` +invalid.js:21:45 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + 20 │ + > 21 │ try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 22 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 19 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused /* trailing inner */) /* trailing outer */ { } + 20 20 │ + 21 │ - try·{·/*·...·*/·}·catch·/*·leading·outer·*/·(/*·leading·inner·1·*/·{·/*·leading·inner·2·*/·unused·/*·trailing·inner·2·*/·}·/*·trailing·inner·1·*/)·/*·trailing·outer·*/·{·} + 21 │ + try·{·/*·...·*/·}·catch·/*·leading·outer·*/·/*·trailing·outer·*/·{·} + 22 22 │ + 23 23 │ + + +``` + +``` +invalid.js:24:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + > 24 │ try { /* ... */ } catch ({ used: alias }) { } + │ ^^^^^^^^^^^^^^^^^ + 25 │ + 26 │ try { /* ... */ } catch ({ nested: { unused } }) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 24 │ try·{·/*·...·*/·}·catch·({·used:·alias·})·{·} + │ ------------------ + +``` + +``` +invalid.js:26:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 24 │ try { /* ... */ } catch ({ used: alias }) { } + 25 │ + > 26 │ try { /* ... */ } catch ({ nested: { unused } }) { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ + 28 │ try { /* ... */ } catch ({ ...rest }) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 26 │ try·{·/*·...·*/·}·catch·({·nested:·{·unused·}·})·{·} + │ ------------------------- + +``` + +``` +invalid.js:28:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 26 │ try { /* ... */ } catch ({ nested: { unused } }) { } + 27 │ + > 28 │ try { /* ... */ } catch ({ ...rest }) { } + │ ^^^^^^^^^^^^^ + 29 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 28 │ try·{·/*·...·*/·}·catch·({·...rest·})·{·} + │ -------------- + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.ts new file mode 100644 index 000000000000..25a1d2b7da39 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.ts @@ -0,0 +1,28 @@ +// should generate diagnostics + +try { /* ... */ } catch (unused: any) { } + +try { /* ... */ } catch (_unused: any) { } + + +try { /* ... */ } catch ({ unused }: any) { } + +try { /* ... */ } catch ({ _unused }: any) { } + +try { /* ... */ } catch ({ unused, _unused }: any) { } + + +try { /* ... */ } catch (/* leading inner */ unused: any /* trailing inner */) { } + +try { /* ... */ } catch /* leading outer */ (unused: any) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused: any /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + +try { /* ... */ } catch ({ used: alias }: any) { } + +try { /* ... */ } catch ({ nested: { unused } }: any) { } + +try { /* ... */ } catch ({ ...rest }: any) { } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.ts.snap new file mode 100644 index 000000000000..7633cfb8b587 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/invalid.ts.snap @@ -0,0 +1,295 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: invalid.ts +--- +# Input +```ts +// should generate diagnostics + +try { /* ... */ } catch (unused: any) { } + +try { /* ... */ } catch (_unused: any) { } + + +try { /* ... */ } catch ({ unused }: any) { } + +try { /* ... */ } catch ({ _unused }: any) { } + +try { /* ... */ } catch ({ unused, _unused }: any) { } + + +try { /* ... */ } catch (/* leading inner */ unused: any /* trailing inner */) { } + +try { /* ... */ } catch /* leading outer */ (unused: any) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + +try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused: any /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + +try { /* ... */ } catch ({ used: alias }: any) { } + +try { /* ... */ } catch ({ nested: { unused } }: any) { } + +try { /* ... */ } catch ({ ...rest }: any) { } + +``` + +# Diagnostics +``` +invalid.ts:3:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 1 │ // should generate diagnostics + 2 │ + > 3 │ try { /* ... */ } catch (unused: any) { } + │ ^^^^^^^^^^^^^ + 4 │ + 5 │ try { /* ... */ } catch (_unused: any) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 3 │ try·{·/*·...·*/·}·catch·(unused:·any)·{·} + │ -------------- + +``` + +``` +invalid.ts:5:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 3 │ try { /* ... */ } catch (unused: any) { } + 4 │ + > 5 │ try { /* ... */ } catch (_unused: any) { } + │ ^^^^^^^^^^^^^^ + 6 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 5 │ try·{·/*·...·*/·}·catch·(_unused:·any)·{·} + │ --------------- + +``` + +``` +invalid.ts:8:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + > 8 │ try { /* ... */ } catch ({ unused }: any) { } + │ ^^^^^^^^^^^^^^^^^ + 9 │ + 10 │ try { /* ... */ } catch ({ _unused }: any) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 8 │ try·{·/*·...·*/·}·catch·({·unused·}:·any)·{·} + │ ------------------ + +``` + +``` +invalid.ts:10:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 8 │ try { /* ... */ } catch ({ unused }: any) { } + 9 │ + > 10 │ try { /* ... */ } catch ({ _unused }: any) { } + │ ^^^^^^^^^^^^^^^^^^ + 11 │ + 12 │ try { /* ... */ } catch ({ unused, _unused }: any) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 10 │ try·{·/*·...·*/·}·catch·({·_unused·}:·any)·{·} + │ ------------------- + +``` + +``` +invalid.ts:12:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 10 │ try { /* ... */ } catch ({ _unused }: any) { } + 11 │ + > 12 │ try { /* ... */ } catch ({ unused, _unused }: any) { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 12 │ try·{·/*·...·*/·}·catch·({·unused,·_unused·}:·any)·{·} + │ --------------------------- + +``` + +``` +invalid.ts:15:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + > 15 │ try { /* ... */ } catch (/* leading inner */ unused: any /* trailing inner */) { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │ + 17 │ try { /* ... */ } catch /* leading outer */ (unused: any) /* trailing outer */ { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 15 │ try·{·/*·...·*/·}·catch·(/*·leading·inner·*/·unused:·any·/*·trailing·inner·*/)·{·} + │ ------------------------------------------------------- + +``` + +``` +invalid.ts:17:45 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 15 │ try { /* ... */ } catch (/* leading inner */ unused: any /* trailing inner */) { } + 16 │ + > 17 │ try { /* ... */ } catch /* leading outer */ (unused: any) /* trailing outer */ { } + │ ^^^^^^^^^^^^^ + 18 │ + 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 15 15 │ try { /* ... */ } catch (/* leading inner */ unused: any /* trailing inner */) { } + 16 16 │ + 17 │ - try·{·/*·...·*/·}·catch·/*·leading·outer·*/·(unused:·any)·/*·trailing·outer·*/·{·} + 17 │ + try·{·/*·...·*/·}·catch·/*·leading·outer·*/·/*·trailing·outer·*/·{·} + 18 18 │ + 19 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + + +``` + +``` +invalid.ts:19:45 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 17 │ try { /* ... */ } catch /* leading outer */ (unused: any) /* trailing outer */ { } + 18 │ + > 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 20 │ + 21 │ try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused: any /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 17 17 │ try { /* ... */ } catch /* leading outer */ (unused: any) /* trailing outer */ { } + 18 18 │ + 19 │ - try·{·/*·...·*/·}·catch·/*·leading·outer·*/·(/*·leading·inner·*/·unused:·any·/*·trailing·inner·*/)·/*·trailing·outer·*/·{·} + 19 │ + try·{·/*·...·*/·}·catch·/*·leading·outer·*/·/*·trailing·outer·*/·{·} + 20 20 │ + 21 21 │ try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused: any /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + + +``` + +``` +invalid.ts:21:45 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + 20 │ + > 21 │ try { /* ... */ } catch /* leading outer */ (/* leading inner 1 */ { /* leading inner 2 */ unused: any /* trailing inner 2 */ } /* trailing inner 1 */) /* trailing outer */ { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 22 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 19 19 │ try { /* ... */ } catch /* leading outer */ (/* leading inner */ unused: any /* trailing inner */) /* trailing outer */ { } + 20 20 │ + 21 │ - try·{·/*·...·*/·}·catch·/*·leading·outer·*/·(/*·leading·inner·1·*/·{·/*·leading·inner·2·*/·unused:·any·/*·trailing·inner·2·*/·}·/*·trailing·inner·1·*/)·/*·trailing·outer·*/·{·} + 21 │ + try·{·/*·...·*/·}·catch·/*·leading·outer·*/·/*·trailing·outer·*/·{·} + 22 22 │ + 23 23 │ + + +``` + +``` +invalid.ts:24:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + > 24 │ try { /* ... */ } catch ({ used: alias }: any) { } + │ ^^^^^^^^^^^^^^^^^^^^^^ + 25 │ + 26 │ try { /* ... */ } catch ({ nested: { unused } }: any) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 24 │ try·{·/*·...·*/·}·catch·({·used:·alias·}:·any)·{·} + │ ----------------------- + +``` + +``` +invalid.ts:26:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 24 │ try { /* ... */ } catch ({ used: alias }: any) { } + 25 │ + > 26 │ try { /* ... */ } catch ({ nested: { unused } }: any) { } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ + 28 │ try { /* ... */ } catch ({ ...rest }: any) { } + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 26 │ try·{·/*·...·*/·}·catch·({·nested:·{·unused·}·}:·any)·{·} + │ ------------------------------ + +``` + +``` +invalid.ts:28:25 lint/nursery/noUselessCatchBinding FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i This catch binding is unused. + + 26 │ try { /* ... */ } catch ({ nested: { unused } }: any) { } + 27 │ + > 28 │ try { /* ... */ } catch ({ ...rest }: any) { } + │ ^^^^^^^^^^^^^^^^^^ + 29 │ + + i Since ECMAScript 2019, catch bindings are optional; you can omit the catch binding if you don't need it. + + i Unsafe fix: Remove the catch binding. + + 28 │ try·{·/*·...·*/·}·catch·({·...rest·}:·any)·{·} + │ ------------------- + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.js new file mode 100644 index 000000000000..8509d0bef2ad --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.js @@ -0,0 +1,24 @@ +// should not generate diagnostics + +try { /* ... */ } catch { } + + +try { /* ... */ } catch (used) { console.error(used); } + +try { /* ... */ } catch ({ used }) { console.error(used); } + +try { /* ... */ } catch ({ used, unused }) { console.error(used); } + + +try { /* ... */ } catch (used) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used }) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used, unused }) { const log = () => console.error(used); log(); } + + +try { /* ... */ } catch ({ used: alias }) { console.error(alias); } + +try { /* ... */ } catch ({ nested: { unused } }) { console.error(unused); } + +try { /* ... */ } catch ({ ...rest }) { console.error(rest); } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.js.snap new file mode 100644 index 000000000000..aef2ee0619af --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.js.snap @@ -0,0 +1,33 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: valid.js +--- +# Input +```js +// should not generate diagnostics + +try { /* ... */ } catch { } + + +try { /* ... */ } catch (used) { console.error(used); } + +try { /* ... */ } catch ({ used }) { console.error(used); } + +try { /* ... */ } catch ({ used, unused }) { console.error(used); } + + +try { /* ... */ } catch (used) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used }) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used, unused }) { const log = () => console.error(used); log(); } + + +try { /* ... */ } catch ({ used: alias }) { console.error(alias); } + +try { /* ... */ } catch ({ nested: { unused } }) { console.error(unused); } + +try { /* ... */ } catch ({ ...rest }) { console.error(rest); } + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.ts b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.ts new file mode 100644 index 000000000000..fc478a855063 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.ts @@ -0,0 +1,24 @@ +// should not generate diagnostics + +try { /* ... */ } catch { } + + +try { /* ... */ } catch (used: any) { console.error(used); } + +try { /* ... */ } catch ({ used }: any) { console.error(used); } + +try { /* ... */ } catch ({ used, unused }: any) { console.error(used); } + + +try { /* ... */ } catch (used: any) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used }: any) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used, unused }: any) { const log = () => console.error(used); log(); } + + +try { /* ... */ } catch ({ used: alias }: any) { console.error(alias); } + +try { /* ... */ } catch ({ nested: { used } }: any) { console.error(used); } + +try { /* ... */ } catch ({ ...rest }: any) { console.error(rest); } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.ts.snap new file mode 100644 index 000000000000..bdddcc8aefcf --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUselessCatchBinding/valid.ts.snap @@ -0,0 +1,33 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 152 +expression: valid.ts +--- +# Input +```ts +// should not generate diagnostics + +try { /* ... */ } catch { } + + +try { /* ... */ } catch (used: any) { console.error(used); } + +try { /* ... */ } catch ({ used }: any) { console.error(used); } + +try { /* ... */ } catch ({ used, unused }: any) { console.error(used); } + + +try { /* ... */ } catch (used: any) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used }: any) { const log = () => console.error(used); log(); } + +try { /* ... */ } catch ({ used, unused }: any) { const log = () => console.error(used); log(); } + + +try { /* ... */ } catch ({ used: alias }: any) { console.error(alias); } + +try { /* ... */ } catch ({ nested: { used } }: any) { console.error(used); } + +try { /* ... */ } catch ({ ...rest }: any) { console.error(rest); } + +``` diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 0a34aaf83e6a..71147f85c345 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -206,6 +206,7 @@ pub mod no_unused_template_literal; pub mod no_unused_variables; pub mod no_unwanted_polyfillio; pub mod no_useless_catch; +pub mod no_useless_catch_binding; pub mod no_useless_constructor; pub mod no_useless_continue; pub mod no_useless_else; diff --git a/crates/biome_rule_options/src/no_useless_catch_binding.rs b/crates/biome_rule_options/src/no_useless_catch_binding.rs new file mode 100644 index 000000000000..0820c0f33e64 --- /dev/null +++ b/crates/biome_rule_options/src/no_useless_catch_binding.rs @@ -0,0 +1,8 @@ +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)] +/// Options for the `noUselessCatchBinding` rule. +/// Currently empty; reserved for future extensions (e.g. allowlist of names). +pub struct NoUselessCatchBindingOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 15bb9ec7a4ec..7cf2946fbd98 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1665,6 +1665,10 @@ export interface Nursery { * Warn when importing non-existing exports. */ noUnresolvedImports?: RuleConfiguration_for_NoUnresolvedImportsOptions; + /** + * Disallow unused catch bindings. + */ + noUselessCatchBinding?: RuleFixConfiguration_for_NoUselessCatchBindingOptions; /** * Disallow the use of useless undefined. */ @@ -2988,6 +2992,9 @@ export type RuleConfiguration_for_NoUnnecessaryConditionsOptions = export type RuleConfiguration_for_NoUnresolvedImportsOptions = | RulePlainConfiguration | RuleWithOptions_for_NoUnresolvedImportsOptions; +export type RuleFixConfiguration_for_NoUselessCatchBindingOptions = + | RulePlainConfiguration + | RuleWithFixOptions_for_NoUselessCatchBindingOptions; export type RuleFixConfiguration_for_NoUselessUndefinedOptions = | RulePlainConfiguration | RuleWithFixOptions_for_NoUselessUndefinedOptions; @@ -5420,6 +5427,20 @@ export interface RuleWithOptions_for_NoUnresolvedImportsOptions { */ options: NoUnresolvedImportsOptions; } +export interface RuleWithFixOptions_for_NoUselessCatchBindingOptions { + /** + * The kind of the code actions emitted by the rule + */ + fix?: FixKind; + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoUselessCatchBindingOptions; +} export interface RuleWithFixOptions_for_NoUselessUndefinedOptions { /** * The kind of the code actions emitted by the rule @@ -7985,6 +8006,10 @@ export interface NoSecretsOptions { export interface NoShadowOptions {} export interface NoUnnecessaryConditionsOptions {} export interface NoUnresolvedImportsOptions {} +/** + * Options for the `noUselessCatchBinding` rule. Currently empty; reserved for future extensions (e.g. allowlist of names). + */ +export interface NoUselessCatchBindingOptions {} export interface NoUselessUndefinedOptions {} export interface NoVueDataObjectDeclarationOptions {} export interface NoVueReservedKeysOptions {} @@ -8681,25 +8706,25 @@ export type Category = | "lint/correctness/noVoidElementsWithChildren" | "lint/correctness/noVoidTypeReturn" | "lint/correctness/useExhaustiveDependencies" + | "lint/correctness/useGraphqlNamedOperations" | "lint/correctness/useHookAtTopLevel" | "lint/correctness/useImportExtensions" | "lint/correctness/useIsNan" | "lint/correctness/useJsonImportAttributes" | "lint/correctness/useJsxKeyInIterable" - | "lint/correctness/useGraphqlNamedOperations" | "lint/correctness/useParseIntRadix" | "lint/correctness/useSingleJsDocAsterisk" | "lint/correctness/useUniqueElementIds" | "lint/correctness/useValidForDirection" | "lint/correctness/useValidTypeof" | "lint/correctness/useYield" - | "lint/nursery/noNextAsyncClientComponent" | "lint/nursery/noColorInvalidHex" | "lint/nursery/noFloatingPromises" | "lint/nursery/noImplicitCoercion" | "lint/nursery/noImportCycles" | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noMisusedPromises" + | "lint/nursery/noNextAsyncClientComponent" | "lint/nursery/noNonNullAssertedOptionalChain" | "lint/nursery/noQwikUseVisibleTask" | "lint/nursery/noSecrets" @@ -8708,6 +8733,7 @@ export type Category = | "lint/nursery/noUnresolvedImports" | "lint/nursery/noUnwantedPolyfillio" | "lint/nursery/noUselessBackrefInRegex" + | "lint/nursery/noUselessCatchBinding" | "lint/nursery/noUselessUndefined" | "lint/nursery/noVueDataObjectDeclaration" | "lint/nursery/noVueReservedKeys" @@ -8891,20 +8917,20 @@ export type Category = | "lint/suspicious/noUnknownAtRules" | "lint/suspicious/noUnsafeDeclarationMerging" | "lint/suspicious/noUnsafeNegation" - | "lint/suspicious/noUselessRegexBackrefs" | "lint/suspicious/noUselessEscapeInString" + | "lint/suspicious/noUselessRegexBackrefs" | "lint/suspicious/noVar" | "lint/suspicious/noWith" | "lint/suspicious/useAdjacentOverloadSignatures" | "lint/suspicious/useAwait" | "lint/suspicious/useBiomeIgnoreFolder" - | "lint/suspicious/useIterableCallbackReturn" | "lint/suspicious/useDefaultSwitchClauseLast" | "lint/suspicious/useErrorMessage" | "lint/suspicious/useGetterReturn" | "lint/suspicious/useGoogleFontDisplay" | "lint/suspicious/useGuardForIn" | "lint/suspicious/useIsArray" + | "lint/suspicious/useIterableCallbackReturn" | "lint/suspicious/useNamespaceKeyword" | "lint/suspicious/useNumberToFixedDigitsArgument" | "lint/suspicious/useStaticResponseMethods" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index b88197bcd22f..acd7e8f008c2 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -4605,6 +4605,17 @@ "type": "object", "additionalProperties": false }, + "NoUselessCatchBindingConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoUselessCatchBindingOptions" } + ] + }, + "NoUselessCatchBindingOptions": { + "description": "Options for the `noUselessCatchBinding` rule. Currently empty; reserved for future extensions (e.g. allowlist of names).", + "type": "object", + "additionalProperties": false + }, "NoUselessCatchConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -4972,6 +4983,13 @@ { "type": "null" } ] }, + "noUselessCatchBinding": { + "description": "Disallow unused catch bindings.", + "anyOf": [ + { "$ref": "#/definitions/NoUselessCatchBindingConfiguration" }, + { "type": "null" } + ] + }, "noUselessUndefined": { "description": "Disallow the use of useless undefined.", "anyOf": [ @@ -9068,6 +9086,25 @@ }, "additionalProperties": false }, + "RuleWithNoUselessCatchBindingOptions": { + "type": "object", + "required": ["level"], + "properties": { + "fix": { + "description": "The kind of the code actions emitted by the rule", + "anyOf": [{ "$ref": "#/definitions/FixKind" }, { "type": "null" }] + }, + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoUselessCatchBindingOptions" }] + } + }, + "additionalProperties": false + }, "RuleWithNoUselessCatchOptions": { "type": "object", "required": ["level"],