feat(es/minifier): Implement more rules (#1730)

swc_ecma_codegen:
 - Fix codegen of `-0.0`.

swc_ecma_transforms_base:
 - `fixer`: Handle tag of tagged template literals.


swc_ecma_minifier:
 - Implement more rules for strings.
 - Implement more rules for teplate literals.
 - Implement more rules for comparisons.
 - Implement more rules for destructuring bindings.
 - Implement more rules for switch statements.
 - Implement more rules for expressions.
This commit is contained in:
강동윤 2021-05-23 11:19:11 +09:00 committed by GitHub
parent a518c83485
commit 3522fc71e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1064 additions and 163 deletions

View File

@ -31,7 +31,7 @@ typescript = ["swc_ecma_transforms/typescript"]
swc_ecma_ast = {version = "0.45.0", path = "./ast"}
swc_ecma_codegen = {version = "0.55.0", path = "./codegen", optional = true}
swc_ecma_dep_graph = {version = "0.25.0", path = "./dep-graph", optional = true}
swc_ecma_minifier = {version = "0.2.0-beta.0", path = "./minifier", optional = true}
swc_ecma_minifier = {version = "0.2.1-beta.0", path = "./minifier", optional = true}
swc_ecma_parser = {version = "0.57.0", path = "./parser", optional = true}
swc_ecma_transforms = {version = "0.50.0", path = "./transforms", optional = true}
swc_ecma_utils = {version = "0.36.0", path = "./utils", optional = true}

View File

@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0/MIT"
name = "swc_ecma_codegen"
repository = "https://github.com/swc-project/swc.git"
version = "0.55.1"
version = "0.55.2"
[dependencies]
bitflags = "1"

View File

@ -432,7 +432,7 @@ impl<'a> Emitter<'a> {
self.wr.write_str_lit(num.span, "Infinity")?;
} else {
if num.value.is_sign_negative() && num.value == 0.0 {
self.wr.write_str_lit(num.span, "-0.0")?;
self.wr.write_str_lit(num.span, "-0")?;
} else {
self.wr.write_str_lit(num.span, &format!("{}", num.value))?;
}

View File

@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"]
license = "Apache-2.0/MIT"
name = "swc_ecma_minifier"
repository = "https://github.com/swc-project/swc.git"
version = "0.2.0-beta.0"
version = "0.2.1-beta.0"
[dependencies]
fxhash = "0.2.1"

View File

@ -11,11 +11,11 @@
#
set -eu
export RUST_LOG=swc_ecma_minifier=trace
if [ -z "$@" ]; then
./scripts/sort.sh
export RUST_LOG=swc_ecma_minifier=trace
GOLDEN_ONLY=1 cargo test --test compress --all-features
fi

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
#
# This script updates golden.txt and ignored.txt
#
#
# Note that even if a test is ignored, this script will invoke it.
#
set -eu
# Clean the ignore list
echo '' > tests/ignored.txt
scripts/add-golden.sh
scripts/ignore.sh ''

View File

@ -5,6 +5,7 @@ use swc_atoms::js_word;
use swc_common::SyntaxContext;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::undefined;
use swc_ecma_utils::ExprExt;
@ -20,6 +21,7 @@ impl Optimizer<'_> {
self.eval_numbers(e);
self.eval_str_method_call(e);
self.eval_array_method_call(e);
}
fn eval_global_vars(&mut self, e: &mut Expr) {
@ -160,6 +162,25 @@ impl Optimizer<'_> {
}
match e {
Expr::Bin(bin @ BinExpr { op: op!("**"), .. }) => {
let l = bin.left.as_number();
let r = bin.right.as_number();
if let Known(l) = l {
if let Known(r) = r {
self.changed = true;
log::trace!("evaluate: Evaulated `{:?} ** {:?}`", l, r);
let value = l.powf(r);
*e = Expr::Lit(Lit::Num(Number {
span: bin.span,
value,
}));
return;
}
}
}
Expr::Bin(bin @ BinExpr { op: op!("/"), .. }) => {
let ln = bin.left.as_number();
@ -235,4 +256,181 @@ impl Optimizer<'_> {
_ => {}
}
}
fn eval_array_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
return;
}
let call = match e {
Expr::Call(e) => e,
_ => return,
};
let has_spread = call.args.iter().any(|arg| arg.spread.is_some());
for arg in &call.args {
if arg.expr.may_have_side_effects() {
return;
}
}
let callee = match &mut call.callee {
ExprOrSuper::Super(_) => return,
ExprOrSuper::Expr(e) => &mut **e,
};
match callee {
Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
}) => {
if obj.may_have_side_effects() {
return;
}
let arr = match &mut **obj {
Expr::Array(arr) => arr,
_ => return,
};
let has_spread_elem = arr.elems.iter().any(|s| match s {
Some(ExprOrSpread {
spread: Some(..), ..
}) => true,
_ => false,
});
// Ignore array
let method_name = match &**prop {
Expr::Ident(i) => i,
_ => return,
};
match &*method_name.sym {
"slice" => {
if has_spread || has_spread_elem {
return;
}
match call.args.len() {
0 => {
self.changed = true;
log::trace!("evaluate: Dropping array.slice call");
*e = *obj.take();
return;
}
1 => {
if let Known(start) = call.args[0].expr.as_number() {
let start = start.floor() as usize;
self.changed = true;
log::trace!("evaluate: Reducing array.slice({}) call", start);
if start >= arr.elems.len() {
*e = Expr::Array(ArrayLit {
span: *span,
elems: Default::default(),
});
return;
}
let elems = arr.elems.drain(start..).collect();
*e = Expr::Array(ArrayLit { span: *span, elems });
return;
}
}
_ => {
let start = call.args[0].expr.as_number();
let end = call.args[1].expr.as_number();
if let Known(start) = start {
let start = start.floor() as usize;
if let Known(end) = end {
let end = end.floor() as usize;
self.changed = true;
log::trace!(
"evaluate: Reducing array.slice({}, {}) call",
start,
end
);
let end = end.min(arr.elems.len());
if start >= arr.elems.len() {
*e = Expr::Array(ArrayLit {
span: *span,
elems: Default::default(),
});
return;
}
let elems = arr.elems.drain(start..end).collect();
*e = Expr::Array(ArrayLit { span: *span, elems });
return;
}
}
}
}
}
_ => {}
}
}
_ => {}
}
}
///
/// - `Object(1) && 1 && 2` => `Object(1) && 2`.
pub(super) fn optimize_bin_and_or(&mut self, e: &mut BinExpr) {
if !self.options.evaluate {
return;
}
if e.left.is_invalid() || e.right.is_invalid() {
return;
}
match e.op {
op!("&&") | op!("||") => {}
_ => return,
}
match &mut *e.left {
Expr::Bin(left) => {
if left.op != e.op {
return;
}
// Remove rhs of lhs if possible.
let v = left.right.as_pure_bool();
if let Known(v) = v {
// As we used as_pure_bool, we can drop it.
if v && e.op == op!("&&") {
self.changed = true;
log::trace!("Removing `b` from `a && b && c` because b is always truthy");
left.right.take();
return;
}
if !v && e.op == op!("||") {
self.changed = true;
log::trace!("Removing `b` from `a || b || c` because b is always falsy");
left.right.take();
return;
}
}
}
_ => return,
}
}
}

