diff --git a/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js b/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js index b71455030685..80186d638761 100644 --- a/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js +++ b/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js @@ -1 +1,6 @@ //// [objectSpreadSetonlyAccessor.ts] +({ + ...{ + set foo (_v){} + } +}); diff --git a/crates/swc_atoms/words.txt b/crates/swc_atoms/words.txt index 07b7a5fe01b0..dcdf635a8ace 100644 --- a/crates/swc_atoms/words.txt +++ b/crates/swc_atoms/words.txt @@ -1369,3 +1369,4 @@ ychannelselector yield zoomAndPan zoomandpan +__proto__ \ No newline at end of file diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index 8f3cd3d4a68a..a140edf0915f 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -1918,15 +1918,23 @@ where #[cfg(feature = "debug")] let start = dump(&n.expr, true); - let expr = self.ignore_return_value(&mut n.expr); - n.expr = expr.map(Box::new).unwrap_or_else(|| { - report_change!("visit_mut_expr_stmt: Dropped an expression statement"); - - #[cfg(feature = "debug")] - dump_change_detail!("Removed {}", start); - - undefined(DUMMY_SP) - }); + // Fix https://github.com/swc-project/swc/issues/6422 + let is_object_lit_with_spread = n + .expr + .as_object() + .map(|object_lit| object_lit.props.iter().any(|prop| prop.is_spread())) + .unwrap_or(false); + + if !is_object_lit_with_spread { + let expr = self.ignore_return_value(&mut n.expr); + n.expr = expr.map(Box::new).unwrap_or_else(|| { + report_change!("visit_mut_expr_stmt: Dropped an expression statement"); + #[cfg(feature = "debug")] + dump_change_detail!("Removed {}", start); + + undefined(DUMMY_SP) + }); + } } else { match &mut *n.expr { Expr::Seq(e) => { diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/config.json new file mode 100644 index 000000000000..480beee76639 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/config.json @@ -0,0 +1,5 @@ +{ + "collapse_vars": true, + "unused": true, + "toplevel": true +} \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/input.js new file mode 100644 index 000000000000..84de0d303429 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/input.js @@ -0,0 +1,21 @@ +let getter_effect = 'FAIL'; +let setter_effect = 'FAIL'; +let proto = { + get foo() { + getter_effect = 'PASS'; + }, + set bar(value) { + setter_effect = 'PASS'; + } +}; +let obj1 = { + __proto__: proto +}; +let obj2 = { + __proto__: proto +}; +let unused = obj1.foo; +obj2.bar = 0; + +assert.strictEqual(getter_effect, 'PASS'); +assert.strictEqual(setter_effect, 'PASS'); \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/output.js new file mode 100644 index 000000000000..e3d96e8a2cbc --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/output.js @@ -0,0 +1,18 @@ +let getter_effect = 'FAIL'; +let setter_effect = 'FAIL'; +let proto = { + get foo () { + getter_effect = 'PASS'; + }, + set bar (value){ + setter_effect = 'PASS'; + } +}; +({ + __proto__: proto +}).foo; +({ + __proto__: proto +}).bar = 0; +assert.strictEqual(getter_effect, 'PASS'); +assert.strictEqual(setter_effect, 'PASS'); diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/config.json new file mode 100644 index 000000000000..8e1ba5e679a7 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/config.json @@ -0,0 +1,4 @@ +{ + "toplevel": true, + "unused": true +} \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/input.js new file mode 100644 index 000000000000..373eacbebfc1 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/input.js @@ -0,0 +1,10 @@ +import assert from 'assert' +let result = 'FAIL'; +const unused = { + ...{ + get prop() { + result = 'PASS'; + } + } +}; +assert.strictEqual(result, 'PASS'); \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/output.js new file mode 100644 index 000000000000..d899d5cc5b61 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/output.js @@ -0,0 +1,10 @@ +import assert from 'assert'; +let result = 'FAIL'; +({ + ...{ + get prop () { + result = 'PASS'; + } + } +}); +assert.strictEqual(result, 'PASS'); diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/config.json new file mode 100644 index 000000000000..8e1ba5e679a7 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/config.json @@ -0,0 +1,4 @@ +{ + "toplevel": true, + "unused": true +} \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/input.js new file mode 100644 index 000000000000..da075da2c7d7 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/input.js @@ -0,0 +1,18 @@ +import assert from 'assert' +let result = 0; +const unused = { + ...{ + get prop() { + result = 1; + } + }, + [assert.strictEqual(result, 1)]: null, + [result = 2]: null, + [assert.strictEqual(result, 2)]: null, + ...{ + get prop() { + result = 3; + } + } +}; +assert.strictEqual(result, 3); \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/output.js new file mode 100644 index 000000000000..eaeef3af06c2 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/output.js @@ -0,0 +1,18 @@ +import assert from 'assert'; +let result = 0; +({ + ...{ + get prop () { + result = 1; + } + }, + [assert.strictEqual(result, 1)]: null, + [result = 2]: null, + [assert.strictEqual(result, 2)]: null, + ...{ + get prop () { + result = 3; + } + } +}); +assert.strictEqual(result, 3); diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index f2aa790f538a..c11d36ebb596 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -1375,18 +1375,33 @@ pub trait ExprExt { } } Expr::Object(obj) => { - let is_static_accessor = |prop: &PropOrSpread| { - if let PropOrSpread::Prop(prop) = prop { - if let Prop::Getter(_) | Prop::Setter(_) = &**prop { - true - } else { - false - } - } else { - false - } + let can_have_side_effect = |prop: &PropOrSpread| match prop { + PropOrSpread::Spread(_) => true, + PropOrSpread::Prop(prop) => match prop.as_ref() { + Prop::Getter(_) + | Prop::Setter(_) + | Prop::Method(_) + | Prop::Shorthand(Ident { + sym: js_word!("__proto__"), + .. + }) + | Prop::KeyValue(KeyValueProp { + key: + PropName::Ident(Ident { + sym: js_word!("__proto__"), + .. + }) + | PropName::Str(Str { + value: js_word!("__proto__"), + .. + }) + | PropName::Computed(_), + .. + }) => true, + _ => false, + }, }; - if obj.props.iter().any(is_static_accessor) { + if obj.props.iter().any(can_have_side_effect) { return true; } }