From e92607671337d449e37dc8a123dd4228a6105a08 Mon Sep 17 00:00:00 2001 From: levi Date: Fri, 15 Mar 2024 21:37:33 +1300 Subject: [PATCH 01/80] Initial commit: string literals --- .../src/simplify/expr/mod.rs | 8 ++++++++ .../src/simplify/expr/tests.rs | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index ed33657634e4..356f77ed21fd 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -164,6 +164,14 @@ impl SimplifyExpr { *expr = *Expr::undefined(*span) } else if let Some(value) = nth_char(value, idx as _) { self.changed = true; + KnownOp::Index(idx) => { + self.changed = true; + + if idx < 0 || idx as usize >= value.len() { + *expr = *undefined(*span) + } else { + let value = nth_char(value, idx as _); + *expr = Expr::Lit(Lit::Str(Str { raw: None, value: value.into(), diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 9b4b388a6c01..50153fe62e92 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1575,3 +1575,8 @@ fn test_export_default_paren_expr() { fold_same("import fn from './b'; export default (function fn1 () {});"); fold("export default ((foo));", "export default foo;"); } + +#[test] +fn test_issue8747() { + fold("''[0]", "void 0"); +} From e7122e1cf6bcd5448a167470d4e820d2ab5bc34a Mon Sep 17 00:00:00 2001 From: levi Date: Fri, 15 Mar 2024 21:48:37 +1300 Subject: [PATCH 02/80] WIP: array literals --- .../src/simplify/expr/mod.rs | 6 ++++++ .../src/simplify/expr/tests.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 356f77ed21fd..9e50124a9c28 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -213,6 +213,12 @@ impl SimplifyExpr { _ => unreachable!(), }; + if idx < 0 || idx as usize >= elems.len() { + self.changed = true; + *expr = *undefined(*span); + return; + } + if elems.len() > idx as _ && idx >= 0 { let after_has_side_effect = elems.iter().skip((idx + 1) as _).any(|elem| match elem { diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 50153fe62e92..b8fe156df5a8 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1579,4 +1579,5 @@ fn test_export_default_paren_expr() { #[test] fn test_issue8747() { fold("''[0]", "void 0"); + fold("[][0]", "void 0"); } From aaecddace62f1ddc78e021cfb9d88b1280bfa8c3 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 16 Mar 2024 20:00:39 +1300 Subject: [PATCH 03/80] Correctly handle array indexing --- .../src/simplify/expr/mod.rs | 62 +++++++++++-------- .../src/simplify/expr/tests.rs | 2 + 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 9e50124a9c28..c69f8c19b8d2 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -107,12 +107,21 @@ impl SimplifyExpr { return; } - #[derive(Clone, PartialEq, Eq)] + #[derive(Clone, PartialEq)] enum KnownOp { /// [a, b].length Len, - Index(i64), + // [a, b][0] + // + // ({0.5: 'test'})[0.5] + // + /// Note: callers need to check `v.fract() == 0.0` in some cases. + /// ie non-integer indexes for arrays always result in `undefined` but + /// not for objects (because indexing an object returns the value of the + /// key, ie `0.5` will not return `undefined` if a key `0.5` exists + /// and its value is not `undefined`). + Index(f64), /// ({}).foo IndexStr(JsWord), @@ -127,19 +136,16 @@ impl SimplifyExpr { } } MemberProp::Computed(ComputedPropName { expr, .. }) => { - if !self.in_callee { - if let Expr::Lit(Lit::Num(Number { value, .. })) = &**expr { - if value.fract() == 0.0 { - KnownOp::Index(*value as _) - } else { - return; - } - } else { - return; - } - } else { + if self.in_callee { return; } + + // JavaScript cast index expression to a number + let Known(index) = expr.as_pure_number(&self.expr_ctx) else { + return; + }; + + KnownOp::Index(index) } _ => return, }; @@ -166,8 +172,8 @@ impl SimplifyExpr { self.changed = true; KnownOp::Index(idx) => { self.changed = true; - - if idx < 0 || idx as usize >= value.len() { + + if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { *expr = *undefined(*span) } else { let value = nth_char(value, idx as _); @@ -183,6 +189,8 @@ impl SimplifyExpr { }, // [1, 2, 3].length + // + // [1, 2, 3][0] Expr::Array(ArrayLit { elems, span }) => { // do nothing if spread exists let has_spread = elems.iter().any(|elem| { @@ -213,23 +221,25 @@ impl SimplifyExpr { _ => unreachable!(), }; - if idx < 0 || idx as usize >= elems.len() { + // If the fraction part is non-zero, or if the index is out of bounds, + // then the result is always undefined. + if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() { self.changed = true; *expr = *undefined(*span); return; } + // idx is treated as an integer from this point. + // + // We also know for certain the index is not out of bounds. + let idx = idx as i64; - if elems.len() > idx as _ && idx >= 0 { - let after_has_side_effect = - elems.iter().skip((idx + 1) as _).any(|elem| match elem { - Some(elem) => elem.expr.may_have_side_effects(&self.expr_ctx), - None => false, - }); + let after_has_side_effect = + elems.iter().skip((idx + 1) as _).any(|elem| match elem { + Some(elem) => elem.expr.may_have_side_effects(&self.expr_ctx), + None => false, + }); - if after_has_side_effect { - return; - } - } else { + if after_has_side_effect { return; } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index b8fe156df5a8..92e75da64b58 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1580,4 +1580,6 @@ fn test_export_default_paren_expr() { fn test_issue8747() { fold("''[0]", "void 0"); fold("[][0]", "void 0"); + fold("[][[]]", "void 0"); + fold("[1][0.5]", "void 0"); } From 6c990d124d3c9b95d0caacc21edc3c1856288276 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 17 Mar 2024 14:31:04 +1300 Subject: [PATCH 04/80] Update unit test --- .../src/simplify/expr/tests.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 92e75da64b58..905827ae3d7e 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1578,8 +1578,21 @@ fn test_export_default_paren_expr() { #[test] fn test_issue8747() { + // Indexing a string with an out-of-bounds index returns undefined. fold("''[0]", "void 0"); + // Indexing a string with a non-integer index returns undefined. + fold("'a'[0.5]", "void 0"); + // Index with an expression. + fold("''[[]]", "void 0"); + fold("'a'[[]]", "void 0"); + + // Indexing an array has the same logic as indexing a string. fold("[][0]", "void 0"); - fold("[][[]]", "void 0"); fold("[1][0.5]", "void 0"); + fold("[][[]]", "void 0"); + fold("[1][[]]", "void 0"); + + // Indexing objects + fold("({0.5: 'a'})[0.5]", "0.5"); + fold_same("({0.5: 'a', 1: fn()})[0.5]"); } From 054420863bcdaadead023e0c436bea3bc35b193a Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 17 Mar 2024 15:05:47 +1300 Subject: [PATCH 05/80] Fix broken index logic --- .../src/simplify/expr/mod.rs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index c69f8c19b8d2..1e53e1042047 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -139,13 +139,16 @@ impl SimplifyExpr { if self.in_callee { return; } - - // JavaScript cast index expression to a number - let Known(index) = expr.as_pure_number(&self.expr_ctx) else { + + if let Expr::Lit(Lit::Num(Number { value, .. })) = &**expr { + // x[5] + KnownOp::Index(*value) + } else if let Known(s) = expr.as_pure_string(&self.expr_ctx) { + // x[''] or x[...] where ... is an expression like [], ie x[[]] + KnownOp::IndexStr(JsWord::from(s)) + } else { return; - }; - - KnownOp::Index(index) + } } _ => return, }; @@ -172,7 +175,7 @@ impl SimplifyExpr { self.changed = true; KnownOp::Index(idx) => { self.changed = true; - + if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { *expr = *undefined(*span) } else { @@ -184,13 +187,20 @@ impl SimplifyExpr { span: *span, })) }; + }, + + // 'foo'[''] + KnownOp::IndexStr(_) => { + self.changed = true; + *expr = *undefined(*span); } - _ => {} }, // [1, 2, 3].length // // [1, 2, 3][0] + // + // [1, 2, 3][''] Expr::Array(ArrayLit { elems, span }) => { // do nothing if spread exists let has_spread = elems.iter().any(|elem| { @@ -287,6 +297,10 @@ impl SimplifyExpr { exprs.push(val); *expr = Expr::Seq(SeqExpr { span: *span, exprs }); + } else if matches!(op, KnownOp::IndexStr(..)) { + self.changed = true; + *expr = *undefined(*span); + return; } } From dd4a56437093fcb7d61e6791a00e5e343ca18cb3 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 18 Mar 2024 12:50:57 +1300 Subject: [PATCH 06/80] Fix indexing `ObjectLit`s --- .../src/simplify/expr/mod.rs | 103 ++++++++++-------- .../src/simplify/expr/tests.rs | 5 +- crates/swc_ecma_utils/src/lib.rs | 4 +- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 1e53e1042047..e15ab25eede9 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, iter, iter::once}; -use swc_atoms::JsWord; +use swc_atoms::{Atom, JsWord}; use swc_common::{ pass::{CompilerPass, Repeated}, util::take::Take, @@ -15,6 +15,8 @@ use swc_ecma_transforms_base::{ use swc_ecma_utils::{ is_literal, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, + prop_name_eq, to_int32, undefined, BoolType, ExprCtx, ExprExt, NullType, + NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, }; use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith}; use Value::{Known, Unknown}; @@ -188,7 +190,7 @@ impl SimplifyExpr { })) }; }, - + // 'foo'[''] KnownOp::IndexStr(_) => { self.changed = true; @@ -305,58 +307,63 @@ impl SimplifyExpr { } // { foo: true }['foo'] - Expr::Object(ObjectLit { props, span }) => match op { - KnownOp::IndexStr(key) if is_literal(props) && key != *"yield" => { - // do nothing if spread exists - let has_spread = props - .iter() - .any(|prop| matches!(prop, PropOrSpread::Spread(..))); + // + // { 0.5: true }[0.5] + Expr::Object(ObjectLit { props, span }) => { + // get key + let key = match op { + KnownOp::Index(i) => Atom::from(i.to_string()), + KnownOp::IndexStr(key) if key != *"yield" => key, // TODO: needs is_literal(props) check? + _ => return + }; - if has_spread { - return; - } + // do nothing if spread exists + let has_spread = props + .iter() + .any(|prop| matches!(prop, PropOrSpread::Spread(..))); - let idx = props.iter().rev().position(|p| match p { - PropOrSpread::Prop(p) => match &**p { - Prop::Shorthand(i) => i.sym == key, - Prop::KeyValue(k) => prop_name_eq(&k.key, &key), - Prop::Assign(p) => p.key.sym == key, - Prop::Getter(..) => false, - Prop::Setter(..) => false, - // TODO - Prop::Method(..) => false, - }, - _ => unreachable!(), - }); - let idx = idx.map(|idx| props.len() - 1 - idx); - // + if has_spread { + return; + } - if let Some(i) = idx { - let v = props.remove(i); - self.changed = true; + let idx = props.iter().rev().position(|p| match p { + PropOrSpread::Prop(p) => match &**p { + Prop::Shorthand(i) => i.sym == key, + Prop::KeyValue(k) => prop_name_eq(&k.key, &key), + Prop::Assign(p) => p.key.sym == key, + Prop::Getter(..) => false, + Prop::Setter(..) => false, + // TODO + Prop::Method(..) => false, + }, + _ => unreachable!(), + }); + let idx = idx.map(|idx| props.len() - 1 - idx); - *expr = self.expr_ctx.preserve_effects( - *span, - match v { - PropOrSpread::Prop(p) => match *p { - Prop::Shorthand(i) => Expr::Ident(i), - Prop::KeyValue(p) => *p.value, - Prop::Assign(p) => *p.value, - Prop::Getter(..) => unreachable!(), - Prop::Setter(..) => unreachable!(), - // TODO - Prop::Method(..) => unreachable!(), - }, - _ => unreachable!(), + if let Some(i) = idx { + let v = props.remove(i); + self.changed = true; + + *expr = self.expr_ctx.preserve_effects( + *span, + match v { + PropOrSpread::Prop(p) => match *p { + Prop::Shorthand(i) => Expr::Ident(i), + Prop::KeyValue(p) => *p.value, + Prop::Assign(p) => *p.value, + Prop::Getter(..) => unreachable!(), + Prop::Setter(..) => unreachable!(), + // TODO + Prop::Method(..) => unreachable!(), }, - once(Box::new(Expr::Object(ObjectLit { - props: props.take(), - span: *span, - }))), - ); - } + _ => unreachable!(), + }, + once(Box::new(Expr::Object(ObjectLit { + props: props.take(), + span: *span, + }))), + ); } - _ => {} }, _ => {} diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 905827ae3d7e..2e36b385564a 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1593,6 +1593,7 @@ fn test_issue8747() { fold("[1][[]]", "void 0"); // Indexing objects - fold("({0.5: 'a'})[0.5]", "0.5"); - fold_same("({0.5: 'a', 1: fn()})[0.5]"); + fold("({0.5: 'a'})[0.5]", "'a';"); + fold("({'0.5': 'a'})[0.5]", "'a';"); + fold("({0.5: 'a'})['0.5']", "'a';"); } diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index f1511dc9dac4..0a432069e209 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -1886,7 +1886,7 @@ impl Visit for LiteralVisitor { PropName::Str(ref s) => self.cost += 2 + s.value.len(), PropName::Ident(ref id) => self.cost += 2 + id.sym.len(), PropName::Num(n) => { - if n.value.fract() < 1e-10 { + if n.value.fract() < 1e-10 { // TODO: This breaks object simplifying. Why is this here? // TODO: Count digits self.cost += 5; } else { @@ -2675,7 +2675,7 @@ pub fn prop_name_eq(p: &PropName, key: &str) -> bool { match p { PropName::Ident(i) => i.sym == *key, PropName::Str(s) => s.value == *key, - PropName::Num(_) => false, + PropName::Num(n) => n.value.to_string() == *key, PropName::BigInt(_) => false, PropName::Computed(e) => match &*e.expr { Expr::Lit(Lit::Str(Str { value, .. })) => *value == *key, From 7916e48e7f8ac1e06495744c7ff232b458b8ffb1 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 18 Mar 2024 18:13:36 +1300 Subject: [PATCH 07/80] Remove fraction check from `LiteralVisitor` --- .../src/simplify/expr/mod.rs | 3 ++- crates/swc_ecma_utils/src/lib.rs | 10 +++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index e15ab25eede9..a9242834b878 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -16,6 +16,7 @@ use swc_ecma_utils::{ is_literal, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, prop_name_eq, to_int32, undefined, BoolType, ExprCtx, ExprExt, NullType, + is_literal, prop_name_eq, to_int32, undefined, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, }; use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith}; @@ -313,7 +314,7 @@ impl SimplifyExpr { // get key let key = match op { KnownOp::Index(i) => Atom::from(i.to_string()), - KnownOp::IndexStr(key) if key != *"yield" => key, // TODO: needs is_literal(props) check? + KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key, _ => return }; diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index 0a432069e209..074b38ac41d0 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -1885,13 +1885,9 @@ impl Visit for LiteralVisitor { match node { PropName::Str(ref s) => self.cost += 2 + s.value.len(), PropName::Ident(ref id) => self.cost += 2 + id.sym.len(), - PropName::Num(n) => { - if n.value.fract() < 1e-10 { // TODO: This breaks object simplifying. Why is this here? - // TODO: Count digits - self.cost += 5; - } else { - self.is_lit = false - } + PropName::Num(..) => { + // TODO: Count digits + self.cost += 5; } PropName::BigInt(_) => self.is_lit = false, PropName::Computed(..) => self.is_lit = false, From 2075a25b463f6fb70d39e6e0ae799c48452fdb86 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 18 Mar 2024 21:37:07 +1300 Subject: [PATCH 08/80] cargo fmt --- .../src/simplify/expr/mod.rs | 14 +++++++------- .../src/simplify/expr/tests.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index a9242834b878..6cd5750b1221 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -118,11 +118,11 @@ impl SimplifyExpr { // [a, b][0] // // ({0.5: 'test'})[0.5] - // /// Note: callers need to check `v.fract() == 0.0` in some cases. - /// ie non-integer indexes for arrays always result in `undefined` but - /// not for objects (because indexing an object returns the value of the - /// key, ie `0.5` will not return `undefined` if a key `0.5` exists + /// ie non-integer indexes for arrays always result in `undefined` + /// but not for objects (because indexing an object + /// returns the value of the key, ie `0.5` will not + /// return `undefined` if a key `0.5` exists /// and its value is not `undefined`). Index(f64), @@ -190,7 +190,7 @@ impl SimplifyExpr { span: *span, })) }; - }, + } // 'foo'[''] KnownOp::IndexStr(_) => { @@ -315,7 +315,7 @@ impl SimplifyExpr { let key = match op { KnownOp::Index(i) => Atom::from(i.to_string()), KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key, - _ => return + _ => return, }; // do nothing if spread exists @@ -365,7 +365,7 @@ impl SimplifyExpr { }))), ); } - }, + } _ => {} } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 2e36b385564a..8112a516c215 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1585,13 +1585,13 @@ fn test_issue8747() { // Index with an expression. fold("''[[]]", "void 0"); fold("'a'[[]]", "void 0"); - + // Indexing an array has the same logic as indexing a string. fold("[][0]", "void 0"); fold("[1][0.5]", "void 0"); fold("[][[]]", "void 0"); fold("[1][[]]", "void 0"); - + // Indexing objects fold("({0.5: 'a'})[0.5]", "'a';"); fold("({'0.5': 'a'})[0.5]", "'a';"); From 07f427dbc9c0555450499cd3ce8b6b0c42ea3dc8 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 18 Mar 2024 22:23:38 +1300 Subject: [PATCH 09/80] Update test files --- ...structuringArrayBindingPatternAndAssignment2.2.minified.js | 2 +- .../tsc-references/destructuringControlFlow.2.minified.js | 4 +--- .../destructuringEvaluationOrder(target=es5).2.minified.js | 2 +- crates/swc/tests/tsc-references/enumBasics.2.minified.js | 1 + .../tests/tsc-references/parserForStatement9.2.minified.js | 2 ++ .../templateStringInIndexExpressionES6.2.minified.js | 1 - 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/swc/tests/tsc-references/destructuringArrayBindingPatternAndAssignment2.2.minified.js b/crates/swc/tests/tsc-references/destructuringArrayBindingPatternAndAssignment2.2.minified.js index 645f448a283c..d7a68a59e955 100644 --- a/crates/swc/tests/tsc-references/destructuringArrayBindingPatternAndAssignment2.2.minified.js +++ b/crates/swc/tests/tsc-references/destructuringArrayBindingPatternAndAssignment2.2.minified.js @@ -1,7 +1,7 @@ //// [destructuringArrayBindingPatternAndAssignment2.ts] import { _ as _sliced_to_array } from "@swc/helpers/_/_sliced_to_array"; import { _ as _to_consumable_array } from "@swc/helpers/_/_to_consumable_array"; -var _ref_1 = (_sliced_to_array([][0], 1)[0], _sliced_to_array([][1], 1)); +var _ref_1 = (_sliced_to_array(void 0, 1)[0], _sliced_to_array(void 0, 1)); _sliced_to_array(_ref_1[0], 1)[0]; var _undefined = _sliced_to_array(void 0, 2), _undefined_1 = (_sliced_to_array(_undefined[0], 1)[0], _sliced_to_array(_undefined[1], 1)); _sliced_to_array(_undefined_1[0], 1)[0]; diff --git a/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js b/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js index 320c4695dc8c..7efeb216d6ca 100644 --- a/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js +++ b/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js @@ -1,5 +1,3 @@ //// [destructuringControlFlow.ts] import "@swc/helpers/_/_sliced_to_array"; -[ - "foo" -][1].toUpperCase(); +(void 0).toUpperCase(); diff --git a/crates/swc/tests/tsc-references/destructuringEvaluationOrder(target=es5).2.minified.js b/crates/swc/tests/tsc-references/destructuringEvaluationOrder(target=es5).2.minified.js index bb915583548d..8d0a296b2d5e 100644 --- a/crates/swc/tests/tsc-references/destructuringEvaluationOrder(target=es5).2.minified.js +++ b/crates/swc/tests/tsc-references/destructuringEvaluationOrder(target=es5).2.minified.js @@ -6,7 +6,7 @@ import { _ as _sliced_to_array } from "@swc/helpers/_/_sliced_to_array"; import { _ as _to_property_key } from "@swc/helpers/_/_to_property_key"; var trace = [], order = function(n) { return trace.push(n); -}, tmp = [][0]; +}, tmp = void 0; (void 0 === tmp ? order(0) : tmp)[order(1)]; var tmp1 = {}; (void 0 === tmp1 ? order(0) : tmp1)[order(1)]; diff --git a/crates/swc/tests/tsc-references/enumBasics.2.minified.js b/crates/swc/tests/tsc-references/enumBasics.2.minified.js index c8e0e8e104e9..6a68deb0ed71 100644 --- a/crates/swc/tests/tsc-references/enumBasics.2.minified.js +++ b/crates/swc/tests/tsc-references/enumBasics.2.minified.js @@ -2,3 +2,4 @@ (E1 = E11 || (E11 = {}))[E1.A = 0] = "A", E1[E1.B = 1] = "B", E1[E1.C = 2] = "C"; var E2, E3, E4, E5, E6, E7, E8, E9, E1, E11, E21, E31, E41, E51, E61, E71, E81, E91, e = E11; E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = 'foo'.foo] = "A", (E8 = E81 || (E81 = {}))[E8.B = 'foo'.foo] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; +E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = void 0] = "A", (E8 = E81 || (E81 = {}))[E8.B = void 0] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; diff --git a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js index 60c2ce4a14e0..0364e3dd8b7e 100644 --- a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js +++ b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js @@ -1,3 +1,5 @@ //// [parserForStatement9.ts] for(var tmp = [][0], x = void 0 === tmp ? ('a' in {}) : tmp; !x; x = !x)console.log(x); for(var _ref_x = {}.x, x1 = void 0 === _ref_x ? ('a' in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); +for(var tmp = void 0, x = void 0 === tmp ? ("a" in {}) : tmp; !x; x = !x)console.log(x); +for(var _ref_x = {}.x, x1 = void 0 === _ref_x ? ("a" in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); diff --git a/crates/swc/tests/tsc-references/templateStringInIndexExpressionES6.2.minified.js b/crates/swc/tests/tsc-references/templateStringInIndexExpressionES6.2.minified.js index 4d27068f2f2c..e76fe481e054 100644 --- a/crates/swc/tests/tsc-references/templateStringInIndexExpressionES6.2.minified.js +++ b/crates/swc/tests/tsc-references/templateStringInIndexExpressionES6.2.minified.js @@ -1,2 +1 @@ //// [templateStringInIndexExpressionES6.ts] -"abc0abc"["0"]; From ee8f34b3931aad7015576e96d082c100bdaf3e50 Mon Sep 17 00:00:00 2001 From: levi Date: Mon, 1 Apr 2024 05:47:39 +1300 Subject: [PATCH 10/80] Handle `x['0']` --- .../src/simplify/expr/mod.rs | 9 +++++++-- .../src/simplify/expr/tests.rs | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 6cd5750b1221..84022414f2c1 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -147,8 +147,13 @@ impl SimplifyExpr { // x[5] KnownOp::Index(*value) } else if let Known(s) = expr.as_pure_string(&self.expr_ctx) { - // x[''] or x[...] where ... is an expression like [], ie x[[]] - KnownOp::IndexStr(JsWord::from(s)) + if let Ok(n) = s.parse::() { + // x['0'] is treated as x[0] + KnownOp::Index(n) + } else { + // x[''] or x[...] where ... is an expression like [], ie x[[]] + KnownOp::IndexStr(JsWord::from(s)) + } } else { return; } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 8112a516c215..2ce6a5ce173e 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1585,6 +1585,9 @@ fn test_issue8747() { // Index with an expression. fold("''[[]]", "void 0"); fold("'a'[[]]", "void 0"); + // Index with a valid index. + fold("'a'[0]", "\"a\";"); + fold("'a'['0']", "\"a\";"); // Indexing an array has the same logic as indexing a string. fold("[][0]", "void 0"); From 7cfc9842a5af7c41da920a5743740a14a5128d6e Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:17:19 +1300 Subject: [PATCH 11/80] Don't return `KnownOp::Len` if `obj` is an ObjectLiteral --- .../swc_ecma_transforms_optimization/src/simplify/expr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 84022414f2c1..4d34dc3355ef 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -130,7 +130,7 @@ impl SimplifyExpr { IndexStr(JsWord), } let op = match prop { - MemberProp::Ident(Ident { sym, .. }) if &**sym == "length" => KnownOp::Len, + MemberProp::Ident(Ident { sym, .. }) if &**sym == "length" && !matches!(**obj, Expr::Object(..)) => KnownOp::Len, MemberProp::Ident(Ident { sym, .. }) => { if !self.in_callee { KnownOp::IndexStr(sym.clone()) From 25fcd452f659f46cd40d1e7fe6bc22da9083c195 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:18:04 +1300 Subject: [PATCH 12/80] Handle `x['length']` --- .../swc_ecma_transforms_optimization/src/simplify/expr/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 4d34dc3355ef..896d4b7c00c0 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -150,6 +150,9 @@ impl SimplifyExpr { if let Ok(n) = s.parse::() { // x['0'] is treated as x[0] KnownOp::Index(n) + } else if s == "length" && !matches!(**obj, Expr::Object(..)) { + // Length of non-object type + KnownOp::Len } else { // x[''] or x[...] where ... is an expression like [], ie x[[]] KnownOp::IndexStr(JsWord::from(s)) From ca089fbc140c3bea218bdef361ec35e8254202fc Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:18:59 +1300 Subject: [PATCH 13/80] Correctly handle builtin properties --- .../src/simplify/expr/mod.rs | 131 +++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 896d4b7c00c0..cc999fcffec6 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -164,6 +164,109 @@ impl SimplifyExpr { _ => return, }; + // Properties for objects. + // We use these to determine if a key for an object (array, string, or object) is valid. + // If it's not a valid key, we replace it with `undefined`, otherwise we leave it as-is. + + // Function properties. + // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function + let func_props = [ + // Constructor + "constructor", + + // Properties + "arguments", "caller", + + // Methods + "apply", "bind", "call", + "toString", + ]; + + // Array properties (excluding `length`). + // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array + let array_props = [ + // Constructor + "constructor", + + // Methods + "at", "concat", "copyWithin", + "entries", "every", "fill", + "filter", "find", "findIndex", + "findLast", "findLastIndex", + "flat", "flatMap", "forEach", + "includes", "indexOf", "join", + "keys", "lastIndexOf", "map", + "pop", "push", "reduce", + "reduceRight", "reverse", "shift", + "slice", "some", "sort", + "splice", "toLocaleString", "toReversed", + "toSorted", "toSpliced", "toString", + "unshift", "values", "with" + ]; + + // String properties (excluding `length`). + // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String + let str_props = [ + // Constructor + "constructor", + + // Methods + "anchor", "at", "big", + "blink", "bold", "charAt", + "charCodeAt", "codePointAt", "concat", + "endsWith", "fixed", "fontcolor", + "fontsize", "includes", "indexOf", + "isWellFormed", "italics", "lastIndexOf", + "link", "localeCompare", "match", + "matchAll", "normalize", "padEnd", + "padStart", "repeat", "replace", + "replaceAll", "search", "slice", + "small", "split", "startsWith", + "strike", "sub", "substr", + "substring", "sup", "toLocaleLowerCase", + "toLocaleUpperCase", "toLowerCase", "toString", + "toUpperCase", "toWellFormed", "trim", + "trimEnd", "trimStart", "valueOf" + ]; + + // Object properties. + // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object + let obj_props = [ + // Constructor + "constructor", + + // Properties + "__proto__", + + // Methods + "__defineGetter__", "__defineSetter__", "__lookupGetter__", + "__lookupSetter__", "hasOwnProperty", "isPrototypeOf", + "propertyIsEnumerable", "toLocaleString", "toString", + "valueOf", + ]; + + // Checks if the given property is an object property. + let is_obj_prop = |prop: &str| -> bool { + obj_props.contains(&prop) + }; + + // Checks if the given property is a function property. + let is_func_prop = |prop: &str| -> bool { + func_props.contains(&prop) + }; + + // Checks if the given property is an array property. + let is_array_prop = |prop: &str| -> bool { + // Inherits: Function, Object + array_props.contains(&prop) || is_func_prop(prop) || is_obj_prop(prop) + }; + + // Checks if the given property is a string property. + let is_str_prop = |prop: &str| -> bool { + // Inherits: Function, Object + str_props.contains(&prop) || is_func_prop(prop) || is_obj_prop(prop) + }; + match &mut **obj { Expr::Lit(Lit::Str(Str { value, span, .. })) => match op { // 'foo'.length @@ -201,7 +304,13 @@ impl SimplifyExpr { } // 'foo'[''] - KnownOp::IndexStr(_) => { + KnownOp::IndexStr(key) => { + if is_str_prop(key.as_str()) { + // Valid property + return; + } + + // Invalid property, resulting in undefined self.changed = true; *expr = *undefined(*span); } @@ -309,6 +418,17 @@ impl SimplifyExpr { *expr = Expr::Seq(SeqExpr { span: *span, exprs }); } else if matches!(op, KnownOp::IndexStr(..)) { + let key = match op { + KnownOp::IndexStr(key) => key, + _ => unreachable!() + }; + + if is_array_prop(key.as_str()) { + // Valid property + return; + } + + // Invalid property, resulting to undefined self.changed = true; *expr = *undefined(*span); return; @@ -372,6 +492,15 @@ impl SimplifyExpr { span: *span, }))), ); + } else { + if is_obj_prop(key.as_str()) { + // Valid property + return; + } + + // Invalid property, resulting in undefined + self.changed = true; + *expr = *undefined(*span); } } From 823ed752850e152be3688376d58a1850c9a4120d Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:19:36 +1300 Subject: [PATCH 14/80] Don't replace ArrayLiteral if doing so may have side effects --- .../src/simplify/expr/mod.rs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index cc999fcffec6..32c43fbb4611 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -328,16 +328,22 @@ impl SimplifyExpr { .map(|elem| elem.spread.is_some()) .unwrap_or(false) }); - + if has_spread { return; } - if op == KnownOp::Len - && !elems - .iter() - .filter_map(|e| e.as_ref()) - .any(|e| e.expr.may_have_side_effects(&self.expr_ctx)) - { + + // do nothing if replacement will have side effects + let may_have_side_effects = elems + .iter() + .filter_map(|e| e.as_ref()) + .any(|e| e.expr.may_have_side_effects(&self.expr_ctx)); + + if may_have_side_effects { + return; + } + + if op == KnownOp::Len { self.changed = true; *expr = Expr::Lit(Lit::Num(Number { From 0c8386ea015d1a2cde01cad92903a37b4b9c33bb Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:19:48 +1300 Subject: [PATCH 15/80] Update unit test --- .../src/simplify/expr/tests.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 2ce6a5ce173e..fca9259d23f5 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1588,6 +1588,17 @@ fn test_issue8747() { // Index with a valid index. fold("'a'[0]", "\"a\";"); fold("'a'['0']", "\"a\";"); + // Index with a valid key. + fold_same("[].toString"); + fold_same("''.toString"); + fold_same("({}).toString"); + // Index with length, resulting in replacement. + fold("[].length", "0"); + fold("[]['length']", "0"); + fold("''.length", "0"); + fold("({}).length", "void 0"); + fold("({})['length']", "void 0"); + fold("({length: 'foo'}).length", "'foo'"); // Indexing an array has the same logic as indexing a string. fold("[][0]", "void 0"); @@ -1599,4 +1610,6 @@ fn test_issue8747() { fold("({0.5: 'a'})[0.5]", "'a';"); fold("({'0.5': 'a'})[0.5]", "'a';"); fold("({0.5: 'a'})['0.5']", "'a';"); + fold("({}).foo", "void 0"); + fold_same("({foo: bar()}).foo"); } From 4b4d8ad1c58741dd48398bbb8bb8f4a890db415a Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:20:45 +1300 Subject: [PATCH 16/80] Update minifier test files --- .../tests/benches-full/vue.js | 2 +- .../tests/fixture/pr/6169/1/output.js | 4 +- .../output.js | 2 +- .../compress/arrow/object_parens/output.js | 14 ++--- .../compress/comparing/issue_2857_6/output.js | 18 +++--- .../evaluate/unsafe_array_bad_index/output.js | 2 +- .../unsafe_string_bad_index/output.js | 2 +- .../array_literal_with_spread_2a/output.js | 16 +---- .../array_literal_with_spread_3a/output.js | 20 ++----- .../array_literal_with_spread_3b/output.js | 5 +- .../array_literal_with_spread_4a/output.js | 60 +++++++++++++++---- 11 files changed, 76 insertions(+), 69 deletions(-) diff --git a/crates/swc_ecma_minifier/tests/benches-full/vue.js b/crates/swc_ecma_minifier/tests/benches-full/vue.js index eef7427c2866..a48c5e2744ee 100644 --- a/crates/swc_ecma_minifier/tests/benches-full/vue.js +++ b/crates/swc_ecma_minifier/tests/benches-full/vue.js @@ -185,7 +185,7 @@ UA && UA.indexOf('android'); var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA) || 'ios' === weexPlatform; UA && /chrome\/\d+/.test(UA), UA && /phantomjs/.test(UA); - var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = {}.watch, supportsPassive = !1; + var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = void 0, supportsPassive = !1; if (inBrowser) try { var opts = {}; Object.defineProperty(opts, 'passive', { diff --git a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js index 7eb42feb65d3..eb611cad0feb 100644 --- a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js @@ -1,3 +1 @@ -[ - "foo" -][1].toUpperCase(); +(void 0).toUpperCase(); diff --git a/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js b/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js index 91bdb072f148..e6c92b799ba1 100644 --- a/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js +++ b/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js @@ -1 +1 @@ -[]({c(){a=({}).b}}); +[]({c(){a=void 0}}); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js b/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js index d321beba44fb..e7333cadee7a 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js @@ -1,9 +1,9 @@ -() => ({}); -() => ({}); -() => ({}[0]); -() => 1; -() => 1; -() => 2; -() => { +()=>({}); +()=>({}); +()=>void 0; +()=>1; +()=>1; +()=>2; +()=>{ foo(); }; diff --git a/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js b/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js index dd9f7ac8ecc8..b0fa090b0e35 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js @@ -1,11 +1,11 @@ function f(a) { - if (null == {}.b) return void 0 !== a.b && null !== a.b; + if (true) return void 0 !== a.b && null !== a.b; } -console.log( - f({ - a: [null], - get b() { - return this.a.shift(); - }, - }) -); +console.log(f({ + a: [ + null + ], + get b () { + return this.a.shift(); + } +})); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js index b3b7416e6105..136efa1f74dd 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js @@ -1 +1 @@ -console.log([1, 2, 3, 4].a + 1, [1, 2, 3, 4]["a"] + 1, [1, 2, 3, 4][3.14] + 1); +console.log(NaN, NaN, NaN); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_string_bad_index/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_string_bad_index/output.js index da5d6acced78..136efa1f74dd 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_string_bad_index/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_string_bad_index/output.js @@ -1 +1 @@ -console.log("1234".a + 1, "1234"["a"] + 1, "1234"[3.14] + 1); +console.log(NaN, NaN, NaN); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js index c7119a35b1c9..ccd48d4a1a03 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js @@ -1,19 +1,7 @@ -console.log([ - 10, - 20, - 30, - 40, - 50 -]["length"]); +console.log(5); console.log(10); console.log(20); console.log(30); console.log(40); console.log(50); -console.log([ - 10, - 20, - 30, - 40, - 50 -][5]); +console.log(void 0); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js index 61dd47dc736c..818c44afcbb9 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js @@ -1,24 +1,12 @@ console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js index 7ddb30b53629..450e86d1e6e8 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js @@ -1,10 +1,7 @@ var nothing = []; console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log([ ...nothing, 10, diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js index 0f741d7ea1e4..80c6cfa44f68 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js @@ -2,15 +2,51 @@ function t(x) { console.log("(" + x + ")"); return 10 * x; } -console.log([t(1), t(2)][0]); -console.log((t(1), t(2))); -console.log([t(1), t(2)][2]); -console.log([t(1), t(2)][0]); -console.log((t(1), t(2))); -console.log([t(1), t(2)][2]); -console.log([t(1), t(2)][0]); -console.log((t(1), t(2))); -console.log([t(1), t(2)][2]); -console.log([t(1), t(2)][0]); -console.log((t(1), t(2))); -console.log([t(1), t(2)][2]); +console.log([ + t(1), + t(2) +][0]); +console.log([ + t(1), + t(2) +][1]); +console.log([ + t(1), + t(2) +][2]); +console.log([ + t(1), + t(2) +][0]); +console.log([ + t(1), + t(2) +][1]); +console.log([ + t(1), + t(2) +][2]); +console.log([ + t(1), + t(2) +][0]); +console.log([ + t(1), + t(2) +][1]); +console.log([ + t(1), + t(2) +][2]); +console.log([ + t(1), + t(2) +][0]); +console.log([ + t(1), + t(2) +][1]); +console.log([ + t(1), + t(2) +][2]); From a0997fb1ebeaf6ae5d3a35da9353abbc72b68bd8 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 1 Apr 2024 10:30:42 +1300 Subject: [PATCH 17/80] cargo fmt --- .../src/simplify/expr/mod.rs | 169 ++++++++++++------ 1 file changed, 115 insertions(+), 54 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 32c43fbb4611..e5314e5efcfa 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -130,7 +130,11 @@ impl SimplifyExpr { IndexStr(JsWord), } let op = match prop { - MemberProp::Ident(Ident { sym, .. }) if &**sym == "length" && !matches!(**obj, Expr::Object(..)) => KnownOp::Len, + MemberProp::Ident(Ident { sym, .. }) + if &**sym == "length" && !matches!(**obj, Expr::Object(..)) => + { + KnownOp::Len + } MemberProp::Ident(Ident { sym, .. }) => { if !self.in_callee { KnownOp::IndexStr(sym.clone()) @@ -165,20 +169,22 @@ impl SimplifyExpr { }; // Properties for objects. - // We use these to determine if a key for an object (array, string, or object) is valid. - // If it's not a valid key, we replace it with `undefined`, otherwise we leave it as-is. + // We use these to determine if a key for an object (array, string, or object) + // is valid. If it's not a valid key, we replace it with `undefined`, + // otherwise we leave it as-is. // Function properties. // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function let func_props = [ // Constructor "constructor", - // Properties - "arguments", "caller", - + "arguments", + "caller", // Methods - "apply", "bind", "call", + "apply", + "bind", + "call", "toString", ]; @@ -187,21 +193,45 @@ impl SimplifyExpr { let array_props = [ // Constructor "constructor", - // Methods - "at", "concat", "copyWithin", - "entries", "every", "fill", - "filter", "find", "findIndex", - "findLast", "findLastIndex", - "flat", "flatMap", "forEach", - "includes", "indexOf", "join", - "keys", "lastIndexOf", "map", - "pop", "push", "reduce", - "reduceRight", "reverse", "shift", - "slice", "some", "sort", - "splice", "toLocaleString", "toReversed", - "toSorted", "toSpliced", "toString", - "unshift", "values", "with" + "at", + "concat", + "copyWithin", + "entries", + "every", + "fill", + "filter", + "find", + "findIndex", + "findLast", + "findLastIndex", + "flat", + "flatMap", + "forEach", + "includes", + "indexOf", + "join", + "keys", + "lastIndexOf", + "map", + "pop", + "push", + "reduce", + "reduceRight", + "reverse", + "shift", + "slice", + "some", + "sort", + "splice", + "toLocaleString", + "toReversed", + "toSorted", + "toSpliced", + "toString", + "unshift", + "values", + "with", ]; // String properties (excluding `length`). @@ -209,24 +239,55 @@ impl SimplifyExpr { let str_props = [ // Constructor "constructor", - // Methods - "anchor", "at", "big", - "blink", "bold", "charAt", - "charCodeAt", "codePointAt", "concat", - "endsWith", "fixed", "fontcolor", - "fontsize", "includes", "indexOf", - "isWellFormed", "italics", "lastIndexOf", - "link", "localeCompare", "match", - "matchAll", "normalize", "padEnd", - "padStart", "repeat", "replace", - "replaceAll", "search", "slice", - "small", "split", "startsWith", - "strike", "sub", "substr", - "substring", "sup", "toLocaleLowerCase", - "toLocaleUpperCase", "toLowerCase", "toString", - "toUpperCase", "toWellFormed", "trim", - "trimEnd", "trimStart", "valueOf" + "anchor", + "at", + "big", + "blink", + "bold", + "charAt", + "charCodeAt", + "codePointAt", + "concat", + "endsWith", + "fixed", + "fontcolor", + "fontsize", + "includes", + "indexOf", + "isWellFormed", + "italics", + "lastIndexOf", + "link", + "localeCompare", + "match", + "matchAll", + "normalize", + "padEnd", + "padStart", + "repeat", + "replace", + "replaceAll", + "search", + "slice", + "small", + "split", + "startsWith", + "strike", + "sub", + "substr", + "substring", + "sup", + "toLocaleLowerCase", + "toLocaleUpperCase", + "toLowerCase", + "toString", + "toUpperCase", + "toWellFormed", + "trim", + "trimEnd", + "trimStart", + "valueOf", ]; // Object properties. @@ -234,26 +295,26 @@ impl SimplifyExpr { let obj_props = [ // Constructor "constructor", - // Properties "__proto__", - // Methods - "__defineGetter__", "__defineSetter__", "__lookupGetter__", - "__lookupSetter__", "hasOwnProperty", "isPrototypeOf", - "propertyIsEnumerable", "toLocaleString", "toString", + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "toLocaleString", + "toString", "valueOf", ]; // Checks if the given property is an object property. - let is_obj_prop = |prop: &str| -> bool { - obj_props.contains(&prop) - }; + let is_obj_prop = |prop: &str| -> bool { obj_props.contains(&prop) }; // Checks if the given property is a function property. - let is_func_prop = |prop: &str| -> bool { - func_props.contains(&prop) - }; + let is_func_prop = |prop: &str| -> bool { func_props.contains(&prop) }; // Checks if the given property is an array property. let is_array_prop = |prop: &str| -> bool { @@ -328,21 +389,21 @@ impl SimplifyExpr { .map(|elem| elem.spread.is_some()) .unwrap_or(false) }); - + if has_spread { return; } - + // do nothing if replacement will have side effects let may_have_side_effects = elems .iter() .filter_map(|e| e.as_ref()) .any(|e| e.expr.may_have_side_effects(&self.expr_ctx)); - + if may_have_side_effects { return; } - + if op == KnownOp::Len { self.changed = true; @@ -426,7 +487,7 @@ impl SimplifyExpr { } else if matches!(op, KnownOp::IndexStr(..)) { let key = match op { KnownOp::IndexStr(key) => key, - _ => unreachable!() + _ => unreachable!(), }; if is_array_prop(key.as_str()) { From c2cd898369b2257c67684061998c9deee03e9219 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 03:12:47 +1300 Subject: [PATCH 18/80] Minifier shouldn't handle potential `pristine_globals` cases --- .../src/simplify/expr/mod.rs | 323 ++++++------------ .../src/simplify/expr/tests.rs | 49 +-- 2 files changed, 129 insertions(+), 243 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index e5314e5efcfa..081a133eef77 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -117,9 +117,10 @@ impl SimplifyExpr { // [a, b][0] // - // ({0.5: 'test'})[0.5] + // {0.5: "bar"}[0.5] + // /// Note: callers need to check `v.fract() == 0.0` in some cases. - /// ie non-integer indexes for arrays always result in `undefined` + /// ie non-integer indexes for arrays result in `undefined` /// but not for objects (because indexing an object /// returns the value of the key, ie `0.5` will not /// return `undefined` if a key `0.5` exists @@ -167,170 +168,18 @@ impl SimplifyExpr { } _ => return, }; - - // Properties for objects. - // We use these to determine if a key for an object (array, string, or object) - // is valid. If it's not a valid key, we replace it with `undefined`, - // otherwise we leave it as-is. - - // Function properties. - // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function - let func_props = [ - // Constructor - "constructor", - // Properties - "arguments", - "caller", - // Methods - "apply", - "bind", - "call", - "toString", - ]; - - // Array properties (excluding `length`). - // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array - let array_props = [ - // Constructor - "constructor", - // Methods - "at", - "concat", - "copyWithin", - "entries", - "every", - "fill", - "filter", - "find", - "findIndex", - "findLast", - "findLastIndex", - "flat", - "flatMap", - "forEach", - "includes", - "indexOf", - "join", - "keys", - "lastIndexOf", - "map", - "pop", - "push", - "reduce", - "reduceRight", - "reverse", - "shift", - "slice", - "some", - "sort", - "splice", - "toLocaleString", - "toReversed", - "toSorted", - "toSpliced", - "toString", - "unshift", - "values", - "with", - ]; - - // String properties (excluding `length`). - // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String - let str_props = [ - // Constructor - "constructor", - // Methods - "anchor", - "at", - "big", - "blink", - "bold", - "charAt", - "charCodeAt", - "codePointAt", - "concat", - "endsWith", - "fixed", - "fontcolor", - "fontsize", - "includes", - "indexOf", - "isWellFormed", - "italics", - "lastIndexOf", - "link", - "localeCompare", - "match", - "matchAll", - "normalize", - "padEnd", - "padStart", - "repeat", - "replace", - "replaceAll", - "search", - "slice", - "small", - "split", - "startsWith", - "strike", - "sub", - "substr", - "substring", - "sup", - "toLocaleLowerCase", - "toLocaleUpperCase", - "toLowerCase", - "toString", - "toUpperCase", - "toWellFormed", - "trim", - "trimEnd", - "trimStart", - "valueOf", - ]; - - // Object properties. - // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object - let obj_props = [ - // Constructor - "constructor", - // Properties - "__proto__", - // Methods - "__defineGetter__", - "__defineSetter__", - "__lookupGetter__", - "__lookupSetter__", - "hasOwnProperty", - "isPrototypeOf", - "propertyIsEnumerable", - "toLocaleString", - "toString", - "valueOf", - ]; - - // Checks if the given property is an object property. - let is_obj_prop = |prop: &str| -> bool { obj_props.contains(&prop) }; - - // Checks if the given property is a function property. - let is_func_prop = |prop: &str| -> bool { func_props.contains(&prop) }; - - // Checks if the given property is an array property. - let is_array_prop = |prop: &str| -> bool { - // Inherits: Function, Object - array_props.contains(&prop) || is_func_prop(prop) || is_obj_prop(prop) - }; - - // Checks if the given property is a string property. - let is_str_prop = |prop: &str| -> bool { - // Inherits: Function, Object - str_props.contains(&prop) || is_func_prop(prop) || is_obj_prop(prop) - }; + + // Note: pristine_globals refers to the compress config option pristine_globals. + // Any potential cases where globals are not pristine are handled in compress, + // e.g. x[-1] is not changed as the object's prototype may be modified. + // For example, Array.prototype[-1] = "foo" will result in [][-1] returning "foo". match &mut **obj { Expr::Lit(Lit::Str(Str { value, span, .. })) => match op { // 'foo'.length + // + // Prototype changes do not affect .length, so we don't need to worry + // about pristine_globals here. KnownOp::Len => { self.changed = true; @@ -352,36 +201,30 @@ impl SimplifyExpr { self.changed = true; if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { - *expr = *undefined(*span) - } else { - let value = nth_char(value, idx as _); - - *expr = Expr::Lit(Lit::Str(Str { - raw: None, - value: value.into(), - span: *span, - })) - }; - } - - // 'foo'[''] - KnownOp::IndexStr(key) => { - if is_str_prop(key.as_str()) { - // Valid property + // Prototype changes affect indexing if the index is out of bounds, so we + // don't replace out-of-bound indexes. return; } - // Invalid property, resulting in undefined self.changed = true; - *expr = *undefined(*span); + + let value = nth_char(value, idx as _); + *expr = Expr::Lit(Lit::Str(Str { + raw: None, + value: value.into(), + span: *span, + })) } + + // 'foo'[''] + // + // Handled in compress + KnownOp::IndexStr(..) => {} }, // [1, 2, 3].length // // [1, 2, 3][0] - // - // [1, 2, 3][''] Expr::Array(ArrayLit { elems, span }) => { // do nothing if spread exists let has_spread = elems.iter().any(|elem| { @@ -403,41 +246,84 @@ impl SimplifyExpr { if may_have_side_effects { return; } + + match op { + KnownOp::Len => { + // Prototype changes do not affect .length + self.changed = true; - if op == KnownOp::Len { - self.changed = true; + *expr = Expr::Lit(Lit::Num(Number { + value: elems.len() as _, + span: *span, + raw: None, + })); + } + + KnownOp::Index(idx) => { + // If the fraction part is non-zero, or if the index is out of bounds, + // then we handle this in compress as Array's prototype may be modified. + if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() { + return; + } + // idx is treated as an integer from this point. + // + // We also know for certain the index is not out of bounds. + let idx = idx as i64; + + let after_has_side_effect = + elems.iter().skip((idx + 1) as _).any(|elem| match elem { + Some(elem) => elem.expr.may_have_side_effects(&self.expr_ctx), + None => false, + }); - *expr = Expr::Lit(Lit::Num(Number { - value: elems.len() as _, - span: *span, - raw: None, - })); - } else if matches!(op, KnownOp::Index(..)) { - let idx = match op { - KnownOp::Index(i) => i, - _ => unreachable!(), - }; + if after_has_side_effect { + return; + } - // If the fraction part is non-zero, or if the index is out of bounds, - // then the result is always undefined. - if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() { self.changed = true; - *expr = *undefined(*span); - return; - } - // idx is treated as an integer from this point. - // - // We also know for certain the index is not out of bounds. - let idx = idx as i64; - - let after_has_side_effect = - elems.iter().skip((idx + 1) as _).any(|elem| match elem { - Some(elem) => elem.expr.may_have_side_effects(&self.expr_ctx), - None => false, - }); - if after_has_side_effect { - return; + let (before, e, after) = if elems.len() > idx as _ && idx >= 0 { + let before = elems.drain(..(idx as usize)).collect(); + let mut iter = elems.take().into_iter(); + let e = iter.next().flatten(); + let after = iter.collect(); + + (before, e, after) + } else { + let before = elems.take(); + + (before, None, vec![]) + }; + + let v = match e { + None => undefined(*span), + Some(e) => e.expr, + }; + + let mut exprs = vec![]; + for elem in before.into_iter().flatten() { + self.expr_ctx + .extract_side_effects_to(&mut exprs, *elem.expr); + } + + let val = v; + + for elem in after.into_iter().flatten() { + self.expr_ctx + .extract_side_effects_to(&mut exprs, *elem.expr); + } + + if exprs.is_empty() { + *expr = Expr::Seq(SeqExpr { + span: val.span(), + exprs: vec![0.into(), val], + }); + return; + } + + exprs.push(val); + + *expr = Expr::Seq(SeqExpr { span: *span, exprs }); } self.changed = true; @@ -499,6 +385,9 @@ impl SimplifyExpr { self.changed = true; *expr = *undefined(*span); return; + + // Handled in compress + KnownOp::IndexStr(..) => {} } } @@ -536,6 +425,7 @@ impl SimplifyExpr { }); let idx = idx.map(|idx| props.len() - 1 - idx); + // Only replace if key exists; non-existent keys are handled in compress if let Some(i) = idx { let v = props.remove(i); self.changed = true; @@ -559,15 +449,6 @@ impl SimplifyExpr { span: *span, }))), ); - } else { - if is_obj_prop(key.as_str()) { - // Valid property - return; - } - - // Invalid property, resulting in undefined - self.changed = true; - *expr = *undefined(*span); } } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index fca9259d23f5..48d65eacc4d4 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1578,38 +1578,43 @@ fn test_export_default_paren_expr() { #[test] fn test_issue8747() { - // Indexing a string with an out-of-bounds index returns undefined. - fold("''[0]", "void 0"); - // Indexing a string with a non-integer index returns undefined. - fold("'a'[0.5]", "void 0"); - // Index with an expression. - fold("''[[]]", "void 0"); - fold("'a'[[]]", "void 0"); // Index with a valid index. fold("'a'[0]", "\"a\";"); fold("'a'['0']", "\"a\";"); - // Index with a valid key. - fold_same("[].toString"); - fold_same("''.toString"); - fold_same("({}).toString"); + + // Index with an invalid index. + // An invalid index is an out-of-bound index. These are not replaced as prototype + // changes could cause undefined behaviour. Refer to pristine_globals in compress. + fold_same("'a'[0.5]"); + fold_same("'a'[-1]"); + + fold_same("[1][0.5]"); + fold_same("[1][-1]"); + + // Index with an expression. + fold("'a'[0 + []]", "\"a\";"); + //fold("[1][0 + []]", "1"); + + // Don't replace if side effects exist. + fold_same("[f()][0]"); + fold_same("({foo: f()}).foo"); + // Index with length, resulting in replacement. + // Prototype changes don't affect .length in String and Array, + // but it is affected in Object. fold("[].length", "0"); fold("[]['length']", "0"); + fold("''.length", "0"); - fold("({}).length", "void 0"); - fold("({})['length']", "void 0"); + fold("''['length']", "0"); + + fold_same("({}).length"); + fold_same("({})['length']"); fold("({length: 'foo'}).length", "'foo'"); + fold("({length: 'foo'})['length']", "'foo'"); - // Indexing an array has the same logic as indexing a string. - fold("[][0]", "void 0"); - fold("[1][0.5]", "void 0"); - fold("[][[]]", "void 0"); - fold("[1][[]]", "void 0"); - - // Indexing objects + // Indexing objects has a few special cases that were broken that we test here. fold("({0.5: 'a'})[0.5]", "'a';"); fold("({'0.5': 'a'})[0.5]", "'a';"); fold("({0.5: 'a'})['0.5']", "'a';"); - fold("({}).foo", "void 0"); - fold_same("({foo: bar()}).foo"); } From da150a2374000a436773eef987131868350007b4 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 03:25:10 +1300 Subject: [PATCH 19/80] cargo fmt --- .../src/simplify/expr/mod.rs | 12 ++++++------ .../src/simplify/expr/tests.rs | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 081a133eef77..883138ad15eb 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -118,7 +118,6 @@ impl SimplifyExpr { // [a, b][0] // // {0.5: "bar"}[0.5] - // /// Note: callers need to check `v.fract() == 0.0` in some cases. /// ie non-integer indexes for arrays result in `undefined` /// but not for objects (because indexing an object @@ -168,11 +167,12 @@ impl SimplifyExpr { } _ => return, }; - + // Note: pristine_globals refers to the compress config option pristine_globals. // Any potential cases where globals are not pristine are handled in compress, // e.g. x[-1] is not changed as the object's prototype may be modified. - // For example, Array.prototype[-1] = "foo" will result in [][-1] returning "foo". + // For example, Array.prototype[-1] = "foo" will result in [][-1] returning + // "foo". match &mut **obj { Expr::Lit(Lit::Str(Str { value, span, .. })) => match op { @@ -207,7 +207,7 @@ impl SimplifyExpr { } self.changed = true; - + let value = nth_char(value, idx as _); *expr = Expr::Lit(Lit::Str(Str { raw: None, @@ -246,7 +246,7 @@ impl SimplifyExpr { if may_have_side_effects { return; } - + match op { KnownOp::Len => { // Prototype changes do not affect .length @@ -258,7 +258,7 @@ impl SimplifyExpr { raw: None, })); } - + KnownOp::Index(idx) => { // If the fraction part is non-zero, or if the index is out of bounds, // then we handle this in compress as Array's prototype may be modified. diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 48d65eacc4d4..6928ff91dc31 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1581,33 +1581,34 @@ fn test_issue8747() { // Index with a valid index. fold("'a'[0]", "\"a\";"); fold("'a'['0']", "\"a\";"); - + // Index with an invalid index. - // An invalid index is an out-of-bound index. These are not replaced as prototype - // changes could cause undefined behaviour. Refer to pristine_globals in compress. + // An invalid index is an out-of-bound index. These are not replaced as + // prototype changes could cause undefined behaviour. Refer to + // pristine_globals in compress. fold_same("'a'[0.5]"); fold_same("'a'[-1]"); - + fold_same("[1][0.5]"); fold_same("[1][-1]"); - + // Index with an expression. fold("'a'[0 + []]", "\"a\";"); //fold("[1][0 + []]", "1"); - + // Don't replace if side effects exist. fold_same("[f()][0]"); fold_same("({foo: f()}).foo"); - + // Index with length, resulting in replacement. // Prototype changes don't affect .length in String and Array, // but it is affected in Object. fold("[].length", "0"); fold("[]['length']", "0"); - + fold("''.length", "0"); fold("''['length']", "0"); - + fold_same("({}).length"); fold_same("({})['length']"); fold("({length: 'foo'}).length", "'foo'"); From e7f6670b7aafc5a062309a209868084aa8536532 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 03:30:38 +1300 Subject: [PATCH 20/80] Update test files --- .../tests/benches-full/vue.js | 2 +- .../tests/fixture/pr/6169/1/output.js | 4 +++- .../output.js | 2 +- .../compress/arrow/object_parens/output.js | 2 +- .../compress/comparing/issue_2857_6/output.js | 2 +- .../evaluate/unsafe_array_bad_index/output.js | 17 +++++++++++++++- .../array_literal_with_spread_2a/output.js | 8 +++++++- .../array_literal_with_spread_3a/output.js | 20 +++++++++++++++---- .../array_literal_with_spread_3b/output.js | 5 ++++- 9 files changed, 50 insertions(+), 12 deletions(-) diff --git a/crates/swc_ecma_minifier/tests/benches-full/vue.js b/crates/swc_ecma_minifier/tests/benches-full/vue.js index a48c5e2744ee..eef7427c2866 100644 --- a/crates/swc_ecma_minifier/tests/benches-full/vue.js +++ b/crates/swc_ecma_minifier/tests/benches-full/vue.js @@ -185,7 +185,7 @@ UA && UA.indexOf('android'); var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA) || 'ios' === weexPlatform; UA && /chrome\/\d+/.test(UA), UA && /phantomjs/.test(UA); - var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = void 0, supportsPassive = !1; + var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = {}.watch, supportsPassive = !1; if (inBrowser) try { var opts = {}; Object.defineProperty(opts, 'passive', { diff --git a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js index eb611cad0feb..7eb42feb65d3 100644 --- a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js @@ -1 +1,3 @@ -(void 0).toUpperCase(); +[ + "foo" +][1].toUpperCase(); diff --git a/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js b/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js index e6c92b799ba1..91bdb072f148 100644 --- a/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js +++ b/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js @@ -1 +1 @@ -[]({c(){a=void 0}}); +[]({c(){a=({}).b}}); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js b/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js index e7333cadee7a..e2342a7a4269 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js @@ -1,6 +1,6 @@ ()=>({}); ()=>({}); -()=>void 0; +()=>({})[0]; ()=>1; ()=>1; ()=>2; diff --git a/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js b/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js index b0fa090b0e35..3dc75b4c85d7 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js @@ -1,5 +1,5 @@ function f(a) { - if (true) return void 0 !== a.b && null !== a.b; + if (null == ({}).b) return void 0 !== a.b && null !== a.b; } console.log(f({ a: [ diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js index 136efa1f74dd..b2bb86265438 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js @@ -1 +1,16 @@ -console.log(NaN, NaN, NaN); +console.log([ + 1, + 2, + 3, + 4 +].a + 1, [ + 1, + 2, + 3, + 4 +]["a"] + 1, [ + 1, + 2, + 3, + 4 +][3.14] + 1); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js index ccd48d4a1a03..845ea5e68da4 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js @@ -4,4 +4,10 @@ console.log(20); console.log(30); console.log(40); console.log(50); -console.log(void 0); +console.log([ + 10, + 20, + 30, + 40, + 50 +][5]); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js index 818c44afcbb9..61dd47dc736c 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js @@ -1,12 +1,24 @@ console.log(10); console.log(20); -console.log(void 0); +console.log([ + 10, + 20 +][2]); console.log(10); console.log(20); -console.log(void 0); +console.log([ + 10, + 20 +][2]); console.log(10); console.log(20); -console.log(void 0); +console.log([ + 10, + 20 +][2]); console.log(10); console.log(20); -console.log(void 0); +console.log([ + 10, + 20 +][2]); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js index 450e86d1e6e8..7ddb30b53629 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js @@ -1,7 +1,10 @@ var nothing = []; console.log(10); console.log(20); -console.log(void 0); +console.log([ + 10, + 20 +][2]); console.log([ ...nothing, 10, From c1a13e2bd65ed620ac6232adbb2fd363993d6962 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 04:35:05 +1300 Subject: [PATCH 21/80] Don't add a dummy value if no side effects exist --- .../src/simplify/expr/mod.rs | 51 ++++++++++--------- .../src/simplify/expr/tests.rs | 2 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 883138ad15eb..15a2e5d31eab 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -265,16 +265,16 @@ impl SimplifyExpr { if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() { return; } - // idx is treated as an integer from this point. - // - // We also know for certain the index is not out of bounds. - let idx = idx as i64; + // Don't change if after has side effects. let after_has_side_effect = - elems.iter().skip((idx + 1) as _).any(|elem| match elem { - Some(elem) => elem.expr.may_have_side_effects(&self.expr_ctx), - None => false, - }); + elems + .iter() + .skip((idx as usize + 1) as _) + .any(|elem| match elem { + Some(elem) => elem.expr.may_have_side_effects(&self.expr_ctx), + None => false, + }); if after_has_side_effect { return; @@ -282,47 +282,48 @@ impl SimplifyExpr { self.changed = true; - let (before, e, after) = if elems.len() > idx as _ && idx >= 0 { - let before = elems.drain(..(idx as usize)).collect(); - let mut iter = elems.take().into_iter(); - let e = iter.next().flatten(); - let after = iter.collect(); - - (before, e, after) - } else { - let before = elems.take(); - - (before, None, vec![]) - }; + // elements before target element + let before: Vec> = + elems.drain(..(idx as usize)).collect(); + let mut iter = elems.take().into_iter(); + // element at idx + let e = iter.next().flatten(); + // elements after target element + let after: Vec> = iter.collect(); + // element value let v = match e { None => undefined(*span), Some(e) => e.expr, }; + // Replacement expressions. let mut exprs = vec![]; + + // Add before side effects. for elem in before.into_iter().flatten() { self.expr_ctx .extract_side_effects_to(&mut exprs, *elem.expr); } + // Element value. let val = v; + // Add after side effects. for elem in after.into_iter().flatten() { self.expr_ctx .extract_side_effects_to(&mut exprs, *elem.expr); } if exprs.is_empty() { - *expr = Expr::Seq(SeqExpr { - span: val.span(), - exprs: vec![0.into(), val], - }); + // No side effects exist, replace with value. + *expr = *val; return; } + // Side effects exist, add value to the end and replace + // with a SeqExpr. exprs.push(val); - *expr = Expr::Seq(SeqExpr { span: *span, exprs }); } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 6928ff91dc31..388cffab3c90 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1594,7 +1594,7 @@ fn test_issue8747() { // Index with an expression. fold("'a'[0 + []]", "\"a\";"); - //fold("[1][0 + []]", "1"); + fold("[1][0 + []]", "1"); // Don't replace if side effects exist. fold_same("[f()][0]"); From e50ab82fbc3b319db8183e3410389bc85fad5d3e Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 04:41:32 +1300 Subject: [PATCH 22/80] Update test files --- .../tests/terser/compress/evaluate/issue_2231_3/output.js | 2 +- .../tests/terser/compress/issue_t50/issue_t50/output.js | 8 +------- .../terser/compress/issue_t50/issue_t50_const/output.js | 8 +------- .../terser/compress/issue_t50/issue_t50_let/output.js | 8 +------- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js index 7b47d8d664ab..85ce559e8f22 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js @@ -1 +1 @@ -console.log((0, "foo")); +console.log("foo"); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/output.js b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/output.js index 1a566b601381..e76beab2604a 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/output.js @@ -1,7 +1 @@ -console.log((0, { - a: -1, - b: 5 -}).a, (0, { - a: -10, - b: 5 -}).a); +console.log(-1, -10); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/output.js b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/output.js index 1a566b601381..e76beab2604a 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/output.js @@ -1,7 +1 @@ -console.log((0, { - a: -1, - b: 5 -}).a, (0, { - a: -10, - b: 5 -}).a); +console.log(-1, -10); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/output.js b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/output.js index 1a566b601381..e76beab2604a 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/output.js @@ -1,7 +1 @@ -console.log((0, { - a: -1, - b: 5 -}).a, (0, { - a: -10, - b: 5 -}).a); +console.log(-1, -10); From 3f86a8f2f3ad1c9f79fc61ccd1af910a7fe56fe8 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 06:53:20 +1300 Subject: [PATCH 23/80] Oops --- .../src/simplify/expr/mod.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 15a2e5d31eab..b6ad6e685442 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -237,18 +237,18 @@ impl SimplifyExpr { return; } - // do nothing if replacement will have side effects - let may_have_side_effects = elems - .iter() - .filter_map(|e| e.as_ref()) - .any(|e| e.expr.may_have_side_effects(&self.expr_ctx)); - - if may_have_side_effects { - return; - } - match op { KnownOp::Len => { + // do nothing if replacement will have side effects + let may_have_side_effects = elems + .iter() + .filter_map(|e| e.as_ref()) + .any(|e| e.expr.may_have_side_effects(&self.expr_ctx)); + + if may_have_side_effects { + return; + } + // Prototype changes do not affect .length self.changed = true; From 82c02aabf300d29aa530bb1fc4e275cbcf0a3f66 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 07:40:16 +1300 Subject: [PATCH 24/80] Update unit test The previous commit makes the whole array get replaced by just `f()`, which is correct, but we need a better case now --- .../swc_ecma_transforms_optimization/src/simplify/expr/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 388cffab3c90..d4b05dc5399c 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1597,7 +1597,7 @@ fn test_issue8747() { fold("[1][0 + []]", "1"); // Don't replace if side effects exist. - fold_same("[f()][0]"); + fold_same("[f(), f()][0]"); fold_same("({foo: f()}).foo"); // Index with length, resulting in replacement. From a4a08e26420c74f0b9f17cd8386c8f13a9c31425 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 07:43:37 +1300 Subject: [PATCH 25/80] Add test case for side effects --- .../swc_ecma_transforms_optimization/src/simplify/expr/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index d4b05dc5399c..3d792d9040df 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1598,6 +1598,7 @@ fn test_issue8747() { // Don't replace if side effects exist. fold_same("[f(), f()][0]"); + fold("[x(), 'x', 5][2]", "x(), 5;"); fold_same("({foo: f()}).foo"); // Index with length, resulting in replacement. From 19bf28963c312094aa8bb09a90b5039c69c2b5f5 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 13:03:00 +1200 Subject: [PATCH 26/80] Add compression for `MemberExpr` --- Cargo.lock | 1 + crates/swc_ecma_minifier/Cargo.toml | 69 +-- .../src/compress/pure/member_expr.rs | 397 ++++++++++++++++++ .../src/compress/pure/mod.rs | 20 + .../tests/benches-full/vue.js | 2 +- .../tests/fixture/pr/6169/1/output.js | 4 +- .../output.js | 2 +- .../compress/arrow/object_parens/output.js | 2 +- .../compress/comparing/issue_2857_6/output.js | 2 +- .../evaluate/unsafe_array_bad_index/output.js | 17 +- .../array_literal_with_spread_2a/output.js | 8 +- .../array_literal_with_spread_3a/output.js | 20 +- .../array_literal_with_spread_3b/output.js | 5 +- .../array_literal_with_spread_4a/output.js | 40 +- .../compress/harmony/issue_2345/output.js | 10 +- .../evaluate_array_length/output.js | 12 +- .../reduce_vars/issue_2450_5/output.js | 8 +- .../reduce_vars/issue_3042_1/output.js | 5 +- .../reduce_vars/issue_3042_2/output.js | 11 +- 19 files changed, 508 insertions(+), 127 deletions(-) create mode 100644 crates/swc_ecma_minifier/src/compress/pure/member_expr.rs diff --git a/Cargo.lock b/Cargo.lock index 583d20ec28a0..3e9cb41274ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4477,6 +4477,7 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot", + "phf", "pretty_assertions", "radix_fmt", "rayon", diff --git a/crates/swc_ecma_minifier/Cargo.toml b/crates/swc_ecma_minifier/Cargo.toml index 4c02ef8ecb3f..1daceafffe47 100644 --- a/crates/swc_ecma_minifier/Cargo.toml +++ b/crates/swc_ecma_minifier/Cargo.toml @@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"] license = "Apache-2.0" name = "swc_ecma_minifier" repository = "https://github.com/swc-project/swc.git" -version = "0.197.0" +version = "0.192.17" [package.metadata.docs.rs] all-features = true @@ -33,50 +33,51 @@ serde-impl = [] trace-ast = [] [dependencies] -arrayvec = { workspace = true } -backtrace = { workspace = true, optional = true } -indexmap = { workspace = true } -num-bigint = { workspace = true } -num_cpus = { workspace = true } -once_cell = { workspace = true } -parking_lot = { workspace = true } -pretty_assertions = { workspace = true, optional = true } +arrayvec = "0.7.2" +backtrace = { version = "0.3.61", optional = true } +indexmap = "2.0.0" +num-bigint = "0.4.3" +num_cpus = "1.13.1" +once_cell = "1.18.0" +parking_lot = "0.12.1" +pretty_assertions = { version = "1.3", optional = true } radix_fmt = "=1.0.0" -rayon = { workspace = true, optional = true } -regex = { workspace = true } -rustc-hash = { workspace = true } -ryu-js = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -tracing = { workspace = true } +rayon = { version = "1.7.0", optional = true } +regex = "1.5.3" +rustc-hash = "1.1.0" +ryu-js = "1.0.0" +serde = { version = "1.0.118", features = ["derive"] } +serde_json = "1.0.61" +tracing = "0.1.37" +phf = { version = "0.11.2", features = ["macros"] } swc_atoms = { version = "0.6.5", path = "../swc_atoms" } -swc_common = { version = "0.34.0", path = "../swc_common" } -swc_config = { version = "0.1.13", path = "../swc_config", features = [ +swc_common = { version = "0.33.19", path = "../swc_common" } +swc_config = { version = "0.1.11", path = "../swc_config", features = [ "sourcemap", ] } -swc_ecma_ast = { version = "0.115.0", path = "../swc_ecma_ast", features = [ +swc_ecma_ast = { version = "0.112.5", path = "../swc_ecma_ast", features = [ "serde", ] } -swc_ecma_codegen = { version = "0.151.0", path = "../swc_ecma_codegen" } -swc_ecma_parser = { version = "0.146.0", path = "../swc_ecma_parser" } -swc_ecma_transforms_base = { version = "0.140.0", path = "../swc_ecma_transforms_base" } -swc_ecma_transforms_optimization = { version = "0.201.0", path = "../swc_ecma_transforms_optimization" } -swc_ecma_usage_analyzer = { version = "0.26.0", path = "../swc_ecma_usage_analyzer" } -swc_ecma_utils = { version = "0.130.0", path = "../swc_ecma_utils" } -swc_ecma_visit = { version = "0.101.0", path = "../swc_ecma_visit" } -swc_timer = { version = "0.22.0", path = "../swc_timer" } +swc_ecma_codegen = { version = "0.148.11", path = "../swc_ecma_codegen" } +swc_ecma_parser = { version = "0.143.9", path = "../swc_ecma_parser" } +swc_ecma_transforms_base = { version = "0.137.15", path = "../swc_ecma_transforms_base" } +swc_ecma_transforms_optimization = { version = "0.198.16", path = "../swc_ecma_transforms_optimization" } +swc_ecma_usage_analyzer = { version = "0.23.12", path = "../swc_ecma_usage_analyzer" } +swc_ecma_utils = { version = "0.127.12", path = "../swc_ecma_utils" } +swc_ecma_visit = { version = "0.98.6", path = "../swc_ecma_visit" } +swc_timer = { version = "0.21.20", path = "../swc_timer" } [dev-dependencies] -ansi_term = { workspace = true } -anyhow = { workspace = true } -criterion = { workspace = true } -pretty_assertions = { workspace = true } -walkdir = { workspace = true } +ansi_term = "0.12.1" +anyhow = "1" +criterion = "0.5.1" +pretty_assertions = "1.3" +walkdir = "2" -swc_ecma_testing = { version = "0.23.0", path = "../swc_ecma_testing" } +swc_ecma_testing = { version = "0.22.21", path = "../swc_ecma_testing" } swc_malloc = { version = "0.5.10", path = "../swc_malloc" } -testing = { version = "0.36.0", path = "../testing" } +testing = { version = "0.35.20", path = "../testing" } [[bench]] harness = false diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs new file mode 100644 index 000000000000..12dae1815073 --- /dev/null +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -0,0 +1,397 @@ +use phf::phf_set; +use swc_atoms::{Atom, JsWord}; +use swc_common::Span; +use swc_ecma_ast::{ + ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, + PropOrSpread, SeqExpr, Str, +}; +use swc_ecma_utils::{is_literal, prop_name_eq, undefined, ExprExt, Known}; + +use super::Pure; + +/// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function +static FUNCTION_SYMBOLS: phf::Set<&str> = phf_set!( + // Constructor + "constructor", + // Properties + "arguments", + "caller", + "displayName", + "length", + "name", + "prototype", + // Methods + "apply", + "bind", + "call", + "toString" +); + +/// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +static ARRAY_SYMBOLS: phf::Set<&str> = phf_set!( + // Constructor + "constructor", + // Properties + "length", + // Methods + "at", + "concat", + "copyWithin", + "entries", + "every", + "fill", + "filter", + "find", + "findIndex", + "findLast", + "findLastIndex", + "flat", + "flatMap", + "forEach", + "includes", + "indexOf", + "join", + "keys", + "lastIndexOf", + "map", + "pop", + "push", + "reduce", + "reduceRight", + "reverse", + "shift", + "slice", + "some", + "sort", + "splice", + "toLocaleString", + "toReversed", + "toSorted", + "toSpliced", + "toString", + "unshift", + "values", + "with" +); + +/// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String +static STRING_SYMBOLS: phf::Set<&str> = phf_set!( + // Constructor + "constructor", + // Properties + "length", + // Methods + "anchor", + "at", + "big", + "blink", + "bold", + "charAt", + "charCodeAt", + "codePointAt", + "concat", + "endsWith", + "fixed", + "fontcolor", + "fontsize", + "includes", + "indexOf", + "isWellFormed", + "italics", + "lastIndexOf", + "link", + "localeCompare", + "match", + "matchAll", + "normalize", + "padEnd", + "padStart", + "repeat", + "replace", + "replaceAll", + "search", + "slice", + "small", + "split", + "startsWith", + "strike", + "sub", + "substr", + "substring", + "sup", + "toLocaleLowerCase", + "toLocaleUpperCase", + "toLowerCase", + "toString", + "toUpperCase", + "toWellFormed", + "trim", + "trimEnd", + "trimStart", + "valueOf" +); + +/// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object +static OBJECT_SYMBOLS: phf::Set<&str> = phf_set!( + // Constructor + "constructor", + // Properties + "__proto__", + // Methods + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "toLocaleString", + "toString", + "valueOf", +); + +fn is_object_symbol(sym: &str) -> bool { + OBJECT_SYMBOLS.contains(sym) +} + +fn is_function_symbol(sym: &str) -> bool { + // Inherits: Object + FUNCTION_SYMBOLS.contains(sym) || is_object_symbol(sym) +} + +fn is_array_symbol(sym: &str) -> bool { + // Inherits: Function, Object + ARRAY_SYMBOLS.contains(sym) || is_function_symbol(sym) +} + +fn is_string_symbol(sym: &str) -> bool { + // Inherits: Function, Object + STRING_SYMBOLS.contains(sym) || is_function_symbol(sym) +} + +impl Pure<'_> { + /// Optimizes the following: + /// + /// - `''[0]`, `''[1]`, `''[-1]` -> `void 0` + /// - `''[[]]` -> `void 0` + /// - `''["a"]`, `''.a` -> `void 0` + /// + /// For String, Array and Object literals. + /// Special cases like `''.charCodeAt`, `[].push` etc are kept intact. + /// In-bound indexes (like `[1][0]`) and `length` are handled in the + /// simplifier. + /// + /// Does nothing if `pristine_globals` is `false`. + pub(super) fn optimize_member_expr(&mut self, obj: &Expr, prop: &MemberProp) -> Option { + if !self.options.pristine_globals { + return None; + } + + /// Taken from `simplify::expr`. + /// + /// `x.length` is handled as `IndexStr`, since `x.length` calls for + /// String and Array are handled in `simplify::expr` (the `length` + /// prototype for both of these types cannot be changed). + #[derive(Clone, PartialEq)] + enum KnownOp { + // [a, b][2] + // + // ({})[1] + Index(f64), + + /// ({}).foo + /// + /// ({}).length + IndexStr(JsWord), + } + + let op = match prop { + MemberProp::Ident(Ident { sym, .. }) => { + if self.in_callee { + return None; + } + + KnownOp::IndexStr(sym.clone()) + } + + MemberProp::Computed(c) => match &*c.expr { + Expr::Lit(Lit::Num(n)) => KnownOp::Index(n.value), + + Expr::Ident(..) => { + return None; + } + + _ => { + let Known(s) = c.expr.as_pure_string(&self.expr_ctx) else { + return None; + }; + + if let Ok(n) = s.parse::() { + KnownOp::Index(n) + } else { + KnownOp::IndexStr(JsWord::from(s)) + } + } + }, + + _ => { + return None; + } + }; + + match obj { + Expr::Lit(Lit::Str(Str { value, span, .. })) => { + match op { + KnownOp::Index(idx) => { + if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { + Some(*undefined(*span)) + } else { + // idx is in bounds, this is handled in simplify + None + } + } + + KnownOp::IndexStr(key) => { + if key == "length" { + // handled in simplify::expr + return None; + } + + if is_string_symbol(key.as_str()) { + None + } else { + Some(*undefined(*span)) + } + } + } + } + + Expr::Array(ArrayLit { elems, span, .. }) => { + // do nothing if spread exists + let has_spread = elems.iter().any(|elem| { + elem.as_ref() + .map(|elem| elem.spread.is_some()) + .unwrap_or(false) + }); + + if has_spread { + return None; + } + + // all expressions with side effects + let mut exprs = vec![]; + for elem in elems.iter().filter_map(|e| e.as_ref()) { + self.expr_ctx + .extract_side_effects_to(&mut exprs, (*elem.expr).clone()); + //todo clone + } + // if the array can be removed entirely without side effects. + // if true, side effects exist, and removing the array will + // potentially change the behaviour of the program. + // instead, we replace the MemberExpr with a SeqExpr of all + // elements with side effects, with undefined at the end. + let can_be_fully_removed = exprs.is_empty(); + + // Returns `undefined` if the array can be fully removed, + // or a SeqExpr with `undefined` at the end if there are side effects. + macro_rules! undefined { + () => { + if can_be_fully_removed { + *undefined(*span) + } else { + exprs.push(Box::new(*undefined(*span))); + + Expr::Seq(SeqExpr { span: *span, exprs }) + } + }; + } + + match op { + KnownOp::Index(idx) => { + if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() { + Some(undefined!()) + } else { + // idx is in bounds, this is handled in simplify + None + } + } + + KnownOp::IndexStr(key) => { + if key == "length" { + // handled in simplify::expr + return None; + } + + if is_array_symbol(key.as_str()) { + // replace with an array containing only side effects, + // e.g. [].push or [f()].push + let elems: Vec> = exprs + .into_iter() + .map(|e| { + Some(ExprOrSpread { + spread: None, + expr: e, + }) + }) + .collect(); + + Some(Expr::Member(MemberExpr { + span: *span, + obj: Box::new(Expr::Array(ArrayLit { span: *span, elems })), + prop: MemberProp::Ident(Ident::new(key, Span::default())), + })) + } else { + Some(undefined!()) + } + } + } + } + + Expr::Object(ObjectLit { props, span }) => { + // get key + let key = match op { + KnownOp::Index(i) => Atom::from(i.to_string()), + KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key, + _ => { + return None; + } + }; + + // do nothing if spread exists + let has_spread = props + .iter() + .any(|prop| matches!(prop, PropOrSpread::Spread(..))); + + if has_spread { + return None; + } + + let idx = props.iter().rev().position(|p| match p { + PropOrSpread::Prop(p) => match &**p { + Prop::Shorthand(i) => i.sym == key, + Prop::KeyValue(k) => prop_name_eq(&k.key, &key), + Prop::Assign(p) => p.key.sym == key, + Prop::Getter(..) => false, + Prop::Setter(..) => false, + // TODO + Prop::Method(..) => false, + }, + _ => unreachable!(), + }); + + // valid properties are handled in simplify::expr + if idx.map(|idx| props.len() - 1 - idx).is_some() { + return None; + } + + if is_object_symbol(key.as_str()) { + None + } else { + Some(*undefined(*span)) + } + } + + _ => None, + } + } +} diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 653683f7e253..bc8cf035d470 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -29,6 +29,7 @@ mod drop_console; mod evaluate; mod if_return; mod loops; +mod member_expr; mod misc; mod numbers; mod properties; @@ -66,6 +67,7 @@ pub(crate) fn pure_optimizer<'a>( data, ctx: Default::default(), changed: Default::default(), + in_callee: false, } } @@ -79,6 +81,7 @@ struct Pure<'a> { data: Option<&'a ProgramData>, ctx: Ctx, changed: bool, + in_callee: bool, } impl Repeated for Pure<'_> { @@ -289,6 +292,13 @@ impl VisitMut for Pure<'_> { self.optimize_arrow_body(body); } + fn visit_mut_callee(&mut self, callee: &mut Callee) { + let old_in_callee = self.in_callee; + self.in_callee = true; + callee.visit_mut_children_with(self); + self.in_callee = old_in_callee; + } + fn visit_mut_call_expr(&mut self, e: &mut CallExpr) { { let ctx = Ctx { @@ -382,6 +392,15 @@ impl VisitMut for Pure<'_> { } } + if let Some(member_expr) = e.as_member() { + if let Some(replacement) = + self.optimize_member_expr(&member_expr.obj, &member_expr.prop) + { + *e = replacement; + return; + } + } + self.eval_nested_tpl(e); if e.is_seq() { @@ -685,6 +704,7 @@ impl VisitMut for Pure<'_> { fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) { e.obj.visit_mut_with(self); + if let MemberProp::Computed(c) = &mut e.prop { c.visit_mut_with(self); diff --git a/crates/swc_ecma_minifier/tests/benches-full/vue.js b/crates/swc_ecma_minifier/tests/benches-full/vue.js index eef7427c2866..a48c5e2744ee 100644 --- a/crates/swc_ecma_minifier/tests/benches-full/vue.js +++ b/crates/swc_ecma_minifier/tests/benches-full/vue.js @@ -185,7 +185,7 @@ UA && UA.indexOf('android'); var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA) || 'ios' === weexPlatform; UA && /chrome\/\d+/.test(UA), UA && /phantomjs/.test(UA); - var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = {}.watch, supportsPassive = !1; + var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = void 0, supportsPassive = !1; if (inBrowser) try { var opts = {}; Object.defineProperty(opts, 'passive', { diff --git a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js index 7eb42feb65d3..eb611cad0feb 100644 --- a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js @@ -1,3 +1 @@ -[ - "foo" -][1].toUpperCase(); +(void 0).toUpperCase(); diff --git a/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js b/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js index 91bdb072f148..e6c92b799ba1 100644 --- a/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js +++ b/crates/swc_ecma_minifier/tests/full/size/e4dd4373c192c6fe2fc929bc55d1ed625b974338/output.js @@ -1 +1 @@ -[]({c(){a=({}).b}}); +[]({c(){a=void 0}}); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js b/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js index e2342a7a4269..e7333cadee7a 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/arrow/object_parens/output.js @@ -1,6 +1,6 @@ ()=>({}); ()=>({}); -()=>({})[0]; +()=>void 0; ()=>1; ()=>1; ()=>2; diff --git a/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js b/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js index 3dc75b4c85d7..b0fa090b0e35 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/comparing/issue_2857_6/output.js @@ -1,5 +1,5 @@ function f(a) { - if (null == ({}).b) return void 0 !== a.b && null !== a.b; + if (true) return void 0 !== a.b && null !== a.b; } console.log(f({ a: [ diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js index b2bb86265438..136efa1f74dd 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array_bad_index/output.js @@ -1,16 +1 @@ -console.log([ - 1, - 2, - 3, - 4 -].a + 1, [ - 1, - 2, - 3, - 4 -]["a"] + 1, [ - 1, - 2, - 3, - 4 -][3.14] + 1); +console.log(NaN, NaN, NaN); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js index 845ea5e68da4..ccd48d4a1a03 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_2a/output.js @@ -4,10 +4,4 @@ console.log(20); console.log(30); console.log(40); console.log(50); -console.log([ - 10, - 20, - 30, - 40, - 50 -][5]); +console.log(void 0); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js index 61dd47dc736c..818c44afcbb9 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3a/output.js @@ -1,24 +1,12 @@ console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js index 7ddb30b53629..450e86d1e6e8 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_3b/output.js @@ -1,10 +1,7 @@ var nothing = []; console.log(10); console.log(20); -console.log([ - 10, - 20 -][2]); +console.log(void 0); console.log([ ...nothing, 10, diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js index 80c6cfa44f68..38247e0dcfd2 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/array_literal_with_spread_4a/output.js @@ -6,47 +6,23 @@ console.log([ t(1), t(2) ][0]); -console.log([ - t(1), - t(2) -][1]); -console.log([ - t(1), - t(2) -][2]); +console.log((t(1), t(2))); +console.log((t(1), void t(2))); console.log([ t(1), t(2) ][0]); -console.log([ - t(1), - t(2) -][1]); -console.log([ - t(1), - t(2) -][2]); +console.log((t(1), t(2))); +console.log((t(1), void t(2))); console.log([ t(1), t(2) ][0]); -console.log([ - t(1), - t(2) -][1]); -console.log([ - t(1), - t(2) -][2]); +console.log((t(1), t(2))); +console.log((t(1), void t(2))); console.log([ t(1), t(2) ][0]); -console.log([ - t(1), - t(2) -][1]); -console.log([ - t(1), - t(2) -][2]); +console.log((t(1), t(2))); +console.log((t(1), void t(2))); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/harmony/issue_2345/output.js b/crates/swc_ecma_minifier/tests/terser/compress/harmony/issue_2345/output.js index d10c79467ad9..f41496e19e7e 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/harmony/issue_2345/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/harmony/issue_2345/output.js @@ -1,3 +1,9 @@ console.log("3-2-1"); -var a = [3, 2, 1]; -console.log([...a].join("-")); +var a = [ + 3, + 2, + 1 +]; +console.log([ + ...a +].join("-")); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/properties/evaluate_array_length/output.js b/crates/swc_ecma_minifier/tests/terser/compress/properties/evaluate_array_length/output.js index 024fde7d89a3..b7dead595497 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/properties/evaluate_array_length/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/properties/evaluate_array_length/output.js @@ -1,4 +1,12 @@ a = 3; a = 5; -a = [1, 2, b].length; -a = [1, 2, 3].join(b).length; +a = [ + 1, + 2, + b +].length; +a = [ + 1, + 2, + 3 +].join(b).length; diff --git a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_2450_5/output.js b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_2450_5/output.js index 30bb2374c906..28398ae3857d 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_2450_5/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_2450_5/output.js @@ -1,7 +1,11 @@ var a; function g() {} -[1, 2, 3].forEach(function () { - (function (b) { +[ + 1, + 2, + 3 +].forEach(function() { + (function(b) { console.log(a === b); a = b; })(g); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_1/output.js b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_1/output.js index dfb06fba21ff..b396675941b2 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_1/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_1/output.js @@ -1,5 +1,8 @@ function f() {} -var a = [1, 2].map(function () { +var a = [ + 1, + 2 +].map(function() { return new f(); }); console.log(a[0].constructor === a[1].constructor); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_2/output.js b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_2/output.js index 4ae9f4d0452b..e091db1bf17b 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_2/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/issue_3042_2/output.js @@ -1,13 +1,16 @@ function Foo() { - this.isFoo = function (o) { + this.isFoo = function(o) { return o instanceof Foo; }; } -var fooCollection = new (function () { - this.foos = [1, 1].map(function () { +var fooCollection = new function() { + this.foos = [ + 1, + 1 + ].map(function() { return new Foo(); }); -})(); +}(); console.log(fooCollection.foos[0].isFoo(fooCollection.foos[0])); console.log(fooCollection.foos[0].isFoo(fooCollection.foos[1])); console.log(fooCollection.foos[1].isFoo(fooCollection.foos[0])); From d96c99876fc34a0078236f18279e7e5b3ec7acd7 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 2 Apr 2024 08:48:41 +1300 Subject: [PATCH 27/80] cargo fmt --- .../swc_ecma_transforms_optimization/src/simplify/expr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index b6ad6e685442..5177f7f8e3a3 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -248,7 +248,7 @@ impl SimplifyExpr { if may_have_side_effects { return; } - + // Prototype changes do not affect .length self.changed = true; From 3acffd1e0f4f87e783310dbaf17c9869ba3088f0 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 3 Apr 2024 01:47:28 +1300 Subject: [PATCH 28/80] String does not appear to inherit Function --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 12dae1815073..0840870c5975 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -165,8 +165,9 @@ fn is_array_symbol(sym: &str) -> bool { } fn is_string_symbol(sym: &str) -> bool { - // Inherits: Function, Object - STRING_SYMBOLS.contains(sym) || is_function_symbol(sym) + // Inherits: Object + // MDN says this implements Function, but it doesn't appear to + STRING_SYMBOLS.contains(sym) || is_object_symbol(sym) } impl Pure<'_> { From 01d5b382026eb254c8751d2aa8a8bdb30b1047a3 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 3 Apr 2024 01:51:15 +1300 Subject: [PATCH 29/80] Array also does not inherit Function --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 0840870c5975..af3db2803acc 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -160,8 +160,8 @@ fn is_function_symbol(sym: &str) -> bool { } fn is_array_symbol(sym: &str) -> bool { - // Inherits: Function, Object - ARRAY_SYMBOLS.contains(sym) || is_function_symbol(sym) + // Inherits: Object + ARRAY_SYMBOLS.contains(sym) || is_object_symbol(sym) } fn is_string_symbol(sym: &str) -> bool { From 42bc03bc7feff82f5a226f3f4f284c8e626dd343 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 3 Apr 2024 01:51:28 +1300 Subject: [PATCH 30/80] Remove comment --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index af3db2803acc..eba8ce887f10 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -166,7 +166,6 @@ fn is_array_symbol(sym: &str) -> bool { fn is_string_symbol(sym: &str) -> bool { // Inherits: Object - // MDN says this implements Function, but it doesn't appear to STRING_SYMBOLS.contains(sym) || is_object_symbol(sym) } From 15b42f198ec32a19f4f7ddbc5598ca1d571bc02f Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 3 Apr 2024 01:51:58 +1300 Subject: [PATCH 31/80] `FUNCTION_SYMBOLS` is no longer required --- .../src/compress/pure/member_expr.rs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index eba8ce887f10..2fa6e7637ed7 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -9,24 +9,6 @@ use swc_ecma_utils::{is_literal, prop_name_eq, undefined, ExprExt, Known}; use super::Pure; -/// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function -static FUNCTION_SYMBOLS: phf::Set<&str> = phf_set!( - // Constructor - "constructor", - // Properties - "arguments", - "caller", - "displayName", - "length", - "name", - "prototype", - // Methods - "apply", - "bind", - "call", - "toString" -); - /// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array static ARRAY_SYMBOLS: phf::Set<&str> = phf_set!( // Constructor @@ -154,11 +136,6 @@ fn is_object_symbol(sym: &str) -> bool { OBJECT_SYMBOLS.contains(sym) } -fn is_function_symbol(sym: &str) -> bool { - // Inherits: Object - FUNCTION_SYMBOLS.contains(sym) || is_object_symbol(sym) -} - fn is_array_symbol(sym: &str) -> bool { // Inherits: Object ARRAY_SYMBOLS.contains(sym) || is_object_symbol(sym) From f59430d58c25201a9d226dc394a9a30fe687272f Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 3 Apr 2024 07:06:30 +1300 Subject: [PATCH 32/80] Add unit test files --- .../tests/fixture/member_expr/1/input.js | 72 +++++++++++++++++++ .../tests/fixture/member_expr/1/output.js | 1 + .../tests/fixture/member_expr/2/input.js | 62 ++++++++++++++++ .../tests/fixture/member_expr/2/output.js | 51 +++++++++++++ .../tests/fixture/member_expr/3/input.js | 20 ++++++ .../tests/fixture/member_expr/3/output.js | 13 ++++ .../tests/fixture/member_expr/config.json | 5 ++ 7 files changed, 224 insertions(+) create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/1/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/2/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/config.json diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/1/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/1/input.js new file mode 100644 index 000000000000..95bb389c82e2 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/1/input.js @@ -0,0 +1,72 @@ +// Invalid +''[0]; +''[1]; +''[-1]; + +''.invalid; +''["invalid"]; +''[[]]; +''[0+[]]; + +// Object symbols +''.constructor; +''.__proto__; +''.__defineGetter__; +''.__defineSetter__; +''.__lookupGetter__; +''.__lookupSetter__; +''.hasOwnProperty; +''.isPrototypeOf; +''.propertyIsEnumerable; +''.toLocaleString; +''.toString; +''.valueOf; + +// String symbols +''.length; +''.anchor; +''.at; +''.big; +''.blink; +''.bold; +''.charAt; +''.charCodeAt; +''.codePointAt; +''.concat; +''.endsWith; +''.fixed; +''.fontcolor; +''.fontsize; +''.includes; +''.indexOf; +''.isWellFormed; +''.italics; +''.lastIndexOf; +''.link; +''.localeCompare; +''.match; +''.matchAll; +''.normalize; +''.padEnd; +''.padStart; +''.repeat; +''.replace; +''.replaceAll; +''.search; +''.slice; +''.small; +''.split; +''.startsWith; +''.strike; +''.sub; +''.substr; +''.substring; +''.sup; +''.toLocaleLowerCase; +''.toLocaleUpperCase; +''.toLowerCase; +''.toUpperCase; +''.toWellFormed; +''.trim; +''.trimEnd; +''.trimStart; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js new file mode 100644 index 000000000000..3b9c46b02e81 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js @@ -0,0 +1 @@ +''.constructor, ''.__proto__, ''.__defineGetter__, ''.__defineSetter__, ''.__lookupGetter__, ''.__lookupSetter__, ''.hasOwnProperty, ''.isPrototypeOf, ''.propertyIsEnumerable, ''.toLocaleString, ''.valueOf, ''.anchor, ''.at, ''.big, ''.blink, ''.bold, ''.codePointAt, ''.fixed, ''.fontcolor, ''.fontsize, ''.isWellFormed, ''.italics, ''.link, ''.match, ''.matchAll, ''.normalize, ''.padEnd, ''.padStart, ''.repeat, ''.replace, ''.replaceAll, ''.search, ''.small, ''.strike, ''.sub, ''.sup, ''.toWellFormed; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/2/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/2/input.js new file mode 100644 index 000000000000..1c3554b426aa --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/2/input.js @@ -0,0 +1,62 @@ +// Invalid +[][0]; +[][1]; +[][-1]; + +[].invalid; +[]["invalid"]; +[][[]]; +[][0+[]]; + +// Object symbols +[].constructor; +[].__proto__; +[].__defineGetter__; +[].__defineSetter__; +[].__lookupGetter__; +[].__lookupSetter__; +[].hasOwnProperty; +[].isPrototypeOf; +[].propertyIsEnumerable; +[].toLocaleString; +[].toString; +[].valueOf; + +// Array symbols +[].length; +[].at; +[].concat; +[].copyWithin; +[].entries; +[].every; +[].fill; +[].filter; +[].find; +[].findIndex; +[].findLast; +[].findLastIndex; +[].flat; +[].flatMap; +[].forEach; +[].includes; +[].indexOf; +[].join; +[].keys; +[].lastIndexOf; +[].map; +[].pop; +[].push; +[].reduce; +[].reduceRight; +[].reverse; +[].shift; +[].slice; +[].some; +[].sort; +[].splice; +[].toReversed; +[].toSorted; +[].toSpliced; +[].unshift; +[].values; +[].with; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js new file mode 100644 index 000000000000..ff9397085d66 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js @@ -0,0 +1,51 @@ +[].constructor; +[].__proto__; +[].__defineGetter__; +[].__defineSetter__; +[].__lookupGetter__; +[].__lookupSetter__; +[].hasOwnProperty; +[].isPrototypeOf; +[].propertyIsEnumerable; +[].toLocaleString; +[].toString; +[].valueOf; + +// Array symbols +[].length; +[].at; +[].concat; +[].copyWithin; +[].entries; +[].every; +[].fill; +[].filter; +[].find; +[].findIndex; +[].findLast; +[].findLastIndex; +[].flat; +[].flatMap; +[].forEach; +[].includes; +[].indexOf; +[].join; +[].keys; +[].lastIndexOf; +[].map; +[].pop; +[].push; +[].reduce; +[].reduceRight; +[].reverse; +[].shift; +[].slice; +[].some; +[].sort; +[].splice; +[].toReversed; +[].toSorted; +[].toSpliced; +[].unshift; +[].values; +[].with; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js new file mode 100644 index 000000000000..a24003f174df --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js @@ -0,0 +1,20 @@ +// Invalid +({})[0]; +({}).invalid; +({})["invalid"]; +({})[[]]; +({})[0+[]]; + +// Object symbols +[].constructor; +[].__proto__; +[].__defineGetter__; +[].__defineSetter__; +[].__lookupGetter__; +[].__lookupSetter__; +[].hasOwnProperty; +[].isPrototypeOf; +[].propertyIsEnumerable; +[].toLocaleString; +[].toString; +[].valueOf; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js new file mode 100644 index 000000000000..6f15edd1726e --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js @@ -0,0 +1,13 @@ +// Object symbols +[].constructor; +[].__proto__; +[].__defineGetter__; +[].__defineSetter__; +[].__lookupGetter__; +[].__lookupSetter__; +[].hasOwnProperty; +[].isPrototypeOf; +[].propertyIsEnumerable; +[].toLocaleString; +[].toString; +[].valueOf; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json b/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json new file mode 100644 index 000000000000..4d73347ae757 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json @@ -0,0 +1,5 @@ +{ + "defaults": true, + "toplevel": true, + "unused": "true" +} From 13c0eed8595a64b100d6e17aff64b6cae67be482 Mon Sep 17 00:00:00 2001 From: levi Date: Wed, 3 Apr 2024 07:42:50 +1300 Subject: [PATCH 33/80] Update test files --- .../tests/fixture/member_expr/2/output.js | 52 +------------------ .../tests/fixture/member_expr/3/output.js | 14 +---- .../tests/fixture/member_expr/config.json | 2 +- 3 files changed, 3 insertions(+), 65 deletions(-) diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js index ff9397085d66..c089af16973e 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js @@ -1,51 +1 @@ -[].constructor; -[].__proto__; -[].__defineGetter__; -[].__defineSetter__; -[].__lookupGetter__; -[].__lookupSetter__; -[].hasOwnProperty; -[].isPrototypeOf; -[].propertyIsEnumerable; -[].toLocaleString; -[].toString; -[].valueOf; - -// Array symbols -[].length; -[].at; -[].concat; -[].copyWithin; -[].entries; -[].every; -[].fill; -[].filter; -[].find; -[].findIndex; -[].findLast; -[].findLastIndex; -[].flat; -[].flatMap; -[].forEach; -[].includes; -[].indexOf; -[].join; -[].keys; -[].lastIndexOf; -[].map; -[].pop; -[].push; -[].reduce; -[].reduceRight; -[].reverse; -[].shift; -[].slice; -[].some; -[].sort; -[].splice; -[].toReversed; -[].toSorted; -[].toSpliced; -[].unshift; -[].values; -[].with; +[].constructor, [].__proto__, [].__defineGetter__, [].__defineSetter__, [].__lookupGetter__, [].__lookupSetter__, [].hasOwnProperty, [].isPrototypeOf, [].propertyIsEnumerable, [].toLocaleString, [].toString, [].valueOf, [].at, [].concat, [].copyWithin, [].entries, [].every, [].fill, [].filter, [].find, [].findIndex, [].findLast, [].findLastIndex, [].flat, [].flatMap, [].forEach, [].includes, [].indexOf, [].join, [].keys, [].lastIndexOf, [].map, [].pop, [].push, [].reduce, [].reduceRight, [].reverse, [].shift, [].slice, [].some, [].sort, [].splice, [].toReversed, [].toSorted, [].toSpliced, [].unshift, [].values, [].with; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js index 6f15edd1726e..fff8179a172f 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js @@ -1,13 +1 @@ -// Object symbols -[].constructor; -[].__proto__; -[].__defineGetter__; -[].__defineSetter__; -[].__lookupGetter__; -[].__lookupSetter__; -[].hasOwnProperty; -[].isPrototypeOf; -[].propertyIsEnumerable; -[].toLocaleString; -[].toString; -[].valueOf; +[].constructor, [].__proto__, [].__defineGetter__, [].__defineSetter__, [].__lookupGetter__, [].__lookupSetter__, [].hasOwnProperty, [].isPrototypeOf, [].propertyIsEnumerable, [].toLocaleString, [].toString, [].valueOf; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json b/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json index 4d73347ae757..bd2c83ced03a 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json @@ -1,5 +1,5 @@ { "defaults": true, "toplevel": true, - "unused": "true" + "unused": true } From 9d1864e297fac24539d2a53c8df90319f4e08513 Mon Sep 17 00:00:00 2001 From: levi Date: Thu, 4 Apr 2024 01:19:21 +1300 Subject: [PATCH 34/80] Update test config --- .../tests/fixture/member_expr/1/output.js | 2 +- .../tests/fixture/member_expr/3/input.js | 34 +++++++++---------- .../tests/fixture/member_expr/3/output.js | 2 +- .../tests/fixture/member_expr/config.json | 3 +- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js index 3b9c46b02e81..d0c8fc31bfac 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js @@ -1 +1 @@ -''.constructor, ''.__proto__, ''.__defineGetter__, ''.__defineSetter__, ''.__lookupGetter__, ''.__lookupSetter__, ''.hasOwnProperty, ''.isPrototypeOf, ''.propertyIsEnumerable, ''.toLocaleString, ''.valueOf, ''.anchor, ''.at, ''.big, ''.blink, ''.bold, ''.codePointAt, ''.fixed, ''.fontcolor, ''.fontsize, ''.isWellFormed, ''.italics, ''.link, ''.match, ''.matchAll, ''.normalize, ''.padEnd, ''.padStart, ''.repeat, ''.replace, ''.replaceAll, ''.search, ''.small, ''.strike, ''.sub, ''.sup, ''.toWellFormed; +''.constructor, ''.__proto__, ''.__defineGetter__, ''.__defineSetter__, ''.__lookupGetter__, ''.__lookupSetter__, ''.hasOwnProperty, ''.isPrototypeOf, ''.propertyIsEnumerable, ''.toLocaleString, ''.toString, ''.valueOf, ''.anchor, ''.at, ''.big, ''.blink, ''.bold, ''.charAt, ''.charCodeAt, ''.codePointAt, ''.concat, ''.endsWith, ''.fixed, ''.fontcolor, ''.fontsize, ''.includes, ''.indexOf, ''.isWellFormed, ''.italics, ''.lastIndexOf, ''.link, ''.localeCompare, ''.match, ''.matchAll, ''.normalize, ''.padEnd, ''.padStart, ''.repeat, ''.replace, ''.replaceAll, ''.search, ''.slice, ''.small, ''.split, ''.startsWith, ''.strike, ''.sub, ''.substr, ''.substring, ''.sup, ''.toLocaleLowerCase, ''.toLocaleUpperCase, ''.toLowerCase, ''.toUpperCase, ''.toWellFormed, ''.trim, ''.trimEnd, ''.trimStart; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js index a24003f174df..1eee3862fcb7 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js @@ -1,20 +1,20 @@ // Invalid -({})[0]; -({}).invalid; -({})["invalid"]; -({})[[]]; -({})[0+[]]; +({}[0]); +({}.invalid); +({}["invalid"]); +({}[[]]); +({}[0+[]]); // Object symbols -[].constructor; -[].__proto__; -[].__defineGetter__; -[].__defineSetter__; -[].__lookupGetter__; -[].__lookupSetter__; -[].hasOwnProperty; -[].isPrototypeOf; -[].propertyIsEnumerable; -[].toLocaleString; -[].toString; -[].valueOf; +({}.constructor); +({}.__proto__); +({}.__defineGetter__); +({}.__defineSetter__); +({}.__lookupGetter__); +({}.__lookupSetter__); +({}.hasOwnProperty); +({}.isPrototypeOf); +({}.propertyIsEnumerable); +({}.toLocaleString); +({}.toString); +({}.valueOf); diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js index fff8179a172f..e82e2350aece 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js @@ -1 +1 @@ -[].constructor, [].__proto__, [].__defineGetter__, [].__defineSetter__, [].__lookupGetter__, [].__lookupSetter__, [].hasOwnProperty, [].isPrototypeOf, [].propertyIsEnumerable, [].toLocaleString, [].toString, [].valueOf; +({}).constructor, ({}).__proto__, ({}).__defineGetter__, ({}).__defineSetter__, ({}).__lookupGetter__, ({}).__lookupSetter__, ({}).hasOwnProperty, ({}).isPrototypeOf, ({}).propertyIsEnumerable, ({}).toLocaleString, ({}).toString, ({}).valueOf; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json b/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json index bd2c83ced03a..f91482cbb1d1 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/config.json @@ -1,5 +1,6 @@ { "defaults": true, "toplevel": true, - "unused": true + "unused": false, + "dead_code": false } From f7e608b3d3157951db8c6c9327a8e619fb086865 Mon Sep 17 00:00:00 2001 From: levi Date: Thu, 4 Apr 2024 01:26:28 +1300 Subject: [PATCH 35/80] Better test dir names --- .../tests/fixture/member_expr/{2 => array}/input.js | 0 .../tests/fixture/member_expr/{2 => array}/output.js | 0 .../tests/fixture/member_expr/{3 => object}/input.js | 0 .../tests/fixture/member_expr/{3 => object}/output.js | 0 .../tests/fixture/member_expr/{1 => string}/input.js | 0 .../tests/fixture/member_expr/{1 => string}/output.js | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename crates/swc_ecma_minifier/tests/fixture/member_expr/{2 => array}/input.js (100%) rename crates/swc_ecma_minifier/tests/fixture/member_expr/{2 => array}/output.js (100%) rename crates/swc_ecma_minifier/tests/fixture/member_expr/{3 => object}/input.js (100%) rename crates/swc_ecma_minifier/tests/fixture/member_expr/{3 => object}/output.js (100%) rename crates/swc_ecma_minifier/tests/fixture/member_expr/{1 => string}/input.js (100%) rename crates/swc_ecma_minifier/tests/fixture/member_expr/{1 => string}/output.js (100%) diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/2/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/array/input.js similarity index 100% rename from crates/swc_ecma_minifier/tests/fixture/member_expr/2/input.js rename to crates/swc_ecma_minifier/tests/fixture/member_expr/array/input.js diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/array/output.js similarity index 100% rename from crates/swc_ecma_minifier/tests/fixture/member_expr/2/output.js rename to crates/swc_ecma_minifier/tests/fixture/member_expr/array/output.js diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/object/input.js similarity index 100% rename from crates/swc_ecma_minifier/tests/fixture/member_expr/3/input.js rename to crates/swc_ecma_minifier/tests/fixture/member_expr/object/input.js diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/object/output.js similarity index 100% rename from crates/swc_ecma_minifier/tests/fixture/member_expr/3/output.js rename to crates/swc_ecma_minifier/tests/fixture/member_expr/object/output.js diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/1/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/string/input.js similarity index 100% rename from crates/swc_ecma_minifier/tests/fixture/member_expr/1/input.js rename to crates/swc_ecma_minifier/tests/fixture/member_expr/string/input.js diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/string/output.js similarity index 100% rename from crates/swc_ecma_minifier/tests/fixture/member_expr/1/output.js rename to crates/swc_ecma_minifier/tests/fixture/member_expr/string/output.js From 0268ff634be567aee03bd100ba1caa86b378fbe3 Mon Sep 17 00:00:00 2001 From: levi Date: Thu, 4 Apr 2024 03:33:55 +1300 Subject: [PATCH 36/80] Remove evil clone --- .../src/compress/pure/member_expr.rs | 154 +++++++++++------- .../src/compress/pure/mod.rs | 4 +- 2 files changed, 96 insertions(+), 62 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 2fa6e7637ed7..e8d935819ce7 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -159,7 +159,11 @@ impl Pure<'_> { /// simplifier. /// /// Does nothing if `pristine_globals` is `false`. - pub(super) fn optimize_member_expr(&mut self, obj: &Expr, prop: &MemberProp) -> Option { + pub(super) fn optimize_member_expr( + &mut self, + obj: &mut Expr, + prop: &MemberProp, + ) -> Option { if !self.options.pristine_globals { return None; } @@ -255,73 +259,103 @@ impl Pure<'_> { return None; } - // all expressions with side effects - let mut exprs = vec![]; - for elem in elems.iter().filter_map(|e| e.as_ref()) { - self.expr_ctx - .extract_side_effects_to(&mut exprs, (*elem.expr).clone()); - //todo clone + // A match expression would be easier to read, but we need to do it this way + // unless we want to clone elements or use a macro and deal with mutability + // since we need side effect extraction in multiple places. + // To be honest, this is probably a lot easier to read and offers minimal + // code size anyway. + + let (is_idx_out_of_bounds, key, is_array_symbol, is_length) = match op { + KnownOp::Index(i) => ( + i.fract() != 0.0 || i < 0.0 || i as usize >= elems.len(), + None, + false, + false, + ), + + KnownOp::IndexStr(key) => { + let is_array_symbol = is_array_symbol(key.as_str()); + let is_length = key == "length"; + + (false, Some(key), is_array_symbol, is_length) + } + }; + + if is_length { + // Handled in simplify::expr + return None; } - // if the array can be removed entirely without side effects. - // if true, side effects exist, and removing the array will - // potentially change the behaviour of the program. - // instead, we replace the MemberExpr with a SeqExpr of all - // elements with side effects, with undefined at the end. - let can_be_fully_removed = exprs.is_empty(); - - // Returns `undefined` if the array can be fully removed, - // or a SeqExpr with `undefined` at the end if there are side effects. - macro_rules! undefined { - () => { - if can_be_fully_removed { - *undefined(*span) - } else { - exprs.push(Box::new(*undefined(*span))); - Expr::Seq(SeqExpr { span: *span, exprs }) - } - }; + // If the result is undefined. + // In this case, the optimized expression is: + // (x, y, undefined) + // where x and y are side effects. + // If no side effects exist, the result is simply `undefined` instead of a SeqExpr. + let is_result_undefined = is_idx_out_of_bounds || (key.is_some() && !is_array_symbol); + + // Elements with side effects. + // Will be empty if we don't need side effects. + let mut side_effects = vec![]; + // If we need to compute side effects. + let need_side_effects = is_result_undefined || is_array_symbol; + if need_side_effects { + // Move all side effects into side_effects. + // This completely drains elems. + elems + .drain(..) + .into_iter() + .filter_map(|x| x) + .for_each(|elem| { + self.expr_ctx + .extract_side_effects_to(&mut side_effects, *elem.expr); + }); } - match op { - KnownOp::Index(idx) => { - if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() { - Some(undefined!()) - } else { - // idx is in bounds, this is handled in simplify - None - } + if is_result_undefined { + // Optimization is `undefined`. + + if side_effects.is_empty() { + // No side effects, no need for SeqExpr + return Some(*undefined(*span)); } - KnownOp::IndexStr(key) => { - if key == "length" { - // handled in simplify::expr - return None; - } + // Add undefined to end + side_effects.push(Box::new(*undefined(*span))); - if is_array_symbol(key.as_str()) { - // replace with an array containing only side effects, - // e.g. [].push or [f()].push - let elems: Vec> = exprs - .into_iter() - .map(|e| { - Some(ExprOrSpread { - spread: None, - expr: e, - }) - }) - .collect(); - - Some(Expr::Member(MemberExpr { - span: *span, - obj: Box::new(Expr::Array(ArrayLit { span: *span, elems })), - prop: MemberProp::Ident(Ident::new(key, Span::default())), - })) - } else { - Some(undefined!()) - } - } + return Some(Expr::Seq(SeqExpr { + span: *span, + exprs: side_effects, + })); + } + + if is_array_symbol { + // Optimization is the same array but with only side effects. + // e.g. [1, 2, f()].push becomes [f()].push + + // property + let key = match key { + Some(x) => x, + None => unreachable!(), + }; + + let elems: Vec> = side_effects + .into_iter() + .map(|e| { + Some(ExprOrSpread { + spread: None, + expr: e, + }) + }) + .collect(); + + return Some(Expr::Member(MemberExpr { + span: *span, + obj: Box::new(Expr::Array(ArrayLit { span: *span, elems })), + prop: MemberProp::Ident(Ident::new(key, *span)), + })); } + + return None; } Expr::Object(ObjectLit { props, span }) => { diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index bc8cf035d470..29862b4e2513 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -392,9 +392,9 @@ impl VisitMut for Pure<'_> { } } - if let Some(member_expr) = e.as_member() { + if let Expr::Member(member_expr) = e { if let Some(replacement) = - self.optimize_member_expr(&member_expr.obj, &member_expr.prop) + self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) { *e = replacement; return; From 8d5ae304c5a7fcc80b19cdd0f49075a4e150b11b Mon Sep 17 00:00:00 2001 From: levi Date: Thu, 4 Apr 2024 03:34:20 +1300 Subject: [PATCH 37/80] I don't know why this changed, but it did. --- .../compress/evaluate/unsafe_array/output.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array/output.js index a8167d185605..7509b863c954 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/unsafe_array/output.js @@ -1,12 +1,11 @@ -console.log( - void 0, - [1, 2, 3, a] + 1, - "1,2,3,41", - [1, 2, 3, a][0] + 1, +console.log(void 0, [ + 1, 2, 3, - NaN, - "1,21", - 5, - (void 0)[1] + 1 -); + a +] + 1, "1,2,3,41", [ + 1, + 2, + 3, + a +][0] + 1, 2, 3, NaN, "1,21", 5, (void 0)[1] + 1); From af09c0190bfcecc62026070f0ec98f18fe1ced16 Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 4 Apr 2024 04:50:37 +1300 Subject: [PATCH 38/80] Add array side effect unit test --- .../member_expr/array_side_effects/input.js | 22 +++++++++++++++++++ .../member_expr/array_side_effects/output.js | 7 ++++++ 2 files changed, 29 insertions(+) create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/output.js diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/input.js new file mode 100644 index 000000000000..0a5ece99c9a9 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/input.js @@ -0,0 +1,22 @@ +// Out of bounds +f([][-1]); +f([][1]); +f([][[]]); +f([][0+[]]); + +f([x(), 2, 'a', 1+1, y()][-1]); +f([x(), 2, 'a', 1+1, y()][10]); + +// Invalid property +f([].invalid); +f([]["invalid"]); + +f([x(), 2, 'a', 1+1, y()].invalid); +f([x(), 2, 'a', 1+1, y()]["invalid"]); + +// Valid property +f([].push); +f([]["push"]); + +f([x(), 2, 'a', 1+1, y()].push); +f([x(), 2, 'a', 1+1, y()]["push"]); diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/output.js new file mode 100644 index 000000000000..0eb89962e0ab --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/array_side_effects/output.js @@ -0,0 +1,7 @@ +f(void 0), f(void 0), f(void 0), f(void 0), f((x(), void y())), f((x(), void y())), f(void 0), f(void 0), f((x(), void y())), f((x(), void y())), f([].push), f([].push), f([ + x(), + y() +].push), f([ + x(), + y() +].push); From 167e671d5c93c5e7740b105cf8ff57f633ca66a1 Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 4 Apr 2024 05:10:22 +1300 Subject: [PATCH 39/80] Don't optimize if inside left-hand side of AssignExpr --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 2 +- crates/swc_ecma_minifier/src/compress/pure/mod.rs | 7 +++++++ .../tests/fixture/member_expr/assignment/input.js | 1 + .../tests/fixture/member_expr/assignment/output.js | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/output.js diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index e8d935819ce7..5b45b9a79ea3 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -164,7 +164,7 @@ impl Pure<'_> { obj: &mut Expr, prop: &MemberProp, ) -> Option { - if !self.options.pristine_globals { + if !self.options.pristine_globals || self.in_left_side_assign { return None; } diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 29862b4e2513..ee8fd29c5cfb 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -68,6 +68,7 @@ pub(crate) fn pure_optimizer<'a>( ctx: Default::default(), changed: Default::default(), in_callee: false, + in_left_side_assign: false } } @@ -82,6 +83,7 @@ struct Pure<'a> { ctx: Ctx, changed: bool, in_callee: bool, + in_left_side_assign: bool } impl Repeated for Pure<'_> { @@ -260,11 +262,16 @@ impl VisitMut for Pure<'_> { fn visit_mut_assign_expr(&mut self, e: &mut AssignExpr) { { + let old_in_left_side_assign = self.in_left_side_assign; + self.in_left_side_assign = true; + let ctx = Ctx { is_lhs_of_assign: true, ..self.ctx }; e.left.visit_mut_children_with(&mut *self.with_ctx(ctx)); + + self.in_left_side_assign = old_in_left_side_assign; } e.right.visit_mut_with(self); diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/input.js new file mode 100644 index 000000000000..7e5c43b52f98 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/input.js @@ -0,0 +1 @@ +f({}.x = 5); \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/output.js new file mode 100644 index 000000000000..cec899c133d3 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/assignment/output.js @@ -0,0 +1 @@ +f({}.x = 5); From 194249e96a15dce936ff17d476498c1d5009749e Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 4 Apr 2024 05:27:06 +1300 Subject: [PATCH 40/80] Remove redundant import --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 5b45b9a79ea3..cf4cb1c3e8bf 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -1,6 +1,5 @@ use phf::phf_set; use swc_atoms::{Atom, JsWord}; -use swc_common::Span; use swc_ecma_ast::{ ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, PropOrSpread, SeqExpr, Str, From f82d61579387e57e2a0a7712112e84c7f21a591a Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 4 Apr 2024 06:40:12 +1300 Subject: [PATCH 41/80] Move call to end --- .../swc_ecma_minifier/src/compress/pure/mod.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index ee8fd29c5cfb..57e199ae1f56 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -399,15 +399,6 @@ impl VisitMut for Pure<'_> { } } - if let Expr::Member(member_expr) = e { - if let Some(replacement) = - self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) - { - *e = replacement; - return; - } - } - self.eval_nested_tpl(e); if e.is_seq() { @@ -604,6 +595,14 @@ impl VisitMut for Pure<'_> { if e.is_seq() { debug_assert_valid(e); } + + if let Expr::Member(member_expr) = e { + if let Some(replacement) = + self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) + { + *e = replacement; + } + } } fn visit_mut_expr_or_spreads(&mut self, nodes: &mut Vec) { From 9bbedee946ead7acda5ad20b7eaa09b1f8b764fa Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 4 Apr 2024 06:48:46 +1300 Subject: [PATCH 42/80] Cleanup iterator call --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index cf4cb1c3e8bf..343f3f1ea7a1 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -302,8 +302,7 @@ impl Pure<'_> { // This completely drains elems. elems .drain(..) - .into_iter() - .filter_map(|x| x) + .flatten() .for_each(|elem| { self.expr_ctx .extract_side_effects_to(&mut side_effects, *elem.expr); From 8a9e2cff9e906eb70c2c54d0f854023a82c657c5 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 8 Apr 2024 17:14:25 +1200 Subject: [PATCH 43/80] Handle side effects and `__proto__` in object compression --- .../src/compress/pure/member_expr.rs | 157 ++++++++++++++---- .../member_expr/object_side_effects/input.js | 17 ++ .../member_expr/object_side_effects/output.js | 1 + .../compress/evaluate/prop_function/output.js | 14 +- .../compress/properties/lhs_prop_1/output.js | 4 +- .../compress/reduce_vars/obj_for_1/output.js | 4 +- 6 files changed, 158 insertions(+), 39 deletions(-) create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/output.js diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 343f3f1ea7a1..3ff10a188d64 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -4,7 +4,7 @@ use swc_ecma_ast::{ ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, PropOrSpread, SeqExpr, Str, }; -use swc_ecma_utils::{is_literal, prop_name_eq, undefined, ExprExt, Known}; +use swc_ecma_utils::{prop_name_eq, undefined, ExprExt, Known}; use super::Pure; @@ -145,6 +145,82 @@ fn is_string_symbol(sym: &str) -> bool { STRING_SYMBOLS.contains(sym) || is_object_symbol(sym) } +/// Checks if the given key exists in the given properties, taking the `__proto__` property +/// and order of keys into account (the order of keys matters for nested `__proto__` properties). +/// +/// Returns `None` if the key's existence is uncertain, or `Some` if it is certain. +/// +/// A key's existence is uncertain if a `__proto__` property exists and the value +/// is non-literal. +fn does_key_exist(key: &str, props: &Vec) -> Option { + for prop in props { + match prop { + PropOrSpread::Prop(prop) => match &**prop { + Prop::Shorthand(ident) => { + if ident.sym == key { + return Some(true); + } + }, + + Prop::KeyValue(prop) => { + if key != "__proto__" && prop_name_eq(&prop.key, "__proto__") { + // If __proto__ is defined, we need to check the contents of it, + // as well as any nested __proto__ objects + if let Some(object) = prop.value.as_object() { + // __proto__ is an ObjectLiteral, check if key exists in it + let exists = does_key_exist(key, &object.props); + if exists.is_none() { + return None; + } else if exists.is_some_and(|exists| exists) { + return Some(true); + } + } else { + // __proto__ is not a literal, it is impossible to know if the + // key exists or not + return None; + } + } else { + // Normal key + if prop_name_eq(&prop.key, key) { + return Some(true); + } + } + }, + + // invalid + Prop::Assign(_) => { + return None; + }, + + Prop::Getter(getter) => { + if prop_name_eq(&getter.key, key) { + return Some(true); + } + }, + + Prop::Setter(setter) => { + if prop_name_eq(&setter.key, key) { + return Some(true); + } + }, + + Prop::Method(method) => { + if prop_name_eq(&method.key, key) { + return Some(true); + } + } + }, + + _ => { + return None; + } + } + } + + // No key was found and there's no uncertainty, meaning the key certainly doesn't exist + Some(false) +} + impl Pure<'_> { /// Optimizes the following: /// @@ -357,47 +433,66 @@ impl Pure<'_> { } Expr::Object(ObjectLit { props, span }) => { - // get key + // Do nothing if there are invalid keys. + // + // Objects with one or more keys that are not literals or identifiers + // are impossible to optimize as we don't know for certain if a given + // key is actually invalid, e.g. `{[bar()]: 5}`, since we don't know + // what `bar()` returns. + let contains_invalid_key = props + .iter() + .any(|prop| !matches!(prop, PropOrSpread::Prop(prop) if matches!(&**prop, Prop::KeyValue(kv) if kv.key.is_ident() || kv.key.is_str() || kv.key.is_num()))); + + if contains_invalid_key { + return None; + } + + // Get key as Atom let key = match op { KnownOp::Index(i) => Atom::from(i.to_string()), - KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key, + KnownOp::IndexStr(key) if key != *"yield" => key, _ => { return None; } }; - - // do nothing if spread exists - let has_spread = props - .iter() - .any(|prop| matches!(prop, PropOrSpread::Spread(..))); - - if has_spread { + + // Check if key exists + let exists = does_key_exist(&key, props); + if exists.is_none() || exists.is_some_and(|exists| exists) { + // Valid properties are handled in simplify return None; } - let idx = props.iter().rev().position(|p| match p { - PropOrSpread::Prop(p) => match &**p { - Prop::Shorthand(i) => i.sym == key, - Prop::KeyValue(k) => prop_name_eq(&k.key, &key), - Prop::Assign(p) => p.key.sym == key, - Prop::Getter(..) => false, - Prop::Setter(..) => false, - // TODO - Prop::Method(..) => false, + // Can be optimized fully or partially + Some(self.expr_ctx.preserve_effects( + *span, + + if is_object_symbol(key.as_str()) { + // Valid key, e.g. "hasOwnProperty". Replacement: + // (foo(), bar(), {}.hasOwnProperty) + Expr::Member(MemberExpr { + span: *span, + obj: Box::new(Expr::Object(ObjectLit { + span: *span, + props: vec![], + })), + prop: MemberProp::Ident(Ident::new(key, *span)), + }) + } else { + // Invalid key. Replace with side effects plus `undefined`. + *undefined(*span) }, - _ => unreachable!(), - }); - // valid properties are handled in simplify::expr - if idx.map(|idx| props.len() - 1 - idx).is_some() { - return None; - } - - if is_object_symbol(key.as_str()) { - None - } else { - Some(*undefined(*span)) - } + props + .drain(..) + .map(|x| match x { + PropOrSpread::Prop(prop) => match *prop { + Prop::KeyValue(kv) => kv.value, + _ => unreachable!() + }, + _ => unreachable!() + }) + )) } _ => None, diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/input.js new file mode 100644 index 000000000000..9191ea9f521c --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/input.js @@ -0,0 +1,17 @@ +// foo(), {}.__proto__ +f({a: foo(), b: 5}.__proto__); + +// foo(), bar(), undefined +f({a: foo(), b: bar()}.invalid); + +// foo1(), bar(), baz(), foo2(), undefined +f({ + a: foo1(), + b: { + a: bar(), + b: { + a: baz() + }, + c: foo2() + } +}.invalid); diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/output.js new file mode 100644 index 000000000000..4dcc1c3bc6a1 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/object_side_effects/output.js @@ -0,0 +1 @@ +f((foo(), ({}).__proto__)), f((foo(), void bar())), f((foo1(), bar(), baz(), void foo2())); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/prop_function/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/prop_function/output.js index 41b7f00b639c..6baf59ba132b 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/prop_function/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/prop_function/output.js @@ -1,6 +1,8 @@ -console.log( - { a: { b: 1 }, b: function () {} } + 1, - { b: 1 } + 1, - function () {} + 1, - 2 -); +console.log({ + a: { + b: 1 + }, + b: function() {} +} + 1, { + b: 1 +} + 1, function() {} + 1, 2); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/properties/lhs_prop_1/output.js b/crates/swc_ecma_minifier/tests/terser/compress/properties/lhs_prop_1/output.js index 3b91051ed2d7..bcde4998b254 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/properties/lhs_prop_1/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/properties/lhs_prop_1/output.js @@ -1 +1,3 @@ -console.log(++{ a: 1 }.a); +console.log(++{ + a: 1 +}.a); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/obj_for_1/output.js b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/obj_for_1/output.js index d6f02bdd7dd8..a1a05cb3848a 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/obj_for_1/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/reduce_vars/obj_for_1/output.js @@ -1 +1,3 @@ -for (var i = { a: 1 }.a--; i; i--) console.log(i); +for(var i = { + a: 1 +}.a--; i; i--)console.log(i); From f462bfc70457aa84613ef8a0a1baaa353f003573 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 8 Apr 2024 17:15:39 +1200 Subject: [PATCH 44/80] Remove redundant return statement --- .../src/compress/pure/member_expr.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 3ff10a188d64..cb983c9fc82f 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -396,13 +396,11 @@ impl Pure<'_> { // Add undefined to end side_effects.push(Box::new(*undefined(*span))); - return Some(Expr::Seq(SeqExpr { + Some(Expr::Seq(SeqExpr { span: *span, exprs: side_effects, - })); - } - - if is_array_symbol { + })) + } else if is_array_symbol { // Optimization is the same array but with only side effects. // e.g. [1, 2, f()].push becomes [f()].push @@ -422,14 +420,14 @@ impl Pure<'_> { }) .collect(); - return Some(Expr::Member(MemberExpr { + Some(Expr::Member(MemberExpr { span: *span, obj: Box::new(Expr::Array(ArrayLit { span: *span, elems })), prop: MemberProp::Ident(Ident::new(key, *span)), - })); + })) + } else { + None } - - return None; } Expr::Object(ObjectLit { props, span }) => { From 3d0f3541ebfaf402066bdb4899dd06133cc4a70e Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 8 Apr 2024 17:17:59 +1200 Subject: [PATCH 45/80] cargo fmt --- .../src/compress/pure/member_expr.rs | 68 +++++++++---------- .../src/compress/pure/mod.rs | 6 +- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index cb983c9fc82f..a4cb67acabfd 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -145,13 +145,15 @@ fn is_string_symbol(sym: &str) -> bool { STRING_SYMBOLS.contains(sym) || is_object_symbol(sym) } -/// Checks if the given key exists in the given properties, taking the `__proto__` property -/// and order of keys into account (the order of keys matters for nested `__proto__` properties). -/// -/// Returns `None` if the key's existence is uncertain, or `Some` if it is certain. -/// -/// A key's existence is uncertain if a `__proto__` property exists and the value -/// is non-literal. +/// Checks if the given key exists in the given properties, taking the +/// `__proto__` property and order of keys into account (the order of keys +/// matters for nested `__proto__` properties). +/// +/// Returns `None` if the key's existence is uncertain, or `Some` if it is +/// certain. +/// +/// A key's existence is uncertain if a `__proto__` property exists and the +/// value is non-literal. fn does_key_exist(key: &str, props: &Vec) -> Option { for prop in props { match prop { @@ -160,7 +162,7 @@ fn does_key_exist(key: &str, props: &Vec) -> Option { if ident.sym == key { return Some(true); } - }, + } Prop::KeyValue(prop) => { if key != "__proto__" && prop_name_eq(&prop.key, "__proto__") { @@ -185,24 +187,24 @@ fn does_key_exist(key: &str, props: &Vec) -> Option { return Some(true); } } - }, + } // invalid Prop::Assign(_) => { return None; - }, + } Prop::Getter(getter) => { if prop_name_eq(&getter.key, key) { return Some(true); } - }, + } Prop::Setter(setter) => { if prop_name_eq(&setter.key, key) { return Some(true); } - }, + } Prop::Method(method) => { if prop_name_eq(&method.key, key) { @@ -216,8 +218,9 @@ fn does_key_exist(key: &str, props: &Vec) -> Option { } } } - - // No key was found and there's no uncertainty, meaning the key certainly doesn't exist + + // No key was found and there's no uncertainty, meaning the key certainly + // doesn't exist Some(false) } @@ -365,8 +368,10 @@ impl Pure<'_> { // In this case, the optimized expression is: // (x, y, undefined) // where x and y are side effects. - // If no side effects exist, the result is simply `undefined` instead of a SeqExpr. - let is_result_undefined = is_idx_out_of_bounds || (key.is_some() && !is_array_symbol); + // If no side effects exist, the result is simply `undefined` instead of a + // SeqExpr. + let is_result_undefined = + is_idx_out_of_bounds || (key.is_some() && !is_array_symbol); // Elements with side effects. // Will be empty if we don't need side effects. @@ -376,13 +381,10 @@ impl Pure<'_> { if need_side_effects { // Move all side effects into side_effects. // This completely drains elems. - elems - .drain(..) - .flatten() - .for_each(|elem| { - self.expr_ctx - .extract_side_effects_to(&mut side_effects, *elem.expr); - }); + elems.drain(..).flatten().for_each(|elem| { + self.expr_ctx + .extract_side_effects_to(&mut side_effects, *elem.expr); + }); } if is_result_undefined { @@ -453,7 +455,7 @@ impl Pure<'_> { return None; } }; - + // Check if key exists let exists = does_key_exist(&key, props); if exists.is_none() || exists.is_some_and(|exists| exists) { @@ -464,7 +466,6 @@ impl Pure<'_> { // Can be optimized fully or partially Some(self.expr_ctx.preserve_effects( *span, - if is_object_symbol(key.as_str()) { // Valid key, e.g. "hasOwnProperty". Replacement: // (foo(), bar(), {}.hasOwnProperty) @@ -480,16 +481,13 @@ impl Pure<'_> { // Invalid key. Replace with side effects plus `undefined`. *undefined(*span) }, - - props - .drain(..) - .map(|x| match x { - PropOrSpread::Prop(prop) => match *prop { - Prop::KeyValue(kv) => kv.value, - _ => unreachable!() - }, - _ => unreachable!() - }) + props.drain(..).map(|x| match x { + PropOrSpread::Prop(prop) => match *prop { + Prop::KeyValue(kv) => kv.value, + _ => unreachable!(), + }, + _ => unreachable!(), + }), )) } diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 57e199ae1f56..c7334711c999 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -68,7 +68,7 @@ pub(crate) fn pure_optimizer<'a>( ctx: Default::default(), changed: Default::default(), in_callee: false, - in_left_side_assign: false + in_left_side_assign: false, } } @@ -83,7 +83,7 @@ struct Pure<'a> { ctx: Ctx, changed: bool, in_callee: bool, - in_left_side_assign: bool + in_left_side_assign: bool, } impl Repeated for Pure<'_> { @@ -264,7 +264,7 @@ impl VisitMut for Pure<'_> { { let old_in_left_side_assign = self.in_left_side_assign; self.in_left_side_assign = true; - + let ctx = Ctx { is_lhs_of_assign: true, ..self.ctx From 7469da729b4abe1def47e371996f84374db69f6a Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 8 Apr 2024 17:19:44 +1200 Subject: [PATCH 46/80] Remove redundant `Box::new` call --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index a4cb67acabfd..e09bbdecfcb6 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -396,7 +396,7 @@ impl Pure<'_> { } // Add undefined to end - side_effects.push(Box::new(*undefined(*span))); + side_effects.push(undefined(*span)); Some(Expr::Seq(SeqExpr { span: *span, From eb0c7eea8ac41da8782635eb86fe4590d7d956ff Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 8 Apr 2024 17:24:04 +1200 Subject: [PATCH 47/80] Use `preserve_effects` --- .../src/compress/pure/member_expr.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index e09bbdecfcb6..8a4ae8c8c868 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -389,19 +389,11 @@ impl Pure<'_> { if is_result_undefined { // Optimization is `undefined`. - - if side_effects.is_empty() { - // No side effects, no need for SeqExpr - return Some(*undefined(*span)); - } - - // Add undefined to end - side_effects.push(undefined(*span)); - - Some(Expr::Seq(SeqExpr { - span: *span, - exprs: side_effects, - })) + Some(self.expr_ctx.preserve_effects( + *span, + *undefined(*span), + side_effects + )) } else if is_array_symbol { // Optimization is the same array but with only side effects. // e.g. [1, 2, f()].push becomes [f()].push From 02032cca57f49a7af3bd40668dcfdffef879f8f6 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 8 Apr 2024 17:24:33 +1200 Subject: [PATCH 48/80] cargo fmt --- .../swc_ecma_minifier/src/compress/pure/member_expr.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 8a4ae8c8c868..182a4a8d9521 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -389,11 +389,10 @@ impl Pure<'_> { if is_result_undefined { // Optimization is `undefined`. - Some(self.expr_ctx.preserve_effects( - *span, - *undefined(*span), - side_effects - )) + Some( + self.expr_ctx + .preserve_effects(*span, *undefined(*span), side_effects), + ) } else if is_array_symbol { // Optimization is the same array but with only side effects. // e.g. [1, 2, f()].push becomes [f()].push From 82e4b3e349a1d6cc68fc99470db4207fdbd334b3 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 10 Apr 2024 11:23:13 +1200 Subject: [PATCH 49/80] Exclude `watch` and `unwatch` --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 182a4a8d9521..5bc250af0c63 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -129,6 +129,9 @@ static OBJECT_SYMBOLS: phf::Set<&str> = phf_set!( "toLocaleString", "toString", "valueOf", + // removed, but kept in as these are often checked and polyfilled + "watch", + "unwatch" ); fn is_object_symbol(sym: &str) -> bool { From 5ee31d38be23635cfba260dbf393b2efc56a2685 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 10 Apr 2024 11:23:23 +1200 Subject: [PATCH 50/80] UPDATE=1 cargo test --test projects --test tsc --- .../destructuringParameterDeclaration5.2.minified.js | 2 +- crates/swc/tests/tsc-references/enumBasics.2.minified.js | 1 + .../swc/tests/tsc-references/parserForStatement9.2.minified.js | 2 +- .../tsc-references/typeFromPropertyAssignment35.2.minified.js | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/swc/tests/tsc-references/destructuringParameterDeclaration5.2.minified.js b/crates/swc/tests/tsc-references/destructuringParameterDeclaration5.2.minified.js index 11948df45f64..6b80395c471e 100644 --- a/crates/swc/tests/tsc-references/destructuringParameterDeclaration5.2.minified.js +++ b/crates/swc/tests/tsc-references/destructuringParameterDeclaration5.2.minified.js @@ -42,4 +42,4 @@ new Class(), d0({ y: new SubClass() }).y, ({ y: new Class() -}).y, ({}).y; +}).y; diff --git a/crates/swc/tests/tsc-references/enumBasics.2.minified.js b/crates/swc/tests/tsc-references/enumBasics.2.minified.js index 6a68deb0ed71..c974ee536567 100644 --- a/crates/swc/tests/tsc-references/enumBasics.2.minified.js +++ b/crates/swc/tests/tsc-references/enumBasics.2.minified.js @@ -3,3 +3,4 @@ var E2, E3, E4, E5, E6, E7, E8, E9, E1, E11, E21, E31, E41, E51, E61, E71, E81, E91, e = E11; E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = 'foo'.foo] = "A", (E8 = E81 || (E81 = {}))[E8.B = 'foo'.foo] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = void 0] = "A", (E8 = E81 || (E81 = {}))[E8.B = void 0] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; +E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = "foo".foo] = "A", (E8 = E81 || (E81 = {}))[E8.B = "foo".foo] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; diff --git a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js index 0364e3dd8b7e..b83e61c9fd03 100644 --- a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js +++ b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js @@ -2,4 +2,4 @@ for(var tmp = [][0], x = void 0 === tmp ? ('a' in {}) : tmp; !x; x = !x)console.log(x); for(var _ref_x = {}.x, x1 = void 0 === _ref_x ? ('a' in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); for(var tmp = void 0, x = void 0 === tmp ? ("a" in {}) : tmp; !x; x = !x)console.log(x); -for(var _ref_x = {}.x, x1 = void 0 === _ref_x ? ("a" in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); +for(var _ref_x = void 0, x1 = void 0 === _ref_x ? ("a" in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); diff --git a/crates/swc/tests/tsc-references/typeFromPropertyAssignment35.2.minified.js b/crates/swc/tests/tsc-references/typeFromPropertyAssignment35.2.minified.js index 8cd50b5afeaf..1e93b85d4594 100644 --- a/crates/swc/tests/tsc-references/typeFromPropertyAssignment35.2.minified.js +++ b/crates/swc/tests/tsc-references/typeFromPropertyAssignment35.2.minified.js @@ -5,4 +5,4 @@ Emu.D = function _class() { _class_call_check(this, _class), this._model = 1; }; //// [second.js] -({}).D._wrapperInstance; +(void 0)._wrapperInstance; From d6fa0e79cd6b0bf30b577715413aa9de27ac7bbe Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 10 Apr 2024 11:28:34 +1200 Subject: [PATCH 51/80] UPDATE=1 cargo test -p swc_ecma_minifier --features concurrent --- crates/swc_ecma_minifier/tests/benches-full/vue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_minifier/tests/benches-full/vue.js b/crates/swc_ecma_minifier/tests/benches-full/vue.js index a48c5e2744ee..eef7427c2866 100644 --- a/crates/swc_ecma_minifier/tests/benches-full/vue.js +++ b/crates/swc_ecma_minifier/tests/benches-full/vue.js @@ -185,7 +185,7 @@ UA && UA.indexOf('android'); var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA) || 'ios' === weexPlatform; UA && /chrome\/\d+/.test(UA), UA && /phantomjs/.test(UA); - var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = void 0, supportsPassive = !1; + var isFF = UA && UA.match(/firefox\/(\d+)/), nativeWatch = {}.watch, supportsPassive = !1; if (inBrowser) try { var opts = {}; Object.defineProperty(opts, 'passive', { From a691c25311f5f040989dc34f1d0520cd5866a21b Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 10 Apr 2024 14:54:26 +1200 Subject: [PATCH 52/80] Remove redundant import --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 5bc250af0c63..696695ca877e 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -2,7 +2,7 @@ use phf::phf_set; use swc_atoms::{Atom, JsWord}; use swc_ecma_ast::{ ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, - PropOrSpread, SeqExpr, Str, + PropOrSpread, Str, }; use swc_ecma_utils::{prop_name_eq, undefined, ExprExt, Known}; From f3a2ff006847ae74b9b0b746095eae2a7d651eb0 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 10 Apr 2024 15:56:48 +1200 Subject: [PATCH 53/80] Fix `test_fold_array_lit_spread_get_elem` --- .../src/simplify/expr/tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 3d792d9040df..b9f7f454fbd3 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1005,11 +1005,11 @@ fn test_fold_get_elem2_2() { #[test] fn test_fold_array_lit_spread_get_elem() { - fold("x = [...[0 ]][0]", "x = (0, 0);"); - fold("x = [0, 1, ...[2, 3, 4]][3]", "x = (0, 3);"); - fold("x = [...[0, 1], 2, ...[3, 4]][3]", "x = (0, 3);"); - fold("x = [...[...[0, 1], 2, 3], 4][0]", "x = (0, 0)"); - fold("x = [...[...[0, 1], 2, 3], 4][3]", "x = (0, 3)"); + fold("x = [...[0 ]][0]", "x = 0;"); + fold("x = [0, 1, ...[2, 3, 4]][3]", "x = 3;"); + fold("x = [...[0, 1], 2, ...[3, 4]][3]", "x = 3;"); + fold("x = [...[...[0, 1], 2, 3], 4][0]", "x = 0;"); + fold("x = [...[...[0, 1], 2, 3], 4][3]", "x = 3;"); // fold("x = [...[]][100]", "x = void 0;"); // fold("x = [...[0]][100]", "x = void 0;"); } From c2f2e1f82c1acf153e8ed1c1b75e8d8c7168f1df Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 10 Apr 2024 15:58:11 +1200 Subject: [PATCH 54/80] Fix `test_fold_get_elem1` --- .../src/simplify/expr/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index b9f7f454fbd3..3623c0e26145 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -974,15 +974,15 @@ fn test_fold_comparison4() { #[test] fn test_fold_get_elem1() { - fold("x = [,10][0]", "x = (0, void 0)"); - fold("x = [10, 20][0]", "x = (0, 10)"); - fold("x = [10, 20][1]", "x = (0, 20)"); + fold("x = [,10][0]", "x = void 0;"); + fold("x = [10, 20][0]", "x = 10;"); + fold("x = [10, 20][1]", "x = 20;"); // fold("x = [10, 20][-1]", "x = void 0;"); // fold("x = [10, 20][2]", "x = void 0;"); fold("x = [foo(), 0][1]", "x = (foo(), 0);"); - fold("x = [0, foo()][1]", "x = (0, foo())"); + fold("x = [0, foo()][1]", "x = foo();"); // fold("x = [0, foo()][0]", "x = (foo(), 0)"); fold_same("for([1][0] in {});"); } From 570bb695230957069a6af1289417ff89d454eff7 Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 11 Apr 2024 07:49:13 +1200 Subject: [PATCH 55/80] Add `self.changed = true;` --- crates/swc_ecma_minifier/src/compress/pure/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index c7334711c999..5a86ddb58e1c 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -601,6 +601,7 @@ impl VisitMut for Pure<'_> { self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) { *e = replacement; + self.changed = true; } } } From 38960d6ea472809aa60fba19389164ff4cb70507 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 23 Apr 2024 01:09:51 +1200 Subject: [PATCH 56/80] Use `ctx` --- .../src/compress/pure/member_expr.rs | 4 ++-- .../swc_ecma_minifier/src/compress/pure/mod.rs | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 696695ca877e..3172551d60ca 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -245,7 +245,7 @@ impl Pure<'_> { obj: &mut Expr, prop: &MemberProp, ) -> Option { - if !self.options.pristine_globals || self.in_left_side_assign { + if !self.options.pristine_globals || self.ctx.is_lhs_of_assign { return None; } @@ -269,7 +269,7 @@ impl Pure<'_> { let op = match prop { MemberProp::Ident(Ident { sym, .. }) => { - if self.in_callee { + if self.ctx.is_callee { return None; } diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 5a86ddb58e1c..b1f9edc2e66c 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -67,8 +67,6 @@ pub(crate) fn pure_optimizer<'a>( data, ctx: Default::default(), changed: Default::default(), - in_callee: false, - in_left_side_assign: false, } } @@ -82,8 +80,6 @@ struct Pure<'a> { data: Option<&'a ProgramData>, ctx: Ctx, changed: bool, - in_callee: bool, - in_left_side_assign: bool, } impl Repeated for Pure<'_> { @@ -262,16 +258,11 @@ impl VisitMut for Pure<'_> { fn visit_mut_assign_expr(&mut self, e: &mut AssignExpr) { { - let old_in_left_side_assign = self.in_left_side_assign; - self.in_left_side_assign = true; - let ctx = Ctx { is_lhs_of_assign: true, ..self.ctx }; e.left.visit_mut_children_with(&mut *self.with_ctx(ctx)); - - self.in_left_side_assign = old_in_left_side_assign; } e.right.visit_mut_with(self); @@ -299,13 +290,6 @@ impl VisitMut for Pure<'_> { self.optimize_arrow_body(body); } - fn visit_mut_callee(&mut self, callee: &mut Callee) { - let old_in_callee = self.in_callee; - self.in_callee = true; - callee.visit_mut_children_with(self); - self.in_callee = old_in_callee; - } - fn visit_mut_call_expr(&mut self, e: &mut CallExpr) { { let ctx = Ctx { From 9eb723faa4f1144e92b3b6767a05165a9bd82dd5 Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 26 Apr 2024 00:11:49 +1200 Subject: [PATCH 57/80] Fix doc --- .../src/simplify/expr/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 5177f7f8e3a3..efe4bd985ac4 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -115,9 +115,9 @@ impl SimplifyExpr { /// [a, b].length Len, - // [a, b][0] - // - // {0.5: "bar"}[0.5] + /// [a, b][0] + /// + /// {0.5: "bar"}[0.5] /// Note: callers need to check `v.fract() == 0.0` in some cases. /// ie non-integer indexes for arrays result in `undefined` /// but not for objects (because indexing an object From 9be7be7127fd3b4d9d40ae07fd0a5401c4af184e Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 26 Apr 2024 03:51:03 +1200 Subject: [PATCH 58/80] Handle SeqExpr in `optimize_member_expr` --- .../src/compress/pure/member_expr.rs | 29 ++++++++++++++++++- .../tests/fixture/member_expr/seq/input.js | 2 ++ .../tests/fixture/member_expr/seq/output.js | 1 + .../compress/evaluate/issue_2231_3/output.js | 2 +- .../compress/issue_t50/issue_t50/config.json | 2 +- .../issue_t50/issue_t50_const/config.json | 2 +- .../issue_t50/issue_t50_let/config.json | 2 +- .../src/simplify/expr/mod.rs | 14 ++++++--- 8 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/seq/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/seq/output.js diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 3172551d60ca..81d3f296fc42 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -2,7 +2,7 @@ use phf::phf_set; use swc_atoms::{Atom, JsWord}; use swc_ecma_ast::{ ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, - PropOrSpread, Str, + PropOrSpread, SeqExpr, Str, }; use swc_ecma_utils::{prop_name_eq, undefined, ExprExt, Known}; @@ -302,6 +302,33 @@ impl Pure<'_> { }; match obj { + Expr::Seq(SeqExpr { exprs, span }) => { + // Optimize when last value in a SeqExpr is being indexed + // while preserving side effects. + // + // (0, {a: 5}).a + // + // (0, f(), {a: 5}).a + // + // (0, f(), [1, 2])[0] + // + // etc. + + // Try to optimize with obj being the last expr + let Some(last) = exprs.last_mut() else { + return None; + }; + let Some(replacement) = self.optimize_member_expr(last, prop) else { + return None; + }; + + // Replace last element with replacement + let mut exprs: Vec> = exprs.drain(..(exprs.len() - 1)).collect(); + exprs.push(Box::new(replacement)); + + Some(Expr::Seq(SeqExpr { span: *span, exprs })) + } + Expr::Lit(Lit::Str(Str { value, span, .. })) => { match op { KnownOp::Index(idx) => { diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/seq/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/seq/input.js new file mode 100644 index 000000000000..92a240ed9344 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/seq/input.js @@ -0,0 +1,2 @@ +console.log((f(), [2, 4])[5]); +console.log((f(), {b: 2}).a); diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/seq/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/seq/output.js new file mode 100644 index 000000000000..883367d7963a --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/seq/output.js @@ -0,0 +1 @@ +console.log(void f()), console.log(void f()); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js index 85ce559e8f22..7b47d8d664ab 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/issue_2231_3/output.js @@ -1 +1 @@ -console.log("foo"); +console.log((0, "foo")); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/config.json b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/config.json index dccb2dcc1f2c..547e3ffc38e5 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/config.json +++ b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50/config.json @@ -1,4 +1,4 @@ { "defaults": true, - "passes": 2 + "passes": 3 } diff --git a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/config.json b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/config.json index dccb2dcc1f2c..547e3ffc38e5 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/config.json +++ b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_const/config.json @@ -1,4 +1,4 @@ { "defaults": true, - "passes": 2 + "passes": 3 } diff --git a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/config.json b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/config.json index dccb2dcc1f2c..547e3ffc38e5 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/config.json +++ b/crates/swc_ecma_minifier/tests/terser/compress/issue_t50/issue_t50_let/config.json @@ -1,4 +1,4 @@ { "defaults": true, - "passes": 2 + "passes": 3 } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index efe4bd985ac4..aeb02034bf04 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -315,14 +315,20 @@ impl SimplifyExpr { .extract_side_effects_to(&mut exprs, *elem.expr); } + // Note: we always replace with a SeqExpr so that + // `this` remains undefined in strict mode. + if exprs.is_empty() { - // No side effects exist, replace with value. - *expr = *val; + // No side effects exist, replace with: + // (0, val) + *expr = Expr::Seq(SeqExpr { + span: val.span(), + exprs: vec![0.into(), val], + }); return; } - // Side effects exist, add value to the end and replace - // with a SeqExpr. + // Add value and replace with SeqExpr exprs.push(val); *expr = Expr::Seq(SeqExpr { span: *span, exprs }); } From 94d9db54308de3724d7e07748ff1479c104c1763 Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 10 May 2024 15:07:41 +1200 Subject: [PATCH 59/80] Remove dup `self.changed = true;` --- .../swc_ecma_transforms_optimization/src/simplify/expr/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index aeb02034bf04..1a1cf9b4101c 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -198,8 +198,6 @@ impl SimplifyExpr { } else if let Some(value) = nth_char(value, idx as _) { self.changed = true; KnownOp::Index(idx) => { - self.changed = true; - if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { // Prototype changes affect indexing if the index is out of bounds, so we // don't replace out-of-bound indexes. From 65ceb8d34c1c3a7dc6c26192256cc23b8ca36f7e Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 10 May 2024 15:42:56 +1200 Subject: [PATCH 60/80] Apply clippy suggestions --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 81d3f296fc42..9f8d1235931c 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -315,12 +315,7 @@ impl Pure<'_> { // etc. // Try to optimize with obj being the last expr - let Some(last) = exprs.last_mut() else { - return None; - }; - let Some(replacement) = self.optimize_member_expr(last, prop) else { - return None; - }; + let replacement = self.optimize_member_expr(exprs.last_mut()?, prop)?; // Replace last element with replacement let mut exprs: Vec> = exprs.drain(..(exprs.len() - 1)).collect(); From fc27b522e24036d645f4f77c733eefe5cc417562 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 12 May 2024 23:31:55 +1200 Subject: [PATCH 61/80] Cleanup `optimize_member_expr` This fixes unit tests being stuck running for 60s+ --- .../src/compress/pure/member_expr.rs | 192 +++++++++++------- .../tests/fixture/member_expr/array/input.js | 14 +- .../tests/fixture/member_expr/array/output.js | 2 +- 3 files changed, 125 insertions(+), 83 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 9f8d1235931c..1efdbcba6dd9 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -1,5 +1,6 @@ use phf::phf_set; use swc_atoms::{Atom, JsWord}; +use swc_common::Spanned; use swc_ecma_ast::{ ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, PropOrSpread, SeqExpr, Str, @@ -362,89 +363,111 @@ impl Pure<'_> { return None; } - // A match expression would be easier to read, but we need to do it this way - // unless we want to clone elements or use a macro and deal with mutability - // since we need side effect extraction in multiple places. - // To be honest, this is probably a lot easier to read and offers minimal - // code size anyway. + match op { + KnownOp::Index(idx) => { + if idx >= 0.0 && (idx as usize) < elems.len() && idx.fract() == 0.0 { + // idx is in bounds, handled in simplify + return None; + } - let (is_idx_out_of_bounds, key, is_array_symbol, is_length) = match op { - KnownOp::Index(i) => ( - i.fract() != 0.0 || i < 0.0 || i as usize >= elems.len(), - None, - false, - false, - ), + // Replacement is certain at this point, and is always undefined - KnownOp::IndexStr(key) => { - let is_array_symbol = is_array_symbol(key.as_str()); - let is_length = key == "length"; + // Extract side effects + let mut exprs = vec![]; + elems.drain(..).flatten().for_each(|elem| { + self.expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr); + }); - (false, Some(key), is_array_symbol, is_length) - } - }; + Some(if exprs.is_empty() { + // No side effects, replacement is: + // (0, void 0) + Expr::Seq(SeqExpr { + span: *span, + exprs: vec![0.into(), undefined(*span)] + }) + } else { + // Side effects exist, replacement is: + // (x(), y(), void 0) + // Where `x()` and `y()` are side effects. + exprs.push(undefined(*span)); - if is_length { - // Handled in simplify::expr - return None; - } + Expr::Seq(SeqExpr { + span: *span, + exprs + }) + }) + } - // If the result is undefined. - // In this case, the optimized expression is: - // (x, y, undefined) - // where x and y are side effects. - // If no side effects exist, the result is simply `undefined` instead of a - // SeqExpr. - let is_result_undefined = - is_idx_out_of_bounds || (key.is_some() && !is_array_symbol); - - // Elements with side effects. - // Will be empty if we don't need side effects. - let mut side_effects = vec![]; - // If we need to compute side effects. - let need_side_effects = is_result_undefined || is_array_symbol; - if need_side_effects { - // Move all side effects into side_effects. - // This completely drains elems. - elems.drain(..).flatten().for_each(|elem| { - self.expr_ctx - .extract_side_effects_to(&mut side_effects, *elem.expr); - }); - } + KnownOp::IndexStr(key) if key != "length" /* handled in simplify */ => { + // If the property is a known symbol, e.g. [].push + let is_known_symbol = is_array_symbol(&key); + + if is_known_symbol { + // We need to check if this is already optimized as if we don't, + // it'll lead to infinite optimization when the visitor visits + // again. + // + // A known symbol expression is already optimized if all + // non-side effects have been removed. + let optimized_len = elems + .iter() + .flatten() + .filter(|elem| elem.expr.may_have_side_effects(&self.expr_ctx)) + .count(); + + if optimized_len == elems.len() { + // Already optimized + return None; + } + } - if is_result_undefined { - // Optimization is `undefined`. - Some( - self.expr_ctx - .preserve_effects(*span, *undefined(*span), side_effects), - ) - } else if is_array_symbol { - // Optimization is the same array but with only side effects. - // e.g. [1, 2, f()].push becomes [f()].push - - // property - let key = match key { - Some(x) => x, - None => unreachable!(), - }; + // Extract side effects + let mut exprs = vec![]; + elems.drain(..).flatten().for_each(|elem| { + self.expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr); + }); - let elems: Vec> = side_effects - .into_iter() - .map(|e| { - Some(ExprOrSpread { - spread: None, - expr: e, + Some(if is_known_symbol { + // [x(), y()].push + Expr::Member(MemberExpr { + span: *span, + obj: Box::new(Expr::Array(ArrayLit { + span: *span, + elems: exprs + .into_iter() + .map(|elem| Some(ExprOrSpread { + spread: None, + expr: elem, + })) + .collect() + })), + prop: prop.clone(), }) + } else { + let val = undefined(*span); + + if exprs.is_empty() { + // No side effects, replacement is: + // (0, void 0) + Expr::Seq(SeqExpr { + span: val.span(), + exprs: vec![0.into(), val] + }) + } else { + // Side effects exist, replacement is: + // (x(), y(), void 0) + // Where `x()` and `y()` are side effects. + exprs.push(val); + + Expr::Seq(SeqExpr { + span: *span, + exprs + }) + } }) - .collect(); - - Some(Expr::Member(MemberExpr { - span: *span, - obj: Box::new(Expr::Array(ArrayLit { span: *span, elems })), - prop: MemberProp::Ident(Ident::new(key, *span)), - })) - } else { - None + } + + _ => None } } @@ -479,10 +502,29 @@ impl Pure<'_> { return None; } + let is_known_symbol = is_object_symbol(&key); + if is_known_symbol { + // Like with arrays, we need to check if this is already optimized + // before returning Some so we don't end up in an infinite loop. + // + // The same logic with arrays applies; read above. + let optimized_len = props + .iter() + .filter(|prop| { + matches!(prop, PropOrSpread::Prop(prop) if matches!(&**prop, Prop::KeyValue(prop) if prop.value.may_have_side_effects(&self.expr_ctx))) + }) + .count(); + + if optimized_len == props.len() { + // Already optimized + return None; + } + } + // Can be optimized fully or partially Some(self.expr_ctx.preserve_effects( *span, - if is_object_symbol(key.as_str()) { + if is_known_symbol { // Valid key, e.g. "hasOwnProperty". Replacement: // (foo(), bar(), {}.hasOwnProperty) Expr::Member(MemberExpr { diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/array/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/array/input.js index 1c3554b426aa..d9da2f27b3b2 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/array/input.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/array/input.js @@ -1,12 +1,12 @@ // Invalid -[][0]; -[][1]; -[][-1]; +f([][0]); +f([][1]); +f([][-1]); -[].invalid; -[]["invalid"]; -[][[]]; -[][0+[]]; +f([].invalid); +f([]["invalid"]); +f([][[]]); +f([][0+[]]); // Object symbols [].constructor; diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/array/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/array/output.js index c089af16973e..525a610dd6d6 100644 --- a/crates/swc_ecma_minifier/tests/fixture/member_expr/array/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/array/output.js @@ -1 +1 @@ -[].constructor, [].__proto__, [].__defineGetter__, [].__defineSetter__, [].__lookupGetter__, [].__lookupSetter__, [].hasOwnProperty, [].isPrototypeOf, [].propertyIsEnumerable, [].toLocaleString, [].toString, [].valueOf, [].at, [].concat, [].copyWithin, [].entries, [].every, [].fill, [].filter, [].find, [].findIndex, [].findLast, [].findLastIndex, [].flat, [].flatMap, [].forEach, [].includes, [].indexOf, [].join, [].keys, [].lastIndexOf, [].map, [].pop, [].push, [].reduce, [].reduceRight, [].reverse, [].shift, [].slice, [].some, [].sort, [].splice, [].toReversed, [].toSorted, [].toSpliced, [].unshift, [].values, [].with; +f(void 0), f(void 0), f(void 0), f(void 0), f(void 0), f(void 0), f(void 0), [].constructor, [].__proto__, [].__defineGetter__, [].__defineSetter__, [].__lookupGetter__, [].__lookupSetter__, [].hasOwnProperty, [].isPrototypeOf, [].propertyIsEnumerable, [].toLocaleString, [].toString, [].valueOf, [].at, [].concat, [].copyWithin, [].entries, [].every, [].fill, [].filter, [].find, [].findIndex, [].findLast, [].findLastIndex, [].flat, [].flatMap, [].forEach, [].includes, [].indexOf, [].join, [].keys, [].lastIndexOf, [].map, [].pop, [].push, [].reduce, [].reduceRight, [].reverse, [].shift, [].slice, [].some, [].sort, [].splice, [].toReversed, [].toSorted, [].toSpliced, [].unshift, [].values, [].with; From 95a1784f6e483934ed7f3f2e4576cf5315ad1697 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 12 May 2024 23:42:57 +1200 Subject: [PATCH 62/80] Fix optimization tests --- .../src/simplify/expr/tests.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 3623c0e26145..d9a00b7d20f6 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -974,15 +974,15 @@ fn test_fold_comparison4() { #[test] fn test_fold_get_elem1() { - fold("x = [,10][0]", "x = void 0;"); - fold("x = [10, 20][0]", "x = 10;"); - fold("x = [10, 20][1]", "x = 20;"); + fold("x = [,10][0]", "x = (0, void 0);"); + fold("x = [10, 20][0]", "x = (0, 10);"); + fold("x = [10, 20][1]", "x = (0, 20);"); // fold("x = [10, 20][-1]", "x = void 0;"); // fold("x = [10, 20][2]", "x = void 0;"); fold("x = [foo(), 0][1]", "x = (foo(), 0);"); - fold("x = [0, foo()][1]", "x = foo();"); + fold("x = [0, foo()][1]", "x = (0, foo());"); // fold("x = [0, foo()][0]", "x = (foo(), 0)"); fold_same("for([1][0] in {});"); } @@ -1005,11 +1005,11 @@ fn test_fold_get_elem2_2() { #[test] fn test_fold_array_lit_spread_get_elem() { - fold("x = [...[0 ]][0]", "x = 0;"); - fold("x = [0, 1, ...[2, 3, 4]][3]", "x = 3;"); - fold("x = [...[0, 1], 2, ...[3, 4]][3]", "x = 3;"); - fold("x = [...[...[0, 1], 2, 3], 4][0]", "x = 0;"); - fold("x = [...[...[0, 1], 2, 3], 4][3]", "x = 3;"); + fold("x = [...[0 ]][0]", "x = (0, 0);"); + fold("x = [0, 1, ...[2, 3, 4]][3]", "x = (0, 3);"); + fold("x = [...[0, 1], 2, ...[3, 4]][3]", "x = (0, 3);"); + fold("x = [...[...[0, 1], 2, 3], 4][0]", "x = (0, 0);"); + fold("x = [...[...[0, 1], 2, 3], 4][3]", "x = (0, 3);"); // fold("x = [...[]][100]", "x = void 0;"); // fold("x = [...[0]][100]", "x = void 0;"); } @@ -1594,7 +1594,7 @@ fn test_issue8747() { // Index with an expression. fold("'a'[0 + []]", "\"a\";"); - fold("[1][0 + []]", "1"); + fold("[1][0 + []]", "0, 1;"); // Don't replace if side effects exist. fold_same("[f(), f()][0]"); From e872c029f4baf02ab34421d15ae1cc5808f955f3 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 26 May 2024 09:42:21 +1200 Subject: [PATCH 63/80] Fix optimization with `OptCall` --- .../src/compress/pure/ctx.rs | 2 ++ .../src/compress/pure/member_expr.rs | 2 +- .../src/compress/pure/mod.rs | 24 +++++++++++++++++++ .../tests/fixture/member_expr/callee/input.js | 6 +++++ .../fixture/member_expr/callee/output.js | 5 ++++ 5 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/callee/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/member_expr/callee/output.js diff --git a/crates/swc_ecma_minifier/src/compress/pure/ctx.rs b/crates/swc_ecma_minifier/src/compress/pure/ctx.rs index dea0635c1b43..1cddde32bd9b 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/ctx.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/ctx.rs @@ -14,6 +14,8 @@ pub(super) struct Ctx { #[allow(unused)] pub is_callee: bool, + pub is_opt_call: bool, + pub _in_try_block: bool, pub is_lhs_of_assign: bool, diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 1efdbcba6dd9..2ebba7449eda 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -246,7 +246,7 @@ impl Pure<'_> { obj: &mut Expr, prop: &MemberProp, ) -> Option { - if !self.options.pristine_globals || self.ctx.is_lhs_of_assign { + if !self.options.pristine_globals || self.ctx.is_lhs_of_assign || self.ctx.is_opt_call { return None; } diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index b1f9edc2e66c..1abffe1d0e86 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -306,6 +306,20 @@ impl VisitMut for Pure<'_> { self.drop_arguments_of_symbol_call(e); } + fn visit_mut_opt_call(&mut self, opt_call: &mut OptCall) { + { + let ctx = Ctx { + is_opt_call: true, + ..self.ctx + }; + opt_call.callee.visit_mut_with(&mut *self.with_ctx(ctx)); + } + + opt_call.args.visit_mut_with(self); + + self.eval_spread_array(&mut opt_call.args); + } + fn visit_mut_class_member(&mut self, m: &mut ClassMember) { m.visit_mut_children_with(self); @@ -581,11 +595,20 @@ impl VisitMut for Pure<'_> { } if let Expr::Member(member_expr) = e { + #[cfg(feature = "debug")] + debug!( + "before: optimize_member_expr: {}", + dump(&*member_expr, false) + ); + if let Some(replacement) = self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) { *e = replacement; self.changed = true; + + #[cfg(feature = "debug")] + debug!("after: optimize_member_expr: {}", dump(&*e, false)); } } } @@ -875,6 +898,7 @@ impl VisitMut for Pure<'_> { let ctx = Ctx { is_update_arg: false, is_callee: false, + is_opt_call: false, in_delete: false, in_first_expr: true, ..self.ctx diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/callee/input.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/callee/input.js new file mode 100644 index 000000000000..3042394fa0fd --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/callee/input.js @@ -0,0 +1,6 @@ +try { + const foo = {}; + foo?.bar.baz?.(); +} catch (e) { + console.log('PASS'); +} diff --git a/crates/swc_ecma_minifier/tests/fixture/member_expr/callee/output.js b/crates/swc_ecma_minifier/tests/fixture/member_expr/callee/output.js new file mode 100644 index 000000000000..089d30f03851 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/member_expr/callee/output.js @@ -0,0 +1,5 @@ +try { + ({}).bar.baz?.(); +} catch (e) { + console.log('PASS'); +} From 68a4bb96eaaa6463839e9f617b2fef9465519858 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 26 May 2024 10:01:21 +1200 Subject: [PATCH 64/80] Move code into its own `eval` function --- .../src/compress/pure/evaluate.rs | 30 ++++++++++++++++++- .../src/compress/pure/mod.rs | 18 +---------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 897133d17055..5c573da3e0f9 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -2,9 +2,14 @@ use radix_fmt::Radix; use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::{number::ToJsString, ExprExt, IsEmpty, Value}; +use swc_ecma_utils::{undefined, ExprExt, IsEmpty, Value}; +evuse tracing::debug; use super::Pure; -use crate::compress::util::{eval_as_number, is_pure_undefined_or_null}; +use crate::{ + compress::util::{eval_as_number, is_pure_undefined_or_null}, + debug::dump, +}; impl Pure<'_> { pub(super) fn eval_array_method_call(&mut self, e: &mut Expr) { @@ -639,6 +644,29 @@ impl Pure<'_> { } } + pub(super) fn eval_member_expr(&mut self, e: &mut Expr) { + let member_expr = match e { + Expr::Member(x) => x, + _ => return, + }; + + #[cfg(feature = "debug")] + debug!( + "before: optimize_member_expr: {}", + dump(&*member_expr, false) + ); + + if let Some(replacement) = + self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) + { + *e = replacement; + self.changed = true; + + #[cfg(feature = "debug")] + debug!("after: optimize_member_expr: {}", dump(&*e, false)); + } + } + fn eval_trivial_two(&mut self, a: &Expr, b: &mut Expr) { if let Expr::Assign(AssignExpr { left: a_left, diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 1abffe1d0e86..20b9be8c5983 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -594,23 +594,7 @@ impl VisitMut for Pure<'_> { debug_assert_valid(e); } - if let Expr::Member(member_expr) = e { - #[cfg(feature = "debug")] - debug!( - "before: optimize_member_expr: {}", - dump(&*member_expr, false) - ); - - if let Some(replacement) = - self.optimize_member_expr(&mut member_expr.obj, &member_expr.prop) - { - *e = replacement; - self.changed = true; - - #[cfg(feature = "debug")] - debug!("after: optimize_member_expr: {}", dump(&*e, false)); - } - } + self.eval_member_expr(e); } fn visit_mut_expr_or_spreads(&mut self, nodes: &mut Vec) { From 2e96ce6834669b90c7a14fa33d0abc3c90e03b2f Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 26 May 2024 10:02:00 +1200 Subject: [PATCH 65/80] Fix error --- crates/swc_ecma_minifier/src/compress/pure/evaluate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 5c573da3e0f9..66e3e5bc085a 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -3,7 +3,7 @@ use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::{number::ToJsString, ExprExt, IsEmpty, Value}; use swc_ecma_utils::{undefined, ExprExt, IsEmpty, Value}; -evuse tracing::debug; +use tracing::debug; use super::Pure; use crate::{ From a7f8c7d13d9454249f60470a42294455c14080cd Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 26 May 2024 10:10:21 +1200 Subject: [PATCH 66/80] Remove redundant imports --- crates/swc_ecma_minifier/src/compress/pure/evaluate.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 66e3e5bc085a..0caf464cff37 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -3,13 +3,9 @@ use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::{number::ToJsString, ExprExt, IsEmpty, Value}; use swc_ecma_utils::{undefined, ExprExt, IsEmpty, Value}; -use tracing::debug; use super::Pure; -use crate::{ - compress::util::{eval_as_number, is_pure_undefined_or_null}, - debug::dump, -}; +use crate::compress::util::{eval_as_number, is_pure_undefined_or_null}; impl Pure<'_> { pub(super) fn eval_array_method_call(&mut self, e: &mut Expr) { From 3e10aa655028a1078ca0de8fbfc80f3193ddf10d Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 26 May 2024 10:18:25 +1200 Subject: [PATCH 67/80] Optimize imports --- crates/swc_ecma_minifier/src/compress/pure/evaluate.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 0caf464cff37..1fd56ed2cce8 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -3,6 +3,8 @@ use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::{number::ToJsString, ExprExt, IsEmpty, Value}; use swc_ecma_utils::{undefined, ExprExt, IsEmpty, Value}; +#[cfg(feature = "debug")] +use {crate::debug::dump, tracing::debug}; use super::Pure; use crate::compress::util::{eval_as_number, is_pure_undefined_or_null}; From 84dab43ed7519c1421e6e27aa0aff1d3bd142dfc Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 26 May 2024 10:38:28 +1200 Subject: [PATCH 68/80] Update parserForStatement9.2.minified.js --- .../swc/tests/tsc-references/parserForStatement9.2.minified.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js index b83e61c9fd03..32d379ba1049 100644 --- a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js +++ b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js @@ -3,3 +3,5 @@ for(var tmp = [][0], x = void 0 === tmp ? ('a' in {}) : tmp; !x; x = !x)console. for(var _ref_x = {}.x, x1 = void 0 === _ref_x ? ('a' in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); for(var tmp = void 0, x = void 0 === tmp ? ("a" in {}) : tmp; !x; x = !x)console.log(x); for(var _ref_x = void 0, x1 = void 0 === _ref_x ? ("a" in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); +for(var tmp = void 0, x = void 0 === tmp ? ('a' in {}) : tmp; !x; x = !x)console.log(x); +for(var _ref_x = void 0, x1 = void 0 === _ref_x ? ('a' in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); From d910b1aa9297b9e3441ef1c0d2dcd8934e2f7413 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 27 May 2024 13:37:32 +1200 Subject: [PATCH 69/80] Cleanup simplify --- .../src/simplify/expr/mod.rs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 1a1cf9b4101c..3f2302eb2615 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -130,17 +130,15 @@ impl SimplifyExpr { IndexStr(JsWord), } let op = match prop { - MemberProp::Ident(Ident { sym, .. }) - if &**sym == "length" && !matches!(**obj, Expr::Object(..)) => - { + MemberProp::Ident(Ident { sym, .. }) if &**sym == "length" && !obj.is_object() => { KnownOp::Len } MemberProp::Ident(Ident { sym, .. }) => { - if !self.in_callee { - KnownOp::IndexStr(sym.clone()) - } else { + if self.in_callee { return; } + + KnownOp::IndexStr(sym.clone()) } MemberProp::Computed(ComputedPropName { expr, .. }) => { if self.in_callee { @@ -151,15 +149,15 @@ impl SimplifyExpr { // x[5] KnownOp::Index(*value) } else if let Known(s) = expr.as_pure_string(&self.expr_ctx) { - if let Ok(n) = s.parse::() { - // x['0'] is treated as x[0] - KnownOp::Index(n) - } else if s == "length" && !matches!(**obj, Expr::Object(..)) { + if s == "length" && !obj.is_object() { // Length of non-object type KnownOp::Len + } else if let Ok(n) = s.parse::() { + // x['0'] is treated as x[0] + KnownOp::Index(n) } else { // x[''] or x[...] where ... is an expression like [], ie x[[]] - KnownOp::IndexStr(JsWord::from(s)) + KnownOp::IndexStr(s.into()) } } else { return; From cefb0c010a7efb4b8a25f6d2dd4e62eb0a53a76d Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 30 May 2024 17:57:52 +1200 Subject: [PATCH 70/80] Handle `__proto__` object when indexing known object keys --- .../src/simplify/expr/mod.rs | 127 +++++++++++------- .../src/simplify/expr/tests.rs | 5 + 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 3f2302eb2615..f90d161b5472 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -405,54 +405,22 @@ impl SimplifyExpr { _ => return, }; - // do nothing if spread exists - let has_spread = props - .iter() - .any(|prop| matches!(prop, PropOrSpread::Spread(..))); - - if has_spread { + // Get `key`s value. Non-existent keys are handled in compress. + // This also checks if spread exists. + let Some(v) = get_key_value(&key, props) else { return; - } - - let idx = props.iter().rev().position(|p| match p { - PropOrSpread::Prop(p) => match &**p { - Prop::Shorthand(i) => i.sym == key, - Prop::KeyValue(k) => prop_name_eq(&k.key, &key), - Prop::Assign(p) => p.key.sym == key, - Prop::Getter(..) => false, - Prop::Setter(..) => false, - // TODO - Prop::Method(..) => false, - }, - _ => unreachable!(), - }); - let idx = idx.map(|idx| props.len() - 1 - idx); + }; - // Only replace if key exists; non-existent keys are handled in compress - if let Some(i) = idx { - let v = props.remove(i); - self.changed = true; + self.changed = true; - *expr = self.expr_ctx.preserve_effects( - *span, - match v { - PropOrSpread::Prop(p) => match *p { - Prop::Shorthand(i) => Expr::Ident(i), - Prop::KeyValue(p) => *p.value, - Prop::Assign(p) => *p.value, - Prop::Getter(..) => unreachable!(), - Prop::Setter(..) => unreachable!(), - // TODO - Prop::Method(..) => unreachable!(), - }, - _ => unreachable!(), - }, - once(Box::new(Expr::Object(ObjectLit { - props: props.take(), - span: *span, - }))), - ); - } + *expr = self.expr_ctx.preserve_effects( + *span, + v, + once(Box::new(Expr::Object(ObjectLit { + props: props.take(), + span: *span, + }))), + ); } _ => {} @@ -1831,3 +1799,72 @@ fn nth_char(s: &str, mut idx: usize) -> Option> { fn need_zero_for_this(e: &Expr) -> bool { e.directness_maters() || e.is_seq() } + +/// Gets the value of the given key from the given object properties, if the key exists. +/// If the key does exist, `Some` is returned and the property is removed from the given +/// properties. +fn get_key_value(key: &str, props: &mut Vec) -> Option { + // It's impossible to know the value for certain if a spread property exists. + let has_spread = props + .iter() + .any(|prop| prop.is_spread()); + + if has_spread { + return None; + } + + for (i, prop) in props.iter_mut().enumerate() { + let prop = match prop { + PropOrSpread::Prop(x) => &mut **x, + PropOrSpread::Spread(_) => unreachable!() + }; + + match prop { + Prop::Shorthand(ident) if ident.sym == key => { + let prop = match props.remove(i) { + PropOrSpread::Prop(x) => *x, + _ => unreachable!() + }; + let ident = match prop { + Prop::Shorthand(x) => x, + _ => unreachable!() + }; + return Some(Expr::Ident(ident)); + } + + Prop::KeyValue(prop) => { + if key != "__proto__" && prop_name_eq(&prop.key, "__proto__") { + // If __proto__ is defined, we need to check the contents of it, + // as well as any nested __proto__ objects + let Expr::Object(ObjectLit { props, .. }) = &mut *prop.value else { + // __proto__ is not an ObjectLiteral. It's unsafe to keep trying to find + // a value for this key, since __proto__ might also contain the key. + return None; + }; + + // Get key value from __props__ object. Only return if + // the result is Some. If None, we keep searching in the + // parent object. + let v = get_key_value(key, props); + if v.is_some() { + return v; + } + } else if prop_name_eq(&prop.key, key) { + let prop = match props.remove(i) { + PropOrSpread::Prop(x) => *x, + _ => unreachable!() + }; + let prop = match prop { + Prop::KeyValue(x) => x, + _ => unreachable!() + }; + return Some(*prop.value); + } + } + + _ => {} + } + } + + None +} diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index d9a00b7d20f6..33e21362f055 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1619,4 +1619,9 @@ fn test_issue8747() { fold("({0.5: 'a'})[0.5]", "'a';"); fold("({'0.5': 'a'})[0.5]", "'a';"); fold("({0.5: 'a'})['0.5']", "'a';"); + // Indexing objects that have a spread operator in `__proto__` can still be optimized + // if the key comes before the `__proto__` object. + fold("({1: 'bar', __proto__: {...[1]}})[1]", "({...[1]}), 'bar';"); + // Spread operator comes first, can't be evaluated. + fold_same("({...[1], 1: 'bar'})[1]"); } From 7c7836e6e2e37389602d0a586ac9e6da6f1263fd Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 30 May 2024 17:58:13 +1200 Subject: [PATCH 71/80] cargo fmt --- .../src/simplify/expr/mod.rs | 20 +++++++++---------- .../src/simplify/expr/tests.rs | 4 ++-- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index f90d161b5472..3cbec4bb8dad 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -1800,14 +1800,12 @@ fn need_zero_for_this(e: &Expr) -> bool { e.directness_maters() || e.is_seq() } -/// Gets the value of the given key from the given object properties, if the key exists. -/// If the key does exist, `Some` is returned and the property is removed from the given -/// properties. +/// Gets the value of the given key from the given object properties, if the key +/// exists. If the key does exist, `Some` is returned and the property is +/// removed from the given properties. fn get_key_value(key: &str, props: &mut Vec) -> Option { // It's impossible to know the value for certain if a spread property exists. - let has_spread = props - .iter() - .any(|prop| prop.is_spread()); + let has_spread = props.iter().any(|prop| prop.is_spread()); if has_spread { return None; @@ -1816,18 +1814,18 @@ fn get_key_value(key: &str, props: &mut Vec) -> Option { for (i, prop) in props.iter_mut().enumerate() { let prop = match prop { PropOrSpread::Prop(x) => &mut **x, - PropOrSpread::Spread(_) => unreachable!() + PropOrSpread::Spread(_) => unreachable!(), }; match prop { Prop::Shorthand(ident) if ident.sym == key => { let prop = match props.remove(i) { PropOrSpread::Prop(x) => *x, - _ => unreachable!() + _ => unreachable!(), }; let ident = match prop { Prop::Shorthand(x) => x, - _ => unreachable!() + _ => unreachable!(), }; return Some(Expr::Ident(ident)); } @@ -1852,11 +1850,11 @@ fn get_key_value(key: &str, props: &mut Vec) -> Option { } else if prop_name_eq(&prop.key, key) { let prop = match props.remove(i) { PropOrSpread::Prop(x) => *x, - _ => unreachable!() + _ => unreachable!(), }; let prop = match prop { Prop::KeyValue(x) => x, - _ => unreachable!() + _ => unreachable!(), }; return Some(*prop.value); } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 33e21362f055..58070e5558e1 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1619,8 +1619,8 @@ fn test_issue8747() { fold("({0.5: 'a'})[0.5]", "'a';"); fold("({'0.5': 'a'})[0.5]", "'a';"); fold("({0.5: 'a'})['0.5']", "'a';"); - // Indexing objects that have a spread operator in `__proto__` can still be optimized - // if the key comes before the `__proto__` object. + // Indexing objects that have a spread operator in `__proto__` can still be + // optimized if the key comes before the `__proto__` object. fold("({1: 'bar', __proto__: {...[1]}})[1]", "({...[1]}), 'bar';"); // Spread operator comes first, can't be evaluated. fold_same("({...[1], 1: 'bar'})[1]"); From 6bc56e90b71fa8d4a0a1961757ca78f6bf4a1577 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 2 Jun 2024 22:24:48 +1200 Subject: [PATCH 72/80] Remove `is_opt_call` --- crates/swc_ecma_minifier/src/compress/pure/ctx.rs | 2 -- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 2 +- crates/swc_ecma_minifier/src/compress/pure/mod.rs | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/ctx.rs b/crates/swc_ecma_minifier/src/compress/pure/ctx.rs index 1cddde32bd9b..dea0635c1b43 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/ctx.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/ctx.rs @@ -14,8 +14,6 @@ pub(super) struct Ctx { #[allow(unused)] pub is_callee: bool, - pub is_opt_call: bool, - pub _in_try_block: bool, pub is_lhs_of_assign: bool, diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index 2ebba7449eda..a016df5d47e8 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -246,7 +246,7 @@ impl Pure<'_> { obj: &mut Expr, prop: &MemberProp, ) -> Option { - if !self.options.pristine_globals || self.ctx.is_lhs_of_assign || self.ctx.is_opt_call { + if !self.options.pristine_globals || self.ctx.is_lhs_of_assign || self.ctx.is_callee { return None; } diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 20b9be8c5983..4a4dcfc90dbf 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -309,7 +309,7 @@ impl VisitMut for Pure<'_> { fn visit_mut_opt_call(&mut self, opt_call: &mut OptCall) { { let ctx = Ctx { - is_opt_call: true, + is_callee: true, ..self.ctx }; opt_call.callee.visit_mut_with(&mut *self.with_ctx(ctx)); @@ -882,7 +882,6 @@ impl VisitMut for Pure<'_> { let ctx = Ctx { is_update_arg: false, is_callee: false, - is_opt_call: false, in_delete: false, in_first_expr: true, ..self.ctx From cc0eabe9d105ebc65fb5945dc77ff312777fd1bd Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 13:25:47 +1200 Subject: [PATCH 73/80] Manually fix some files --- .../src/compress/pure/evaluate.rs | 1 - .../src/compress/pure/member_expr.rs | 10 +-- .../src/simplify/expr/mod.rs | 69 ------------------- 3 files changed, 5 insertions(+), 75 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 1fd56ed2cce8..71cd1119ad19 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -2,7 +2,6 @@ use radix_fmt::Radix; use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::{number::ToJsString, ExprExt, IsEmpty, Value}; -use swc_ecma_utils::{undefined, ExprExt, IsEmpty, Value}; #[cfg(feature = "debug")] use {crate::debug::dump, tracing::debug}; diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index a016df5d47e8..c6c71d677423 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -5,7 +5,7 @@ use swc_ecma_ast::{ ArrayLit, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, ObjectLit, Prop, PropOrSpread, SeqExpr, Str, }; -use swc_ecma_utils::{prop_name_eq, undefined, ExprExt, Known}; +use swc_ecma_utils::{prop_name_eq, ExprExt, Known}; use super::Pure; @@ -329,7 +329,7 @@ impl Pure<'_> { match op { KnownOp::Index(idx) => { if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { - Some(*undefined(*span)) + Some(*Expr::undefined(*span)) } else { // idx is in bounds, this is handled in simplify None @@ -345,7 +345,7 @@ impl Pure<'_> { if is_string_symbol(key.as_str()) { None } else { - Some(*undefined(*span)) + Some(*Expr::undefined(*span)) } } } @@ -383,13 +383,13 @@ impl Pure<'_> { // (0, void 0) Expr::Seq(SeqExpr { span: *span, - exprs: vec![0.into(), undefined(*span)] + exprs: vec![0.into(), Expr::undefined(*span)] }) } else { // Side effects exist, replacement is: // (x(), y(), void 0) // Where `x()` and `y()` are side effects. - exprs.push(undefined(*span)); + exprs.push(Expr::undefined(*span)); Expr::Seq(SeqExpr { span: *span, diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 3cbec4bb8dad..bbb177d41232 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -15,9 +15,6 @@ use swc_ecma_transforms_base::{ use swc_ecma_utils::{ is_literal, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, - prop_name_eq, to_int32, undefined, BoolType, ExprCtx, ExprExt, NullType, - is_literal, prop_name_eq, to_int32, undefined, BoolType, ExprCtx, ExprExt, NullType, - NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, }; use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith}; use Value::{Known, Unknown}; @@ -189,12 +186,6 @@ impl SimplifyExpr { } // 'foo'[1] - KnownOp::Index(idx) if (idx as usize) < value.len() => { - if idx < 0 { - self.changed = true; - *expr = *Expr::undefined(*span) - } else if let Some(value) = nth_char(value, idx as _) { - self.changed = true; KnownOp::Index(idx) => { if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() { // Prototype changes affect indexing if the index is out of bounds, so we @@ -329,66 +320,6 @@ impl SimplifyExpr { *expr = Expr::Seq(SeqExpr { span: *span, exprs }); } - self.changed = true; - - let (before, e, after) = if elems.len() > idx as _ && idx >= 0 { - let before = elems.drain(..(idx as usize)).collect(); - let mut iter = elems.take().into_iter(); - let e = iter.next().flatten(); - let after = iter.collect(); - - (before, e, after) - } else { - let before = elems.take(); - - (before, None, vec![]) - }; - - let v = match e { - None => Expr::undefined(*span), - Some(e) => e.expr, - }; - - let mut exprs = vec![]; - for elem in before.into_iter().flatten() { - self.expr_ctx - .extract_side_effects_to(&mut exprs, *elem.expr); - } - - let val = v; - - for elem in after.into_iter().flatten() { - self.expr_ctx - .extract_side_effects_to(&mut exprs, *elem.expr); - } - - if exprs.is_empty() { - *expr = Expr::Seq(SeqExpr { - span: val.span(), - exprs: vec![0.into(), val], - }); - return; - } - - exprs.push(val); - - *expr = Expr::Seq(SeqExpr { span: *span, exprs }); - } else if matches!(op, KnownOp::IndexStr(..)) { - let key = match op { - KnownOp::IndexStr(key) => key, - _ => unreachable!(), - }; - - if is_array_prop(key.as_str()) { - // Valid property - return; - } - - // Invalid property, resulting to undefined - self.changed = true; - *expr = *undefined(*span); - return; - // Handled in compress KnownOp::IndexStr(..) => {} } From b88a29db3308b077c013789647ec8b23fea344c0 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 13:33:23 +1200 Subject: [PATCH 74/80] Manually fix swc_ecma_minifier/Cargo.toml --- crates/swc_ecma_minifier/Cargo.toml | 78 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/crates/swc_ecma_minifier/Cargo.toml b/crates/swc_ecma_minifier/Cargo.toml index 1daceafffe47..7e267f7378cf 100644 --- a/crates/swc_ecma_minifier/Cargo.toml +++ b/crates/swc_ecma_minifier/Cargo.toml @@ -7,11 +7,11 @@ include = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"] license = "Apache-2.0" name = "swc_ecma_minifier" repository = "https://github.com/swc-project/swc.git" -version = "0.192.17" +version = "0.197.0" - [package.metadata.docs.rs] - all-features = true - rustdoc-args = ["--cfg", "docsrs"] +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [lib] bench = false @@ -33,52 +33,52 @@ serde-impl = [] trace-ast = [] [dependencies] -arrayvec = "0.7.2" -backtrace = { version = "0.3.61", optional = true } -indexmap = "2.0.0" -num-bigint = "0.4.3" -num_cpus = "1.13.1" -once_cell = "1.18.0" -parking_lot = "0.12.1" -pretty_assertions = { version = "1.3", optional = true } +arrayvec = { workspace = true } +backtrace = { workspace = true, optional = true } +indexmap = { workspace = true } +num-bigint = { workspace = true } +num_cpus = { workspace = true } +once_cell = { workspace = true } +parking_lot = { workspace = true } +pretty_assertions = { workspace = true, optional = true } radix_fmt = "=1.0.0" -rayon = { version = "1.7.0", optional = true } -regex = "1.5.3" -rustc-hash = "1.1.0" -ryu-js = "1.0.0" -serde = { version = "1.0.118", features = ["derive"] } -serde_json = "1.0.61" -tracing = "0.1.37" -phf = { version = "0.11.2", features = ["macros"] } +rayon = { workspace = true, optional = true } +regex = { workspace = true } +rustc-hash = { workspace = true } +ryu-js = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tracing = { workspace = true } +phf = { workspace = true } swc_atoms = { version = "0.6.5", path = "../swc_atoms" } -swc_common = { version = "0.33.19", path = "../swc_common" } -swc_config = { version = "0.1.11", path = "../swc_config", features = [ +swc_common = { version = "0.34.0", path = "../swc_common" } +swc_config = { version = "0.1.13", path = "../swc_config", features = [ "sourcemap", ] } -swc_ecma_ast = { version = "0.112.5", path = "../swc_ecma_ast", features = [ +swc_ecma_ast = { version = "0.115.0", path = "../swc_ecma_ast", features = [ "serde", ] } -swc_ecma_codegen = { version = "0.148.11", path = "../swc_ecma_codegen" } -swc_ecma_parser = { version = "0.143.9", path = "../swc_ecma_parser" } -swc_ecma_transforms_base = { version = "0.137.15", path = "../swc_ecma_transforms_base" } -swc_ecma_transforms_optimization = { version = "0.198.16", path = "../swc_ecma_transforms_optimization" } -swc_ecma_usage_analyzer = { version = "0.23.12", path = "../swc_ecma_usage_analyzer" } -swc_ecma_utils = { version = "0.127.12", path = "../swc_ecma_utils" } -swc_ecma_visit = { version = "0.98.6", path = "../swc_ecma_visit" } -swc_timer = { version = "0.21.20", path = "../swc_timer" } +swc_ecma_codegen = { version = "0.151.0", path = "../swc_ecma_codegen" } +swc_ecma_parser = { version = "0.146.0", path = "../swc_ecma_parser" } +swc_ecma_transforms_base = { version = "0.140.0", path = "../swc_ecma_transforms_base" } +swc_ecma_transforms_optimization = { version = "0.201.0", path = "../swc_ecma_transforms_optimization" } +swc_ecma_usage_analyzer = { version = "0.26.0", path = "../swc_ecma_usage_analyzer" } +swc_ecma_utils = { version = "0.130.0", path = "../swc_ecma_utils" } +swc_ecma_visit = { version = "0.101.0", path = "../swc_ecma_visit" } +swc_timer = { version = "0.22.0", path = "../swc_timer" } [dev-dependencies] -ansi_term = "0.12.1" -anyhow = "1" -criterion = "0.5.1" -pretty_assertions = "1.3" -walkdir = "2" +ansi_term = { workspace = true } +anyhow = { workspace = true } +criterion = { workspace = true } +pretty_assertions = { workspace = true } +walkdir = { workspace = true } -swc_ecma_testing = { version = "0.22.21", path = "../swc_ecma_testing" } +swc_ecma_testing = { version = "0.23.0", path = "../swc_ecma_testing" } swc_malloc = { version = "0.5.10", path = "../swc_malloc" } -testing = { version = "0.35.20", path = "../testing" } +testing = { version = "0.36.0", path = "../testing" } [[bench]] harness = false -name = "full" +name = "full" \ No newline at end of file From 98f4ebac57bbe3ae4108db184f363b43452eeb67 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 13:40:55 +1200 Subject: [PATCH 75/80] Update call to `nth_char` --- .../src/simplify/expr/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index bbb177d41232..a04646b54f3f 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -193,9 +193,12 @@ impl SimplifyExpr { return; } + let Some(value) = nth_char(value, idx as _) else { + return; + }; + self.changed = true; - let value = nth_char(value, idx as _); *expr = Expr::Lit(Lit::Str(Str { raw: None, value: value.into(), From fb62635be32b51ff370cdefa33534844c913b67d Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 13:41:05 +1200 Subject: [PATCH 76/80] Fix remaining `undefined` calls --- crates/swc_ecma_minifier/src/compress/pure/member_expr.rs | 4 ++-- .../swc_ecma_transforms_optimization/src/simplify/expr/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs index c6c71d677423..22b57e09c253 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/member_expr.rs @@ -444,7 +444,7 @@ impl Pure<'_> { prop: prop.clone(), }) } else { - let val = undefined(*span); + let val = Expr::undefined(*span); if exprs.is_empty() { // No side effects, replacement is: @@ -537,7 +537,7 @@ impl Pure<'_> { }) } else { // Invalid key. Replace with side effects plus `undefined`. - *undefined(*span) + *Expr::undefined(*span) }, props.drain(..).map(|x| match x { PropOrSpread::Prop(prop) => match *prop { diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index a04646b54f3f..2712e478dc98 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -283,7 +283,7 @@ impl SimplifyExpr { // element value let v = match e { - None => undefined(*span), + None => Expr::undefined(*span), Some(e) => e.expr, }; From af081f2f16779f75f4b3c514050d8d1c6713ddff Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 14:03:53 +1200 Subject: [PATCH 77/80] Update unit test file --- crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js index eb611cad0feb..7eb42feb65d3 100644 --- a/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/pr/6169/1/output.js @@ -1 +1,3 @@ -(void 0).toUpperCase(); +[ + "foo" +][1].toUpperCase(); From f5a12fda92834b11d2c65c0869f0decba93004dc Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 14:57:27 +1200 Subject: [PATCH 78/80] Update unit test files --- .../tsc-references/destructuringControlFlow.2.minified.js | 4 +++- crates/swc/tests/tsc-references/enumBasics.2.minified.js | 2 -- .../tests/tsc-references/parserForStatement9.2.minified.js | 4 ---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js b/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js index 7efeb216d6ca..320c4695dc8c 100644 --- a/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js +++ b/crates/swc/tests/tsc-references/destructuringControlFlow.2.minified.js @@ -1,3 +1,5 @@ //// [destructuringControlFlow.ts] import "@swc/helpers/_/_sliced_to_array"; -(void 0).toUpperCase(); +[ + "foo" +][1].toUpperCase(); diff --git a/crates/swc/tests/tsc-references/enumBasics.2.minified.js b/crates/swc/tests/tsc-references/enumBasics.2.minified.js index c974ee536567..c8e0e8e104e9 100644 --- a/crates/swc/tests/tsc-references/enumBasics.2.minified.js +++ b/crates/swc/tests/tsc-references/enumBasics.2.minified.js @@ -2,5 +2,3 @@ (E1 = E11 || (E11 = {}))[E1.A = 0] = "A", E1[E1.B = 1] = "B", E1[E1.C = 2] = "C"; var E2, E3, E4, E5, E6, E7, E8, E9, E1, E11, E21, E31, E41, E51, E61, E71, E81, E91, e = E11; E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = 'foo'.foo] = "A", (E8 = E81 || (E81 = {}))[E8.B = 'foo'.foo] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; -E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = void 0] = "A", (E8 = E81 || (E81 = {}))[E8.B = void 0] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; -E11[e.A], (E2 = E21 || (E21 = {}))[E2.A = 1] = "A", E2[E2.B = 2] = "B", E2[E2.C = 3] = "C", (E3 = E31 || (E31 = {}))[E3.X = 3] = "X", E3[E3.Y = 7] = "Y", E3[E3.Z = NaN] = "Z", (E4 = E41 || (E41 = {}))[E4.X = 0] = "X", E4[E4.Y = 1] = "Y", E4[E4.Z = 3] = "Z", (E5 = E51 || (E51 = {}))[E5.A = 0] = "A", E5[E5.B = 3] = "B", E5[E5.C = 4] = "C", (E6 = E61 || (E61 = {}))[E6.A = 0] = "A", E6[E6.B = 0] = "B", E6[E6.C = 1] = "C", (E7 = E71 || (E71 = {}))[E7.A = "foo".foo] = "A", (E8 = E81 || (E81 = {}))[E8.B = "foo".foo] = "B", (E9 = E91 || (E91 = {}))[E9.A = 0] = "A", E9[E9.B = 0] = "B", E81.B, E71.A, E41.Z, E31.X, E31.Z; diff --git a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js index 32d379ba1049..356823ddeb48 100644 --- a/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js +++ b/crates/swc/tests/tsc-references/parserForStatement9.2.minified.js @@ -1,7 +1,3 @@ //// [parserForStatement9.ts] -for(var tmp = [][0], x = void 0 === tmp ? ('a' in {}) : tmp; !x; x = !x)console.log(x); -for(var _ref_x = {}.x, x1 = void 0 === _ref_x ? ('a' in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); -for(var tmp = void 0, x = void 0 === tmp ? ("a" in {}) : tmp; !x; x = !x)console.log(x); -for(var _ref_x = void 0, x1 = void 0 === _ref_x ? ("a" in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); for(var tmp = void 0, x = void 0 === tmp ? ('a' in {}) : tmp; !x; x = !x)console.log(x); for(var _ref_x = void 0, x1 = void 0 === _ref_x ? ('a' in {}) : _ref_x; !x1; x1 = !x1)console.log(x1); From 25fad28d199e3e76c5d30cdc0deff75ef632d9b0 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 19:54:47 +1200 Subject: [PATCH 79/80] Add `report_change` call --- crates/swc_ecma_minifier/src/compress/pure/evaluate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 71cd1119ad19..d65833dfda50 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -658,6 +658,7 @@ impl Pure<'_> { { *e = replacement; self.changed = true; + report_change!("member_expr: Optimized member expression"); #[cfg(feature = "debug")] debug!("after: optimize_member_expr: {}", dump(&*e, false)); From 6db7b187fd35953972d6a54cc03c03fa0b9359a6 Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Jun 2024 19:55:20 +1200 Subject: [PATCH 80/80] `test_issue8747` -> `test_issue_8747` --- .../swc_ecma_transforms_optimization/src/simplify/expr/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 58070e5558e1..b70c0743a1e9 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1577,7 +1577,7 @@ fn test_export_default_paren_expr() { } #[test] -fn test_issue8747() { +fn test_issue_8747() { // Index with a valid index. fold("'a'[0]", "\"a\";"); fold("'a'['0']", "\"a\";");