Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/nine-meals-agree.md
Original file line number Diff line number Diff line change
@@ -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;
```
12 changes: 12 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 65 additions & 44 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion crates/biome_js_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 ,] } }
195 changes: 195 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery/use_destructuring.rs
Original file line number Diff line number Diff line change
@@ -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<UseDestructuringQuery>;
type State = UseDestructuringState;
type Signals = Option<Self::State>;
type Options = UseDestructuringOptions;

fn run(ctx: &RuleContext<Self>) -> 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<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
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<UseDestructuringState> {
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);
}
}
Comment on lines +158 to +168
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's read the file to understand the full context
wc -l crates/biome_js_analyze/src/lint/nursery/use_destructuring.rs

Repository: biomejs/biome

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Read the specific section mentioned in the review
sed -n '140,180p' crates/biome_js_analyze/src/lint/nursery/use_destructuring.rs

Repository: biomejs/biome

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for how value_token() is used for string literals in the codebase
rg -n "value_token\(\)" --type rust -B 2 -A 2 crates/biome_js_analyze/

Repository: biomejs/biome

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Check if inner_string_text exists in the codebase
rg -n "inner_string_text" --type rust

Repository: biomejs/biome

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Look for JsStringLiteralExpression and its methods in the syntax crate
rg -n "impl.*JsStringLiteralExpression" --type rust -A 20

Repository: biomejs/biome

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Check how string literals are typically handled in similar contexts
rg -n "JsStringLiteralExpression" --type rust -B 2 -A 2 crates/biome_js_analyze/ | head -50

Repository: biomejs/biome

Length of output: 170


String literal property access won't trigger object destructuring due to quote mismatch in comparison.

For var x = foo["x"], the code calls value_token().text_trimmed() on a string literal token, which returns the raw token including quotes (e.g., "x"), not the unquoted value x. Since left is the unquoted variable name, the comparison left == value.text_trimmed() will never match for string literals, breaking this destructuring suggestion pattern.

Use inner_string_text() on JsStringLiteralExpression to extract the unquoted string value:

+                if let AnyJsLiteralExpression::JsStringLiteralExpression(str_expr) = expr {
+                    let value = str_expr.inner_string_text().ok()?;
+                    if left == value.text() {
+                        return Some(UseDestructuringState::Object);
+                    }
+                }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In crates/biome_js_analyze/src/lint/nursery/use_destructuring.rs around lines
158-168, the comparison uses value_token().text_trimmed() which for string
literals includes quotes so it never equals the unquoted `left`; update the
match to handle AnyJsLiteralExpression::JsStringLiteralExpression(expr), call
expr.inner_string_text() (propagate ?/ok as appropriate) to obtain the unquoted
string contents, and compare that unquoted value to `left` so property access
like foo["x"] correctly yields UseDestructuringState::Object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you implying that, we should handle checks for dynamic key ?

var x = foo[x]

??


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,
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading