fix(es/fixer): Preserve parenthesis for optional chaining (#8399)

**Related issue:**
 - Closes #8398
This commit is contained in:
magic-akari 2023-12-11 11:05:58 +08:00 committed by GitHub
parent 3b845c33b3
commit a69f172aac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 170 additions and 145 deletions

View File

@ -0,0 +1,15 @@
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true
},
"target": "es2022",
"minify": {
"compress": true
},
"loose": false
},
"isModule": true,
"minify": true
}

View File

@ -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?.())));

View File

@ -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?.();

View File

@ -46,7 +46,7 @@ var needsExemplar = function() {
var _ = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : x; var _ = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : x;
return void 0; 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 * @param {typeof returnExemplar} a

View File

@ -1,15 +1,15 @@
//// [deleteChain.ts] //// [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;
o1 === null || o1 === void 0 ? true : delete o1.b; o1 === null || o1 === void 0 ? true : delete o1.b;
delete (o1 === null || o1 === void 0 ? void 0 : 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); o2 === null || o2 === void 0 ? true : delete o2.b.c;
(_o3_b = o3.b) === null || _o3_b === void 0 ? true : delete _o3_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; (_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; (_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;
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_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; (_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"]; (_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"];

View File

@ -1,3 +1,3 @@
//// [deleteChain.ts] //// [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;
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); 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;

View File

@ -11,7 +11,7 @@
//// [index.js] //// [index.js]
/** /**
* @typedef {import("./externs")} Foo * @typedef {import("./externs")} Foo
*/ let a = /** @type {Foo} */ /** @type {*} */ undefined; */ let a = /** @type {*} */ undefined;
a = new Foo({ a = new Foo({
doer: Foo.Bar doer: Foo.Bar
}); });

View File

@ -8,8 +8,8 @@ const foo = new Foo();
(_foo_m1 = foo.m) === null || _foo_m1 === void 0 ? void 0 : _foo_m1.call(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_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); (_foo_m3 = foo.m) === null || _foo_m3 === void 0 ? void 0 : _foo_m3.call(foo);
// https://github.com/microsoft/TypeScript/issues/50148 (// 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;
(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;

View File

@ -2,4 +2,4 @@
const foo = new class { const foo = new class {
m() {} 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;

View File

@ -334,10 +334,6 @@ impl VisitMut for Pure<'_> {
} }
fn visit_mut_expr(&mut self, e: &mut Expr) { fn visit_mut_expr(&mut self, e: &mut Expr) {
if let Expr::Paren(p) = e {
*e = *p.expr.take();
}
{ {
let ctx = Ctx { let ctx = Ctx {
in_first_expr: false, in_first_expr: false,

View File

@ -97,7 +97,13 @@ impl VisitMut for InfoMarker<'_> {
fn visit_mut_call_expr(&mut self, n: &mut CallExpr) { fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
n.visit_mut_children_with(self); 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); n.span = n.span.apply_mark(self.marks.noinline);
} }

View File

@ -8,6 +8,7 @@ use crate::HEAVY_TASK_PARALLELS;
/// Optimizer invoked before invoking compressor. /// Optimizer invoked before invoking compressor.
/// ///
/// - Remove parens. /// - Remove parens.
/// TODO: remove completely after #8333
pub(crate) fn precompress_optimizer<'a>() -> impl 'a + VisitMut { pub(crate) fn precompress_optimizer<'a>() -> impl 'a + VisitMut {
PrecompressOptimizer {} PrecompressOptimizer {}
} }
@ -26,14 +27,6 @@ impl Parallel for PrecompressOptimizer {
impl VisitMut for PrecompressOptimizer { impl VisitMut for PrecompressOptimizer {
noop_visit_mut_type!(); 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) { fn visit_mut_pat_or_expr(&mut self, n: &mut PatOrExpr) {
n.visit_mut_children_with(self); n.visit_mut_children_with(self);

View File

@ -39,7 +39,11 @@ use swc_ecma_parser::{
EsConfig, Parser, Syntax, EsConfig, Parser, Syntax,
}; };
use swc_ecma_testing::{exec_node_js, JsExecOptions}; 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_utils::drop_span;
use swc_ecma_visit::{FoldWith, Visit, VisitMut, VisitMutWith, VisitWith}; use swc_ecma_visit::{FoldWith, Visit, VisitMut, VisitMutWith, VisitWith};
use testing::{assert_eq, unignore_fixture, DebugUsingDisplay, NormalizedOutput}; use testing::{assert_eq, unignore_fixture, DebugUsingDisplay, NormalizedOutput};
@ -194,7 +198,12 @@ fn run(
.map_err(|err| { .map_err(|err| {
err.into_diagnostic(handler).emit(); 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. // Ignore parser errors.
// //

View File

@ -0,0 +1 @@
(obj?.a).b;

View File

@ -0,0 +1 @@
(obj?.a).b;

View File

@ -32,7 +32,11 @@ use swc_ecma_parser::{
lexer::{input::SourceFileInput, Lexer}, lexer::{input::SourceFileInput, Lexer},
EsConfig, Parser, Syntax, 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 swc_ecma_visit::{FoldWith, VisitMutWith};
use testing::assert_eq; use testing::assert_eq;
@ -197,7 +201,12 @@ fn run(cm: Lrc<SourceMap>, handler: &Handler, input: &Path, config: &str) -> Opt
.map_err(|err| { .map_err(|err| {
err.into_diagnostic(handler).emit(); 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. // Ignore parser errors.
// //

View File

@ -1,4 +1,4 @@
use std::{hash::BuildHasherDefault, ops::RangeFull}; use std::{hash::BuildHasherDefault, mem, ops::RangeFull};
use indexmap::IndexMap; use indexmap::IndexMap;
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
@ -18,6 +18,7 @@ pub fn fixer(comments: Option<&dyn Comments>) -> impl '_ + Fold + VisitMut {
ctx: Default::default(), ctx: Default::default(),
span_map: Default::default(), span_map: Default::default(),
in_for_stmt_head: Default::default(), in_for_stmt_head: Default::default(),
in_opt_chain: Default::default(),
remove_only: false, remove_only: false,
}) })
} }
@ -28,6 +29,7 @@ pub fn paren_remover(comments: Option<&dyn Comments>) -> impl '_ + Fold + VisitM
ctx: Default::default(), ctx: Default::default(),
span_map: Default::default(), span_map: Default::default(),
in_for_stmt_head: Default::default(), in_for_stmt_head: Default::default(),
in_opt_chain: Default::default(),
remove_only: true, remove_only: true,
}) })
} }
@ -42,6 +44,7 @@ struct Fixer<'a> {
span_map: IndexMap<Span, Span, BuildHasherDefault<FxHasher>>, span_map: IndexMap<Span, Span, BuildHasherDefault<FxHasher>>,
in_for_stmt_head: bool, in_for_stmt_head: bool,
in_opt_chain: bool,
remove_only: bool, remove_only: bool,
} }
@ -84,21 +87,6 @@ macro_rules! array {
} }
impl Fixer<'_> { impl Fixer<'_> {
fn visit_call<T: VisitMutWith<Self>>(
&mut self,
args: &mut Vec<ExprOrSpread>,
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) { fn wrap_callee(&mut self, e: &mut Expr) {
match e { match e {
Expr::Lit(Lit::Num(..) | Lit::Str(..)) => (), Expr::Lit(Lit::Num(..) | Lit::Str(..)) => (),
@ -397,24 +385,47 @@ impl VisitMut for Fixer<'_> {
} }
fn visit_mut_call_expr(&mut self, node: &mut CallExpr) { 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 Callee::Expr(e) = &mut node.callee {
if let Expr::OptChain(_) = &**e { match &**e {
self.wrap(e) Expr::OptChain(_) if !self.in_opt_chain => self.wrap(e),
} else { _ => self.wrap_callee(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) { 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.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) { fn visit_mut_class(&mut self, node: &mut Class) {
@ -471,6 +482,7 @@ impl VisitMut for Fixer<'_> {
} }
} }
self.unwrap_expr(e); self.unwrap_expr(e);
e.visit_mut_children_with(self); e.visit_mut_children_with(self);
self.ctx = ctx; self.ctx = ctx;
@ -575,32 +587,31 @@ impl VisitMut for Fixer<'_> {
} }
fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) { fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
n.obj.visit_mut_with(self); n.visit_mut_children_with(self);
n.prop.visit_mut_with(self);
match n { match *n.obj {
MemberExpr { obj, .. } Expr::Object(..) if self.ctx == Context::ForcedExpr => {}
if obj.is_object() && matches!(self.ctx, Context::ForcedExpr) => {} Expr::Fn(..)
| Expr::Cond(..)
MemberExpr { obj, .. } | Expr::Unary(..)
if obj.is_fn_expr() | Expr::Seq(..)
|| obj.is_cond() | Expr::Update(..)
|| obj.is_unary() | Expr::Bin(..)
|| obj.is_seq() | Expr::Object(..)
|| obj.is_update() | Expr::Assign(..)
|| obj.is_bin() | Expr::Arrow(..)
|| obj.is_object() | Expr::Class(..)
|| obj.is_assign() | Expr::Yield(..)
|| obj.is_arrow() | Expr::Await(..)
|| obj.is_class() | Expr::New(NewExpr { args: None, .. }) => {
|| obj.is_yield_expr() self.wrap(&mut n.obj);
|| obj.is_await_expr() }
|| (obj.is_call() && matches!(self.ctx, Context::Callee { is_new: true })) Expr::Call(..) if self.ctx == Context::Callee { is_new: true } => {
|| matches!(**obj, Expr::New(NewExpr { args: None, .. })) => self.wrap(&mut n.obj);
{ }
self.wrap(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); e.visit_mut_children_with(self);
match &*e.tag { match &*e.tag {
Expr::Arrow(..) Expr::OptChain(..)
| Expr::Arrow(..)
| Expr::Cond(..) | Expr::Cond(..)
| Expr::Bin(..) | Expr::Bin(..)
| Expr::Seq(..) | 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 { if let Context::ForcedExpr = self.ctx {
Context::ForcedExpr => { self.wrap(&mut expr);
*e = Expr::Paren(ParenExpr {
span: *span,
expr: Box::new(expr),
})
}
_ => *e = expr,
}; };
*e = expr;
} }
Expr::Cond(expr) => { Expr::Cond(expr) => {
@ -907,10 +915,7 @@ impl Fixer<'_> {
|| callee.is_await_expr() || callee.is_await_expr()
|| callee.is_assign() => || callee.is_assign() =>
{ {
*callee = Box::new(Expr::Paren(ParenExpr { self.wrap(callee);
span: callee.span(),
expr: callee.take(),
}))
} }
Expr::OptChain(OptChainExpr { base, .. }) => match &mut **base { Expr::OptChain(OptChainExpr { base, .. }) => match &mut **base {
OptChainBase::Call(OptCall { callee, .. }) OptChainBase::Call(OptCall { callee, .. })
@ -919,10 +924,7 @@ impl Fixer<'_> {
|| callee.is_await_expr() || callee.is_await_expr()
|| callee.is_assign() => || callee.is_assign() =>
{ {
*callee = Box::new(Expr::Paren(ParenExpr { self.wrap(callee);
span: callee.span(),
expr: callee.take(),
}))
} }
OptChainBase::Call(OptCall { callee, .. }) if callee.is_fn_expr() => match self.ctx OptChainBase::Call(OptCall { callee, .. }) if callee.is_fn_expr() => match self.ctx
@ -974,51 +976,31 @@ impl Fixer<'_> {
}; };
let expr = Box::new(e.take()); let expr = Box::new(e.take());
*e = Expr::Paren(ParenExpr { expr, span }) *e = Expr::Paren(ParenExpr { expr, span });
} }
/// Removes paren /// Removes paren
fn unwrap_expr(&mut self, e: &mut Expr) { fn unwrap_expr(&mut self, e: &mut Expr) {
match e { loop {
Expr::Seq(SeqExpr { exprs, .. }) if exprs.len() == 1 => { match e {
self.unwrap_expr(exprs.last_mut().unwrap()); Expr::Seq(SeqExpr { exprs, .. }) if exprs.len() == 1 => {
*e = *exprs.last_mut().unwrap().take(); *e = *exprs[0].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()` Expr::Paren(ParenExpr {
if Self::contain_opt_chain(expr) { span: paren_span,
return; expr,
..
}) => {
let expr_span = expr.span();
let paren_span = *paren_span;
*e = *expr.take();
self.span_map.insert(expr_span, paren_span);
} }
let expr_span = expr.span(); _ => return,
let paren_span = *paren_span;
self.unwrap_expr(expr);
*e = *expr.take();
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())
}
_ => false,
} }
} }

View File

@ -1,5 +1,5 @@
use swc_common::{chain, Mark, SyntaxContext}; 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_utils::ExprCtx;
use swc_ecma_visit::as_folder; use swc_ecma_visit::as_folder;
@ -15,6 +15,7 @@ macro_rules! test_stmt {
chain!( chain!(
resolver(unresolved_mark, top_level_mark, false), resolver(unresolved_mark, top_level_mark, false),
paren_remover(None),
expr_simplifier(top_level_mark, Default::default()), expr_simplifier(top_level_mark, Default::default()),
as_folder(super::Remover { as_folder(super::Remover {
changed: false, changed: false,

View File

@ -1278,15 +1278,8 @@ impl VisitMut for SimplifyExpr {
match expr { match expr {
// Do nothing. // Do nothing.
Expr::Lit(_) | Expr::This(..) => return, // Note: Paren should be handled in fixer
Expr::Lit(_) | Expr::This(..) | Expr::Paren(..) => 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;
}
Expr::Seq(seq) if seq.exprs.is_empty() => return, Expr::Seq(seq) if seq.exprs.is_empty() => return,

View File

@ -1,5 +1,5 @@
use swc_common::{chain, Mark, SyntaxContext}; 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_transforms_testing::test_transform;
use swc_ecma_utils::ExprCtx; use swc_ecma_utils::ExprCtx;
use swc_ecma_visit::as_folder; use swc_ecma_visit::as_folder;
@ -15,6 +15,7 @@ fn fold(src: &str, expected: &str) {
chain!( chain!(
resolver(unresolved_mark, top_level_mark, false), resolver(unresolved_mark, top_level_mark, false),
paren_remover(None),
as_folder(SimplifyExpr { as_folder(SimplifyExpr {
expr_ctx: ExprCtx { expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark), unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),

View File

@ -2032,6 +2032,11 @@ impl Minifier<'_> {
&mut swc_ecma_transforms_base::resolver(unresolved_mark, top_level_mark, false), &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( let program = swc_ecma_minifier::optimize(
program, program,
cm.clone(), cm.clone(),