diff --git a/crates/swc/tests/fixture/issues-8xxx/8398/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8398/input/.swcrc new file mode 100644 index 000000000000..a467258d7ccc --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8398/input/.swcrc @@ -0,0 +1,15 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": true + }, + "target": "es2022", + "minify": { + "compress": true + }, + "loose": false + }, + "isModule": true, + "minify": true +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8398/input/index.js b/crates/swc/tests/fixture/issues-8xxx/8398/input/index.js new file mode 100644 index 000000000000..63929badf43c --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8398/input/index.js @@ -0,0 +1,12 @@ +(obj?.a).b; +(obj?.a)(); +(obj?.a)[0]; +(obj?.a)`hello`; + +(obj.a?.()).a; +(obj.a?.())(); +(obj.a?.())[0]; +(obj.a?.())`hello`; + +(((a?.b))); +(((a?.()))); \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8398/output/index.js b/crates/swc/tests/fixture/issues-8xxx/8398/output/index.js new file mode 100644 index 000000000000..df42c1d7e65e --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8398/output/index.js @@ -0,0 +1 @@ +(obj?.a).b,(obj?.a)(),(obj?.a)[0],(obj?.a)`hello`,(obj.a?.()).a,(obj.a?.())(),(obj.a?.())[0],(obj.a?.())`hello`,a?.b,a?.(); diff --git a/crates/swc/tests/tsc-references/checkObjectDefineProperty.1.normal.js b/crates/swc/tests/tsc-references/checkObjectDefineProperty.1.normal.js index bf283e63f5ae..92cf92d8370c 100644 --- a/crates/swc/tests/tsc-references/checkObjectDefineProperty.1.normal.js +++ b/crates/swc/tests/tsc-references/checkObjectDefineProperty.1.normal.js @@ -46,7 +46,7 @@ var needsExemplar = function() { var _ = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : x; return void 0; }; -var expected = /** @type {{name: string, readonly middleInit: string, readonly lastName: string, zip: number, readonly houseNumber: number, zipStr: string}} */ /** @type {*} */ null; +var expected = /** @type {*} */ null; /** * * @param {typeof returnExemplar} a diff --git a/crates/swc/tests/tsc-references/deleteChain.1.normal.js b/crates/swc/tests/tsc-references/deleteChain.1.normal.js index 3f03d680fa16..e914bf5c86ef 100644 --- a/crates/swc/tests/tsc-references/deleteChain.1.normal.js +++ b/crates/swc/tests/tsc-references/deleteChain.1.normal.js @@ -1,15 +1,15 @@ //// [deleteChain.ts] -var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _this, _o4_b1, _o4_b_c_d1, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1; +var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _o4_b_c_d1, _o4_b1, _o4_b_c_d2, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1; o1 === null || o1 === void 0 ? true : delete o1.b; -delete (o1 === null || o1 === void 0 ? void 0 : o1.b); +o1 === null || o1 === void 0 ? true : delete o1.b; +o2 === null || o2 === void 0 ? true : delete o2.b.c; o2 === null || o2 === void 0 ? true : delete o2.b.c; -delete (o2 === null || o2 === void 0 ? void 0 : o2.b.c); (_o3_b = o3.b) === null || _o3_b === void 0 ? true : delete _o3_b.c; -delete ((_o3_b1 = o3.b) === null || _o3_b1 === void 0 ? void 0 : _o3_b1.c); +(_o3_b1 = o3.b) === null || _o3_b1 === void 0 ? true : delete _o3_b1.c; (_o4_b = o4.b) === null || _o4_b === void 0 ? true : (_o4_b_c_d = _o4_b.c.d) === null || _o4_b_c_d === void 0 ? true : delete _o4_b_c_d.e; -(_this = (_o4_b1 = o4.b) === null || _o4_b1 === void 0 ? void 0 : _o4_b1.c.d) === null || _this === void 0 ? true : delete _this.e; -delete ((_o4_b2 = o4.b) === null || _o4_b2 === void 0 ? void 0 : (_o4_b_c_d1 = _o4_b2.c.d) === null || _o4_b_c_d1 === void 0 ? void 0 : _o4_b_c_d1.e); +(_o4_b1 = o4.b) === null || _o4_b1 === void 0 ? true : (_o4_b_c_d1 = _o4_b1.c.d) === null || _o4_b_c_d1 === void 0 ? true : delete _o4_b_c_d1.e; +(_o4_b2 = o4.b) === null || _o4_b2 === void 0 ? true : (_o4_b_c_d2 = _o4_b2.c.d) === null || _o4_b_c_d2 === void 0 ? true : delete _o4_b_c_d2.e; (_o5_b = o5.b) === null || _o5_b === void 0 ? true : (_o5_b_c_d = _o5_b.call(o5).c.d) === null || _o5_b_c_d === void 0 ? true : delete _o5_b_c_d.e; -delete ((_o5_b1 = o5.b) === null || _o5_b1 === void 0 ? void 0 : (_o5_b_c_d1 = _o5_b1.call(o5).c.d) === null || _o5_b_c_d1 === void 0 ? void 0 : _o5_b_c_d1.e); +(_o5_b1 = o5.b) === null || _o5_b1 === void 0 ? true : (_o5_b_c_d1 = _o5_b1.call(o5).c.d) === null || _o5_b_c_d1 === void 0 ? true : delete _o5_b_c_d1.e; (_o6_b = o6.b) === null || _o6_b === void 0 ? true : (_o6_b_c_d = _o6_b["c"].d) === null || _o6_b_c_d === void 0 ? true : delete _o6_b_c_d["e"]; -delete ((_o6_b1 = o6.b) === null || _o6_b1 === void 0 ? void 0 : (_o6_b_c_d1 = _o6_b1["c"].d) === null || _o6_b_c_d1 === void 0 ? void 0 : _o6_b_c_d1["e"]); +(_o6_b1 = o6.b) === null || _o6_b1 === void 0 ? true : (_o6_b_c_d1 = _o6_b1["c"].d) === null || _o6_b_c_d1 === void 0 ? true : delete _o6_b_c_d1["e"]; diff --git a/crates/swc/tests/tsc-references/deleteChain.2.minified.js b/crates/swc/tests/tsc-references/deleteChain.2.minified.js index df8c98213be3..db2ef16dc2e5 100644 --- a/crates/swc/tests/tsc-references/deleteChain.2.minified.js +++ b/crates/swc/tests/tsc-references/deleteChain.2.minified.js @@ -1,3 +1,3 @@ //// [deleteChain.ts] -var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _this, _o4_b1, _o4_b_c_d1, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1; -null == o1 || delete o1.b, delete (null == o1 ? void 0 : o1.b), null == o2 || delete o2.b.c, delete (null == o2 ? void 0 : o2.b.c), null === (_o3_b = o3.b) || void 0 === _o3_b || delete _o3_b.c, delete (null === (_o3_b1 = o3.b) || void 0 === _o3_b1 ? void 0 : _o3_b1.c), null === (_o4_b = o4.b) || void 0 === _o4_b || null === (_o4_b_c_d = _o4_b.c.d) || void 0 === _o4_b_c_d || delete _o4_b_c_d.e, null === (_this = null === (_o4_b1 = o4.b) || void 0 === _o4_b1 ? void 0 : _o4_b1.c.d) || void 0 === _this || delete _this.e, delete (null === (_o4_b2 = o4.b) || void 0 === _o4_b2 ? void 0 : null === (_o4_b_c_d1 = _o4_b2.c.d) || void 0 === _o4_b_c_d1 ? void 0 : _o4_b_c_d1.e), null === (_o5_b = o5.b) || void 0 === _o5_b || null === (_o5_b_c_d = _o5_b.call(o5).c.d) || void 0 === _o5_b_c_d || delete _o5_b_c_d.e, delete (null === (_o5_b1 = o5.b) || void 0 === _o5_b1 ? void 0 : null === (_o5_b_c_d1 = _o5_b1.call(o5).c.d) || void 0 === _o5_b_c_d1 ? void 0 : _o5_b_c_d1.e), null === (_o6_b = o6.b) || void 0 === _o6_b || null === (_o6_b_c_d = _o6_b.c.d) || void 0 === _o6_b_c_d || delete _o6_b_c_d.e, delete (null === (_o6_b1 = o6.b) || void 0 === _o6_b1 ? void 0 : null === (_o6_b_c_d1 = _o6_b1.c.d) || void 0 === _o6_b_c_d1 ? void 0 : _o6_b_c_d1.e); +var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _o4_b_c_d1, _o4_b1, _o4_b_c_d2, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1; +null == o1 || delete o1.b, null == o1 || delete o1.b, null == o2 || delete o2.b.c, null == o2 || delete o2.b.c, null === (_o3_b = o3.b) || void 0 === _o3_b || delete _o3_b.c, null === (_o3_b1 = o3.b) || void 0 === _o3_b1 || delete _o3_b1.c, null === (_o4_b = o4.b) || void 0 === _o4_b || null === (_o4_b_c_d = _o4_b.c.d) || void 0 === _o4_b_c_d || delete _o4_b_c_d.e, null === (_o4_b1 = o4.b) || void 0 === _o4_b1 || null === (_o4_b_c_d1 = _o4_b1.c.d) || void 0 === _o4_b_c_d1 || delete _o4_b_c_d1.e, null === (_o4_b2 = o4.b) || void 0 === _o4_b2 || null === (_o4_b_c_d2 = _o4_b2.c.d) || void 0 === _o4_b_c_d2 || delete _o4_b_c_d2.e, null === (_o5_b = o5.b) || void 0 === _o5_b || null === (_o5_b_c_d = _o5_b.call(o5).c.d) || void 0 === _o5_b_c_d || delete _o5_b_c_d.e, null === (_o5_b1 = o5.b) || void 0 === _o5_b1 || null === (_o5_b_c_d1 = _o5_b1.call(o5).c.d) || void 0 === _o5_b_c_d1 || delete _o5_b_c_d1.e, null === (_o6_b = o6.b) || void 0 === _o6_b || null === (_o6_b_c_d = _o6_b.c.d) || void 0 === _o6_b_c_d || delete _o6_b_c_d.e, null === (_o6_b1 = o6.b) || void 0 === _o6_b1 || null === (_o6_b_c_d1 = _o6_b1.c.d) || void 0 === _o6_b_c_d1 || delete _o6_b_c_d1.e; diff --git a/crates/swc/tests/tsc-references/importTypeInJSDoc.1.normal.js b/crates/swc/tests/tsc-references/importTypeInJSDoc.1.normal.js index 99ff7eda4c02..118e7b69ccdd 100644 --- a/crates/swc/tests/tsc-references/importTypeInJSDoc.1.normal.js +++ b/crates/swc/tests/tsc-references/importTypeInJSDoc.1.normal.js @@ -11,7 +11,7 @@ //// [index.js] /** * @typedef {import("./externs")} Foo - */ let a = /** @type {Foo} */ /** @type {*} */ undefined; + */ let a = /** @type {*} */ undefined; a = new Foo({ doer: Foo.Bar }); diff --git a/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2015).1.normal.js b/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2015).1.normal.js index 444a346e35a6..aeb64f6f80e9 100644 --- a/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2015).1.normal.js +++ b/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2015).1.normal.js @@ -8,8 +8,8 @@ const foo = new Foo(); (_foo_m1 = foo.m) === null || _foo_m1 === void 0 ? void 0 : _foo_m1.call(foo); (_foo_m2 = foo.m) === null || _foo_m2 === void 0 ? void 0 : _foo_m2.call(foo); (_foo_m3 = foo.m) === null || _foo_m3 === void 0 ? void 0 : _foo_m3.call(foo); -// https://github.com/microsoft/TypeScript/issues/50148 -(foo === null || foo === void 0 ? void 0 : foo.m).length; +(// https://github.com/microsoft/TypeScript/issues/50148 +foo === null || foo === void 0 ? void 0 : foo.m).length; (foo === null || foo === void 0 ? void 0 : foo.m).length; (foo === null || foo === void 0 ? void 0 : foo["m"]).length; (foo === null || foo === void 0 ? void 0 : foo["m"]).length; diff --git a/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2022).2.minified.js b/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2022).2.minified.js index c05ec78431b1..cada48ed1268 100644 --- a/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2022).2.minified.js +++ b/crates/swc/tests/tsc-references/optionalChainingInTypeAssertions(target=es2022).2.minified.js @@ -2,4 +2,4 @@ const foo = new class { m() {} }(); -foo.m?.(), foo.m?.(), foo.m?.(), foo.m?.(), foo?.m.length, foo?.m.length, foo?.m.length, foo?.m.length; +foo.m?.(), foo.m?.(), foo.m?.(), foo.m?.(), (foo?.m).length, (foo?.m).length, (foo?.m).length, (foo?.m).length; diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 374e1196c7fe..814f122b5f4c 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -334,10 +334,6 @@ impl VisitMut for Pure<'_> { } fn visit_mut_expr(&mut self, e: &mut Expr) { - if let Expr::Paren(p) = e { - *e = *p.expr.take(); - } - { let ctx = Ctx { in_first_expr: false, diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs index 3d564b95da93..eac05979bf82 100644 --- a/crates/swc_ecma_minifier/src/metadata/mod.rs +++ b/crates/swc_ecma_minifier/src/metadata/mod.rs @@ -97,7 +97,13 @@ impl VisitMut for InfoMarker<'_> { fn visit_mut_call_expr(&mut self, n: &mut CallExpr) { n.visit_mut_children_with(self); - if has_noinline(self.comments, n.span) { + // TODO: remove after we figure out how to move comments properly + if has_noinline(self.comments, n.span) + || match &n.callee { + Callee::Expr(e) => has_noinline(self.comments, e.span()), + _ => false, + } + { n.span = n.span.apply_mark(self.marks.noinline); } diff --git a/crates/swc_ecma_minifier/src/pass/precompress.rs b/crates/swc_ecma_minifier/src/pass/precompress.rs index 49b160660f75..efd758a510bf 100644 --- a/crates/swc_ecma_minifier/src/pass/precompress.rs +++ b/crates/swc_ecma_minifier/src/pass/precompress.rs @@ -8,6 +8,7 @@ use crate::HEAVY_TASK_PARALLELS; /// Optimizer invoked before invoking compressor. /// /// - Remove parens. +/// TODO: remove completely after #8333 pub(crate) fn precompress_optimizer<'a>() -> impl 'a + VisitMut { PrecompressOptimizer {} } @@ -26,14 +27,6 @@ impl Parallel for PrecompressOptimizer { impl VisitMut for PrecompressOptimizer { noop_visit_mut_type!(); - fn visit_mut_expr(&mut self, e: &mut Expr) { - e.visit_mut_children_with(self); - - if let Expr::Paren(p) = e { - *e = *p.expr.take(); - } - } - fn visit_mut_pat_or_expr(&mut self, n: &mut PatOrExpr) { n.visit_mut_children_with(self); diff --git a/crates/swc_ecma_minifier/tests/compress.rs b/crates/swc_ecma_minifier/tests/compress.rs index c13ac95f099c..8049903b74b9 100644 --- a/crates/swc_ecma_minifier/tests/compress.rs +++ b/crates/swc_ecma_minifier/tests/compress.rs @@ -39,7 +39,11 @@ use swc_ecma_parser::{ EsConfig, Parser, Syntax, }; use swc_ecma_testing::{exec_node_js, JsExecOptions}; -use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene, resolver}; +use swc_ecma_transforms_base::{ + fixer::{fixer, paren_remover}, + hygiene::hygiene, + resolver, +}; use swc_ecma_utils::drop_span; use swc_ecma_visit::{FoldWith, Visit, VisitMut, VisitMutWith, VisitWith}; use testing::{assert_eq, unignore_fixture, DebugUsingDisplay, NormalizedOutput}; @@ -194,7 +198,12 @@ fn run( .map_err(|err| { err.into_diagnostic(handler).emit(); }) - .map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false))); + .map(|mut module| { + module.visit_mut_with(&mut paren_remover(Some(&comments))); + module.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false)); + + module + }); // Ignore parser errors. // diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8398/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8398/input.js new file mode 100644 index 000000000000..49f7ae0b3f00 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/8398/input.js @@ -0,0 +1 @@ +(obj?.a).b; diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8398/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8398/output.js new file mode 100644 index 000000000000..49f7ae0b3f00 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/8398/output.js @@ -0,0 +1 @@ +(obj?.a).b; diff --git a/crates/swc_ecma_minifier/tests/terser_exec.rs b/crates/swc_ecma_minifier/tests/terser_exec.rs index 3d5d509fba39..f82cc56266a6 100644 --- a/crates/swc_ecma_minifier/tests/terser_exec.rs +++ b/crates/swc_ecma_minifier/tests/terser_exec.rs @@ -32,7 +32,11 @@ use swc_ecma_parser::{ lexer::{input::SourceFileInput, Lexer}, EsConfig, Parser, Syntax, }; -use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene, resolver}; +use swc_ecma_transforms_base::{ + fixer::{fixer, paren_remover}, + hygiene::hygiene, + resolver, +}; use swc_ecma_visit::{FoldWith, VisitMutWith}; use testing::assert_eq; @@ -197,7 +201,12 @@ fn run(cm: Lrc, handler: &Handler, input: &Path, config: &str) -> Opt .map_err(|err| { err.into_diagnostic(handler).emit(); }) - .map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false))); + .map(|mut module| { + module.visit_mut_with(&mut paren_remover(Some(&comments))); + module.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false)); + + module + }); // Ignore parser errors. // diff --git a/crates/swc_ecma_transforms_base/src/fixer.rs b/crates/swc_ecma_transforms_base/src/fixer.rs index 4b1988e0efa3..a4a9fabf1769 100644 --- a/crates/swc_ecma_transforms_base/src/fixer.rs +++ b/crates/swc_ecma_transforms_base/src/fixer.rs @@ -1,4 +1,4 @@ -use std::{hash::BuildHasherDefault, ops::RangeFull}; +use std::{hash::BuildHasherDefault, mem, ops::RangeFull}; use indexmap::IndexMap; use rustc_hash::FxHasher; @@ -18,6 +18,7 @@ pub fn fixer(comments: Option<&dyn Comments>) -> impl '_ + Fold + VisitMut { ctx: Default::default(), span_map: Default::default(), in_for_stmt_head: Default::default(), + in_opt_chain: Default::default(), remove_only: false, }) } @@ -28,6 +29,7 @@ pub fn paren_remover(comments: Option<&dyn Comments>) -> impl '_ + Fold + VisitM ctx: Default::default(), span_map: Default::default(), in_for_stmt_head: Default::default(), + in_opt_chain: Default::default(), remove_only: true, }) } @@ -42,6 +44,7 @@ struct Fixer<'a> { span_map: IndexMap>, in_for_stmt_head: bool, + in_opt_chain: bool, remove_only: bool, } @@ -84,21 +87,6 @@ macro_rules! array { } impl Fixer<'_> { - fn visit_call>( - &mut self, - args: &mut Vec, - callee: &mut T, - ) -> Context { - let old = self.ctx; - self.ctx = Context::ForcedExpr; - args.visit_mut_with(self); - - self.ctx = Context::Callee { is_new: false }; - callee.visit_mut_with(self); - - old - } - fn wrap_callee(&mut self, e: &mut Expr) { match e { Expr::Lit(Lit::Num(..) | Lit::Str(..)) => (), @@ -397,24 +385,47 @@ impl VisitMut for Fixer<'_> { } fn visit_mut_call_expr(&mut self, node: &mut CallExpr) { - let old = self.visit_call(&mut node.args, &mut node.callee); + let ctx = mem::replace(&mut self.ctx, Context::Callee { is_new: false }); + + node.callee.visit_mut_with(self); if let Callee::Expr(e) = &mut node.callee { - if let Expr::OptChain(_) = &**e { - self.wrap(e) - } else { - self.wrap_callee(e) + match &**e { + Expr::OptChain(_) if !self.in_opt_chain => self.wrap(e), + _ => self.wrap_callee(e), } } - self.ctx = old; + self.ctx = Context::ForcedExpr; + + node.args.visit_mut_with(self); + + self.ctx = ctx; + } + + fn visit_mut_opt_chain_base(&mut self, n: &mut OptChainBase) { + if !n.is_member() { + n.visit_mut_children_with(self); + return; + } + + let in_opt_chain = mem::replace(&mut self.in_opt_chain, true); + n.visit_mut_children_with(self); + self.in_opt_chain = in_opt_chain; } fn visit_mut_opt_call(&mut self, node: &mut OptCall) { - let old = self.visit_call(&mut node.args, &mut node.callee); + let ctx = mem::replace(&mut self.ctx, Context::Callee { is_new: false }); + let in_opt_chain = mem::replace(&mut self.in_opt_chain, true); + node.callee.visit_mut_with(self); self.wrap_callee(&mut node.callee); - self.ctx = old; + self.in_opt_chain = in_opt_chain; + + self.ctx = Context::ForcedExpr; + node.args.visit_mut_with(self); + + self.ctx = ctx; } fn visit_mut_class(&mut self, node: &mut Class) { @@ -471,6 +482,7 @@ impl VisitMut for Fixer<'_> { } } self.unwrap_expr(e); + e.visit_mut_children_with(self); self.ctx = ctx; @@ -575,32 +587,31 @@ impl VisitMut for Fixer<'_> { } fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) { - n.obj.visit_mut_with(self); - n.prop.visit_mut_with(self); - - match n { - MemberExpr { obj, .. } - if obj.is_object() && matches!(self.ctx, Context::ForcedExpr) => {} - - MemberExpr { obj, .. } - if obj.is_fn_expr() - || obj.is_cond() - || obj.is_unary() - || obj.is_seq() - || obj.is_update() - || obj.is_bin() - || obj.is_object() - || obj.is_assign() - || obj.is_arrow() - || obj.is_class() - || obj.is_yield_expr() - || obj.is_await_expr() - || (obj.is_call() && matches!(self.ctx, Context::Callee { is_new: true })) - || matches!(**obj, Expr::New(NewExpr { args: None, .. })) => - { - self.wrap(obj); - } + n.visit_mut_children_with(self); + match *n.obj { + Expr::Object(..) if self.ctx == Context::ForcedExpr => {} + Expr::Fn(..) + | Expr::Cond(..) + | Expr::Unary(..) + | Expr::Seq(..) + | Expr::Update(..) + | Expr::Bin(..) + | Expr::Object(..) + | Expr::Assign(..) + | Expr::Arrow(..) + | Expr::Class(..) + | Expr::Yield(..) + | Expr::Await(..) + | Expr::New(NewExpr { args: None, .. }) => { + self.wrap(&mut n.obj); + } + Expr::Call(..) if self.ctx == Context::Callee { is_new: true } => { + self.wrap(&mut n.obj); + } + Expr::OptChain(..) if !self.in_opt_chain => { + self.wrap(&mut n.obj); + } _ => {} } } @@ -691,7 +702,8 @@ impl VisitMut for Fixer<'_> { e.visit_mut_children_with(self); match &*e.tag { - Expr::Arrow(..) + Expr::OptChain(..) + | Expr::Arrow(..) | Expr::Cond(..) | Expr::Bin(..) | Expr::Seq(..) @@ -857,17 +869,13 @@ impl Fixer<'_> { } } - let expr = Expr::Seq(SeqExpr { span: *span, exprs }); + let mut expr = Expr::Seq(SeqExpr { span: *span, exprs }); - match self.ctx { - Context::ForcedExpr => { - *e = Expr::Paren(ParenExpr { - span: *span, - expr: Box::new(expr), - }) - } - _ => *e = expr, + if let Context::ForcedExpr = self.ctx { + self.wrap(&mut expr); }; + + *e = expr; } Expr::Cond(expr) => { @@ -907,10 +915,7 @@ impl Fixer<'_> { || callee.is_await_expr() || callee.is_assign() => { - *callee = Box::new(Expr::Paren(ParenExpr { - span: callee.span(), - expr: callee.take(), - })) + self.wrap(callee); } Expr::OptChain(OptChainExpr { base, .. }) => match &mut **base { OptChainBase::Call(OptCall { callee, .. }) @@ -919,10 +924,7 @@ impl Fixer<'_> { || callee.is_await_expr() || callee.is_assign() => { - *callee = Box::new(Expr::Paren(ParenExpr { - span: callee.span(), - expr: callee.take(), - })) + self.wrap(callee); } OptChainBase::Call(OptCall { callee, .. }) if callee.is_fn_expr() => match self.ctx @@ -974,51 +976,31 @@ impl Fixer<'_> { }; let expr = Box::new(e.take()); - *e = Expr::Paren(ParenExpr { expr, span }) + *e = Expr::Paren(ParenExpr { expr, span }); } /// Removes paren fn unwrap_expr(&mut self, e: &mut Expr) { - match e { - Expr::Seq(SeqExpr { exprs, .. }) if exprs.len() == 1 => { - self.unwrap_expr(exprs.last_mut().unwrap()); - *e = *exprs.last_mut().unwrap().take(); - } - - Expr::Paren(ParenExpr { - span: paren_span, - expr, - .. - }) => { - // `(a?.b).c !== a?.b.c` - if expr.is_opt_chain() { - return; - } - - // `(a?.b.c)() !== a?.b.c()` - if Self::contain_opt_chain(expr) { - return; + loop { + match e { + Expr::Seq(SeqExpr { exprs, .. }) if exprs.len() == 1 => { + *e = *exprs[0].take(); } - let expr_span = expr.span(); - let paren_span = *paren_span; - self.unwrap_expr(expr); - *e = *expr.take(); + Expr::Paren(ParenExpr { + span: paren_span, + expr, + .. + }) => { + let expr_span = expr.span(); + let paren_span = *paren_span; + *e = *expr.take(); - self.span_map.insert(expr_span, paren_span); - } - _ => {} - } - } + self.span_map.insert(expr_span, paren_span); + } - fn contain_opt_chain(e: &Expr) -> bool { - match e { - Expr::Member(member) => Self::contain_opt_chain(&member.obj), - Expr::OptChain(_) => true, - Expr::Call(CallExpr { callee, .. }) if callee.is_expr() => { - Self::contain_opt_chain(callee.as_expr().unwrap()) + _ => return, } - _ => false, } } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs index b31f08dbd505..a953ca9e91ff 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs @@ -1,5 +1,5 @@ use swc_common::{chain, Mark, SyntaxContext}; -use swc_ecma_transforms_base::resolver; +use swc_ecma_transforms_base::{fixer::paren_remover, resolver}; use swc_ecma_utils::ExprCtx; use swc_ecma_visit::as_folder; @@ -15,6 +15,7 @@ macro_rules! test_stmt { chain!( resolver(unresolved_mark, top_level_mark, false), + paren_remover(None), expr_simplifier(top_level_mark, Default::default()), as_folder(super::Remover { changed: false, 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 6cb66df6cfd4..175324c9ac88 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -1278,15 +1278,8 @@ impl VisitMut for SimplifyExpr { match expr { // Do nothing. - Expr::Lit(_) | Expr::This(..) => return, - - // Remove parenthesis. This may break ast, but it will be fixed up later. - Expr::Paren(ParenExpr { expr: e, .. }) => { - self.changed = true; - - *expr = *e.take(); - return; - } + // Note: Paren should be handled in fixer + Expr::Lit(_) | Expr::This(..) | Expr::Paren(..) => return, Expr::Seq(seq) if seq.exprs.is_empty() => 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 7d2f29f315f3..55a6c7ef91aa 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1,5 +1,5 @@ use swc_common::{chain, Mark, SyntaxContext}; -use swc_ecma_transforms_base::resolver; +use swc_ecma_transforms_base::{fixer::paren_remover, resolver}; use swc_ecma_transforms_testing::test_transform; use swc_ecma_utils::ExprCtx; use swc_ecma_visit::as_folder; @@ -15,6 +15,7 @@ fn fold(src: &str, expected: &str) { chain!( resolver(unresolved_mark, top_level_mark, false), + paren_remover(None), as_folder(SimplifyExpr { expr_ctx: ExprCtx { unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark), diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index 7a851f99b758..626cba6f5d5f 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -2032,6 +2032,11 @@ impl Minifier<'_> { &mut swc_ecma_transforms_base::resolver(unresolved_mark, top_level_mark, false), ); + let program = swc_ecma_visit::FoldWith::fold_with( + program, + &mut swc_ecma_transforms_base::fixer::paren_remover(Some(&comments)), + ); + let program = swc_ecma_minifier::optimize( program, cm.clone(),