View File

@ -46,6 +46,7 @@ mod iife;
mod inline;
mod join_vars;
mod loops;
mod numbers;
mod ops;
mod properties;
mod sequences;
@ -131,6 +132,8 @@ struct Ctx {
in_obj_of_non_computed_member: bool,
in_tpl_expr: bool,
/// Current scope.
scope: SyntaxContext,
}
@ -573,6 +576,7 @@ impl Optimizer<'_> {
| BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
| BinaryOp::Mod
| BinaryOp::BitOr
| BinaryOp::BitXor
@ -1142,89 +1146,6 @@ impl Optimizer<'_> {
}
}
}
/// This compresses a template literal by inlining string literals in
/// expresions into quasis.
///
/// Note that this pass only cares about string literals and conversion to a
/// string literal should be done before calling this pass.
fn compress_tpl(&mut self, tpl: &mut Tpl) {
debug_assert_eq!(tpl.exprs.len() + 1, tpl.quasis.len());
let has_str_lit = tpl.exprs.iter().any(|expr| match &**expr {
Expr::Lit(Lit::Str(..)) => true,
_ => false,
});
if !has_str_lit {
return;
}
let mut quasis = vec![];
let mut exprs = vec![];
let mut cur = String::new();
let mut cur_raw = String::new();
for i in 0..(tpl.exprs.len() + tpl.quasis.len()) {
if i % 2 == 0 {
let i = i / 2;
let q = tpl.quasis[i].take();
cur.push_str(&q.cooked.unwrap().value);
cur_raw.push_str(&q.raw.value);
} else {
let i = i / 2;
let e = tpl.exprs[i].take();
match *e {
Expr::Lit(Lit::Str(s)) => {
cur.push_str(&s.value);
cur_raw.push_str(&s.value);
}
_ => {
quasis.push(TplElement {
span: DUMMY_SP,
tail: true,
cooked: Some(Str {
span: DUMMY_SP,
value: take(&mut cur).into(),
has_escape: false,
kind: Default::default(),
}),
raw: Str {
span: DUMMY_SP,
value: take(&mut cur_raw).into(),
has_escape: false,
kind: Default::default(),
},
});
exprs.push(e);
}
}
}
}
quasis.push(TplElement {
span: DUMMY_SP,
tail: true,
cooked: Some(Str {
span: DUMMY_SP,
value: cur.into(),
has_escape: false,
kind: Default::default(),
}),
raw: Str {
span: DUMMY_SP,
value: cur_raw.into(),
has_escape: false,
kind: Default::default(),
},
});
debug_assert_eq!(exprs.len() + 1, quasis.len());
tpl.quasis = quasis;
tpl.exprs = exprs;
}
}
impl VisitMut for Optimizer<'_> {
@ -1257,6 +1178,30 @@ impl VisitMut for Optimizer<'_> {
}
}
fn visit_mut_bin_expr(&mut self, n: &mut BinExpr) {
n.visit_mut_children_with(self);
self.optimize_bin_operator(n);
self.optimize_bin_and_or(n);
self.optimize_null_or_undefined_cmp(n);
if n.op == op!(bin, "+") {
if let Known(Type::Str) = n.left.get_type() {
self.optimize_expr_in_str_ctx(&mut n.right);
}
if let Known(Type::Str) = n.right.get_type() {
self.optimize_expr_in_str_ctx(&mut n.left);
}
}
if n.op == op!(bin, "+") {
self.concat_tpl(&mut n.left, &mut n.right);
}
}
fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
let ctx = Ctx {
stmt_lablled: false,
@ -1348,6 +1293,19 @@ impl VisitMut for Optimizer<'_> {
};
e.visit_mut_children_with(&mut *self.with_ctx(ctx));
match e {
Expr::Bin(BinExpr { left, right, .. }) => {
if left.is_invalid() {
*e = *right.take();
} else if right.is_invalid() {
*e = *left.take();
}
}
_ => {}
}
self.lift_minus(e);
self.convert_tpl_to_str(e);
self.optimize_str_access_to_arguments(e);
@ -1594,6 +1552,12 @@ impl VisitMut for Optimizer<'_> {
self.with_ctx(ctx).handle_stmt_likes(stmts);
}
fn visit_mut_param(&mut self, n: &mut Param) {
n.visit_mut_children_with(self);
self.drop_unused_param(&mut n.pat);
}
fn visit_mut_prop(&mut self, p: &mut Prop) {
p.visit_mut_children_with(self);
@ -1765,7 +1729,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_switch_stmt(&mut self, n: &mut SwitchStmt) {
let ctx = Ctx {
inline_prevented: true,
inline_prevented: !self.options.switches,
..self.ctx
};
n.discriminant.visit_mut_with(&mut *self.with_ctx(ctx));
@ -1775,6 +1739,11 @@ impl VisitMut for Optimizer<'_> {
n.cases.visit_mut_with(self);
}
/// We don't optimize [Tpl] contained in [TaggedTpl].
fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
n.tag.visit_mut_with(self);
}
fn visit_mut_throw_stmt(&mut self, n: &mut ThrowStmt) {
n.visit_mut_children_with(self);
@ -1784,7 +1753,14 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_tpl(&mut self, n: &mut Tpl) {
debug_assert_eq!(n.exprs.len() + 1, n.quasis.len());
n.visit_mut_children_with(self);
{
let ctx = Ctx {
in_tpl_expr: true,
..self.ctx
};
let mut o = self.with_ctx(ctx);
n.visit_mut_children_with(&mut *o);
}
n.exprs
.iter_mut()
@ -1822,6 +1798,8 @@ impl VisitMut for Optimizer<'_> {
if n.op == op!("!") {
self.with_ctx(ctx).optimize_expr_in_bool_ctx(&mut n.arg);
} else if n.op == op!(unary, "+") || n.op == op!(unary, "-") {
self.with_ctx(ctx).optimize_expr_in_num_ctx(&mut n.arg);
}
}

View File

@ -0,0 +1,60 @@
use crate::compress::optimize::Optimizer;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
impl Optimizer<'_> {
pub(super) fn optimize_expr_in_num_ctx(&mut self, e: &mut Expr) {
match e {
Expr::Lit(Lit::Str(Str { span, value, .. })) => {
let value = if value.is_empty() { 0f64 } else { 1f64 };
self.changed = true;
log::trace!("numbers: Converting a string literal to {:?}", value);
*e = Expr::Lit(Lit::Num(Number { span: *span, value }));
return;
}
_ => {}
}
}
///
/// - `1 / -0` => `- 1 / 0`
pub(super) fn lift_minus(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
match e {
Expr::Bin(arg) => {
if arg.op != op!("*") && arg.op != op!("/") {
return;
}
match &mut *arg.right {
Expr::Unary(UnaryExpr {
op: op!(unary, "-"),
arg: right_arg,
..
}) => {
self.changed = true;
log::trace!("numbers: Lifting `-`");
*e = Expr::Unary(UnaryExpr {
span: arg.span,
op: op!(unary, "-"),
arg: Box::new(Expr::Bin(BinExpr {
span: arg.span,
op: arg.op,
left: arg.left.take(),
right: right_arg.take(),
})),
});
return;
}
_ => {}
}
}
_ => {}
}
}
}

