diff --git a/.changeset/nine-meals-agree.md b/.changeset/nine-meals-agree.md new file mode 100644 index 000000000000..1a1baedcfcca --- /dev/null +++ b/.changeset/nine-meals-agree.md @@ -0,0 +1,11 @@ +--- +'@biomejs/biome': patch +--- + +Added the new nursery rule [`useDestructuring`](https://biomejs.dev/linter/rules/use-destructuring). This rule helps to encourage destructuring from arrays and objects. + +For example, the following code triggers because the variable name `x` matches the property `foo.x`, making it ideal for object destructuring syntax. + +```js +var x = foo.x; +``` diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index 2b002d9a34b0..fd4d22d100c6 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -2269,6 +2269,18 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "prefer-destructuring" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .use_destructuring + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "prefer-exponentiation-operator" => { let group = rules.style.get_or_insert_with(Default::default); let rule = group diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index b045821446d2..0946a3320896 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -378,6 +378,7 @@ pub enum RuleName { UseDefaultSwitchClauseLast, UseDeprecatedDate, UseDeprecatedReason, + UseDestructuring, UseEnumInitializers, UseErrorMessage, UseExhaustiveDependencies, @@ -778,6 +779,7 @@ impl RuleName { Self::UseDefaultSwitchClauseLast => "useDefaultSwitchClauseLast", Self::UseDeprecatedDate => "useDeprecatedDate", Self::UseDeprecatedReason => "useDeprecatedReason", + Self::UseDestructuring => "useDestructuring", Self::UseEnumInitializers => "useEnumInitializers", Self::UseErrorMessage => "useErrorMessage", Self::UseExhaustiveDependencies => "useExhaustiveDependencies", @@ -1174,6 +1176,7 @@ impl RuleName { Self::UseDefaultSwitchClauseLast => RuleGroup::Suspicious, Self::UseDeprecatedDate => RuleGroup::Nursery, Self::UseDeprecatedReason => RuleGroup::Style, + Self::UseDestructuring => RuleGroup::Nursery, Self::UseEnumInitializers => RuleGroup::Style, Self::UseErrorMessage => RuleGroup::Suspicious, Self::UseExhaustiveDependencies => RuleGroup::Correctness, @@ -1579,6 +1582,7 @@ impl std::str::FromStr for RuleName { "useDefaultSwitchClauseLast" => Ok(Self::UseDefaultSwitchClauseLast), "useDeprecatedDate" => Ok(Self::UseDeprecatedDate), "useDeprecatedReason" => Ok(Self::UseDeprecatedReason), + "useDestructuring" => Ok(Self::UseDestructuring), "useEnumInitializers" => Ok(Self::UseEnumInitializers), "useErrorMessage" => Ok(Self::UseErrorMessage), "useExhaustiveDependencies" => Ok(Self::UseExhaustiveDependencies), @@ -4816,7 +4820,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 JSX prop spreading the same identifier multiple times.\nSee https://biomejs.dev/linter/rules/no-duplicated-spread-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicated_spread_props : Option < RuleConfiguration < biome_rule_options :: no_duplicated_spread_props :: NoDuplicatedSpreadPropsOptions >> , # [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 the use of === or !== for comparison with null.\nSee https://biomejs.dev/linter/rules/no-equals-to-null"] # [serde (skip_serializing_if = "Option::is_none")] pub no_equals_to_null : Option < RuleFixConfiguration < biome_rule_options :: no_equals_to_null :: NoEqualsToNullOptions >> , # [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 = "Disallow creating multiline strings by escaping newlines.\nSee https://biomejs.dev/linter/rules/no-multi-str"] # [serde (skip_serializing_if = "Option::is_none")] pub no_multi_str : Option < RuleConfiguration < biome_rule_options :: no_multi_str :: NoMultiStrOptions >> , # [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 = "Disallow the use of the __proto__ property.\nSee https://biomejs.dev/linter/rules/no-proto"] # [serde (skip_serializing_if = "Option::is_none")] pub no_proto : Option < RuleConfiguration < biome_rule_options :: no_proto :: NoProtoOptions >> , # [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 ternary operators.\nSee https://biomejs.dev/linter/rules/no-ternary"] # [serde (skip_serializing_if = "Option::is_none")] pub no_ternary : Option < RuleConfiguration < biome_rule_options :: no_ternary :: NoTernaryOptions >> , # [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 destructuring of props passed to setup in Vue projects.\nSee https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_setup_props_reactivity_loss : Option < RuleConfiguration < biome_rule_options :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLossOptions >> , # [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 that await is only used on Promise values.\nSee https://biomejs.dev/linter/rules/use-await-thenable"] # [serde (skip_serializing_if = "Option::is_none")] pub use_await_thenable : Option < RuleConfiguration < biome_rule_options :: use_await_thenable :: UseAwaitThenableOptions >> , # [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 RegExp#exec over String#match if no global flag is provided.\nSee https://biomejs.dev/linter/rules/use-regexp-exec"] # [serde (skip_serializing_if = "Option::is_none")] pub use_regexp_exec : Option < RuleConfiguration < biome_rule_options :: use_regexp_exec :: UseRegexpExecOptions >> , # [doc = "Enforce the presence of required scripts in package.json.\nSee https://biomejs.dev/linter/rules/use-required-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub use_required_scripts : Option < RuleConfiguration < biome_rule_options :: use_required_scripts :: UseRequiredScriptsOptions >> , # [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 >> , # [doc = "Enforce valid v-text Vue directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-text"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_text : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_text :: UseVueValidVTextOptions >> } +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 JSX prop spreading the same identifier multiple times.\nSee https://biomejs.dev/linter/rules/no-duplicated-spread-props"] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicated_spread_props : Option < RuleConfiguration < biome_rule_options :: no_duplicated_spread_props :: NoDuplicatedSpreadPropsOptions >> , # [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 the use of === or !== for comparison with null.\nSee https://biomejs.dev/linter/rules/no-equals-to-null"] # [serde (skip_serializing_if = "Option::is_none")] pub no_equals_to_null : Option < RuleFixConfiguration < biome_rule_options :: no_equals_to_null :: NoEqualsToNullOptions >> , # [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 = "Disallow creating multiline strings by escaping newlines.\nSee https://biomejs.dev/linter/rules/no-multi-str"] # [serde (skip_serializing_if = "Option::is_none")] pub no_multi_str : Option < RuleConfiguration < biome_rule_options :: no_multi_str :: NoMultiStrOptions >> , # [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 = "Disallow the use of the __proto__ property.\nSee https://biomejs.dev/linter/rules/no-proto"] # [serde (skip_serializing_if = "Option::is_none")] pub no_proto : Option < RuleConfiguration < biome_rule_options :: no_proto :: NoProtoOptions >> , # [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 ternary operators.\nSee https://biomejs.dev/linter/rules/no-ternary"] # [serde (skip_serializing_if = "Option::is_none")] pub no_ternary : Option < RuleConfiguration < biome_rule_options :: no_ternary :: NoTernaryOptions >> , # [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 destructuring of props passed to setup in Vue projects.\nSee https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss"] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_setup_props_reactivity_loss : Option < RuleConfiguration < biome_rule_options :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLossOptions >> , # [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 that await is only used on Promise values.\nSee https://biomejs.dev/linter/rules/use-await-thenable"] # [serde (skip_serializing_if = "Option::is_none")] pub use_await_thenable : Option < RuleConfiguration < biome_rule_options :: use_await_thenable :: UseAwaitThenableOptions >> , # [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 destructuring from arrays and/or objects.\nSee https://biomejs.dev/linter/rules/use-destructuring"] # [serde (skip_serializing_if = "Option::is_none")] pub use_destructuring : Option < RuleConfiguration < biome_rule_options :: use_destructuring :: UseDestructuringOptions >> , # [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 RegExp#exec over String#match if no global flag is provided.\nSee https://biomejs.dev/linter/rules/use-regexp-exec"] # [serde (skip_serializing_if = "Option::is_none")] pub use_regexp_exec : Option < RuleConfiguration < biome_rule_options :: use_regexp_exec :: UseRegexpExecOptions >> , # [doc = "Enforce the presence of required scripts in package.json.\nSee https://biomejs.dev/linter/rules/use-required-scripts"] # [serde (skip_serializing_if = "Option::is_none")] pub use_required_scripts : Option < RuleConfiguration < biome_rule_options :: use_required_scripts :: UseRequiredScriptsOptions >> , # [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 >> , # [doc = "Enforce valid v-text Vue directives.\nSee https://biomejs.dev/linter/rules/use-vue-valid-v-text"] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_text : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_text :: UseVueValidVTextOptions >> } impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ @@ -4858,6 +4862,7 @@ impl Nursery { "useConsistentArrowReturn", "useConsistentGraphqlDescriptions", "useDeprecatedDate", + "useDestructuring", "useExhaustiveSwitchCases", "useExplicitType", "useFind", @@ -4882,7 +4887,7 @@ impl Nursery { ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -4944,6 +4949,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), ]; } impl RuleGroupExt for Nursery { @@ -5145,111 +5151,116 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_destructuring.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } - 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[39])); } - if let Some(rule) = self.use_find.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[40])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.use_find.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } - 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[42])); } - 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[43])); } - if let Some(rule) = self.use_regexp_exec.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[44])); } - if let Some(rule) = self.use_required_scripts.as_ref() + if let Some(rule) = self.use_regexp_exec.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_required_scripts.as_ref() && rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } - 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[47])); } - 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[48])); } - 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[49])); } - 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[50])); } - 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[51])); } - 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[52])); } - 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[53])); } - 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[54])); } - 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[55])); } - 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[56])); } - 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[57])); } - if let Some(rule) = self.use_vue_valid_v_text.as_ref() + 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[58])); } + if let Some(rule) = self.use_vue_valid_v_text.as_ref() + && rule.is_enabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); + } index_set } fn get_disabled_rules(&self) -> FxHashSet> { @@ -5444,111 +5455,116 @@ impl RuleGroupExt for Nursery { { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } - if let Some(rule) = self.use_exhaustive_switch_cases.as_ref() + if let Some(rule) = self.use_destructuring.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } - 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[39])); } - if let Some(rule) = self.use_find.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[40])); } - if let Some(rule) = self.use_max_params.as_ref() + if let Some(rule) = self.use_find.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } - 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[42])); } - 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[43])); } - if let Some(rule) = self.use_regexp_exec.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[44])); } - if let Some(rule) = self.use_required_scripts.as_ref() + if let Some(rule) = self.use_regexp_exec.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } - if let Some(rule) = self.use_sorted_classes.as_ref() + if let Some(rule) = self.use_required_scripts.as_ref() && rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } - 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[47])); } - 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[48])); } - 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[49])); } - 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[50])); } - 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[51])); } - 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[52])); } - 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[53])); } - 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[54])); } - 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[55])); } - 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[56])); } - 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[57])); } - if let Some(rule) = self.use_vue_valid_v_text.as_ref() + 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[58])); } + if let Some(rule) = self.use_vue_valid_v_text.as_ref() + && rule.is_disabled() + { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -5731,6 +5747,10 @@ impl RuleGroupExt for Nursery { .use_deprecated_date .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "useDestructuring" => self + .use_destructuring + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "useExhaustiveSwitchCases" => self .use_exhaustive_switch_cases .as_ref() @@ -5861,6 +5881,7 @@ impl From for Nursery { use_consistent_arrow_return: Some(value.into()), use_consistent_graphql_descriptions: Some(value.into()), use_deprecated_date: Some(value.into()), + use_destructuring: Some(value.into()), use_exhaustive_switch_cases: Some(value.into()), use_explicit_type: Some(value.into()), use_find: Some(value.into()), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index f79989efaf08..654e2a62845f 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -210,6 +210,7 @@ define_categories! { "lint/nursery/useConsistentGraphqlDescriptions": "https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions", "lint/nursery/useConsistentObjectDefinition": "https://biomejs.dev/linter/rules/use-consistent-object-definition", "lint/nursery/useDeprecatedDate": "https://biomejs.dev/linter/rules/use-deprecated-date", + "lint/nursery/useDestructuring": "https://biomejs.dev/linter/rules/use-destructuring", "lint/nursery/useExhaustiveSwitchCases": "https://biomejs.dev/linter/rules/use-exhaustive-switch-cases", "lint/nursery/useExplicitFunctionReturnType": "https://biomejs.dev/linter/rules/use-explicit-type", "lint/nursery/useExplicitType": "https://biomejs.dev/linter/rules/use-explicit-type", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 0cbfc264b9f2..923891adad6d 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -37,6 +37,7 @@ pub mod no_vue_setup_props_reactivity_loss; pub mod use_array_sort_compare; pub mod use_await_thenable; pub mod use_consistent_arrow_return; +pub mod use_destructuring; pub mod use_exhaustive_switch_cases; pub mod use_explicit_type; pub mod use_find; @@ -48,4 +49,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_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , 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_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , 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 :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , 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_regexp_exec :: UseRegexpExec , 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_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , 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_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , 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 :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_destructuring :: UseDestructuring , 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_regexp_exec :: UseRegexpExec , 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/use_destructuring.rs b/crates/biome_js_analyze/src/lint/nursery/use_destructuring.rs new file mode 100644 index 000000000000..5c16fd8f5141 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/use_destructuring.rs @@ -0,0 +1,195 @@ +use biome_analyze::{ + Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, +}; +use biome_console::markup; +use biome_js_syntax::{ + AnyJsAssignment, AnyJsAssignmentPattern, AnyJsBinding, AnyJsBindingPattern, AnyJsExpression, + AnyJsLiteralExpression, AnyJsName, JsAssignmentExpression, JsAssignmentOperator, + JsVariableDeclaration, JsVariableDeclarator, +}; +use biome_rowan::{AstNode, declare_node_union}; +use biome_rule_options::use_destructuring::UseDestructuringOptions; + +declare_lint_rule! { + /// Require destructuring from arrays and/or objects + /// + /// With JavaScript ES6, a new syntax was added for creating variables from an array index or object property, + /// called destructuring. This rule enforces usage of destructuring instead of accessing a property through a member expression. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// var foo = array[0]; + /// ``` + /// + /// ```js,expect_diagnostic + /// var bar = foo.bar; + /// ``` + /// + /// + /// ### Valid + /// + /// ```js + /// var [foo] = array; + /// ``` + /// + /// ```js + /// var { bar } = foo; + /// ``` + /// + pub UseDestructuring { + version: "next", + name: "useDestructuring", + language: "js", + recommended: false, + sources: &[RuleSource::Eslint("prefer-destructuring").same()], + } +} + +impl Rule for UseDestructuring { + type Query = Ast; + type State = UseDestructuringState; + type Signals = Option; + type Options = UseDestructuringOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let query = ctx.query(); + + match query { + UseDestructuringQuery::JsAssignmentExpression(node) => { + let op = node.operator().ok()?; + if op != JsAssignmentOperator::Assign { + return None; + } + let left = node.left().ok()?; + let right = node.right().ok()?; + + if let AnyJsAssignmentPattern::AnyJsAssignment( + AnyJsAssignment::JsIdentifierAssignment(expr), + ) = left + { + let ident = expr.name_token().ok()?; + return should_suggest_destructuring(ident.text_trimmed(), &right); + } + + None + } + UseDestructuringQuery::JsVariableDeclarator(node) => { + let initializer = node.initializer()?; + let declaration = JsVariableDeclaration::cast(node.syntax().parent()?.parent()?)?; + let has_await_using = declaration.await_token().is_some(); + if declaration.kind().ok()?.text_trimmed() == "using" || has_await_using { + return None; + } + + let left = node.id().ok()?; + let right = initializer.expression().ok()?; + + if let AnyJsBindingPattern::AnyJsBinding(AnyJsBinding::JsIdentifierBinding(expr)) = + left + { + let ident = expr.name_token().ok()?; + return should_suggest_destructuring(ident.text_trimmed(), &right); + } + + None + } + } + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + match state { + UseDestructuringState::Array => { + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Use array destructuring instead of accessing array elements by index." + }, + ) + .note(markup! { + "Array destructuring is more readable and expressive than accessing individual elements by index." + }) + .note(markup! { + "Replace the array index access with array destructuring syntax." + }), + ) + } + UseDestructuringState::Object => { + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Use object destructuring instead of accessing object properties." + }, + ) + .note(markup! { + "Object destructuring is more readable and expressive than accessing individual properties." + }) + .note(markup! { + "Replace the property access with object destructuring syntax." + }), + ) + } + } + } +} + +declare_node_union! { + pub UseDestructuringQuery = JsVariableDeclarator | JsAssignmentExpression +} + +fn should_suggest_destructuring( + left: &str, + right: &AnyJsExpression, +) -> Option { + match right { + AnyJsExpression::JsComputedMemberExpression(expr) => { + if expr.is_optional_chain() { + return None; + } + + let member = expr.member().ok()?; + if let AnyJsExpression::AnyJsLiteralExpression(expr) = member { + if matches!(expr, AnyJsLiteralExpression::JsNumberLiteralExpression(_)) { + return Some(UseDestructuringState::Array); + } + + let value = expr.value_token().ok()?; + + if left == value.text_trimmed() { + return Some(UseDestructuringState::Object); + } + } + + None + } + AnyJsExpression::JsStaticMemberExpression(expr) => { + if matches!(expr.member().ok()?, AnyJsName::JsPrivateName(_)) + || matches!(expr.object().ok()?, AnyJsExpression::JsSuperExpression(_)) + { + return None; + } + + if expr.is_optional_chain() { + return None; + } + let member = expr.member().ok()?.value_token().ok()?; + if left == member.text_trimmed() { + return Some(UseDestructuringState::Object); + } + None + } + _ => None, + } +} + +pub enum UseDestructuringState { + Object, + Array, +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/invalid.js new file mode 100644 index 000000000000..ad13ecbd490f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/invalid.js @@ -0,0 +1,87 @@ +/* should generate diagnostics */ +{ + var foo = array[0]; +} +{ + foo = array[0]; +} +{ + var foo = object.foo; +} +{ + var foo = (a, b).foo; +} +{ + var length = (() => {}).length; +} +{ + var foo = (a = b).foo; +} +{ + var foo = (a || b).foo; +} +{ + var foo = f().foo; +} +{ + var foo = object.bar.foo; +} +{ + var foo = object['foo']; +} +{ + foo = object.foo; +} +{ + foo = object['foo']; +} +{ + class Foo extends Bar { + static foo() { + var bar = super.foo.bar; + } + } +} + +{ + var /* comment */ foo = object.foo; +} +{ + var a, + /* comment */ foo = object.foo; +} +{ + var foo /* comment */ = object.foo; +} +{ + var a, + foo /* comment */ = object.foo; +} +{ + var foo /* comment */ = object.foo, + a; +} +{ + var foo = object.foo; /* comment */ +} +{ + var foo = object.foo, + /* comment */ a; +} +{ + var foo = bar(/* comment */).foo; +} +{ + var foo = bar /* comment */.baz.foo; +} +{ + var foo = bar[baz].foo; +} +{ + var foo = object.foo /* comment */, + a; +} +{ + var foo = object.foo, + /* comment */ a; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/invalid.js.snap new file mode 100644 index 000000000000..e7c3b70171ca --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/invalid.js.snap @@ -0,0 +1,532 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +/* should generate diagnostics */ +{ + var foo = array[0]; +} +{ + foo = array[0]; +} +{ + var foo = object.foo; +} +{ + var foo = (a, b).foo; +} +{ + var length = (() => {}).length; +} +{ + var foo = (a = b).foo; +} +{ + var foo = (a || b).foo; +} +{ + var foo = f().foo; +} +{ + var foo = object.bar.foo; +} +{ + var foo = object['foo']; +} +{ + foo = object.foo; +} +{ + foo = object['foo']; +} +{ + class Foo extends Bar { + static foo() { + var bar = super.foo.bar; + } + } +} + +{ + var /* comment */ foo = object.foo; +} +{ + var a, + /* comment */ foo = object.foo; +} +{ + var foo /* comment */ = object.foo; +} +{ + var a, + foo /* comment */ = object.foo; +} +{ + var foo /* comment */ = object.foo, + a; +} +{ + var foo = object.foo; /* comment */ +} +{ + var foo = object.foo, + /* comment */ a; +} +{ + var foo = bar(/* comment */).foo; +} +{ + var foo = bar /* comment */.baz.foo; +} +{ + var foo = bar[baz].foo; +} +{ + var foo = object.foo /* comment */, + a; +} +{ + var foo = object.foo, + /* comment */ a; +} + +``` + +# Diagnostics +``` +invalid.js:3:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use array destructuring instead of accessing array elements by index. + + 1 │ /* should generate diagnostics */ + 2 │ { + > 3 │ var foo = array[0]; + │ ^^^^^^^^^^^^^^ + 4 │ } + 5 │ { + + i Array destructuring is more readable and expressive than accessing individual elements by index. + + i Replace the array index access with array destructuring syntax. + + +``` + +``` +invalid.js:6:2 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use array destructuring instead of accessing array elements by index. + + 4 │ } + 5 │ { + > 6 │ foo = array[0]; + │ ^^^^^^^^^^^^^^ + 7 │ } + 8 │ { + + i Array destructuring is more readable and expressive than accessing individual elements by index. + + i Replace the array index access with array destructuring syntax. + + +``` + +``` +invalid.js:9:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 7 │ } + 8 │ { + > 9 │ var foo = object.foo; + │ ^^^^^^^^^^^^^^^^ + 10 │ } + 11 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:12:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 10 │ } + 11 │ { + > 12 │ var foo = (a, b).foo; + │ ^^^^^^^^^^^^^^^^ + 13 │ } + 14 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:15:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 13 │ } + 14 │ { + > 15 │ var length = (() => {}).length; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │ } + 17 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:18:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 16 │ } + 17 │ { + > 18 │ var foo = (a = b).foo; + │ ^^^^^^^^^^^^^^^^^ + 19 │ } + 20 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:21:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 19 │ } + 20 │ { + > 21 │ var foo = (a || b).foo; + │ ^^^^^^^^^^^^^^^^^^ + 22 │ } + 23 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:24:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 22 │ } + 23 │ { + > 24 │ var foo = f().foo; + │ ^^^^^^^^^^^^^ + 25 │ } + 26 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:27:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 25 │ } + 26 │ { + > 27 │ var foo = object.bar.foo; + │ ^^^^^^^^^^^^^^^^^^^^ + 28 │ } + 29 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:33:2 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 31 │ } + 32 │ { + > 33 │ foo = object.foo; + │ ^^^^^^^^^^^^^^^^ + 34 │ } + 35 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:41:8 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 39 │ class Foo extends Bar { + 40 │ static foo() { + > 41 │ var bar = super.foo.bar; + │ ^^^^^^^^^^^^^^^^^^^ + 42 │ } + 43 │ } + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:47:20 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 46 │ { + > 47 │ var /* comment */ foo = object.foo; + │ ^^^^^^^^^^^^^^^^ + 48 │ } + 49 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:51:17 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 49 │ { + 50 │ var a, + > 51 │ /* comment */ foo = object.foo; + │ ^^^^^^^^^^^^^^^^ + 52 │ } + 53 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:54:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 52 │ } + 53 │ { + > 54 │ var foo /* comment */ = object.foo; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 55 │ } + 56 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:58:3 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 56 │ { + 57 │ var a, + > 58 │ foo /* comment */ = object.foo; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 59 │ } + 60 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:61:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 59 │ } + 60 │ { + > 61 │ var foo /* comment */ = object.foo, + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 62 │ a; + 63 │ } + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:65:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 63 │ } + 64 │ { + > 65 │ var foo = object.foo; /* comment */ + │ ^^^^^^^^^^^^^^^^ + 66 │ } + 67 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:68:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 66 │ } + 67 │ { + > 68 │ var foo = object.foo, + │ ^^^^^^^^^^^^^^^^ + 69 │ /* comment */ a; + 70 │ } + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:72:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 70 │ } + 71 │ { + > 72 │ var foo = bar(/* comment */).foo; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 73 │ } + 74 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:75:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 73 │ } + 74 │ { + > 75 │ var foo = bar /* comment */.baz.foo; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 76 │ } + 77 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:78:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 76 │ } + 77 │ { + > 78 │ var foo = bar[baz].foo; + │ ^^^^^^^^^^^^^^^^^^ + 79 │ } + 80 │ { + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:81:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 79 │ } + 80 │ { + > 81 │ var foo = object.foo /* comment */, + │ ^^^^^^^^^^^^^^^^ + 82 │ a; + 83 │ } + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` + +``` +invalid.js:85:6 lint/nursery/useDestructuring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Use object destructuring instead of accessing object properties. + + 83 │ } + 84 │ { + > 85 │ var foo = object.foo, + │ ^^^^^^^^^^^^^^^^ + 86 │ /* comment */ a; + 87 │ } + + i Object destructuring is more readable and expressive than accessing individual properties. + + i Replace the property access with object destructuring syntax. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/valid.js b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/valid.js new file mode 100644 index 000000000000..b15ebd1acf99 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/valid.js @@ -0,0 +1,114 @@ +/* should not generate diagnostics */ +{ + var [foo] = array; +} +{ + var { foo } = object; +} +{ + var foo; +} +{ + var foo = object.bar; +} +{ + ({ foo } = object); +} +{ + [foo] = array; +} +{ + foo += array[0]; +} +{ + foo += bar.foo; +} +{ + foo &&= array[0]; +} +{ + foo ||= bar.foo; +} +{ + foo ??= bar['foo']; +} +{ + class Foo extends Bar { + static foo() { + var foo = super.foo; + } + } +} +{ + foo = bar[foo]; +} +{ + var foo = bar[foo]; +} +{ + var { + foo: { bar } + } = object; +} +{ + var { bar } = object.foo; +} +{ + var foo = array?.[0]; +} +{ + var foo = object?.foo; +} +{ + class C { + #x; + foo() { + const x = this.#x; + } + } +} +{ + class C { + #x; + foo() { + x = this.#x; + } + } +} +{ + class C { + #x; + foo(a) { + x = a.#x; + } + } +} +{ + class C { + #x; + foo() { + const y = this.#x; + } + } +} +{ + class C { + #x; + foo(a) { + y = a.#x; + } + } +} + +{ + using foo = array[0]; +} +{ + using foo = object.foo; +} +{ + await using foo = array[0]; +} +{ + await using foo = object.foo; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/valid.js.snap new file mode 100644 index 000000000000..ef2586a62950 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDestructuring/valid.js.snap @@ -0,0 +1,122 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +/* should not generate diagnostics */ +{ + var [foo] = array; +} +{ + var { foo } = object; +} +{ + var foo; +} +{ + var foo = object.bar; +} +{ + ({ foo } = object); +} +{ + [foo] = array; +} +{ + foo += array[0]; +} +{ + foo += bar.foo; +} +{ + foo &&= array[0]; +} +{ + foo ||= bar.foo; +} +{ + foo ??= bar['foo']; +} +{ + class Foo extends Bar { + static foo() { + var foo = super.foo; + } + } +} +{ + foo = bar[foo]; +} +{ + var foo = bar[foo]; +} +{ + var { + foo: { bar } + } = object; +} +{ + var { bar } = object.foo; +} +{ + var foo = array?.[0]; +} +{ + var foo = object?.foo; +} +{ + class C { + #x; + foo() { + const x = this.#x; + } + } +} +{ + class C { + #x; + foo() { + x = this.#x; + } + } +} +{ + class C { + #x; + foo(a) { + x = a.#x; + } + } +} +{ + class C { + #x; + foo() { + const y = this.#x; + } + } +} +{ + class C { + #x; + foo(a) { + y = a.#x; + } + } +} + +{ + using foo = array[0]; +} +{ + using foo = object.foo; +} +{ + await using foo = array[0]; +} +{ + await using foo = object.foo; +} + +``` diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index fb03d420f4fb..b113636debf2 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -292,6 +292,7 @@ pub mod use_default_switch_clause; pub mod use_default_switch_clause_last; pub mod use_deprecated_date; pub mod use_deprecated_reason; +pub mod use_destructuring; pub mod use_enum_initializers; pub mod use_error_message; pub mod use_exhaustive_dependencies; diff --git a/crates/biome_rule_options/src/use_destructuring.rs b/crates/biome_rule_options/src/use_destructuring.rs new file mode 100644 index 000000000000..da89eb97441d --- /dev/null +++ b/crates/biome_rule_options/src/use_destructuring.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::{Deserializable, Merge}; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct UseDestructuringOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 555e1bf5be8c..2d8c4b7e13c0 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -2051,6 +2051,11 @@ See https://biomejs.dev/linter/rules/use-deprecated-date */ useDeprecatedDate?: UseDeprecatedDateConfiguration; /** + * Require destructuring from arrays and/or objects. +See https://biomejs.dev/linter/rules/use-destructuring + */ + useDestructuring?: UseDestructuringConfiguration; + /** * Require switch-case statements to be exhaustive. See https://biomejs.dev/linter/rules/use-exhaustive-switch-cases */ @@ -3693,6 +3698,9 @@ export type UseConsistentGraphqlDescriptionsConfiguration = export type UseDeprecatedDateConfiguration = | RulePlainConfiguration | RuleWithUseDeprecatedDateOptions; +export type UseDestructuringConfiguration = + | RulePlainConfiguration + | RuleWithUseDestructuringOptions; export type UseExhaustiveSwitchCasesConfiguration = | RulePlainConfiguration | RuleWithUseExhaustiveSwitchCasesOptions; @@ -5144,6 +5152,10 @@ export interface RuleWithUseDeprecatedDateOptions { level: RulePlainConfiguration; options?: UseDeprecatedDateOptions; } +export interface RuleWithUseDestructuringOptions { + level: RulePlainConfiguration; + options?: UseDestructuringOptions; +} export interface RuleWithUseExhaustiveSwitchCasesOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -6424,6 +6436,7 @@ export interface UseConsistentGraphqlDescriptionsOptions { export interface UseDeprecatedDateOptions { argumentName?: string; } +export type UseDestructuringOptions = {}; export type UseExhaustiveSwitchCasesOptions = {}; export type UseExplicitTypeOptions = {}; export type UseFindOptions = {}; @@ -7247,6 +7260,7 @@ export type Category = | "lint/nursery/useConsistentGraphqlDescriptions" | "lint/nursery/useConsistentObjectDefinition" | "lint/nursery/useDeprecatedDate" + | "lint/nursery/useDestructuring" | "lint/nursery/useExhaustiveSwitchCases" | "lint/nursery/useExplicitFunctionReturnType" | "lint/nursery/useExplicitType" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index c9b0dc8a1796..50bf83ef6ba1 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -5365,6 +5365,13 @@ { "type": "null" } ] }, + "useDestructuring": { + "description": "Require destructuring from arrays and/or objects.\nSee https://biomejs.dev/linter/rules/use-destructuring", + "anyOf": [ + { "$ref": "#/$defs/UseDestructuringConfiguration" }, + { "type": "null" } + ] + }, "useExhaustiveSwitchCases": { "description": "Require switch-case statements to be exhaustive.\nSee https://biomejs.dev/linter/rules/use-exhaustive-switch-cases", "anyOf": [ @@ -8753,6 +8760,15 @@ "additionalProperties": false, "required": ["level"] }, + "RuleWithUseDestructuringOptions": { + "type": "object", + "properties": { + "level": { "$ref": "#/$defs/RulePlainConfiguration" }, + "options": { "$ref": "#/$defs/UseDestructuringOptions" } + }, + "additionalProperties": false, + "required": ["level"] + }, "RuleWithUseEnumInitializersOptions": { "type": "object", "properties": { @@ -11602,6 +11618,16 @@ "type": "object", "additionalProperties": false }, + "UseDestructuringConfiguration": { + "oneOf": [ + { "$ref": "#/$defs/RulePlainConfiguration" }, + { "$ref": "#/$defs/RuleWithUseDestructuringOptions" } + ] + }, + "UseDestructuringOptions": { + "type": "object", + "additionalProperties": false + }, "UseEnumInitializersConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" },