From deed3d851b2c6493b48f5948a5811926e631def4 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 11 Feb 2026 11:43:24 +0000 Subject: [PATCH] fix(transformer): remove unnecessary trailing expression in object rest spread assignment (#19259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Skip the trailing reference (e.g. `_c`) in the generated sequence expression when the destructuring assignment is directly inside an `ExpressionStatement`, since the expression result is not consumed - Before: `_c = c2, {a2} = _c, b2 = objectWithoutProperties(_c, ["a2"]), _c;` - After: `_c = c2, {a2} = _c, b2 = objectWithoutProperties(_c, ["a2"]);` - The trailing reference is still kept when the result is consumed, e.g. `console.log((..., _c))` Closes #7389 (issue 1) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../src/es2018/object_rest_spread.rs | 13 ++- .../assignment-expression/output.js | 2 +- .../object-rest/impure-computed/output.js | 2 +- .../output.js | 2 +- .../snapshots/babel.snap.md | 102 +----------------- .../assignment-expression/output.js | 2 +- 6 files changed, 16 insertions(+), 107 deletions(-) diff --git a/crates/oxc_transformer/src/es2018/object_rest_spread.rs b/crates/oxc_transformer/src/es2018/object_rest_spread.rs index 10697bdd0ac55..2df3ae52b1fff 100644 --- a/crates/oxc_transformer/src/es2018/object_rest_spread.rs +++ b/crates/oxc_transformer/src/es2018/object_rest_spread.rs @@ -275,10 +275,15 @@ impl<'a> ObjectRestSpread<'a> { } } - // Insert final read `_foo`. - // TODO: remove this if the assignment is not a read reference. - // e.g. remove for `({ a2, ...b2 } = c2)`, keep `(x, ({ a2, ...b2 } = c2)`. - expressions.push(reference_builder.create_read_expression(ctx)); + // Insert final read `_foo` only if the expression result is consumed. + // e.g. remove for `({ a2, ...b2 } = c2)`, keep for `(x, ({ a2, ...b2 } = c2))`. + let is_expression_statement = ctx + .ancestors() + .find(|a| !matches!(a, Ancestor::ParenthesizedExpressionExpression(_))) + .is_some_and(|a| matches!(a, Ancestor::ExpressionStatementExpression(_))); + if !is_expression_statement { + expressions.push(reference_builder.create_read_expression(ctx)); + } *expr = ctx.ast.expression_sequence(assign_expr.span, expressions); } diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js b/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js index 30fc5987b448d..24944a2677f99 100644 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js +++ b/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js @@ -1,5 +1,5 @@ ({a1} = c1); var _c; -_c = c2, {a2} = _c, b2 = babelHelpers.objectWithoutProperties(_c, ["a2"]), _c; +_c = c2, {a2} = _c, b2 = babelHelpers.objectWithoutProperties(_c, ["a2"]); var _c2; console.log((_c2 = c3, {a3} = _c2, b3 = babelHelpers.objectWithoutProperties(_c2, ["a3"]), _c2)); diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/impure-computed/output.js b/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/impure-computed/output.js index 811b126a948e6..af5a6ce1ab67d 100644 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/impure-computed/output.js +++ b/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/impure-computed/output.js @@ -19,7 +19,7 @@ var _$z; _$z = { 2: "two", z: "zee" -}, {[key]: y, z} = _$z, x = babelHelpers.objectWithoutProperties(_$z, [key, "z"].map(babelHelpers.toPropertyKey)), _$z; +}, {[key]: y, z} = _$z, x = babelHelpers.objectWithoutProperties(_$z, [key, "z"].map(babelHelpers.toPropertyKey)); expect(y).toBe("two"); expect(x).toEqual({}); expect(z).toBe("zee"); diff --git a/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/template-literal-allLiterals-true-no-hoisting/output.js b/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/template-literal-allLiterals-true-no-hoisting/output.js index 720148c953cb9..ef44560207407 100644 --- a/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/template-literal-allLiterals-true-no-hoisting/output.js +++ b/tasks/transform_conformance/overrides/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/template-literal-allLiterals-true-no-hoisting/output.js @@ -2,5 +2,5 @@ const example = () => { const input = {}; const foo = "foo"; var _input, _ref = `${foo}_bar`; - _input = input, {[_ref]: country} = _input, rest = babelHelpers.objectWithoutProperties(_input, [_ref].map(babelHelpers.toPropertyKey)), _input; + _input = input, {[_ref]: country} = _input, rest = babelHelpers.objectWithoutProperties(_input, [_ref].map(babelHelpers.toPropertyKey)); }; diff --git a/tasks/transform_conformance/snapshots/babel.snap.md b/tasks/transform_conformance/snapshots/babel.snap.md index ba3a7990c1da6..e9c6c05b453bc 100644 --- a/tasks/transform_conformance/snapshots/babel.snap.md +++ b/tasks/transform_conformance/snapshots/babel.snap.md @@ -968,63 +968,7 @@ x Output mismatch # babel-plugin-transform-object-rest-spread (27/40) * object-rest/for-x/input.js -Bindings mismatch: -after transform: ScopeId(0): ["_excluded", "_excluded2", "_excluded3", "_ref", "a", "b"] -rebuilt : ScopeId(0): ["_excluded", "_excluded2", "_excluded3", "_ref", "_ref2", "_ref3", "a"] -Bindings mismatch: -after transform: ScopeId(2): [] -rebuilt : ScopeId(2): ["a", "b"] -Bindings mismatch: -after transform: ScopeId(3): ["_ref2"] -rebuilt : ScopeId(3): [] -Bindings mismatch: -after transform: ScopeId(4): ["_ref3"] -rebuilt : ScopeId(4): [] -Bindings mismatch: -after transform: ScopeId(5): [] -rebuilt : ScopeId(5): ["_ref4", "_ref5"] -Bindings mismatch: -after transform: ScopeId(6): ["_ref4"] -rebuilt : ScopeId(6): [] -Bindings mismatch: -after transform: ScopeId(7): ["_ref5"] -rebuilt : ScopeId(7): [] -Symbol flags mismatch for "a": -after transform: SymbolId(0): SymbolFlags(BlockScopedVariable) -rebuilt : SymbolId(8): SymbolFlags(Function) -Symbol span mismatch for "a": -after transform: SymbolId(0): Span { start: 27, end: 28 } -rebuilt : SymbolId(8): Span { start: 86, end: 87 } -Symbol scope ID mismatch for "a": -after transform: SymbolId(0): ScopeId(2) -rebuilt : SymbolId(8): ScopeId(0) -Symbol redeclarations mismatch for "a": -after transform: SymbolId(0): [Span { start: 27, end: 28 }, Span { start: 86, end: 87 }, Span { start: 189, end: 190 }, Span { start: 274, end: 275 }] -rebuilt : SymbolId(8): [Span { start: 86, end: 87 }, Span { start: 189, end: 190 }, Span { start: 274, end: 275 }] -Symbol reference IDs mismatch for "b": -after transform: SymbolId(1): [ReferenceId(1), ReferenceId(3)] -rebuilt : SymbolId(5): [] -Symbol scope ID mismatch for "_ref2": -after transform: SymbolId(4): ScopeId(3) -rebuilt : SymbolId(6): ScopeId(0) -Symbol scope ID mismatch for "_ref3": -after transform: SymbolId(5): ScopeId(4) -rebuilt : SymbolId(7): ScopeId(0) -Symbol scope ID mismatch for "_ref4": -after transform: SymbolId(7): ScopeId(6) -rebuilt : SymbolId(9): ScopeId(5) -Symbol scope ID mismatch for "_ref5": -after transform: SymbolId(8): ScopeId(7) -rebuilt : SymbolId(10): ScopeId(5) -Reference symbol mismatch for "b": -after transform: SymbolId(1) "b" -rebuilt : -Reference symbol mismatch for "b": -after transform: SymbolId(1) "b" -rebuilt : -Unresolved references mismatch: -after transform: ["babelHelpers"] -rebuilt : ["b", "babelHelpers"] +x Output mismatch * object-rest/for-x-array-pattern/input.js x Output mismatch @@ -1036,21 +980,7 @@ x Output mismatch x Output mismatch * object-rest/for-x-completion-record/input.js -Bindings mismatch: -after transform: ScopeId(0): ["_excluded"] -rebuilt : ScopeId(0): ["_excluded", "_ref", "_ref2"] -Bindings mismatch: -after transform: ScopeId(1): ["_ref"] -rebuilt : ScopeId(1): [] -Bindings mismatch: -after transform: ScopeId(2): ["_ref2"] -rebuilt : ScopeId(2): [] -Symbol scope ID mismatch for "_ref": -after transform: SymbolId(0): ScopeId(1) -rebuilt : SymbolId(1): ScopeId(0) -Symbol scope ID mismatch for "_ref2": -after transform: SymbolId(1): ScopeId(2) -rebuilt : SymbolId(2): ScopeId(0) +x Output mismatch * object-rest/for-x-declaration-shadowed-block-scoped-bindings/input.js x Output mismatch @@ -1146,33 +1076,7 @@ after transform: ["b", "babelHelpers", "f", "q"] rebuilt : ["R", "Y", "b", "babelHelpers", "f", "q"] * object-rest/symbol/input.js -Bindings mismatch: -after transform: ScopeId(0): ["_Symbol$for", "_Symbol$for2", "_Symbol$for3", "_ref", "_ref2", "_ref3", "foo", "rest"] -rebuilt : ScopeId(0): ["_Symbol$for", "_Symbol$for2", "_ref", "_ref2", "foo", "rest"] -Reference symbol mismatch for "_ref3": -after transform: SymbolId(6) "_ref3" -rebuilt : -Reference symbol mismatch for "_Symbol$for3": -after transform: SymbolId(7) "_Symbol$for3" -rebuilt : -Reference symbol mismatch for "_ref3": -after transform: SymbolId(6) "_ref3" -rebuilt : -Reference symbol mismatch for "_ref3": -after transform: SymbolId(6) "_ref3" -rebuilt : -Reference symbol mismatch for "_Symbol$for3": -after transform: SymbolId(7) "_Symbol$for3" -rebuilt : -Reference symbol mismatch for "_ref3": -after transform: SymbolId(6) "_ref3" -rebuilt : -Unresolved references mismatch: -after transform: ["Symbol", "babelHelpers"] -rebuilt : ["Symbol", "_Symbol$for3", "_ref3", "babelHelpers"] -Unresolved reference IDs mismatch for "Symbol": -after transform: [ReferenceId(0), ReferenceId(1), ReferenceId(4)] -rebuilt : [ReferenceId(0), ReferenceId(7)] +x Output mismatch * regression/gh-17274/input.js x Output mismatch diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js index 067f7c7c93973..99525047c8b53 100644 --- a/tasks/transform_conformance/tests/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js +++ b/tasks/transform_conformance/tests/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/output.js @@ -1,6 +1,6 @@ ({a1} = c1); var _c; -_c = c2, {a2} = _c, b2 = babelHelpers.objectWithoutProperties(_c, ["a2"]), _c; +_c = c2, {a2} = _c, b2 = babelHelpers.objectWithoutProperties(_c, ["a2"]); var _c2; a0, _c2 = c2, {a2} = _c2, b2 = babelHelpers.objectWithoutProperties(_c2, ["a2"]), _c2; var _c3;