View File

@ -2,6 +2,8 @@ use super::Optimizer;
use crate::util::ValueExt;
use std::mem::swap;
use swc_atoms::js_word;
use swc_common::EqIgnoreSpan;
use swc_common::Span;
use swc_common::Spanned;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
@ -13,6 +15,163 @@ use swc_ecma_utils::Value;
use Value::Known;
impl Optimizer<'_> {
///
/// - `a === undefined || a === null` => `a == null`
pub(super) fn optimize_null_or_undefined_cmp(&mut self, e: &mut BinExpr) {
fn opt(
span: Span,
top_op: BinaryOp,
e_left: &mut Expr,
e_right: &mut Expr,
) -> Option<BinExpr> {
let (cmp, op, left, right) = match &mut *e_left {
Expr::Bin(left_bin) => {
if left_bin.op != op!("===") && left_bin.op != op!("!==") {
return None;
}
if top_op == op!("&&") && left_bin.op == op!("===") {
return None;
}
if top_op == op!("||") && left_bin.op == op!("!==") {
return None;
}
if !left_bin.right.is_ident() {
return None;
}
let right = match &mut *e_right {
Expr::Bin(right_bin) => {
if right_bin.op != left_bin.op {
return None;
}
if !right_bin.right.eq_ignore_span(&left_bin.right) {
return None;
}
&mut *right_bin.left
}
_ => return None,
};
(
&mut left_bin.right,
left_bin.op,
&mut *left_bin.left,
&mut *right,
)
}
_ => return None,
};
let lt = left.get_type();
let rt = right.get_type();
if let Known(lt) = lt {
if let Known(rt) = rt {
match (lt, rt) {
(Type::Undefined, Type::Null) | (Type::Null, Type::Undefined) => {
if op == op!("===") {
log::trace!(
"Reducing `!== null || !== undefined` check to `!= null`"
);
return Some(BinExpr {
span,
op: op!("=="),
left: cmp.take(),
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
});
} else {
log::trace!(
"Reducing `=== null || === undefined` check to `== null`"
);
return Some(BinExpr {
span,
op: op!("!="),
left: cmp.take(),
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
});
}
}
_ => {}
}
}
}
None
}
if e.op == op!("||") || e.op == op!("&&") {
{
let res = opt(e.span, e.op, &mut e.left, &mut e.right);
if let Some(res) = res {
self.changed = true;
*e = res;
return;
}
}
match (&mut *e.left, &mut *e.right) {
(Expr::Bin(left), right) => {
if e.op == left.op {
let res = opt(right.span(), e.op, &mut left.right, &mut *right);
if let Some(res) = res {
self.changed = true;
*e = BinExpr {
span: e.span,
op: e.op,
left: left.left.take(),
right: Box::new(Expr::Bin(res)),
};
return;
}
}
}
_ => {}
}
}
}
///
/// - `'12' === `foo` => '12' == 'foo'`
pub(super) fn optimize_bin_operator(&mut self, e: &mut BinExpr) {
if !self.options.comparisons {
return;
}
if e.op == op!("===") || e.op == op!("==") || e.op == op!("!=") || e.op == op!("!==") {
if e.left.is_ident() && e.left.eq_ignore_span(&e.right) {
self.changed = true;
log::trace!("Reducing comparison of same variable ({})", e.op);
// TODO(kdy1): Create another method and assign to `Expr` instead of using a
// hack based on take.
e.left = Box::new(Expr::Lit(Lit::Bool(Bool {
span: e.span,
value: e.op == op!("===") || e.op == op!("==="),
})));
e.right.take();
return;
}
}
let lt = e.left.get_type();
let rt = e.right.get_type();
if e.op == op!("===") {
if let Known(lt) = lt {
if let Known(rt) = rt {
if lt == rt {
e.op = op!("==");
self.changed = true;
log::trace!("Reduced `===` to `==` because types of operands are identical")
}
}
}
}
}
///
/// - `1 == 1` => `true`
pub(super) fn optimize_lit_cmp(&mut self, n: &mut BinExpr) -> Option<Expr> {
@ -79,25 +238,6 @@ impl Optimizer<'_> {
}
}
/// Convert expressions to string literal if possible.
pub(super) fn optimize_expr_in_str_ctx(&mut self, n: &mut Expr) {
match n {
Expr::Lit(Lit::Str(..)) => return,
_ => {}
}
let span = n.span();
let value = n.as_string();
if let Known(value) = value {
*n = Expr::Lit(Lit::Str(Str {
span,
value: value.into(),
has_escape: false,
kind: Default::default(),
}))
}
}
/// TODO: Optimize based on the type.
pub(super) fn negate_twice(&mut self, e: &mut Expr) {
self.negate(e);
@ -268,17 +408,32 @@ impl Optimizer<'_> {
e.right = left.take();
}
/// Swap lhs and rhs in certain conditions.
pub(super) fn swap_bin_operands(&mut self, expr: &mut Expr) {
// Swap lhs and rhs in certain conditions.
match expr {
Expr::Bin(test) => {
match test.op {
op!("==") | op!("!=") | op!("&") | op!("^") | op!("*") => {}
op!("===")
| op!("!==")
| op!("==")
| op!("!=")
| op!("&")
| op!("^")
| op!("|")
| op!("*") => {}
_ => return,
}
match (&*test.left, &*test.right) {
(&Expr::Ident(..), &Expr::Lit(..)) => {
(Expr::Ident(..), Expr::Lit(..))
| (
Expr::Ident(..),
Expr::Unary(UnaryExpr {
op: op!("void"), ..
}),
)
| (Expr::Unary(..), Expr::Lit(..))
| (Expr::Tpl(..), Expr::Lit(..)) => {
self.changed = true;
log::trace!("Swapping operands of binary exprssion");
swap(&mut test.left, &mut test.right);

View File

@ -1,5 +1,14 @@
use super::Optimizer;
use std::mem::take;
use swc_atoms::js_word;
use swc_atoms::JsWord;
use swc_common::Spanned;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::ExprExt;
use swc_ecma_utils::Value::Known;
impl Optimizer<'_> {
/// Converts template literals to string if `exprs` of [Tpl] is empty.
@ -18,4 +27,266 @@ impl Optimizer<'_> {
_ => {}
}
}
/// Convert expressions to string literal if possible.
pub(super) fn optimize_expr_in_str_ctx(&mut self, n: &mut Expr) {
match n {
Expr::Lit(Lit::Str(..)) => return,
Expr::Paren(e) => {
self.optimize_expr_in_str_ctx(&mut e.expr);
match &*e.expr {
Expr::Lit(Lit::Str(..)) => {
*n = *e.expr.take();
self.changed = true;
log::trace!("string: Removed a paren in a string context");
}
_ => {}
}
return;
}
_ => {}
}
let span = n.span();
let value = n.as_string();
if let Known(value) = value {
self.changed = true;
log::trace!(
"strings: Converted an expression into a string literal (in string context)"
);
*n = Expr::Lit(Lit::Str(Str {
span,
value: value.into(),
has_escape: false,
kind: Default::default(),
}));
return;
}
match n {
Expr::Lit(Lit::Num(v)) => {
self.changed = true;
log::trace!(
"strings: Converted a numeric literal ({}) into a string literal (in string \
context)",
v.value
);
*n = Expr::Lit(Lit::Str(Str {
span: v.span,
value: format!("{:?}", v.value).into(),
has_escape: false,
kind: Default::default(),
}));
return;
}
Expr::Lit(Lit::Regex(v)) => {
if !self.options.evaluate {
return;
}
self.changed = true;
log::trace!(
"strings: Converted a regex (/{}/{}) into a string literal (in string context)",
v.exp,
v.flags
);
*n = Expr::Lit(Lit::Str(Str {
span: v.span,
value: format!("/{}/{}", v.exp, v.flags).into(),
has_escape: false,
kind: Default::default(),
}));
return;
}
Expr::Ident(i) => {
if !self.options.evaluate || !self.options.reduce_vars {
return;
}
if self
.data
.as_ref()
.and_then(|data| data.vars.get(&i.to_id()))
.map(|v| v.assign_count == 0)
.unwrap_or(false)
{
self.changed = true;
log::trace!(
"strings: Converting a reference ({}{:?}) into `undefined` (in string \
context)",
i.sym,
i.span.ctxt
);
*n = Expr::Lit(Lit::Str(Str {
span: i.span,
value: js_word!("undefined"),
has_escape: false,
kind: Default::default(),
}));
}
}
_ => {}
}
}
/// This compresses a template literal by inlining string literals in
/// expresions into quasis.
///
/// Note that this pass only cares about string literals and conversion to a
/// string literal should be done before calling this pass.
pub(super) fn compress_tpl(&mut self, tpl: &mut Tpl) {
debug_assert_eq!(tpl.exprs.len() + 1, tpl.quasis.len());
let has_str_lit = tpl.exprs.iter().any(|expr| match &**expr {
Expr::Lit(Lit::Str(..)) => true,
_ => false,
});
if !has_str_lit {
return;
}
let mut quasis = vec![];
let mut exprs = vec![];
let mut cur = String::new();
let mut cur_raw = String::new();
for i in 0..(tpl.exprs.len() + tpl.quasis.len()) {
if i % 2 == 0 {
let i = i / 2;
let q = tpl.quasis[i].take();
cur.push_str(&q.cooked.unwrap().value);
cur_raw.push_str(&q.raw.value);
} else {
let i = i / 2;
let e = tpl.exprs[i].take();
match *e {
Expr::Lit(Lit::Str(s)) => {
cur.push_str(&s.value);
cur_raw.push_str(&s.value);
}
_ => {
quasis.push(TplElement {
span: DUMMY_SP,
tail: true,
cooked: Some(Str {
span: DUMMY_SP,
value: take(&mut cur).into(),
has_escape: false,
kind: Default::default(),
}),
raw: Str {
span: DUMMY_SP,
value: take(&mut cur_raw).into(),
has_escape: false,
kind: Default::default(),
},
});
exprs.push(e);
}
}
}
}
quasis.push(TplElement {
span: DUMMY_SP,
tail: true,
cooked: Some(Str {
span: DUMMY_SP,
value: cur.into(),
has_escape: false,
kind: Default::default(),
}),
raw: Str {
span: DUMMY_SP,
value: cur_raw.into(),
has_escape: false,
kind: Default::default(),
},
});
debug_assert_eq!(exprs.len() + 1, quasis.len());
tpl.quasis = quasis;
tpl.exprs = exprs;
}
/// Called for binary operations with `+`.
pub(super) fn concat_tpl(&mut self, l: &mut Expr, r: &mut Expr) {
match (&mut *l, &mut *r) {
(Expr::Tpl(l), Expr::Lit(Lit::Str(rs))) => {
// Append
if let Some(l_last) = l.quasis.last_mut() {
self.changed = true;
log::trace!(
"template: Concatted a string (`{}`) on rhs of `+` to a template literal",
rs.value
);
let l_str = l_last.cooked.as_mut().unwrap();
let new: JsWord = format!("{}{}", l_str.value, rs.value).into();
l_str.value = new.clone();
l_last.raw.value = new;
r.take();
return;
}
}
(Expr::Lit(Lit::Str(ls)), Expr::Tpl(r)) => {
// Append
if let Some(r_first) = r.quasis.first_mut() {
self.changed = true;
log::trace!(
"template: Prepended a string (`{}`) on lhs of `+` to a template literal",
ls.value
);
let r_str = r_first.cooked.as_mut().unwrap();
let new: JsWord = format!("{}{}", ls.value, r_str.value).into();
r_str.value = new.clone();
r_first.raw.value = new;
l.take();
return;
}
}
(Expr::Tpl(l), Expr::Tpl(rt)) => {
// We prepend the last quasis of l to the first quasis of r.
// After doing so, we can append all data of r to l.
{
let l_last = l.quasis.pop().unwrap();
let mut r_first = rt.quasis.first_mut().unwrap();
let r_str = r_first.cooked.as_mut().unwrap();
let new: JsWord =
format!("{}{}", l_last.cooked.unwrap().value, r_str.value).into();
r_str.value = new.clone();
r_first.raw.value = new;
}
l.quasis.extend(rt.quasis.take());
l.exprs.extend(rt.exprs.take());
// Remove r
r.take();
debug_assert!(l.quasis.len() == l.exprs.len() + 1, "{:?} is invalid", l);
self.changed = true;
log::trace!("strings: Merged to template literals");
}
_ => {}
}
}
}

View File

@ -1,3 +1,5 @@
use crate::option::PureGetterOption;
use super::Optimizer;
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
@ -44,7 +46,7 @@ impl Optimizer<'_> {
..
}) => {}
_ => {
self.drop_unused_vars(&mut var.name);
self.drop_unused_vars(&mut var.name, Some(init));
if var.name.is_invalid() {
let side_effects = self.ignore_return_value(init);
@ -58,12 +60,35 @@ impl Optimizer<'_> {
}
},
None => {
self.drop_unused_vars(&mut var.name);
self.drop_unused_vars(&mut var.name, var.init.as_deref_mut());
}
}
}
pub(super) fn drop_unused_vars(&mut self, name: &mut Pat) {
pub(super) fn drop_unused_param(&mut self, pat: &mut Pat) {
if !self.options.unused {
return;
}
if let Some(scope) = self
.data
.as_ref()
.and_then(|data| data.scopes.get(&self.ctx.scope))
{
if scope.has_eval_call || scope.has_with_stmt {
return;
}
}
// Preserve `length` of function.
if pat.is_ident() {
return;
}
self.take_pat_if_unused(pat, None)
}
pub(super) fn drop_unused_vars(&mut self, name: &mut Pat, init: Option<&mut Expr>) {
if !self.options.unused || self.ctx.in_var_decl_of_for_in_or_of_loop || self.ctx.is_exported
{
return;
@ -85,6 +110,24 @@ impl Optimizer<'_> {
}
}
if !name.is_ident() && init.is_none() {
return;
}
self.take_pat_if_unused(name, init);
}
fn take_pat_if_unused(&mut self, name: &mut Pat, mut init: Option<&mut Expr>) {
let had_value = init.is_some();
let can_drop_children = had_value;
if !name.is_ident() {
// TODO: Use smart logic
if self.options.pure_getters != PureGetterOption::Bool(true) {
return;
}
}
match name {
Pat::Ident(i) => {
if self.options.top_retain.contains(&i.id.sym) {
@ -109,6 +152,81 @@ impl Optimizer<'_> {
return;
}
}
Pat::Array(arr) => {
for (idx, elem) in arr.elems.iter_mut().enumerate() {
match elem {
Some(p) => {
if p.is_ident() {
continue;
}
let elem = init
.as_mut()
.and_then(|expr| self.access_numeric_property(expr, idx));
self.take_pat_if_unused(p, elem);
}
None => {}
}
}
arr.elems.retain(|elem| match elem {
Some(Pat::Invalid(..)) => false,
_ => true,
})
}
Pat::Object(obj) => {
// If a rest pattern exists, we can't remove anything at current level.
if obj.props.iter().any(|p| match p {
ObjectPatProp::Rest(_) => true,
_ => false,
}) {
return;
}
for prop in &mut obj.props {
match prop {
ObjectPatProp::KeyValue(p) => {
if p.key.is_computed() {
continue;
}
let prop = init.as_mut().and_then(|value| {
self.access_property_with_prop_name(value, &p.key)
});
if can_drop_children && prop.is_none() {
continue;
}
self.take_pat_if_unused(&mut p.value, prop);
}
ObjectPatProp::Assign(_) => {}
ObjectPatProp::Rest(_) => {}
}
}
obj.props.retain(|p| {
match p {
ObjectPatProp::KeyValue(p) => {
if p.value.is_invalid() {
return false;
}
}
ObjectPatProp::Assign(_) => {}
ObjectPatProp::Rest(_) => {}
}
true
})
}
Pat::Rest(_) => {}
Pat::Assign(_) => {
// TODO
}
_ => {}
}
}

