mirror of
https://github.com/swc-project/swc.git
synced 2024-10-05 12:49:21 +03:00
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:
parent
a518c83485
commit
3522fc71e4
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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))?;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
15
ecmascript/minifier/scripts/update-list.sh
Executable file
15
ecmascript/minifier/scripts/update-list.sh
Executable 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 ''
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
60
ecmascript/minifier/src/compress/optimize/numbers.rs
Normal file
60
ecmascript/minifier/src/compress/optimize/numbers.rs
Normal 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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -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| {
|
||||
|
@ -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")]
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -1,8 +1,8 @@
|
||||
!(function (a) {
|
||||
try {
|
||||
throw 0;
|
||||
} catch (NaN) {
|
||||
a = 0 / 0;
|
||||
} catch (NaN1) {
|
||||
a = NaN;
|
||||
}
|
||||
console.log(a);
|
||||
})();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -1 +1 @@
|
||||
console.log("world", [1][0], true);
|
||||
console.log("world", 1, true);
|
||||
|
@ -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`;
|
||||
|
@ -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"
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -1145,7 +1145,7 @@ pub trait ExprExt {
|
||||
}
|
||||
Expr::OptChain(ref e) => e.expr.may_have_side_effects(),
|
||||
|
||||
Expr::Invalid(..) => unreachable!(),
|
||||
Expr::Invalid(..) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user