View File

@ -2,12 +2,68 @@ use super::Ctx;
use super::Optimizer;
use std::ops::Deref;
use std::ops::DerefMut;
use swc_atoms::JsWord;
use swc_common::comments::Comment;
use swc_common::comments::CommentKind;
use swc_common::Mark;
use swc_common::Span;
use swc_ecma_ast::*;
use swc_ecma_utils::prop_name_eq;
impl<'b> Optimizer<'b> {
pub(super) fn access_property<'e>(
&mut self,
expr: &'e mut Expr,
prop: &JsWord,
) -> Option<&'e mut Expr> {
match expr {
Expr::Object(obj) => {
for obj_prop in obj.props.iter_mut() {
match obj_prop {
PropOrSpread::Spread(_) => {}
PropOrSpread::Prop(p) => match &mut **p {
Prop::Shorthand(_) => {}
Prop::KeyValue(p) => {
if prop_name_eq(&p.key, &prop) {
return Some(&mut *p.value);
}
}
Prop::Assign(_) => {}
Prop::Getter(_) => {}
Prop::Setter(_) => {}
Prop::Method(_) => {}
},
}
}
}
_ => {}
}
None
}
pub(super) fn access_property_with_prop_name<'e>(
&mut self,
expr: &'e mut Expr,
prop: &PropName,
) -> Option<&'e mut Expr> {
match prop {
PropName::Ident(p) => self.access_property(expr, &p.sym),
PropName::Str(p) => self.access_property(expr, &p.value),
PropName::Num(p) => self.access_numeric_property(expr, p.value as _),
PropName::Computed(_) => None,
PropName::BigInt(_) => None,
}
}
pub(super) fn access_numeric_property<'e>(
&mut self,
_expr: &'e mut Expr,
_idx: usize,
) -> Option<&'e mut Expr> {
None
}
/// Check for `/** @const */`.
pub(super) fn has_const_ann(&self, span: Span) -> bool {
self.find_comment(span, |c| {

View File

@ -73,6 +73,22 @@ pub struct ManglePropertiesOptions {
pub regex: Option<Regex>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
pub enum PureGetterOption {
Bool(bool),
#[serde(rename = "strict")]
Strict,
Str(Vec<JsWord>),
}
impl Default for PureGetterOption {
fn default() -> Self {
Self::Strict
}
}
/// https://terser.org/docs/api-reference.html#compress-options
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -211,7 +227,10 @@ pub struct CompressOptions {
#[serde(alias = "properties")]
pub props: bool,
// pure_getters : !false_by_default && "strict",
#[serde(default)]
#[serde(alias = "properties")]
pub pure_getters: PureGetterOption,
// pure_funcs : null,
#[serde(default)]
#[serde(alias = "reduce_funcs")]

View File

@ -1,5 +1,7 @@
//! Compatibility for terser config.
use crate::option::PureGetterOption;
use super::CompressOptions;
use super::TopLevelOptions;
use fxhash::FxHashMap;
@ -336,6 +338,13 @@ impl TerserCompressorOptions {
negate_iife: self.negate_iife.unwrap_or(self.defaults),
passes: self.passes,
props: self.properties.unwrap_or(self.defaults),
pure_getters: match self.pure_getters {
TerserPureGetterOption::Bool(v) => PureGetterOption::Bool(v),
TerserPureGetterOption::Strict => PureGetterOption::Strict,
TerserPureGetterOption::Str(v) => {
PureGetterOption::Str(v.split(',').map(From::from).collect())
}
},
reduce_fns: self.reduce_funcs,
reduce_vars: self.reduce_vars,
sequences: self

View File

@ -53,7 +53,7 @@ fn is_ignored(path: &Path) -> bool {
.collect()
});
let s = path.to_string_lossy().replace("-", "_");
let s = path.to_string_lossy().replace("-", "_").replace("\\", "/");
if IGNORED.iter().any(|ignored| s.contains(&**ignored)) {
return true;

View File

@ -175,7 +175,13 @@ comments/comment_moved_between_return_and_value/input.js
comments/preserve_comments_by_default/input.js
comments/print_every_comment_only_once/input.js
comparing/dont_change_in_or_instanceof_expressions/input.js
comparing/issue_2857_1/input.js
comparing/issue_2857_2/input.js
comparing/issue_2857_3/input.js
comparing/issue_2857_4/input.js
comparing/issue_2857_5/input.js
comparing/keep_comparisons/input.js
comparing/self_comparison_2/input.js
concat_strings/concat_2/input.js
concat_strings/concat_3/input.js
concat_strings/concat_4/input.js
@ -242,6 +248,7 @@ destructuring/destructuring_remove_unused_2/input.js
destructuring/destructuring_vardef_in_loops/input.js
destructuring/empty_object_destructuring_1/input.js
destructuring/empty_object_destructuring_2/input.js
destructuring/export_function_containing_destructuring_decl/input.js
destructuring/export_unreferenced_declarations_1/input.js
destructuring/issue_1886/input.js
destructuring/issue_2044_ecma_5/input.js
@ -257,12 +264,16 @@ destructuring/reduce_vars/input.js
destructuring/unused/input.js
destructuring/unused_destructuring_assign_1/input.js
destructuring/unused_destructuring_assign_2/input.js
destructuring/unused_destructuring_class_method_param/input.js
destructuring/unused_destructuring_decl_2/input.js
destructuring/unused_destructuring_decl_3/input.js
destructuring/unused_destructuring_decl_4/input.js
destructuring/unused_destructuring_decl_6/input.js
destructuring/unused_destructuring_declaration_complex_1/input.js
destructuring/unused_destructuring_declaration_complex_2/input.js
destructuring/unused_destructuring_function_param/input.js
destructuring/unused_destructuring_getter_side_effect_1/input.js
destructuring/unused_destructuring_object_method_param/input.js
directives/class_directives_compression/input.js
directives/simple_statement_is_not_a_directive/input.js
drop_console/drop_console_1/input.js
@ -311,30 +322,37 @@ drop_unused/used_block_decls_in_catch/input.js
drop_unused/vardef_value/input.js
evaluate/Infinity_NaN_undefined_LHS/input.js
evaluate/and/input.js
evaluate/array_slice_index/input.js
evaluate/call_args/input.js
evaluate/call_args_drop_param/input.js
evaluate/delete_binary_1/input.js
evaluate/delete_binary_2/input.js
evaluate/delete_expr_1/input.js
evaluate/delete_expr_2/input.js
evaluate/global_hasOwnProperty/input.js
evaluate/in_boolean_context/input.js
evaluate/issue_1649/input.js
evaluate/issue_1760_1/input.js
evaluate/issue_1760_2/input.js
evaluate/issue_1964_1/input.js
evaluate/issue_1964_2/input.js
evaluate/issue_2207_2/input.js
evaluate/issue_2231_1/input.js
evaluate/issue_2231_2/input.js
evaluate/issue_2535_3/input.js
evaluate/issue_2822/input.js
evaluate/issue_2916_1/input.js
evaluate/optional_expect_when_expect_stdout_present/input.js
evaluate/or/input.js
evaluate/positive_zero/input.js
evaluate/pow/input.js
evaluate/pow_mixed/input.js
evaluate/pow_sequence/input.js
evaluate/pow_sequence_with_constants_and_parens/input.js
evaluate/pow_sequence_with_parens/input.js
evaluate/pow_sequence_with_parens_evaluated/input.js
evaluate/pow_sequence_with_parens_exact/input.js
evaluate/pow_with_number_constants/input.js
evaluate/pow_with_right_side_evaluating_to_unary/input.js
evaluate/prop_function/input.js
evaluate/string_case/input.js
@ -500,6 +518,7 @@ if_return/issue_1317/input.js
if_return/issue_1317_strict/input.js
if_return/issue_1437/input.js
if_return/issue_1437_conditionals/input.js
if_return/issue_2747/input.js
inline/inline_func_with_name_existing_in_block_scope/input.js
inline/inline_into_scope_conflict_enclosed/input.js
inline/noinline_annotation/input.js
@ -654,6 +673,7 @@ node_version/eval_let_6/input.js
nullish/nullish_coalescing_mandatory_parens/input.js
nullish/nullish_coalescing_parens/input.js
nullish/simplify_nullish_coalescing/input.js
numbers/comparisons/input.js
numbers/compress_numbers/input.js
numbers/issue_1710/input.js
numbers/keep_numbers/input.js
@ -961,6 +981,7 @@ switch/issue_1690_1/input.js
switch/issue_1690_2/input.js
switch/issue_1698/input.js
switch/issue_1705_1/input.js
switch/issue_1705_2/input.js
switch/issue_1750/input.js
switch/issue_1758/input.js
switch/issue_2535/input.js
@ -973,7 +994,9 @@ switch/keep_default/input.js
template_string/allow_chained_templates/input.js
template_string/allow_null_character/input.js
template_string/check_escaped_chars/input.js
template_string/do_not_optimize_tagged_template_1/input.js
template_string/do_not_optimize_tagged_template_2/input.js
template_string/equality/input.js
template_string/es2018_revision_of_template_escapes_1/input.js
template_string/issue_1856/input.js
template_string/issue_1856_ascii_only/input.js
@ -983,17 +1006,23 @@ template_string/respect_inline_script/input.js
template_string/return_template_string_with_trailing_backslash/input.js
template_string/semicolons/input.js
template_string/sequence_1/input.js
template_string/sequence_2/input.js
template_string/simple_string/input.js
template_string/tagged_call_with_invalid_escape/input.js
template_string/tagged_call_with_invalid_escape_2/input.js
template_string/tagged_template_function_inline_1/input.js
template_string/tagged_template_function_inline_2/input.js
template_string/tagged_template_function_inline_3/input.js
template_string/tagged_template_function_inline_4/input.js
template_string/tagged_template_parens/input.js
template_string/tagged_template_valid_strict_legacy_octal/input.js
template_string/tagged_template_with_comment/input.js
template_string/tagged_template_with_ill_formed_unicode_escape/input.js
template_string/tagged_template_with_invalid_escape/input.js
template_string/template_ending_with_newline/input.js
template_string/template_evaluate_undefined/input.js
template_string/template_literal_plus/input.js
template_string/template_literal_plus_grouping/input.js
template_string/template_starting_with_newline/input.js
template_string/template_string_evaluate_with_many_segments/input.js
template_string/template_string_prefixes/input.js

View File

@ -152,15 +152,9 @@ collapse_vars/unused_orig/input.js
collapse_vars/var_defs/input.js
collapse_vars/var_side_effects_2/input.js
collapse_vars/var_side_effects_3/input.js
comparing/issue_2857_1/input.js
comparing/issue_2857_2/input.js
comparing/issue_2857_3/input.js
comparing/issue_2857_4/input.js
comparing/issue_2857_5/input.js
comparing/issue_2857_6/input.js
comparing/keep_comparisons_with_unsafe_comps/input.js
comparing/self_comparison_1/input.js
comparing/self_comparison_2/input.js
concat_strings/concat_1/input.js
concat_strings/concat_7/input.js
concat_strings/concat_8/input.js
@ -213,7 +207,6 @@ destructuring/destructuring_with_undefined_as_default_assignment/input.js
destructuring/empty_object_destructuring_3/input.js
destructuring/empty_object_destructuring_4/input.js
destructuring/empty_object_destructuring_misc/input.js
destructuring/export_function_containing_destructuring_decl/input.js
destructuring/export_unreferenced_declarations_2/input.js
destructuring/issue_3205_2/input.js
destructuring/issue_3205_3/input.js
@ -230,14 +223,10 @@ destructuring/mangle_destructuring_decl/input.js
destructuring/mangle_destructuring_decl_array/input.js
destructuring/mangle_destructuring_decl_collapse_vars/input.js
destructuring/unused_destructuring_arrow_param/input.js
destructuring/unused_destructuring_class_method_param/input.js
destructuring/unused_destructuring_decl_1/input.js
destructuring/unused_destructuring_decl_5/input.js
destructuring/unused_destructuring_declaration_complex_1/input.js
destructuring/unused_destructuring_function_param/input.js
destructuring/unused_destructuring_getter_side_effect_2/input.js
destructuring/unused_destructuring_multipass/input.js
destructuring/unused_destructuring_object_method_param/input.js
drop_console/drop_console_2/input.js
drop_console/unexpected_side_effects_dropping_console/input.js
drop_unused/assign_binding/input.js
@ -314,15 +303,11 @@ drop_unused/unused_null_conditional_chain/input.js
drop_unused/used_var_in_catch/input.js
drop_unused/var_catch_toplevel/input.js
drop_unused/variable_refs_outside_unused_class/input.js
evaluate/array_slice_index/input.js
evaluate/delete_expr_2/input.js
evaluate/issue_1760_1/input.js
evaluate/issue_2207_1/input.js
evaluate/issue_2207_3/input.js
evaluate/issue_2231_3/input.js
evaluate/issue_2535_1/input.js
evaluate/issue_2535_2/input.js
evaluate/issue_2535_3/input.js
evaluate/issue_2916_2/input.js
evaluate/issue_2919/input.js
evaluate/issue_2926_1/input.js
@ -331,9 +316,6 @@ evaluate/issue_2968/input.js
evaluate/issue_399/input.js
evaluate/negative_zero/input.js
evaluate/null_conditional_chain_eval/input.js
evaluate/positive_zero/input.js
evaluate/pow_sequence_with_parens_exact/input.js
evaluate/pow_with_number_constants/input.js
evaluate/prototype_function/input.js
evaluate/self_comparison_1/input.js
evaluate/self_comparison_2/input.js
@ -546,7 +528,6 @@ if_return/if_return_5/input.js
if_return/if_return_7/input.js
if_return/if_return_same_value/input.js
if_return/if_var_return/input.js
if_return/issue_2747/input.js
if_return/issue_512/input.js
inline/do_not_repeat_when_variable_larger_than_inlined_node/input.js
inline/dont_inline_funcs_into_default_param/input.js
@ -773,7 +754,6 @@ new/new_statements_3/input.js
nullish/conditional_to_nullish_coalescing/input.js
nullish/conditional_to_nullish_coalescing_2/input.js
nullish/nullish_coalescing_boolean_context/input.js
numbers/comparisons/input.js
numbers/evaluate_1/input.js
numbers/evaluate_2/input.js
numbers/evaluate_3/input.js
@ -1054,25 +1034,16 @@ sequences/side_effects_cascade_3/input.js
sequences/unsafe_undefined/input.js
switch/issue_1663/input.js
switch/issue_1680_2/input.js
switch/issue_1705_2/input.js
switch/issue_1705_3/input.js
template_string/array_join/input.js
template_string/coerce_to_string/input.js
template_string/do_not_optimize_tagged_template_1/input.js
template_string/equality/input.js
template_string/escape_dollar_curly/input.js
template_string/evaluate_nested_templates/input.js
template_string/regex_2/input.js
template_string/sequence_2/input.js
template_string/side_effects/input.js
template_string/simple_string/input.js
template_string/special_chars_in_string/input.js
template_string/tagged_template_function_inline_5/input.js
template_string/tagged_template_parens/input.js
template_string/template_concattenating_string/input.js
template_string/template_evaluate_undefined/input.js
template_string/template_literal_plus/input.js
template_string/template_literal_plus_grouping/input.js
template_string/template_string_with_predefined_constants/input.js
transform/condition_evaluate/input.js
transform/if_return/input.js

View File

@ -1,6 +1,6 @@
console.log(delete undefined);
console.log(!0);
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(!0);
console.log(delete NaN);
console.log((0 / 0, !0));
console.log(!0);

View File

@ -1,6 +1,6 @@
console.log(delete undefined);
console.log(!0);
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(!0);
console.log(delete NaN);
console.log(!0);

View File

@ -1,8 +1,8 @@
!(function (a) {
try {
throw 0;
} catch (NaN) {
a = 0 / 0;
} catch (NaN1) {
a = NaN;
}
console.log(a);
})();

View File

@ -1,4 +1,4 @@
console.log((4 ** 1) ** 2, (4 ** 1) ** (1 / 2));
console.log(16, 2);
var one = 1;
var two = 2;
var four = 4;

View File

@ -2,12 +2,12 @@ var a = NaN;
var b = 1;
var c = 1;
var d = NaN;
var e = 1 / 0;
var e = Infinity;
var f = 0;
var g = NaN;
var h = 1 / 0;
var i = -1 / 0;
var h = Infinity;
var i = -Infinity;
var j = 0.125;
var k = 0.125;
var l = 0.25;
var m = 3 ** -10;
var m = 0.000016935087808430286;

View File

@ -1 +1 @@
console.log("world", [1][0], true);
console.log("world", 1, true);

View File

@ -1,9 +1,10 @@
a`0`;
((a) => b)`1`;
((a)=>b
)`1`;
(a = b)`2`;
(a + b)`3`;
(a ? b : c)`4`;
(a, b, c)`5`;
c`5`;
(~a)`6`;
a.b`7`;
a["b"]`8`;

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms_base"
repository = "https://github.com/swc-project/swc.git"
version = "0.15.3"
version = "0.15.4"
[dependencies]
fxhash = "0.2.1"

View File

@ -187,7 +187,9 @@ impl VisitMut for Fixer<'_> {
// While simplifying, (1 + x) * Nan becomes `1 + x * Nan`.
// But it should be `(1 + x) * Nan`
Expr::Bin(BinExpr { op: op_of_lhs, .. }) => {
if op_of_lhs.precedence() < expr.op.precedence() {
if op_of_lhs.precedence() < expr.op.precedence()
|| (op_of_lhs.precedence() == expr.op.precedence() && expr.op == op!("**"))
{
self.wrap(&mut expr.left);
}
}
@ -425,6 +427,23 @@ impl VisitMut for Fixer<'_> {
self.ctx = old;
}
fn visit_mut_tagged_tpl(&mut self, e: &mut TaggedTpl) {
e.visit_mut_children_with(self);
match &*e.tag {
Expr::Arrow(..)
| Expr::Cond(..)
| Expr::Bin(..)
| Expr::Seq(..)
| Expr::Fn(..)
| Expr::Assign(..)
| Expr::Unary(..) => {
self.wrap(&mut e.tag);
}
_ => {}
}
}
fn visit_mut_module(&mut self, n: &mut Module) {
debug_assert!(self.span_map.is_empty());
self.span_map.clear();
@ -1192,4 +1211,6 @@ var store = global[SHARED] || (global[SHARED] = {});
identical!(deno_10668_1, "console.log(null ?? (undefined && true))");
identical!(deno_10668_2, "console.log(null && (undefined ?? true))");
identical!(minifier_003, "(four ** one) ** two");
}

View File

@ -1145,7 +1145,7 @@ pub trait ExprExt {
}
Expr::OptChain(ref e) => e.expr.may_have_side_effects(),
Expr::Invalid(..) => unreachable!(),
Expr::Invalid(..) => true,
}
}
}