feat(es/minifier): Make minifier parallel (#2009)

swc_ecma_minifier:
 - Introduce `bundle` mode, which can be used to parallelize processing of bundled files.
 - Add a function analyzer that checks if the function references something from the outer scope.
 - Split out parellsizable passes.
 - Split `optimzer` into pure / non-pure.
 - Run pure optimizations in parallel.
This commit is contained in:
강동윤 2021-08-07 17:27:52 +09:00 committed by GitHub
parent 8a39c1db97
commit 026c21ec68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 4595 additions and 3517 deletions

5
Cargo.lock generated
View File

@ -2498,7 +2498,7 @@ dependencies = [
[[package]]
name = "swc_ecma_minifier"
version = "0.18.1"
version = "0.18.2"
dependencies = [
"ansi_term 0.12.1",
"anyhow",
@ -2507,6 +2507,7 @@ dependencies = [
"log",
"once_cell",
"pretty_assertions 0.6.1",
"rayon",
"regex",
"retain_mut",
"serde",
@ -2529,7 +2530,7 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.66.0"
version = "0.66.1"
dependencies = [
"either",
"enum_kind",

View File

@ -70,3 +70,11 @@ lto = true
[profile.bench]
codegen-units = 1
debug = true
# Without this, printing diff consumes more than a minute.
[profile.dev.package.pretty_assertions]
opt-level = 3
[profile.test.package.pretty_assertions]
opt-level = 3

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.18.1"
version = "0.18.2"
[features]
debug = []
@ -18,6 +18,7 @@ indexmap = "1.7.0"
log = "0.4"
once_cell = "1.5.2"
pretty_assertions = {version = "0.6.1", optional = true}
rayon = "1.5.1"
regex = "1.5.3"
retain_mut = "0.1.2"
serde = {version = "1.0.118", features = ["derive"]}

View File

@ -16,6 +16,9 @@ impl UsageAnalyzer {
#[derive(Debug, Default, Clone, Copy)]
pub(super) struct Ctx {
/// See [crate::marks::Marks]
pub skip_standalone: bool,
pub var_decl_kind_of_pat: Option<VarDeclKind>,
pub in_var_decl_with_no_side_effect_for_member_access: bool,

View File

@ -23,7 +23,9 @@ mod ctx;
/// TODO: Track assignments to variables via `arguments`.
/// TODO: Scope-local. (Including block)
pub(crate) fn analyze<N>(n: &N, marks: Marks) -> ProgramData
///
/// If `marks` is [None], markers are ignored.
pub(crate) fn analyze<N>(n: &N, marks: Option<Marks>) -> ProgramData
where
N: VisitWith<UsageAnalyzer>,
{
@ -237,7 +239,7 @@ impl ProgramData {
#[derive(Debug)]
pub(crate) struct UsageAnalyzer {
data: ProgramData,
marks: Marks,
marks: Option<Marks>,
scope: ScopeData,
ctx: Ctx,
}
@ -415,7 +417,11 @@ impl Visit for UsageAnalyzer {
}
fn visit_call_expr(&mut self, n: &CallExpr, _: &dyn Node) {
let inline_prevented = self.ctx.inline_prevented || n.span.has_mark(self.marks.noinline);
let inline_prevented = self.ctx.inline_prevented
|| self
.marks
.map(|marks| n.span.has_mark(marks.noinline))
.unwrap_or_default();
{
let ctx = Ctx {
@ -588,18 +594,35 @@ impl Visit for UsageAnalyzer {
fn visit_function(&mut self, n: &Function, _: &dyn Node) {
n.decorators.visit_with(n, self);
self.with_child(n.span.ctxt, ScopeKind::Fn, |child| {
n.params.visit_with(n, child);
let is_standalone = self
.marks
.map(|marks| n.span.has_mark(marks.standalone))
.unwrap_or_default();
match &n.body {
Some(body) => {
// We use visit_children_with instead of visit_with to bypass block scope
// handler.
body.visit_children_with(child);
// We don't dig into standalone function, as it does not share any variable with
// outer scope.
if self.ctx.skip_standalone && is_standalone {
return;
}
let ctx = Ctx {
skip_standalone: self.ctx.skip_standalone || is_standalone,
..self.ctx
};
self.with_ctx(ctx)
.with_child(n.span.ctxt, ScopeKind::Fn, |child| {
n.params.visit_with(n, child);
match &n.body {
Some(body) => {
// We use visit_children_with instead of visit_with to bypass block scope
// handler.
body.visit_children_with(child);
}
None => {}
}
None => {}
}
})
})
}
fn visit_if_stmt(&mut self, n: &IfStmt, _: &dyn Node) {
@ -665,6 +688,14 @@ impl Visit for UsageAnalyzer {
}
}
fn visit_module(&mut self, n: &Module, _: &dyn Node) {
let ctx = Ctx {
skip_standalone: true,
..self.ctx
};
n.visit_children_with(&mut *self.with_ctx(ctx))
}
fn visit_named_export(&mut self, n: &NamedExport, _: &dyn Node) {
if n.src.is_some() {
return;

View File

@ -4,45 +4,50 @@ use self::optimize::optimizer;
use self::optimize::OptimizerState;
use crate::analyzer::analyze;
use crate::analyzer::ProgramData;
use crate::analyzer::UsageAnalyzer;
use crate::compress::hoist_decls::decl_hoister;
use crate::compress::pure::pure_optimizer;
use crate::debug::dump;
use crate::debug::invoke;
use crate::marks::Marks;
use crate::option::CompressOptions;
use crate::util::now;
use crate::util::unit::CompileUnit;
use crate::util::Optional;
use crate::MAX_PAR_DEPTH;
#[cfg(feature = "pretty_assertions")]
use pretty_assertions::assert_eq;
use rayon::prelude::*;
use std::borrow::Cow;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::thread;
use std::time::Instant;
use swc_common::chain;
use swc_common::pass::CompilerPass;
use swc_common::pass::Repeat;
use swc_common::pass::Repeated;
use swc_common::sync::Lrc;
use swc_common::{chain, SourceMap};
use swc_common::Globals;
use swc_ecma_ast::*;
use swc_ecma_transforms::fixer;
use swc_ecma_transforms::optimization::simplify::dead_branch_remover;
use swc_ecma_transforms::optimization::simplify::expr_simplifier;
use swc_ecma_transforms::pass::JsPass;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::StmtLike;
use swc_ecma_visit::as_folder;
use swc_ecma_visit::noop_visit_mut_type;
use swc_ecma_visit::FoldWith;
use swc_ecma_visit::VisitMut;
use swc_ecma_visit::VisitMutWith;
use swc_ecma_visit::VisitWith;
mod drop_console;
mod hoist_decls;
mod optimize;
mod pure;
mod util;
pub(crate) fn compressor<'a>(
cm: Lrc<SourceMap>,
globals: &'a Globals,
marks: Marks,
options: &'a CompressOptions,
) -> impl 'a + JsPass {
@ -51,30 +56,29 @@ pub(crate) fn compressor<'a>(
visitor: drop_console(),
};
let compressor = Compressor {
cm,
globals,
marks,
options,
changed: false,
pass: 0,
data: None,
optimizer_state: Default::default(),
left_parallel_depth: 0,
};
chain!(
console_remover,
Repeat::new(as_folder(compressor)),
expr_simplifier()
)
chain!(console_remover, as_folder(compressor), expr_simplifier())
}
struct Compressor<'a> {
cm: Lrc<SourceMap>,
globals: &'a Globals,
marks: Marks,
options: &'a CompressOptions,
changed: bool,
pass: usize,
data: Option<ProgramData>,
optimizer_state: OptimizerState,
/// `0` means we should not create more threads.
left_parallel_depth: u8,
}
impl CompilerPass for Compressor<'_> {
@ -83,18 +87,6 @@ impl CompilerPass for Compressor<'_> {
}
}
impl Repeated for Compressor<'_> {
fn changed(&self) -> bool {
self.changed
}
fn reset(&mut self) {
self.changed = false;
self.pass += 1;
self.data = None;
}
}
impl Compressor<'_> {
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
@ -123,14 +115,108 @@ impl Compressor<'_> {
// TODO: drop unused
}
}
impl VisitMut for Compressor<'_> {
noop_visit_mut_type!();
/// Optimize a bundle in a parallel.
fn visit_par<N>(&mut self, nodes: &mut Vec<N>)
where
N: Send + Sync + for<'aa> VisitMutWith<Compressor<'aa>>,
{
log::debug!("visit_par(left_depth = {})", self.left_parallel_depth);
fn visit_mut_module(&mut self, n: &mut Module) {
debug_assert!(self.data.is_none());
self.data = Some(analyze(&*n, self.marks));
if self.left_parallel_depth == 0 || cfg!(target_arch = "wasm32") {
for node in nodes {
let mut v = Compressor {
globals: self.globals,
marks: self.marks,
options: self.options,
changed: false,
pass: self.pass,
data: None,
optimizer_state: Default::default(),
left_parallel_depth: 0,
};
node.visit_mut_with(&mut v);
self.changed |= v.changed;
}
} else {
let results = nodes
.par_iter_mut()
.map(|node| {
swc_common::GLOBALS.set(&self.globals, || {
let mut v = Compressor {
globals: self.globals,
marks: self.marks,
options: self.options,
changed: false,
pass: self.pass,
data: None,
optimizer_state: Default::default(),
left_parallel_depth: self.left_parallel_depth - 1,
};
node.visit_mut_with(&mut v);
v.changed
})
})
.collect::<Vec<_>>();
for changed in results {
self.changed |= changed;
}
}
}
fn optimize_unit_repeatedly<N>(&mut self, n: &mut N)
where
N: CompileUnit + VisitWith<UsageAnalyzer> + for<'aa> VisitMutWith<Compressor<'aa>>,
{
if cfg!(feature = "debug") {
log::debug!(
"Optimizing a compile unit within `{:?}`",
thread::current().name()
);
}
{
let data = analyze(&*n, Some(self.marks));
let mut v = decl_hoister(
DeclHoisterConfig {
hoist_fns: self.options.hoist_fns,
hoist_vars: self.options.hoist_vars,
top_level: self.options.top_level(),
},
&data,
);
n.apply(&mut v);
self.changed |= v.changed();
}
loop {
self.changed = false;
self.optimize_unit(n);
self.pass += 1;
if !self.changed {
break;
}
}
// let last_mark = n.remove_mark();
// assert!(
// N::is_module() || last_mark == self.marks.standalone,
// "{:?}; last={:?}",
// self.marks,
// last_mark
// );
}
/// Optimize a module. `N` can be [Module] or [FnExpr].
fn optimize_unit<N>(&mut self, n: &mut N)
where
N: CompileUnit + VisitWith<UsageAnalyzer> + for<'aa> VisitMutWith<Compressor<'aa>>,
{
self.data = Some(analyze(&*n, Some(self.marks)));
if self.options.passes != 0 && self.options.passes + 1 <= self.pass {
let done = dump(&*n);
@ -144,7 +230,7 @@ impl VisitMut for Compressor<'_> {
}
let start = if cfg!(feature = "debug") {
let start = dump(&n.clone().fold_with(&mut fixer(None)));
let start = n.dump();
log::debug!("===== Start =====\n{}", start);
start
} else {
@ -160,7 +246,7 @@ impl VisitMut for Compressor<'_> {
let start_time = now();
let mut visitor = expr_simplifier();
n.visit_mut_with(&mut visitor);
n.apply(&mut visitor);
self.changed |= visitor.changed();
if visitor.changed() {
log::debug!("compressor: Simplified expressions");
@ -180,7 +266,7 @@ impl VisitMut for Compressor<'_> {
}
if cfg!(feature = "debug") && !visitor.changed() {
let simplified = dump(&n.clone().fold_with(&mut fixer(None)));
let simplified = n.dump();
if start != simplified {
assert_eq!(
DebugUsingDisplay(&start),
@ -192,6 +278,29 @@ impl VisitMut for Compressor<'_> {
}
}
{
log::info!(
"compress/pure: Running pure optimizer (pass = {})",
self.pass
);
let start_time = now();
let mut visitor = pure_optimizer(&self.options, self.marks);
n.apply(&mut visitor);
self.changed |= visitor.changed();
if let Some(start_time) = start_time {
let end_time = Instant::now();
log::info!(
"compress/pure: Pure optimizer took {:?} (pass = {})",
end_time - start_time,
self.pass
);
}
}
{
log::info!("compress: Running optimizer (pass = {})", self.pass);
@ -203,13 +312,12 @@ impl VisitMut for Compressor<'_> {
self.optimizer_state = Default::default();
let mut visitor = optimizer(
self.cm.clone(),
self.marks,
self.options,
self.data.as_ref().unwrap(),
&mut self.optimizer_state,
);
n.visit_mut_with(&mut visitor);
n.apply(&mut visitor);
self.changed |= visitor.changed();
if let Some(start_time) = start_time {
@ -235,7 +343,7 @@ impl VisitMut for Compressor<'_> {
let start_time = now();
let mut v = dead_branch_remover();
n.map_with_mut(|n| n.fold_with(&mut v));
n.apply(&mut v);
if let Some(start_time) = start_time {
let end_time = Instant::now();
@ -261,26 +369,39 @@ impl VisitMut for Compressor<'_> {
self.changed |= v.changed();
}
}
}
impl VisitMut for Compressor<'_> {
noop_visit_mut_type!();
fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
if n.function.span.has_mark(self.marks.standalone) {
self.optimize_unit_repeatedly(n);
return;
}
n.visit_mut_children_with(self);
}
fn visit_mut_module(&mut self, n: &mut Module) {
let is_bundle_mode = n.span.has_mark(self.marks.bundle_of_standalones);
if is_bundle_mode {
self.left_parallel_depth = MAX_PAR_DEPTH - 1;
} else {
self.optimize_unit_repeatedly(n);
return;
}
n.visit_mut_children_with(self);
self.optimize_unit_repeatedly(n);
invoke(&*n);
}
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
{
let mut v = decl_hoister(
DeclHoisterConfig {
hoist_fns: self.options.hoist_fns,
hoist_vars: self.options.hoist_vars,
top_level: self.options.top_level(),
},
self.data.as_ref().unwrap(),
);
stmts.visit_mut_with(&mut v);
self.changed |= v.changed();
}
self.handle_stmt_likes(stmts);
stmts.retain(|stmt| match stmt {
@ -298,24 +419,8 @@ impl VisitMut for Compressor<'_> {
});
}
fn visit_mut_script(&mut self, n: &mut Script) {
debug_assert!(self.data.is_none());
self.data = Some(analyze(&*n, self.marks));
{
let mut v = decl_hoister(
DeclHoisterConfig {
hoist_fns: self.options.hoist_fns,
hoist_vars: self.options.hoist_vars,
top_level: self.options.top_level(),
},
self.data.as_ref().unwrap(),
);
n.body.visit_mut_with(&mut v);
self.changed |= v.changed();
}
n.visit_mut_children_with(self);
fn visit_mut_prop_or_spreads(&mut self, nodes: &mut Vec<PropOrSpread>) {
self.visit_par(nodes);
}
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {

View File

@ -1,137 +1,18 @@
use super::Optimizer;
use crate::compress::optimize::{is_pure_undefined, Ctx};
use crate::compress::optimize::Ctx;
use crate::compress::util::negate_cost;
use crate::debug::dump;
use crate::util::make_bool;
use swc_atoms::js_word;
use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::Type;
use swc_ecma_utils::Value;
use swc_ecma_utils::Value::Known;
use swc_ecma_utils::Value::Unknown;
use swc_ecma_utils::{undefined, ExprExt};
/// Methods related to the options `bools` and `bool_as_ints`.
impl Optimizer<'_> {
///
/// - `!condition() || !-3.5` => `!condition()`
///
/// In this case, if lhs is false, rhs is also false so it's removable.
pub(super) fn remove_useless_pipes(&mut self, e: &mut Expr) {
if !self.options.bools {
return;
}
match e {
Expr::Bin(BinExpr {
left,
op: op @ op!("&&"),
right,
..
})
| Expr::Bin(BinExpr {
left,
op: op @ op!("||"),
right,
..
}) => {
let lt = left.get_type();
let rt = right.get_type();
match (lt, rt) {
(Known(Type::Bool), Known(Type::Bool)) => {
let rb = right.as_pure_bool();
let rb = match rb {
Known(v) => v,
Unknown => return,
};
//
let can_remove = if *op == op!("&&") { rb } else { !rb };
if can_remove {
if *op == op!("&&") {
log::debug!("booleans: Compressing `!foo && true` as `!foo`");
} else {
log::debug!("booleans: Compressing `!foo || false` as `!foo`");
}
self.changed = true;
*e = *left.take();
return;
}
}
_ => {}
}
}
_ => {}
}
}
/// `!(a && b)` => `!a || !b`
pub(super) fn optimize_bools(&mut self, e: &mut Expr) {
if !self.options.bools {
return;
}
match e {
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => match &mut **arg {
Expr::Bin(BinExpr {
op: op!("&&"),
left,
right,
..
}) => {
if negate_cost(&left, self.ctx.in_bool_ctx, false).unwrap_or(isize::MAX) >= 0
|| negate_cost(&right, self.ctx.in_bool_ctx, false).unwrap_or(isize::MAX)
>= 0
{
return;
}
log::debug!("Optimizing `!(a && b)` as `!a || !b`");
self.changed = true;
self.negate(arg);
*e = *arg.take();
return;
}
Expr::Unary(UnaryExpr {
op: op!("!"),
arg: arg_of_arg,
..
}) => match &mut **arg_of_arg {
Expr::Bin(BinExpr {
op: op!("||"),
left,
right,
..
}) => {
if negate_cost(&left, self.ctx.in_bool_ctx, false).unwrap_or(isize::MAX) > 0
&& negate_cost(&right, self.ctx.in_bool_ctx, false)
.unwrap_or(isize::MAX)
> 0
{
return;
}
log::debug!("Optimizing `!!(a || b)` as `!a && !b`");
self.changed = true;
self.negate(arg_of_arg);
*e = *arg.take();
return;
}
_ => {}
},
_ => {}
},
_ => {}
}
}
/// **This negates bool**.
///
/// Returns true if it's negated.
@ -174,9 +55,8 @@ impl Optimizer<'_> {
//
// `_ || 'undefined' == typeof require`
log::debug!(
"bools({}): Negating: (!a && !b) => !(a || b) (because both expression are good for \
"bools: Negating: (!a && !b) => !(a || b) (because both expression are good for \
negation)",
self.line_col(e.span)
);
let start = dump(&*e);
@ -202,277 +82,6 @@ impl Optimizer<'_> {
true
}
pub(super) fn compress_useless_deletes(&mut self, e: &mut Expr) {
if !self.options.bools {
return;
}
let delete = match e {
Expr::Unary(
u @ UnaryExpr {
op: op!("delete"), ..
},
) => u,
_ => return,
};
if delete.arg.may_have_side_effects() {
return;
}
let convert_to_true = match &*delete.arg {
Expr::Seq(..)
| Expr::Cond(..)
| Expr::Bin(BinExpr { op: op!("&&"), .. })
| Expr::Bin(BinExpr { op: op!("||"), .. }) => true,
// V8 and terser test ref have different opinion.
Expr::Ident(Ident {
sym: js_word!("Infinity"),
..
}) => false,
Expr::Ident(Ident {
sym: js_word!("undefined"),
..
}) => false,
Expr::Ident(Ident {
sym: js_word!("NaN"),
..
}) => false,
e if is_pure_undefined(&e) => true,
Expr::Ident(..) => true,
// NaN
Expr::Bin(BinExpr {
op: op!("/"),
right,
..
}) => {
let rn = right.as_number();
let v = if let Known(rn) = rn {
if rn != 0.0 {
true
} else {
false
}
} else {
false
};
if v {
true
} else {
self.changed = true;
let span = delete.arg.span();
log::debug!("booleans: Compressing `delete` as sequence expression");
*e = Expr::Seq(SeqExpr {
span,
exprs: vec![delete.arg.take(), Box::new(make_bool(span, true))],
});
return;
}
}
_ => false,
};
if convert_to_true {
self.changed = true;
let span = delete.arg.span();
log::debug!("booleans: Compressing `delete` => true");
*e = make_bool(span, true);
return;
}
}
pub(super) fn compress_comparsion_of_typeof(&mut self, e: &mut BinExpr) {
fn should_optimize(l: &Expr, r: &Expr) -> bool {
match (l, r) {
(
Expr::Unary(UnaryExpr {
op: op!("typeof"), ..
}),
Expr::Lit(..),
) => true,
_ => false,
}
}
match e.op {
op!("===") | op!("!==") => {}
_ => return,
}
if should_optimize(&e.left, &e.right) || should_optimize(&e.right, &e.left) {
log::debug!("bools: Compressing comparison of `typeof` with literal");
self.changed = true;
e.op = match e.op {
op!("===") => {
op!("==")
}
op!("!==") => {
op!("!=")
}
_ => {
unreachable!()
}
}
}
}
/// This method converts `!1` to `0`.
pub(super) fn optimize_expr_in_bool_ctx(&mut self, n: &mut Expr) {
if !self.options.bools {
return;
}
match n {
Expr::Bin(BinExpr {
op: op!("&&") | op!("||"),
left,
right,
..
}) => {
// Regardless if it's truthy or falsy, we can optimize it because it will be
// casted as bool anyway.
self.optimize_expr_in_bool_ctx(&mut **left);
self.optimize_expr_in_bool_ctx(&mut **right);
return;
}
Expr::Seq(e) => {
if let Some(last) = e.exprs.last_mut() {
self.optimize_expr_in_bool_ctx(&mut **last);
}
}
_ => {}
}
match n {
Expr::Unary(UnaryExpr {
span,
op: op!("!"),
arg,
}) => match &mut **arg {
Expr::Lit(Lit::Num(Number { value, .. })) => {
log::debug!("Optimizing: number => number (in bool context)");
self.changed = true;
*n = Expr::Lit(Lit::Num(Number {
span: *span,
value: if *value == 0.0 { 1.0 } else { 0.0 },
}))
}
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => {
log::debug!("bools: !!expr => expr (in bool ctx)");
self.changed = true;
*n = *arg.take();
return;
}
_ => {}
},
Expr::Unary(UnaryExpr {
span,
op: op!("typeof"),
arg,
}) => {
log::debug!("Optimizing: typeof => true (in bool context)");
self.changed = true;
match &**arg {
Expr::Ident(..) => {
*n = Expr::Lit(Lit::Num(Number {
span: *span,
value: 1.0,
}))
}
_ => {
// Return value of typeof is always truthy
let true_expr = Box::new(Expr::Lit(Lit::Num(Number {
span: *span,
value: 1.0,
})));
*n = Expr::Seq(SeqExpr {
span: *span,
exprs: vec![arg.take(), true_expr],
})
}
}
}
Expr::Lit(Lit::Str(s)) => {
log::debug!("Converting string as boolean expressions");
self.changed = true;
*n = Expr::Lit(Lit::Num(Number {
span: s.span,
value: if s.value.is_empty() { 0.0 } else { 1.0 },
}));
}
Expr::Lit(Lit::Num(num)) => {
if num.value == 1.0 || num.value == 0.0 {
return;
}
if self.options.bools {
log::debug!("booleans: Converting number as boolean expressions");
self.changed = true;
*n = Expr::Lit(Lit::Num(Number {
span: num.span,
value: if num.value == 0.0 { 0.0 } else { 1.0 },
}));
}
}
Expr::Bin(BinExpr {
op: op!("??"),
left,
right,
..
}) => {
// Optimize if (a ?? false); as if (a);
if let Value::Known(false) = right.as_pure_bool() {
log::debug!(
"Dropping right operand of `??` as it's always false (in bool context)"
);
self.changed = true;
*n = *left.take();
}
}
Expr::Bin(BinExpr {
op: op!("||"),
left,
right,
..
}) => {
// `a || false` => `a` (as it will be casted to boolean anyway)
if let Known(false) = right.as_pure_bool() {
log::debug!("bools: `expr || false` => `expr` (in bool context)");
self.changed = true;
*n = *left.take();
return;
}
}
_ => {
let span = n.span();
let v = n.as_pure_bool();
if let Known(v) = v {
log::debug!("Optimizing expr as {} (in bool context)", v);
*n = make_bool(span, v);
return;
}
}
}
}
pub(super) fn compress_if_stmt_as_expr(&mut self, s: &mut Stmt) {
if !self.options.bools {
return;
@ -561,229 +170,3 @@ impl Optimizer<'_> {
}
}
}
pub(crate) fn is_ok_to_negate_for_cond(e: &Expr) -> bool {
match e {
Expr::Update(..) => false,
_ => true,
}
}
pub(crate) fn is_ok_to_negate_rhs(rhs: &Expr) -> bool {
match rhs {
Expr::Member(..) => true,
Expr::Bin(BinExpr {
op: op!("===") | op!("!==") | op!("==") | op!("!="),
..
}) => true,
Expr::Call(..) | Expr::New(..) => false,
Expr::Update(..) => false,
Expr::Bin(BinExpr {
op: op!("&&") | op!("||"),
left,
right,
..
}) => is_ok_to_negate_rhs(&left) && is_ok_to_negate_rhs(&right),
Expr::Bin(BinExpr { left, right, .. }) => {
is_ok_to_negate_rhs(&left) && is_ok_to_negate_rhs(&right)
}
Expr::Assign(e) => is_ok_to_negate_rhs(&e.right),
Expr::Unary(UnaryExpr {
op: op!("!") | op!("delete"),
..
}) => true,
Expr::Seq(e) => {
if let Some(last) = e.exprs.last() {
is_ok_to_negate_rhs(&last)
} else {
true
}
}
Expr::Cond(e) => is_ok_to_negate_rhs(&e.cons) && is_ok_to_negate_rhs(&e.alt),
_ => {
if !rhs.may_have_side_effects() {
return true;
}
if cfg!(feature = "debug") {
log::warn!("unimplemented: is_ok_to_negate_rhs: `{}`", dump(&*rhs));
}
false
}
}
}
/// A negative value means that it's efficient to negate the expression.
pub(crate) fn negate_cost(e: &Expr, in_bool_ctx: bool, is_ret_val_ignored: bool) -> Option<isize> {
fn cost(
e: &Expr,
in_bool_ctx: bool,
bin_op: Option<BinaryOp>,
is_ret_val_ignored: bool,
) -> isize {
match e {
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => {
// TODO: Check if this argument is actually start of line.
match &**arg {
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
..
}) => match &**callee {
Expr::Fn(..) => return 0,
_ => {}
},
_ => {}
}
if in_bool_ctx {
let c = -cost(arg, true, None, is_ret_val_ignored);
return c.min(-1);
}
match &**arg {
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => -1,
_ => 1,
}
}
Expr::Bin(BinExpr {
op: op!("===") | op!("!==") | op!("==") | op!("!="),
..
}) => 0,
Expr::Bin(BinExpr {
op: op @ op!("||") | op @ op!("&&"),
left,
right,
..
}) => {
let l_cost = cost(&left, in_bool_ctx, Some(*op), false);
if !is_ret_val_ignored && !is_ok_to_negate_rhs(&right) {
return l_cost + 3;
}
l_cost + cost(&right, in_bool_ctx, Some(*op), is_ret_val_ignored)
}
Expr::Cond(CondExpr { cons, alt, .. })
if is_ok_to_negate_for_cond(&cons) && is_ok_to_negate_for_cond(&alt) =>
{
cost(&cons, in_bool_ctx, bin_op, is_ret_val_ignored)
+ cost(&alt, in_bool_ctx, bin_op, is_ret_val_ignored)
}
Expr::Cond(..)
| Expr::Update(..)
| Expr::Bin(BinExpr {
op: op!("in") | op!("instanceof"),
..
}) => 3,
Expr::Assign(..) => {
if is_ret_val_ignored {
0
} else {
3
}
}
Expr::Seq(e) => {
if let Some(last) = e.exprs.last() {
return cost(&last, in_bool_ctx, bin_op, is_ret_val_ignored);
}
if is_ret_val_ignored {
0
} else {
1
}
}
_ => {
if is_ret_val_ignored {
0
} else {
1
}
}
}
}
let cost = cost(e, in_bool_ctx, None, is_ret_val_ignored);
if cfg!(feature = "debug") {
log::trace!("negation cost of `{}` = {}", dump(&*e), cost);
}
Some(cost)
}
#[cfg(test)]
mod tests {
use super::negate_cost;
use swc_common::{input::SourceFileInput, FileName};
use swc_ecma_parser::{lexer::Lexer, Parser};
fn assert_negate_cost(s: &str, in_bool_ctx: bool, is_ret_val_ignored: bool, expected: isize) {
testing::run_test2(false, |cm, _| {
let fm = cm.new_source_file(FileName::Anon, s.to_string());
let lexer = Lexer::new(
Default::default(),
swc_ecma_ast::EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let e = parser
.parse_expr()
.expect("failed to parse input as an expression");
let actual = negate_cost(&e, in_bool_ctx, is_ret_val_ignored).unwrap();
assert_eq!(
actual, expected,
"Expected negation cost of {} to be {}, but got {}",
s, expected, actual,
);
Ok(())
})
.unwrap();
}
#[test]
fn logical_1() {
assert_negate_cost(
"this[key] && !this.hasOwnProperty(key) || (this[key] = value)",
false,
true,
2,
);
}
#[test]
#[ignore]
fn logical_2() {
assert_negate_cost(
"(!this[key] || this.hasOwnProperty(key)) && (this[key] = value)",
false,
true,
-2,
);
}
}

View File

@ -1,58 +1,14 @@
use super::Optimizer;
use fxhash::FxHashMap;
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::prepend;
use swc_ecma_utils::Id;
use swc_ecma_utils::StmtLike;
use swc_ecma_visit::noop_visit_mut_type;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::VisitMut;
use swc_ecma_visit::VisitMutWith;
use swc_ecma_visit::VisitWith;
/// Methods related to the option `collapse_vars`.
impl Optimizer<'_> {
pub(super) fn collapse_seq_exprs(&mut self, e: &mut Expr) {
if !self.options.collapse_vars {
return;
}
let seq = match e {
Expr::Seq(seq) => seq,
_ => return,
};
if seq.exprs.len() < 2 {
return;
}
match (
&*seq.exprs[seq.exprs.len() - 2],
&*seq.exprs[seq.exprs.len() - 1],
) {
(Expr::Assign(assign), Expr::Ident(ident)) => {
// Check if lhs is same as `ident`.
match &assign.left {
PatOrExpr::Expr(_) => {}
PatOrExpr::Pat(left) => match &**left {
Pat::Ident(left) => {
if left.id.sym == ident.sym && left.id.span.ctxt == ident.span.ctxt {
seq.exprs.pop();
}
}
_ => {}
},
}
}
_ => {}
}
}
pub(super) fn collapse_assignment_to_vars(&mut self, e: &mut Expr) {
if !self.options.collapse_vars {
return;
@ -120,286 +76,6 @@ impl Optimizer<'_> {
_ => {}
}
}
/// Collapse single-use non-constant variables, side effects permitting.
///
/// This merges all variables to first variable declartion with an
/// initializer. If such variable declaration is not found, variables are
/// prepended to `stmts`.
pub(super) fn collapse_vars_without_init<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,
Vec<T>:
VisitWith<VarWithOutInitCounter> + VisitMutWith<VarMover> + VisitMutWith<VarPrepender>,
{
if !self.options.collapse_vars {
return;
}
{
// let mut found_other = false;
// let mut need_work = false;
// for stmt in &*stmts {
// match stmt.as_stmt() {
// Some(Stmt::Decl(Decl::Var(
// v
// @
// VarDecl {
// kind: VarDeclKind::Var,
// ..
// },
// ))) => {
// if v.decls.iter().any(|v| v.init.is_none()) {
// if found_other {
// need_work = true;
// }
// } else {
// found_other = true;
// }
// }
// _ => {
// found_other = true;
// }
// }
// }
// Check for nested variable declartions.
let mut v = VarWithOutInitCounter::default();
stmts.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
if !v.need_work {
return;
}
}
self.changed = true;
log::debug!("collapse_vars: Collapsing variables without an initializer");
let vars = {
let mut v = VarMover {
vars: Default::default(),
var_decl_kind: Default::default(),
};
stmts.visit_mut_with(&mut v);
v.vars
};
// Prepend vars
let mut prepender = VarPrepender { vars };
stmts.visit_mut_with(&mut prepender);
if !prepender.vars.is_empty() {
prepend(
stmts,
T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: Default::default(),
decls: prepender.vars,
}))),
);
}
}
}
/// See if there's two [VarDecl] which has [VarDeclarator] without the
/// initializer.
#[derive(Default)]
pub(super) struct VarWithOutInitCounter {
need_work: bool,
found_var_without_init: bool,
found_var_with_init: bool,
}
impl Visit for VarWithOutInitCounter {
noop_visit_type!();
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &dyn Node) {}
fn visit_constructor(&mut self, _: &Constructor, _: &dyn Node) {}
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
fn visit_var_decl(&mut self, v: &VarDecl, _: &dyn Node) {
v.visit_children_with(self);
if v.kind != VarDeclKind::Var {
return;
}
let mut found_init = false;
for d in &v.decls {
if d.init.is_some() {
found_init = true;
} else {
if found_init {
self.need_work = true;
return;
}
}
}
if v.decls.iter().all(|v| v.init.is_none()) {
if self.found_var_without_init || self.found_var_with_init {
self.need_work = true;
}
self.found_var_without_init = true;
} else {
self.found_var_with_init = true;
}
}
fn visit_var_decl_or_pat(&mut self, _: &VarDeclOrPat, _: &dyn Node) {}
}
/// Moves all varaible without initializer.
pub(super) struct VarMover {
vars: Vec<VarDeclarator>,
var_decl_kind: Option<VarDeclKind>,
}
impl VisitMut for VarMover {
noop_visit_mut_type!();
/// Noop
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
/// Noop
fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
/// Noop
fn visit_mut_function(&mut self, _: &mut Function) {}
fn visit_mut_module_item(&mut self, s: &mut ModuleItem) {
s.visit_mut_children_with(self);
match s {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Var(d),
..
})) if d.decls.is_empty() => {
s.take();
return;
}
_ => {}
}
}
fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
n.visit_mut_children_with(self);
match n {
Some(VarDeclOrExpr::VarDecl(var)) => {
if var.decls.is_empty() {
*n = None;
}
}
_ => {}
}
}
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
s.visit_mut_children_with(self);
match s {
Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
s.take();
return;
}
_ => {}
}
}
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
let old = self.var_decl_kind.take();
self.var_decl_kind = Some(v.kind);
v.visit_mut_children_with(self);
self.var_decl_kind = old;
}
fn visit_mut_var_decl_or_pat(&mut self, _: &mut VarDeclOrPat) {}
fn visit_mut_var_declarators(&mut self, d: &mut Vec<VarDeclarator>) {
d.visit_mut_children_with(self);
if self.var_decl_kind.unwrap() != VarDeclKind::Var {
return;
}
if d.iter().all(|v| v.init.is_some()) {
return;
}
let has_init = d.iter().any(|v| v.init.is_some());
if has_init {
let mut new = vec![];
for v in d.take() {
if v.init.is_some() {
new.push(v)
} else {
self.vars.push(v)
}
}
*d = new;
}
let mut new = vec![];
if has_init {
new.extend(self.vars.drain(..));
}
for v in d.take() {
if v.init.is_some() {
new.push(v)
} else {
self.vars.push(v)
}
}
*d = new;
}
}
pub(super) struct VarPrepender {
vars: Vec<VarDeclarator>,
}
impl VisitMut for VarPrepender {
noop_visit_mut_type!();
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
if self.vars.is_empty() {
return;
}
if v.kind != VarDeclKind::Var {
return;
}
if v.decls.iter().any(|d| d.init.is_some()) {
let mut decls = self.vars.take();
decls.extend(v.decls.take());
v.decls = decls;
}
}
/// Noop
fn visit_mut_function(&mut self, _: &mut Function) {}
/// Noop
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
/// Noop
fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
}
struct Inliner<'a> {

View File

@ -1,41 +0,0 @@
use super::Optimizer;
use swc_common::SyntaxContext;
use swc_ecma_ast::*;
/// Methods related to the option `computed_props`.
impl Optimizer<'_> {
/// If a key of is `'str'` (like `{ 'str': 1 }`) change it to [Ident] like
/// (`{ str: 1, }`)
pub(super) fn optimize_computed_prop_name_as_normal(&mut self, p: &mut PropName) {
if !self.options.computed_props {
return;
}
match p {
PropName::Computed(c) => match &mut *c.expr {
Expr::Lit(Lit::Str(s)) => {
if s.value == *"constructor" || s.value == *"__proto__" {
return;
}
if s.value.is_empty() || s.value.starts_with(|c: char| c.is_numeric()) {
*p = PropName::Str(s.clone());
} else {
*p = PropName::Ident(Ident::new(
s.value.clone(),
s.span.with_ctxt(SyntaxContext::empty()),
));
}
return;
}
Expr::Lit(Lit::Num(n)) => {
*p = PropName::Num(n.clone());
return;
}
_ => {}
},
_ => {}
}
}
}

View File

@ -1,8 +1,6 @@
use super::Optimizer;
use crate::compress::optimize::bools::negate_cost;
use crate::compress::optimize::Ctx;
use crate::debug::dump;
use crate::util::make_bool;
use crate::compress::util::negate_cost;
use crate::util::SpanExt;
use crate::DISABLE_BUGGY_PASSES;
use std::mem::swap;
@ -17,39 +15,10 @@ use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::ExprExt;
use swc_ecma_utils::ExprFactory;
use swc_ecma_utils::StmtLike;
use swc_ecma_utils::Type;
use swc_ecma_utils::Value::Known;
/// Methods related to the option `conditionals`. All methods are noop if
/// `conditionals` is false.
impl Optimizer<'_> {
pub(super) fn negate_cond_expr(&mut self, cond: &mut CondExpr) {
if negate_cost(&cond.test, true, false).unwrap_or(isize::MAX) >= 0 {
return;
}
self.changed = true;
log::debug!("conditionals: `a ? foo : bar` => `!a ? bar : foo` (considered cost)");
let start_str = dump(&*cond);
{
let ctx = Ctx {
in_bool_ctx: true,
..self.ctx
};
self.with_ctx(ctx).negate(&mut cond.test);
}
swap(&mut cond.cons, &mut cond.alt);
if cfg!(feature = "debug") {
log::trace!(
"[Change] Negated cond: `{}` => `{}`",
start_str,
dump(&*cond)
)
}
}
/// Negates the condition of a `if` statement to reduce body size.
pub(super) fn negate_if_stmt(&mut self, stmt: &mut IfStmt) {
let alt = match stmt.alt.as_deref_mut() {
@ -86,7 +55,6 @@ impl Optimizer<'_> {
_ => return,
}
}
/// This method may change return value.
///
/// - `a ? b : false` => `a && b`
@ -121,161 +89,6 @@ impl Optimizer<'_> {
}
}
/// Removes useless operands of an logical expressions.
pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
if !self.options.conditionals {
return;
}
let bin = match e {
Expr::Bin(b) => b,
_ => return,
};
if bin.op != op!("||") && bin.op != op!("&&") {
return;
}
if bin.left.may_have_side_effects() {
return;
}
let lt = bin.left.get_type();
let rt = bin.right.get_type();
let _lb = bin.left.as_pure_bool();
let rb = bin.right.as_pure_bool();
if let (Known(Type::Bool), Known(Type::Bool)) = (lt, rt) {
// `!!b || true` => true
if let Known(true) = rb {
self.changed = true;
log::debug!("conditionals: `!!foo || true` => `true`");
*e = make_bool(bin.span, true);
return;
}
}
}
pub(super) fn compress_cond_with_logical_as_logical(&mut self, e: &mut Expr) {
if !self.options.conditionals {
return;
}
let cond = match e {
Expr::Cond(v) => v,
_ => return,
};
let cons_span = cond.cons.span();
match (&mut *cond.cons, &mut *cond.alt) {
(Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
if (*cons.right).eq_ignore_span(&*alt) =>
{
log::debug!("conditionals: `x ? y || z : z` => `x || y && z`");
self.changed = true;
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("||"),
left: Box::new(Expr::Bin(BinExpr {
span: cons_span,
op: op!("&&"),
left: cond.test.take(),
right: cons.left.take(),
})),
right: cons.right.take(),
});
return;
}
_ => {}
}
}
///
/// - `foo ? bar : false` => `!!foo && bar`
/// - `!foo ? true : bar` => `!foo || bar`
/// - `foo ? false : bar` => `!foo && bar`
pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
let cond = match e {
Expr::Cond(cond) => cond,
_ => return,
};
let lt = cond.cons.get_type();
if let Known(Type::Bool) = lt {
let lb = cond.cons.as_pure_bool();
if let Known(true) = lb {
log::debug!("conditionals: `foo ? true : bar` => `!!foo || bar`");
// Negate twice to convert `test` to boolean.
self.negate_twice(&mut cond.test);
self.changed = true;
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("||"),
left: cond.test.take(),
right: cond.alt.take(),
});
return;
}
// TODO: Verify this rule.
if let Known(false) = lb {
log::debug!("conditionals: `foo ? false : bar` => `!foo && bar`");
self.changed = true;
self.negate(&mut cond.test);
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("&&"),
left: cond.test.take(),
right: cond.alt.take(),
});
return;
}
}
let rt = cond.alt.get_type();
if let Known(Type::Bool) = rt {
let rb = cond.alt.as_pure_bool();
if let Known(false) = rb {
log::debug!("conditionals: `foo ? bar : false` => `!!foo && bar`");
self.changed = true;
// Negate twice to convert `test` to boolean.
self.negate_twice(&mut cond.test);
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("&&"),
left: cond.test.take(),
right: cond.cons.take(),
});
return;
}
if let Known(true) = rb {
log::debug!("conditionals: `foo ? bar : true` => `!foo || bar");
self.changed = true;
// Negate twice to convert `test` to boolean.
self.negate(&mut cond.test);
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("||"),
left: cond.test.take(),
right: cond.cons.take(),
});
return;
}
}
}
///
/// # Example
///

View File

@ -1,7 +1,6 @@
use super::Optimizer;
use crate::compress::optimize::is_pure_undefined_or_null;
use crate::compress::util::eval_as_number;
use crate::DISABLE_BUGGY_PASSES;
use std::f64;
use std::num::FpCategory;
use swc_atoms::js_word;
use swc_common::Spanned;
@ -23,15 +22,8 @@ impl Optimizer<'_> {
self.eval_global_vars(e);
self.eval_numbers(e);
self.eval_number_method_call(e);
self.eval_known_static_method_call(e);
self.eval_str_method_call(e);
self.eval_array_method_call(e);
self.eval_fn_method_call(e);
self.eval_opt_chain(e);
}
fn eval_global_vars(&mut self, e: &mut Expr) {
@ -314,122 +306,7 @@ impl Optimizer<'_> {
}
}
/// Handle calls on string literals, like `'foo'.toUpperCase()`.
fn eval_str_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.in_lhs_of_assign {
return;
}
let call = match e {
Expr::Call(v) => v,
_ => return,
};
let (s, method) = match &call.callee {
ExprOrSuper::Super(_) => return,
ExprOrSuper::Expr(callee) => match &**callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => match (&**obj, &**prop) {
(Expr::Lit(Lit::Str(s)), Expr::Ident(prop)) => (s.clone(), prop.sym.clone()),
_ => return,
},
_ => return,
},
};
let new_val = match &*method {
"toLowerCase" => s.value.to_lowercase(),
"toUpperCase" => s.value.to_uppercase(),
"charCodeAt" => {
if call.args.len() != 1 {
return;
}
if let Expr::Lit(Lit::Num(Number { value, .. })) = &*call.args[0].expr {
if value.fract() != 0.0 {
return;
}
let idx = value.round() as i64 as usize;
let c = s.value.chars().nth(idx);
match c {
Some(v) => {
self.changed = true;
log::debug!(
"evaluate: Evaluated `charCodeAt` of a string literal as `{}`",
v
);
*e = Expr::Lit(Lit::Num(Number {
span: call.span,
value: v as usize as f64,
}))
}
None => {
self.changed = true;
log::debug!(
"evaluate: Evaluated `charCodeAt` of a string literal as `NaN`",
);
*e = Expr::Ident(Ident::new(
js_word!("NaN"),
e.span().with_ctxt(SyntaxContext::empty()),
))
}
}
}
return;
}
_ => return,
};
self.changed = true;
log::debug!("evaluate: Evaluated `{}` of a string literal", method);
*e = Expr::Lit(Lit::Str(Str {
value: new_val.into(),
..s
}));
}
fn eval_numbers(&mut self, e: &mut Expr) {
if self.options.unsafe_passes && self.options.unsafe_math {
match e {
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
args,
..
}) => {
if args.len() == 1 && args[0].spread.is_none() {
match &**callee {
Expr::Ident(Ident {
sym: js_word!("Number"),
..
}) => {
self.changed = true;
log::debug!(
"evaluate: Reducing a call to `Number` into an unary operation"
);
*e = Expr::Unary(UnaryExpr {
span: *span,
op: op!(unary, "+"),
arg: args.take().into_iter().next().unwrap().expr,
});
}
_ => {}
}
}
}
_ => {}
}
}
if !self.options.evaluate {
return;
}
@ -440,7 +317,7 @@ impl Optimizer<'_> {
match e {
Expr::Call(..) => {
if let Some(value) = self.eval_as_number(&e) {
if let Some(value) = eval_as_number(&e) {
self.changed = true;
log::debug!("evaluate: Evaluated an expression as `{}`", value);
@ -550,406 +427,6 @@ impl Optimizer<'_> {
}
}
/// Evaluates method calls of a numeric constant.
fn eval_number_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
let (num, method, args) = match e {
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
args,
..
}) => match &mut **callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => match &mut **obj {
Expr::Lit(Lit::Num(obj)) => match &mut **prop {
Expr::Ident(prop) => (obj, prop, args),
_ => return,
},
_ => return,
},
_ => return,
},
_ => return,
};
if args.iter().any(|arg| arg.expr.may_have_side_effects()) {
return;
}
match &*method.sym {
"toFixed" => {
if let Some(precision) = self.eval_as_number(&args[0].expr) {
let precision = precision.floor() as usize;
let value = num_to_fixed(num.value, precision + 1);
self.changed = true;
log::debug!(
"evaluate: Evaluating `{}.toFixed({})` as `{}`",
num,
precision,
value
);
*e = Expr::Lit(Lit::Str(Str {
span: e.span(),
value: value.into(),
has_escape: false,
kind: Default::default(),
}))
}
}
_ => {}
}
}
/// This method does **not** modifies `e`.
///
/// This method is used to test if a whole call can be replaced, while
/// preserving standalone constants.
fn eval_as_number(&mut self, e: &Expr) -> Option<f64> {
match e {
Expr::Bin(BinExpr {
op: op!(bin, "-"),
left,
right,
..
}) => {
let l = self.eval_as_number(&left)?;
let r = self.eval_as_number(&right)?;
return Some(l - r);
}
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
args,
..
}) => {
for arg in &*args {
if arg.spread.is_some() || arg.expr.may_have_side_effects() {
return None;
}
}
match &**callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
let prop = match &**prop {
Expr::Ident(i) => i,
_ => return None,
};
match &**obj {
Expr::Ident(obj) if &*obj.sym == "Math" => match &*prop.sym {
"cos" => {
let v = self.eval_as_number(&args.first()?.expr)?;
return Some(v.cos());
}
"sin" => {
let v = self.eval_as_number(&args.first()?.expr)?;
return Some(v.sin());
}
"max" => {
let mut numbers = vec![];
for arg in args {
let v = self.eval_as_number(&arg.expr)?;
if v.is_infinite() || v.is_nan() {
return None;
}
numbers.push(v);
}
return Some(
numbers
.into_iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f64::NEG_INFINITY),
);
}
"min" => {
let mut numbers = vec![];
for arg in args {
let v = self.eval_as_number(&arg.expr)?;
if v.is_infinite() || v.is_nan() {
return None;
}
numbers.push(v);
}
return Some(
numbers
.into_iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f64::INFINITY),
);
}
"pow" => {
if args.len() != 2 {
return None;
}
let first = self.eval_as_number(&args[0].expr)?;
let second = self.eval_as_number(&args[1].expr)?;
return Some(first.powf(second));
}
_ => {}
},
_ => {}
}
}
_ => {}
}
}
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
let prop = match &**prop {
Expr::Ident(i) => i,
_ => return None,
};
match &**obj {
Expr::Ident(obj) if &*obj.sym == "Math" => match &*prop.sym {
"PI" => return Some(f64::consts::PI),
"E" => return Some(f64::consts::E),
"LN10" => return Some(f64::consts::LN_10),
_ => {}
},
_ => {}
}
}
_ => {
if let Known(v) = e.as_number() {
return Some(v);
}
}
}
None
}
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.in_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::debug!("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::debug!("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::debug!(
"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;
}
}
}
}
}
_ => {}
}
}
_ => {}
}
}
fn eval_fn_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.in_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 {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
if obj.may_have_side_effects() {
return;
}
let _f = match &mut **obj {
Expr::Fn(v) => v,
_ => return,
};
let method_name = match &**prop {
Expr::Ident(i) => i,
_ => return,
};
match &*method_name.sym {
"valueOf" => {
if has_spread {
return;
}
self.changed = true;
log::debug!(
"evaludate: Reduced `funtion.valueOf()` into a function expression"
);
*e = *obj.take();
return;
}
_ => {}
}
}
_ => {}
}
}
///
/// - `Object(1) && 1 && 2` => `Object(1) && 2`.
pub(super) fn optimize_bin_and_or(&mut self, e: &mut BinExpr) {
@ -995,79 +472,4 @@ impl Optimizer<'_> {
_ => return,
}
}
fn eval_opt_chain(&mut self, e: &mut Expr) {
let opt = match e {
Expr::OptChain(e) => e,
_ => return,
};
match &mut *opt.expr {
Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(obj),
..
}) => {
//
if is_pure_undefined_or_null(&obj) {
self.changed = true;
log::debug!(
"evaluate: Reduced an optioanl chaining operation because object is \
always null or undefined"
);
*e = *undefined(*span);
return;
}
}
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
..
}) => {
if is_pure_undefined_or_null(&callee) {
self.changed = true;
log::debug!(
"evaluate: Reduced a call expression with optioanl chaining operation \
because object is always null or undefined"
);
*e = *undefined(*span);
return;
}
}
_ => {}
}
}
}
/// https://stackoverflow.com/questions/60497397/how-do-you-format-a-float-to-the-first-significant-decimal-and-with-specified-pr
fn num_to_fixed(float: f64, precision: usize) -> String {
// compute absolute value
let a = float.abs();
// if abs value is greater than 1, then precision becomes less than "standard"
let precision = if a >= 1. {
// reduce by number of digits, minimum 0
let n = (1. + a.log10().floor()) as usize;
if n <= precision {
precision - n
} else {
0
}
// if precision is less than 1 (but non-zero), then precision becomes
// greater than "standard"
} else if a > 0. {
// increase number of digits
let n = -(1. + a.log10().floor()) as usize;
precision + n
// special case for 0
} else {
0
};
// format with the given computed precision
format!("{0:.1$}", float, precision)
}

View File

@ -1,6 +1,6 @@
use super::Optimizer;
use crate::{
compress::optimize::util::is_directive,
compress::util::is_directive,
util::{sort::is_sorted_by, MoudleItemExt},
DISABLE_BUGGY_PASSES,
};

View File

@ -1,5 +1,5 @@
use super::Optimizer;
use crate::compress::optimize::is_pure_undefined;
use crate::compress::util::is_pure_undefined;
use crate::debug::dump;
use crate::util::ExprOptExt;
use swc_common::Spanned;
@ -17,19 +17,6 @@ use swc_ecma_visit::VisitWith;
/// Methods related to the option `if_return`. All methods are noop if
/// `if_return` is false.
impl Optimizer<'_> {
pub(super) fn drop_undefined_from_return_arg(&mut self, s: &mut ReturnStmt) {
match s.arg.as_deref() {
Some(e) => {
if is_pure_undefined(e) {
self.changed = true;
log::debug!("Dropped `undefined` from `return undefined`");
s.arg.take();
}
}
None => {}
}
}
/// # Input
///
/// ```js

View File

@ -1,4 +1,4 @@
use crate::compress::optimize::util::is_directive;
use crate::compress::util::is_directive;
use super::Optimizer;
use swc_ecma_ast::*;

View File

@ -1,6 +1,5 @@
use crate::compress::optimize::unused::UnreachableHandler;
use crate::compress::optimize::Optimizer;
use swc_common::Spanned;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
@ -9,45 +8,6 @@ use swc_ecma_utils::Value::Known;
/// Methods related to the option `loops`.
impl Optimizer<'_> {
///
/// - `while(test);` => `for(;;test);
/// - `do; while(true)` => `for(;;);
pub(super) fn loop_to_for_stmt(&mut self, s: &mut Stmt) {
if !self.options.loops {
return;
}
match s {
Stmt::While(stmt) => {
self.changed = true;
log::debug!("loops: Converting a while loop to a for loop");
*s = Stmt::For(ForStmt {
span: stmt.span,
init: None,
test: Some(stmt.test.take()),
update: None,
body: stmt.body.take(),
});
}
Stmt::DoWhile(stmt) => {
let val = stmt.test.as_pure_bool();
if let Known(true) = val {
self.changed = true;
log::debug!("loops: Converting an always-true do-while loop to a for loop");
*s = Stmt::For(ForStmt {
span: stmt.span,
init: None,
test: Some(stmt.test.take()),
update: None,
body: stmt.body.take(),
});
}
}
_ => {}
}
}
/// `for(a;b;c;) break;` => `a;b;`
pub(super) fn optimize_loops_with_break(&mut self, s: &mut Stmt) {
if !self.options.loops {
@ -98,70 +58,6 @@ impl Optimizer<'_> {
*s = Stmt::Empty(EmptyStmt { span: DUMMY_SP })
}
/// # Input
///
/// ```js
/// for(; size--;)
/// if (!(result = eq(a[size], b[size], aStack, bStack)))
/// break;
/// ```
///
///
/// # Output
///
/// ```js
/// for (; size-- && (result = eq(a[size], b[size], aStack, bStack)););
/// ```
pub(super) fn merge_for_if_break(&mut self, s: &mut ForStmt) {
match &mut *s.body {
Stmt::If(IfStmt {
test,
cons,
alt: None,
..
}) => {
match &**cons {
Stmt::Break(BreakStmt { label: None, .. }) => {
// We only care about instant breaks.
//
// Note: As the minifier of swc is very fast, we don't
// care about block statements with a single break as a
// body.
//
// If it's optimizable, other pass for if statements
// will remove block and with the next pass we can apply
// this pass.
self.changed = true;
log::debug!("loops: Compressing for-if-break into a for statement");
// We negate because this `test` is used as a condition for `break`.
self.negate(test);
match s.test.take() {
Some(left) => {
s.test = Some(Box::new(Expr::Bin(BinExpr {
span: s.test.span(),
op: op!("&&"),
left,
right: test.take(),
})));
}
None => {
s.test = Some(test.take());
}
}
// Remove body
s.body.take();
}
_ => {}
}
}
_ => {}
}
}
///
/// - `while(false) { var a; foo() }` => `var a;`
pub(super) fn optiimze_loops_if_cond_is_false(&mut self, stmt: &mut Stmt) {

View File

@ -1,34 +0,0 @@
use super::{util::is_valid_identifier, Optimizer};
use swc_ecma_ast::*;
impl Optimizer<'_> {
pub(super) fn optimize_prop_name(&mut self, name: &mut PropName) {
match name {
PropName::Str(s) => {
if s.value.is_reserved() || s.value.is_reserved_in_es3() {
return;
}
if is_valid_identifier(&s.value, false) {
self.changed = true;
log::debug!("misc: Optimizing string property name");
*name = PropName::Ident(Ident {
span: s.span,
sym: s.value.clone(),
optional: false,
});
return;
}
}
_ => {}
}
}
pub(super) fn remove_useless_return(&mut self, stmts: &mut Vec<Stmt>) {
if let Some(Stmt::Return(ReturnStmt { arg: None, .. })) = stmts.last() {
self.changed = true;
log::debug!("misc: Removing useless return");
stmts.pop();
}
}
}

View File

@ -1,5 +1,6 @@
use crate::analyzer::ProgramData;
use crate::analyzer::UsageAnalyzer;
use crate::compress::util::is_pure_undefined;
use crate::marks::Marks;
use crate::option::CompressOptions;
use crate::util::contains_leaping_yield;
@ -12,9 +13,7 @@ use swc_atoms::js_word;
use swc_atoms::JsWord;
use swc_common::iter::IdentifyLast;
use swc_common::pass::Repeated;
use swc_common::sync::Lrc;
use swc_common::Mark;
use swc_common::SourceMap;
use swc_common::Spanned;
use swc_common::SyntaxContext;
use swc_common::DUMMY_SP;
@ -39,10 +38,8 @@ use Value::Known;
use self::util::replace_id_with_expr;
mod arguments;
mod arrows;
mod bools;
mod collapse_vars;
mod computed_props;
mod conditionals;
mod dead_code;
mod evaluate;
@ -53,14 +50,10 @@ mod iife;
mod inline;
mod join_vars;
mod loops;
mod misc;
mod numbers;
mod ops;
mod properties;
mod sequences;
mod strings;
mod switches;
mod unsafes;
mod unused;
mod util;
@ -72,7 +65,6 @@ pub(super) struct OptimizerState {
/// This pass is simillar to `node.optimize` of terser.
pub(super) fn optimizer<'a>(
cm: Lrc<SourceMap>,
marks: Marks,
options: &'a CompressOptions,
data: &'a ProgramData,
@ -86,7 +78,6 @@ pub(super) fn optimizer<'a>(
let done = Mark::fresh(Mark::root());
let done_ctxt = SyntaxContext::empty().apply_mark(done);
Optimizer {
cm,
marks,
changed: false,
options,
@ -111,6 +102,9 @@ pub(super) fn optimizer<'a>(
/// This should not be modified directly. Use `.with_ctx()` instead.
#[derive(Debug, Default, Clone, Copy)]
struct Ctx {
/// See [crate::marks::Marks]
skip_standalone: bool,
/// `true` if the [VarDecl] has const annotation.
has_const_ann: bool,
@ -198,8 +192,6 @@ impl Ctx {
}
struct Optimizer<'a> {
cm: Lrc<SourceMap>,
marks: Marks,
changed: bool,
@ -250,11 +242,7 @@ impl Optimizer<'_> {
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike + ModuleItemLike + MoudleItemExt + VisitMutWith<Self>,
Vec<T>: VisitMutWith<Self>
+ VisitWith<UsageAnalyzer>
+ VisitWith<self::collapse_vars::VarWithOutInitCounter>
+ VisitMutWith<self::collapse_vars::VarPrepender>
+ VisitMutWith<self::collapse_vars::VarMover>,
Vec<T>: VisitMutWith<Self> + VisitWith<UsageAnalyzer>,
{
match self.data {
Some(..) => {}
@ -314,8 +302,6 @@ impl Optimizer<'_> {
self.ctx.in_asm |= use_asm;
self.drop_useless_blocks(stmts);
self.reorder_stmts(stmts);
self.merge_simillar_ifs(stmts);
@ -323,8 +309,6 @@ impl Optimizer<'_> {
self.make_sequences(stmts);
self.collapse_vars_without_init(stmts);
self.drop_else_token(stmts);
self.break_assignments_in_seqs(stmts);
@ -1166,7 +1150,7 @@ impl Optimizer<'_> {
if s.value.contains(|c: char| !c.is_ascii()) {
return true;
}
if s.value.contains("\\\0") {
if s.value.contains("\\\0") || s.value.contains("//") {
return true;
}
@ -1514,14 +1498,10 @@ impl VisitMut for Optimizer<'_> {
self.compress_typeof_undefined(n);
self.compress_comparsion_of_typeof(n);
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);
@ -1531,10 +1511,6 @@ impl VisitMut for Optimizer<'_> {
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) {
@ -1548,12 +1524,6 @@ impl VisitMut for Optimizer<'_> {
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
fn visit_mut_block_stmt_or_expr(&mut self, body: &mut BlockStmtOrExpr) {
body.visit_mut_children_with(self);
self.optimize_arrow_body(body);
}
fn visit_mut_call_expr(&mut self, e: &mut CallExpr) {
let inline_prevented = self.ctx.inline_prevented || self.has_noinline(e.span);
@ -1607,8 +1577,6 @@ impl VisitMut for Optimizer<'_> {
e.args.visit_mut_with(&mut *self.with_ctx(ctx));
}
self.optimize_symbol_call_unsafely(e);
self.inline_args_of_iife(e);
}
@ -1640,14 +1608,6 @@ impl VisitMut for Optimizer<'_> {
e.visit_mut_children_with(self);
}
fn visit_mut_cond_expr(&mut self, n: &mut CondExpr) {
n.visit_mut_children_with(self);
self.negate_cond_expr(n);
self.optimize_expr_in_bool_ctx(&mut n.test);
}
fn visit_mut_decl(&mut self, decl: &mut Decl) {
decl.visit_mut_children_with(self);
@ -1706,20 +1666,10 @@ impl VisitMut for Optimizer<'_> {
self.remove_invalid(e);
self.concat_str(e);
self.lift_minus(e);
self.convert_tpl_to_str(e);
self.optimize_str_access_to_arguments(e);
self.replace_props(e);
self.swap_bin_operands(e);
self.collapse_seq_exprs(e);
self.drop_unused_assignments(e);
self.compress_regexp(e);
@ -1728,20 +1678,12 @@ impl VisitMut for Optimizer<'_> {
self.compress_typeofs(e);
self.compress_useless_deletes(e);
self.optimize_nullish_coalescing(e);
self.compress_logical_exprs_as_bang_bang(e, false);
self.compress_useless_cond_expr(e);
self.compress_conds_as_logical(e);
self.drop_logical_operands(e);
self.drop_useless_addition_of_str(e);
self.inline(e);
match e {
@ -1757,22 +1699,10 @@ impl VisitMut for Optimizer<'_> {
}
self.compress_cond_expr_if_simillar(e);
self.compress_cond_with_logical_as_logical(e);
self.compress_negated_bin_eq(e);
self.handle_negated_seq(e);
self.compress_array_join(e);
self.remove_useless_pipes(e);
self.optimize_bools(e);
self.handle_property_access(e);
self.lift_seqs_of_bin(e);
self.lift_seqs_of_cond_assign(e);
if self.options.negate_iife {
self.negate_iife_in_cond(e);
}
@ -1926,16 +1856,10 @@ impl VisitMut for Optimizer<'_> {
s.visit_mut_children_with(&mut *self.with_ctx(ctx));
self.with_ctx(ctx).merge_for_if_break(s);
self.with_ctx(ctx).optimize_init_of_for_stmt(s);
self.with_ctx(ctx).drop_if_break(s);
if let Some(test) = &mut s.test {
self.with_ctx(ctx).optimize_expr_in_bool_ctx(test);
}
match &mut *s.body {
Stmt::Block(body) => {
self.negate_if_terminate(&mut body.stmts, false, true);
@ -1953,10 +1877,19 @@ impl VisitMut for Optimizer<'_> {
n.decorators.visit_mut_with(&mut *self.with_ctx(ctx));
}
let is_standalone = n.span.has_mark(self.marks.standalone);
// We don't dig into standalone function, as it does not share any variable with
// outer scope.
if self.ctx.skip_standalone && is_standalone {
return;
}
let old_in_asm = self.ctx.in_asm;
{
let ctx = Ctx {
skip_standalone: self.ctx.skip_standalone || is_standalone,
stmt_lablled: false,
in_fn_like: true,
scope: n.span.ctxt,
@ -1970,13 +1903,8 @@ impl VisitMut for Optimizer<'_> {
Some(body) => {
// Bypass block scope handler.
body.visit_mut_children_with(optimizer);
optimizer.remove_useless_return(&mut body.stmts);
optimizer.negate_if_terminate(&mut body.stmts, true, false);
if let Some(last) = body.stmts.last_mut() {
optimizer.drop_unused_stmt_at_end_of_fn(last);
}
}
None => {}
}
@ -2011,8 +1939,6 @@ impl VisitMut for Optimizer<'_> {
self.negate_if_stmt(n);
self.optimize_expr_in_bool_ctx(&mut n.test);
self.merge_nested_if(n);
self.merge_else_if(n);
@ -2045,15 +1971,12 @@ impl VisitMut for Optimizer<'_> {
};
n.prop.visit_mut_with(&mut *self.with_ctx(ctx));
}
self.optimize_property_of_member_expr(n);
self.handle_known_computed_member_expr(n);
}
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
let ctx = Ctx {
top_level: true,
skip_standalone: true,
..self.ctx
};
self.with_ctx(ctx).handle_stmt_likes(stmts);
@ -2133,24 +2056,9 @@ impl VisitMut for Optimizer<'_> {
n.retain(|p| !p.pat.is_invalid());
}
fn visit_mut_prop(&mut self, p: &mut Prop) {
p.visit_mut_children_with(self);
self.optimize_arrow_method_prop(p);
}
fn visit_mut_prop_name(&mut self, p: &mut PropName) {
p.visit_mut_children_with(self);
self.optimize_computed_prop_name_as_normal(p);
self.optimize_prop_name(p);
}
fn visit_mut_return_stmt(&mut self, n: &mut ReturnStmt) {
n.visit_mut_children_with(self);
self.drop_undefined_from_return_arg(n);
if let Some(arg) = &mut n.arg {
self.optimize_in_fn_termiation(&mut **arg);
}
@ -2170,8 +2078,6 @@ impl VisitMut for Optimizer<'_> {
self.shift_assignment(n);
self.merge_seq_call(n);
{
let exprs = n
.exprs
@ -2260,19 +2166,6 @@ impl VisitMut for Optimizer<'_> {
_ => {}
}
if self.options.drop_debugger {
match s {
Stmt::Debugger(..) => {
self.changed = true;
*s = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
log::debug!("drop_debugger: Dropped a debugger statement");
return;
}
_ => {}
}
}
self.loop_to_for_stmt(s);
self.optiimze_loops_if_cond_is_false(s);
self.optimize_loops_with_break(s);
@ -2384,14 +2277,6 @@ impl VisitMut for Optimizer<'_> {
n.exprs
.iter_mut()
.for_each(|expr| self.optimize_expr_in_str_ctx(&mut **expr));
self.compress_tpl(n);
debug_assert_eq!(
n.exprs.len() + 1,
n.quasis.len(),
"tagged template literal compressor created an invalid template literal"
);
}
fn visit_mut_try_stmt(&mut self, n: &mut TryStmt) {
@ -2414,12 +2299,6 @@ impl VisitMut for Optimizer<'_> {
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
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);
}
}
fn visit_mut_update_expr(&mut self, n: &mut UpdateExpr) {
@ -2519,8 +2398,6 @@ impl VisitMut for Optimizer<'_> {
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
self.optimize_expr_in_bool_ctx(&mut n.test);
}
fn visit_mut_yield_expr(&mut self, n: &mut YieldExpr) {
@ -2538,31 +2415,6 @@ impl VisitMut for Optimizer<'_> {
}
}
fn is_pure_undefined(e: &Expr) -> bool {
match e {
Expr::Ident(Ident {
sym: js_word!("undefined"),
..
}) => true,
Expr::Unary(UnaryExpr {
op: UnaryOp::Void,
arg,
..
}) if !arg.may_have_side_effects() => true,
_ => false,
}
}
fn is_pure_undefined_or_null(e: &Expr) -> bool {
is_pure_undefined(e)
|| match e {
Expr::Lit(Lit::Null(..)) => true,
_ => false,
}
}
/// If true, `0` in `(0, foo.bar)()` is preserved.
fn is_callee_this_aware(callee: &Expr) -> bool {
match &*callee {

View File

@ -1,16 +1,9 @@
use super::Optimizer;
use crate::compress::optimize::bools::is_ok_to_negate_for_cond;
use crate::compress::optimize::bools::is_ok_to_negate_rhs;
use crate::compress::optimize::is_pure_undefined;
use crate::debug::dump;
use crate::compress::util::negate;
use crate::util::make_bool;
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::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ident::IdentLike;
@ -20,124 +13,6 @@ 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::debug!(
"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::debug!(
"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) {
@ -274,168 +149,9 @@ impl Optimizer<'_> {
self.negate(e);
}
/// Creates `!e` where e is the expression passed as an argument.
///
/// # Note
///
/// This method returns `!e` if `!!e` is given as a argument.
///
/// TODO: Handle special cases like !1 or !0
pub(super) fn negate(&mut self, e: &mut Expr) {
let start_str = dump(&*e);
self.changed = true;
match e {
Expr::Bin(bin @ BinExpr { op: op!("=="), .. })
| Expr::Bin(bin @ BinExpr { op: op!("!="), .. })
| Expr::Bin(bin @ BinExpr { op: op!("==="), .. })
| Expr::Bin(bin @ BinExpr { op: op!("!=="), .. }) => {
bin.op = match bin.op {
op!("==") => {
op!("!=")
}
op!("!=") => {
op!("==")
}
op!("===") => {
op!("!==")
}
op!("!==") => {
op!("===")
}
_ => {
unreachable!()
}
};
self.changed = true;
log::debug!("negate: binary");
return;
}
Expr::Bin(BinExpr {
left,
right,
op: op @ op!("&&"),
..
}) if is_ok_to_negate_rhs(&right) => {
log::debug!("negate: a && b => !a || !b");
self.negate(&mut **left);
self.negate(&mut **right);
*op = op!("||");
return;
}
Expr::Bin(BinExpr {
left,
right,
op: op @ op!("||"),
..
}) if is_ok_to_negate_rhs(&right) => {
log::debug!("negate: a || b => !a && !b");
self.negate(&mut **left);
self.negate(&mut **right);
*op = op!("&&");
return;
}
Expr::Cond(CondExpr { cons, alt, .. })
if is_ok_to_negate_for_cond(&cons) && is_ok_to_negate_for_cond(&alt) =>
{
log::debug!("negate: cond");
self.negate(&mut **cons);
self.negate(&mut **alt);
return;
}
Expr::Seq(SeqExpr { exprs, .. }) => {
if let Some(last) = exprs.last_mut() {
log::debug!("negate: seq");
self.negate(&mut **last);
return;
}
}
_ => {}
}
let mut arg = Box::new(e.take());
match &mut *arg {
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => match &mut **arg {
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => {
log::debug!("negate: !!bool => !bool");
*e = *arg.take();
return;
}
Expr::Bin(BinExpr { op: op!("in"), .. })
| Expr::Bin(BinExpr {
op: op!("instanceof"),
..
}) => {
log::debug!("negate: !bool => bool");
*e = *arg.take();
return;
}
_ => {
if self.ctx.in_bool_ctx {
log::debug!("negate: !expr => expr (in bool context)");
*e = *arg.take();
return;
}
}
},
_ => {}
}
log::debug!("negate: e => !e");
*e = Expr::Unary(UnaryExpr {
span: DUMMY_SP,
op: op!("!"),
arg,
});
if cfg!(feature = "debug") {
log::trace!("[Change] Negated `{}` as `{}`", start_str, dump(&*e));
}
}
pub(super) fn handle_negated_seq(&mut self, n: &mut Expr) {
match &mut *n {
Expr::Unary(e @ UnaryExpr { op: op!("!"), .. })
| Expr::Unary(
e @ UnaryExpr {
op: op!("delete"), ..
},
) => {
match &mut *e.arg {
Expr::Seq(SeqExpr { exprs, .. }) => {
if exprs.is_empty() {
return;
}
log::debug!("optimizing negated sequences");
{
let last = exprs.last_mut().unwrap();
self.optimize_expr_in_bool_ctx(last);
// Negate last element.
self.negate(last);
}
*n = *e.arg.take();
}
_ => {}
}
}
_ => {}
}
negate(e, self.ctx.in_bool_ctx)
}
/// This method does
@ -502,123 +218,6 @@ impl Optimizer<'_> {
e.right = left.take();
}
/// Rules:
/// - `l > i` => `i < l`
fn can_swap_bin_operands(&mut self, l: &Expr, r: &Expr, is_for_rel: bool) -> bool {
match (l, r) {
(Expr::Member(_), _) if is_for_rel => false,
(Expr::Update(..) | Expr::Assign(..), Expr::Lit(..)) if is_for_rel => false,
(
Expr::Member(..)
| Expr::Call(..)
| Expr::Assign(..)
| Expr::Update(..)
| Expr::Bin(BinExpr {
op: op!("&&") | op!("||"),
..
}),
Expr::Lit(..),
) => true,
(
Expr::Member(..) | Expr::Call(..) | Expr::Assign(..),
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}),
) if match &**arg {
Expr::Lit(..) => true,
_ => false,
} =>
{
true
}
(Expr::Member(..) | Expr::Call(..) | Expr::Assign(..), r) if is_pure_undefined(r) => {
true
}
(Expr::Ident(..), Expr::Lit(..)) if is_for_rel => false,
(Expr::Ident(..), Expr::Ident(..)) => false,
(Expr::Ident(..), Expr::Lit(..))
| (
Expr::Ident(..) | Expr::Member(..),
Expr::Unary(UnaryExpr {
op: op!("void") | op!("!"),
..
}),
)
| (
Expr::This(..),
Expr::Unary(UnaryExpr {
op: op!("void"), ..
}),
)
| (Expr::Unary(..), Expr::Lit(..))
| (Expr::Tpl(..), Expr::Lit(..)) => true,
_ => false,
}
}
fn try_swap_bin(&mut self, op: BinaryOp, left: &mut Expr, right: &mut Expr) -> bool {
fn is_supported(op: BinaryOp) -> bool {
match op {
op!("===")
| op!("!==")
| op!("==")
| op!("!=")
| op!("&")
| op!("^")
| op!("|")
| op!("*") => true,
_ => false,
}
}
if !is_supported(op) {
return false;
}
if self.can_swap_bin_operands(&left, &right, false) {
log::debug!("Swapping operands of binary exprssion");
swap(left, right);
return true;
}
false
}
/// Swap lhs and rhs in certain conditions.
pub(super) fn swap_bin_operands(&mut self, expr: &mut Expr) {
match expr {
Expr::Bin(e @ BinExpr { op: op!("<="), .. })
| Expr::Bin(e @ BinExpr { op: op!("<"), .. }) => {
if self.options.comparisons && self.can_swap_bin_operands(&e.left, &e.right, true) {
self.changed = true;
log::debug!("comparisons: Swapping operands of {}", e.op);
e.op = if e.op == op!("<=") {
op!(">=")
} else {
op!(">")
};
swap(&mut e.left, &mut e.right);
}
}
Expr::Bin(bin) => {
if self.try_swap_bin(bin.op, &mut bin.left, &mut bin.right) {
self.changed = true;
}
}
_ => {}
}
}
/// Remove meaningless literals in a binary expressions.
///
/// # Parameters

View File

@ -1,18 +1,17 @@
use super::{is_pure_undefined, Optimizer};
use crate::compress::optimize::util::{
get_lhs_ident, get_lhs_ident_mut, is_directive, replace_id_with_expr,
};
use crate::compress::optimize::util::replace_id_with_expr;
use crate::compress::util::{get_lhs_ident, get_lhs_ident_mut, is_directive};
use crate::debug::dump;
use crate::util::{idents_used_by, idents_used_by_ignoring_nested, ExprOptExt};
use retain_mut::RetainMut;
use std::mem::take;
use swc_atoms::js_word;
use swc_common::Spanned;
use swc_common::DUMMY_SP;
use swc_common::{Spanned, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::{contains_this_expr, undefined, ExprExt, ExprFactory, Id, StmtLike};
use swc_ecma_utils::{contains_this_expr, undefined, ExprExt, Id, StmtLike};
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
@ -333,210 +332,6 @@ impl Optimizer<'_> {
*stmts = new_stmts;
}
/// `(a = foo, a.apply())` => `(a = foo).apply()`
///
/// This is useful for outputs of swc/babel
pub(super) fn merge_seq_call(&mut self, e: &mut SeqExpr) {
if !self.options.sequences() {
return;
}
for idx in 0..e.exprs.len() {
let (e1, e2) = e.exprs.split_at_mut(idx);
let a = match e1.last_mut() {
Some(v) => &mut **v,
None => continue,
};
let b = match e2.first_mut() {
Some(v) => &mut **v,
None => continue,
};
match (&mut *a, &mut *b) {
(
Expr::Assign(a_assign),
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(b_callee),
args,
..
}),
) => {
let var_name = get_lhs_ident(&a_assign.left);
let var_name = match var_name {
Some(v) => v,
None => continue,
};
match &mut **b_callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(b_callee_obj),
computed: false,
prop,
..
}) => {
//
if !b_callee_obj.is_ident_ref_to(var_name.sym.clone()) {
continue;
}
match &**prop {
Expr::Ident(Ident { sym, .. }) => match &**sym {
"apply" | "call" => {}
_ => continue,
},
_ => {}
}
let span = a_assign.span.with_ctxt(SyntaxContext::empty());
let obj = a.take();
let new = Expr::Call(CallExpr {
span,
callee: MemberExpr {
span: DUMMY_SP,
obj: obj.as_obj(),
prop: prop.take(),
computed: false,
}
.as_callee(),
args: args.take(),
type_args: Default::default(),
});
b.take();
self.changed = true;
log::debug!(
"sequences: Reducing `(a = foo, a.call())` to `((a = foo).call())`"
);
*a = new;
}
_ => {}
};
}
_ => {}
}
}
}
///
/// - `(a, b, c) && d` => `a, b, c && d`
pub(super) fn lift_seqs_of_bin(&mut self, e: &mut Expr) {
let bin = match e {
Expr::Bin(b) => b,
_ => return,
};
match &mut *bin.left {
Expr::Seq(left) => {
if left.exprs.is_empty() {
return;
}
self.changed = true;
log::debug!("sequences: Lifting sequence in a binary expression");
let left_last = left.exprs.pop().unwrap();
let mut exprs = left.exprs.take();
exprs.push(Box::new(Expr::Bin(BinExpr {
span: left.span,
op: bin.op,
left: left_last,
right: bin.right.take(),
})));
*e = Expr::Seq(SeqExpr {
span: bin.span,
exprs,
})
}
_ => {}
}
}
pub(super) fn normalize_sequences(&self, seq: &mut SeqExpr) {
for e in &mut seq.exprs {
match &mut **e {
Expr::Seq(e) => {
self.normalize_sequences(&mut *e);
}
_ => {}
}
}
if seq.exprs.iter().any(|v| v.is_seq()) {
let mut new = vec![];
for e in seq.exprs.take() {
match *e {
Expr::Seq(s) => {
new.extend(s.exprs);
}
_ => new.push(e),
}
}
seq.exprs = new;
}
}
///
/// - `x = (foo(), bar(), baz()) ? 10 : 20` => `foo(), bar(), x = baz() ? 10
/// : 20;`
pub(super) fn lift_seqs_of_cond_assign(&mut self, e: &mut Expr) {
if !self.options.sequences() {
return;
}
let assign = match e {
Expr::Assign(v) => v,
_ => return,
};
let cond = match &mut *assign.right {
Expr::Cond(v) => v,
_ => return,
};
match &mut *cond.test {
Expr::Seq(test) => {
//
if test.exprs.len() >= 2 {
let mut new_seq = vec![];
new_seq.extend(test.exprs.drain(..test.exprs.len() - 1));
self.changed = true;
log::debug!("sequences: Lifting sequences in a assignment with cond expr");
let new_cond = CondExpr {
span: cond.span,
test: test.exprs.pop().unwrap(),
cons: cond.cons.take(),
alt: cond.alt.take(),
};
new_seq.push(Box::new(Expr::Assign(AssignExpr {
span: assign.span,
op: assign.op,
left: assign.left.take(),
right: Box::new(Expr::Cond(new_cond)),
})));
*e = Expr::Seq(SeqExpr {
span: assign.span,
exprs: new_seq,
});
return;
}
}
_ => {}
}
}
/// Break assignments in sequences.
///
/// This may result in less parenthesis.
@ -865,6 +660,32 @@ impl Optimizer<'_> {
});
}
pub(super) fn normalize_sequences(&self, seq: &mut SeqExpr) {
for e in &mut seq.exprs {
match &mut **e {
Expr::Seq(e) => {
self.normalize_sequences(&mut *e);
}
_ => {}
}
}
if seq.exprs.iter().any(|v| v.is_seq()) {
let mut new = vec![];
for e in seq.exprs.take() {
match *e {
Expr::Seq(s) => {
new.extend(s.exprs);
}
_ => new.push(e),
}
}
seq.exprs = new;
}
}
pub(super) fn merge_sequences_in_seq_expr(&mut self, e: &mut SeqExpr) {
self.normalize_sequences(e);

View File

@ -1,34 +1,13 @@
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::Type;
use swc_ecma_utils::Value::Known;
impl Optimizer<'_> {
/// Converts template literals to string if `exprs` of [Tpl] is empty.
pub(super) fn convert_tpl_to_str(&mut self, e: &mut Expr) {
match e {
Expr::Tpl(t) if t.quasis.len() == 1 && t.exprs.is_empty() => {
if let Some(c) = &t.quasis[0].cooked {
if c.value.chars().all(|c| match c {
'\u{0020}'..='\u{007e}' => true,
_ => false,
}) {
*e = Expr::Lit(Lit::Str(c.clone()));
}
}
}
_ => {}
}
}
pub(super) fn optimize_expr_in_str_ctx_unsafely(&mut self, e: &mut Expr) {
if !self.options.unsafe_passes {
return;
@ -182,268 +161,4 @@ 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.
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::debug!(
"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::debug!(
"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::debug!("strings: Merged to template literals");
}
_ => {}
}
}
///
/// - `a + 'foo' + 'bar'` => `a + 'foobar'`
pub(super) fn concat_str(&mut self, e: &mut Expr) {
match e {
Expr::Bin(
bin @ BinExpr {
op: op!(bin, "+"), ..
},
) => match &mut *bin.left {
Expr::Bin(
left
@ BinExpr {
op: op!(bin, "+"), ..
},
) => {
let type_of_second = left.right.get_type();
let type_of_third = bin.right.get_type();
if let Known(Type::Str) = type_of_second {
if let Known(Type::Str) = type_of_third {
if let Known(second_str) = left.right.as_string() {
if let Known(third_str) = bin.right.as_string() {
let new_str = format!("{}{}", second_str, third_str);
let left_span = left.span;
self.changed = true;
log::debug!(
"string: Concatting `{} + {}` to `{}`",
second_str,
third_str,
new_str
);
*e = Expr::Bin(BinExpr {
span: bin.span,
op: op!(bin, "+"),
left: left.left.take(),
right: Box::new(Expr::Lit(Lit::Str(Str {
span: left_span,
value: new_str.into(),
has_escape: false,
kind: Default::default(),
}))),
});
return;
}
}
}
}
}
_ => {}
},
_ => {}
}
}
pub(super) fn drop_useless_addition_of_str(&mut self, e: &mut Expr) {
match e {
Expr::Bin(BinExpr {
op: op!(bin, "+"),
left,
right,
..
}) => {
let lt = left.get_type();
let rt = right.get_type();
if let Known(Type::Str) = lt {
if let Known(Type::Str) = rt {
match &**left {
Expr::Lit(Lit::Str(Str {
value: js_word!(""),
..
})) => {
self.changed = true;
log::debug!(
"string: Dropping empty string literal (in lhs) because it \
does not changes type"
);
*e = *right.take();
return;
}
_ => {}
}
match &**right {
Expr::Lit(Lit::Str(Str {
value: js_word!(""),
..
})) => {
self.changed = true;
log::debug!(
"string: Dropping empty string literal (in rhs) because it \
does not changes type"
);
*e = *left.take();
return;
}
_ => {}
}
}
}
}
_ => {}
}
}
}

View File

@ -9,90 +9,12 @@ use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::contains_ident_ref;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::StmtLike;
use swc_ecma_visit::noop_visit_mut_type;
use swc_ecma_visit::VisitMut;
use swc_ecma_visit::VisitMutWith;
/// Methods related to the option `unused`.
impl Optimizer<'_> {
pub(super) fn drop_useless_blocks<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,
{
fn is_inliable(b: &BlockStmt) -> bool {
b.stmts.iter().all(|s| match s {
Stmt::Decl(Decl::Fn(FnDecl {
ident:
Ident {
sym: js_word!("undefined"),
..
},
..
})) => false,
Stmt::Decl(
Decl::Var(VarDecl {
kind: VarDeclKind::Var,
..
})
| Decl::Fn(..),
) => true,
Stmt::Decl(..) => false,
_ => true,
})
}
if stmts.iter().all(|stmt| match stmt.as_stmt() {
Some(Stmt::Block(b)) if is_inliable(b) => false,
_ => true,
}) {
return;
}
self.changed = true;
log::debug!("Dropping useless block");
let mut new = vec![];
for stmt in stmts.take() {
match stmt.try_into_stmt() {
Ok(v) => match v {
Stmt::Block(v) if is_inliable(&v) => {
new.extend(v.stmts.into_iter().map(T::from_stmt));
}
_ => new.push(T::from_stmt(v)),
},
Err(v) => {
new.push(v);
}
}
}
*stmts = new;
}
pub(super) fn drop_unused_stmt_at_end_of_fn(&mut self, s: &mut Stmt) {
match s {
Stmt::Return(r) => match r.arg.as_deref_mut() {
Some(Expr::Unary(UnaryExpr {
span,
op: op!("void"),
arg,
})) => {
log::debug!("unused: Removing `return void` in end of a function");
self.changed = true;
*s = Stmt::Expr(ExprStmt {
span: *span,
expr: arg.take(),
});
return;
}
_ => {}
},
_ => {}
}
}
pub(super) fn drop_unused_var_declarator(&mut self, var: &mut VarDeclarator) {
match &mut var.init {
Some(init) => match &**init {

View File

@ -11,15 +11,8 @@ use swc_ecma_utils::Id;
use swc_ecma_visit::noop_visit_mut_type;
use swc_ecma_visit::VisitMut;
use swc_ecma_visit::VisitMutWith;
use unicode_xid::UnicodeXID;
impl<'b> Optimizer<'b> {
pub(super) fn line_col(&self, span: Span) -> String {
let loc = self.cm.lookup_char_pos(span.lo);
format!("{}:{}", loc.line, loc.col_display)
}
pub(super) fn access_property<'e>(
&mut self,
expr: &'e mut Expr,
@ -170,40 +163,6 @@ pub(crate) fn class_has_side_effect(c: &Class) -> bool {
false
}
pub(crate) fn get_lhs_ident(e: &PatOrExpr) -> Option<&Ident> {
match e {
PatOrExpr::Expr(v) => match &**v {
Expr::Ident(i) => Some(i),
_ => None,
},
PatOrExpr::Pat(v) => match &**v {
Pat::Ident(i) => Some(&i.id),
Pat::Expr(v) => match &**v {
Expr::Ident(i) => Some(i),
_ => None,
},
_ => None,
},
}
}
pub(crate) fn get_lhs_ident_mut(e: &mut PatOrExpr) -> Option<&mut Ident> {
match e {
PatOrExpr::Expr(v) => match &mut **v {
Expr::Ident(i) => Some(i),
_ => None,
},
PatOrExpr::Pat(v) => match &mut **v {
Pat::Ident(i) => Some(&mut i.id),
Pat::Expr(v) => match &mut **v {
Expr::Ident(i) => Some(i),
_ => None,
},
_ => None,
},
}
}
pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {
match e {
Expr::Lit(..) => return false,
@ -212,29 +171,6 @@ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {
}
}
pub(crate) fn is_directive(e: &Stmt) -> bool {
match e {
Stmt::Expr(s) => match &*s.expr {
Expr::Lit(Lit::Str(Str { value, .. })) => value.starts_with("use "),
_ => false,
},
_ => false,
}
}
pub(crate) fn is_valid_identifier(s: &str, ascii_only: bool) -> bool {
if ascii_only {
if s.chars().any(|c| !c.is_ascii()) {
return false;
}
}
s.starts_with(|c: char| c.is_xid_start())
&& s.chars().all(|c: char| c.is_xid_continue())
&& !s.contains("𝒶")
&& !s.is_reserved()
}
pub(crate) fn replace_id_with_expr<N>(node: &mut N, from: Id, to: Box<Expr>)
where
N: VisitMutWith<ExprReplacer>,

View File

@ -1,15 +1,11 @@
use super::Optimizer;
use super::Pure;
use swc_common::Spanned;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::VisitWith;
use swc_ecma_utils::contains_this_expr;
/// Methods related to the option `arrows`.
impl Optimizer<'_> {
impl Pure<'_> {
pub(super) fn optimize_arrow_body(&mut self, b: &mut BlockStmtOrExpr) {
if !self.options.arrows {
return;
@ -43,12 +39,8 @@ impl Optimizer<'_> {
match p {
Prop::KeyValue(kv) => {
//
{
let mut v = ThisVisitor { found: false };
kv.value.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
if v.found {
return;
}
if contains_this_expr(&kv.value) {
return;
}
match &mut *kv.value {
@ -90,15 +82,3 @@ impl Optimizer<'_> {
}
}
}
struct ThisVisitor {
found: bool,
}
impl Visit for ThisVisitor {
noop_visit_type!();
fn visit_this_expr(&mut self, _: &ThisExpr, _: &dyn Node) {
self.found = true;
}
}

View File

@ -0,0 +1,681 @@
use std::mem::swap;
use super::Pure;
use crate::compress::util::is_pure_undefined;
use crate::compress::util::negate;
use crate::compress::util::negate_cost;
use crate::util::make_bool;
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::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ExprExt;
use swc_ecma_utils::Type;
use swc_ecma_utils::Value;
impl Pure<'_> {
pub(super) fn negate_twice(&mut self, e: &mut Expr) {
self.changed = true;
negate(e, false);
negate(e, false);
}
pub(super) fn negate(&mut self, e: &mut Expr, in_bool_ctx: bool) {
self.changed = true;
negate(e, in_bool_ctx)
}
/// `!(a && b)` => `!a || !b`
pub(super) fn optimize_bools(&mut self, e: &mut Expr) {
if !self.options.bools {
return;
}
match e {
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => match &mut **arg {
Expr::Bin(BinExpr {
op: op!("&&"),
left,
right,
..
}) => {
if negate_cost(&left, false, false).unwrap_or(isize::MAX) >= 0
|| negate_cost(&right, false, false).unwrap_or(isize::MAX) >= 0
{
return;
}
log::debug!("Optimizing `!(a && b)` as `!a || !b`");
self.changed = true;
self.negate(arg, false);
*e = *arg.take();
return;
}
Expr::Unary(UnaryExpr {
op: op!("!"),
arg: arg_of_arg,
..
}) => match &mut **arg_of_arg {
Expr::Bin(BinExpr {
op: op!("||"),
left,
right,
..
}) => {
if negate_cost(&left, false, false).unwrap_or(isize::MAX) > 0
&& negate_cost(&right, false, false).unwrap_or(isize::MAX) > 0
{
return;
}
log::debug!("Optimizing `!!(a || b)` as `!a && !b`");
self.negate(arg_of_arg, false);
*e = *arg.take();
return;
}
_ => {}
},
_ => {}
},
_ => {}
}
}
pub(super) fn compress_cmp_of_typeof_with_lit(&mut self, e: &mut BinExpr) {
fn should_optimize(l: &Expr, r: &Expr) -> bool {
match (l, r) {
(
Expr::Unary(UnaryExpr {
op: op!("typeof"), ..
}),
Expr::Lit(..),
) => true,
_ => false,
}
}
match e.op {
op!("===") | op!("!==") => {}
_ => return,
}
if should_optimize(&e.left, &e.right) || should_optimize(&e.right, &e.left) {
log::debug!("bools: Compressing comparison of `typeof` with literal");
self.changed = true;
e.op = match e.op {
op!("===") => {
op!("==")
}
op!("!==") => {
op!("!=")
}
_ => {
unreachable!()
}
}
}
}
///
/// - `!condition() || !-3.5` => `!condition()`
///
/// In this case, if lhs is false, rhs is also false so it's removable.
pub(super) fn remove_useless_logical_rhs(&mut self, e: &mut Expr) {
if !self.options.bools {
return;
}
match e {
Expr::Bin(BinExpr {
left,
op: op @ op!("&&"),
right,
..
})
| Expr::Bin(BinExpr {
left,
op: op @ op!("||"),
right,
..
}) => {
let lt = left.get_type();
let rt = right.get_type();
match (lt, rt) {
(Value::Known(Type::Bool), Value::Known(Type::Bool)) => {
let rb = right.as_pure_bool();
let rb = match rb {
Value::Known(v) => v,
Value::Unknown => return,
};
//
let can_remove = if *op == op!("&&") { rb } else { !rb };
if can_remove {
if *op == op!("&&") {
log::debug!("booleans: Compressing `!foo && true` as `!foo`");
} else {
log::debug!("booleans: Compressing `!foo || false` as `!foo`");
}
self.changed = true;
*e = *left.take();
return;
}
}
_ => {}
}
}
_ => {}
}
}
/// Note: This should be invoked before calling `handle_negated_seq`.
pub(super) fn compress_useless_deletes(&mut self, e: &mut Expr) {
if !self.options.bools {
return;
}
let delete = match e {
Expr::Unary(
u @ UnaryExpr {
op: op!("delete"), ..
},
) => u,
_ => return,
};
if delete.arg.may_have_side_effects() {
return;
}
let convert_to_true = match &*delete.arg {
Expr::Seq(..)
| Expr::Cond(..)
| Expr::Bin(BinExpr { op: op!("&&"), .. })
| Expr::Bin(BinExpr { op: op!("||"), .. }) => true,
// V8 and terser test ref have different opinion.
Expr::Ident(Ident {
sym: js_word!("Infinity"),
..
}) => false,
Expr::Ident(Ident {
sym: js_word!("undefined"),
..
}) => false,
Expr::Ident(Ident {
sym: js_word!("NaN"),
..
}) => false,
e if is_pure_undefined(&e) => true,
Expr::Ident(..) => true,
// NaN
Expr::Bin(BinExpr {
op: op!("/"),
right,
..
}) => {
let rn = right.as_number();
let v = if let Value::Known(rn) = rn {
if rn != 0.0 {
true
} else {
false
}
} else {
false
};
if v {
true
} else {
self.changed = true;
let span = delete.arg.span();
log::debug!("booleans: Compressing `delete` as sequence expression");
*e = Expr::Seq(SeqExpr {
span,
exprs: vec![delete.arg.take(), Box::new(make_bool(span, true))],
});
return;
}
}
_ => false,
};
if convert_to_true {
self.changed = true;
let span = delete.arg.span();
log::debug!("booleans: Compressing `delete` => true");
*e = make_bool(span, true);
return;
}
}
pub(super) fn handle_negated_seq(&mut self, n: &mut Expr) {
match &mut *n {
Expr::Unary(e @ UnaryExpr { op: op!("!"), .. })
| Expr::Unary(
e @ UnaryExpr {
op: op!("delete"), ..
},
) => {
match &mut *e.arg {
Expr::Seq(SeqExpr { exprs, .. }) => {
if exprs.is_empty() {
return;
}
log::debug!("bools: Optimizing negated sequences");
{
let last = exprs.last_mut().unwrap();
self.optimize_expr_in_bool_ctx(last);
// Negate last element.
negate(last, false);
}
*n = *e.arg.take();
}
_ => {}
}
}
_ => {}
}
}
/// This method converts `!1` to `0`.
pub(super) fn optimize_expr_in_bool_ctx(&mut self, n: &mut Expr) {
if !self.options.bools {
return;
}
match n {
Expr::Bin(BinExpr {
op: op!("&&") | op!("||"),
left,
right,
..
}) => {
// Regardless if it's truthy or falsy, we can optimize it because it will be
// casted as bool anyway.
self.optimize_expr_in_bool_ctx(&mut **left);
self.optimize_expr_in_bool_ctx(&mut **right);
return;
}
Expr::Seq(e) => {
if let Some(last) = e.exprs.last_mut() {
self.optimize_expr_in_bool_ctx(&mut **last);
}
}
_ => {}
}
match n {
Expr::Unary(UnaryExpr {
span,
op: op!("!"),
arg,
}) => match &mut **arg {
Expr::Lit(Lit::Num(Number { value, .. })) => {
log::debug!("Optimizing: number => number (in bool context)");
self.changed = true;
*n = Expr::Lit(Lit::Num(Number {
span: *span,
value: if *value == 0.0 { 1.0 } else { 0.0 },
}))
}
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => {
log::debug!("bools: !!expr => expr (in bool ctx)");
self.changed = true;
*n = *arg.take();
return;
}
_ => {}
},
Expr::Unary(UnaryExpr {
span,
op: op!("typeof"),
arg,
}) => {
log::debug!("Optimizing: typeof => true (in bool context)");
self.changed = true;
match &**arg {
Expr::Ident(..) => {
*n = Expr::Lit(Lit::Num(Number {
span: *span,
value: 1.0,
}))
}
_ => {
// Return value of typeof is always truthy
let true_expr = Box::new(Expr::Lit(Lit::Num(Number {
span: *span,
value: 1.0,
})));
*n = Expr::Seq(SeqExpr {
span: *span,
exprs: vec![arg.take(), true_expr],
})
}
}
}
Expr::Lit(Lit::Str(s)) => {
log::debug!("Converting string as boolean expressions");
self.changed = true;
*n = Expr::Lit(Lit::Num(Number {
span: s.span,
value: if s.value.is_empty() { 0.0 } else { 1.0 },
}));
}
Expr::Lit(Lit::Num(num)) => {
if num.value == 1.0 || num.value == 0.0 {
return;
}
if self.options.bools {
log::debug!("booleans: Converting number as boolean expressions");
self.changed = true;
*n = Expr::Lit(Lit::Num(Number {
span: num.span,
value: if num.value == 0.0 { 0.0 } else { 1.0 },
}));
}
}
Expr::Bin(BinExpr {
op: op!("??"),
left,
right,
..
}) => {
// Optimize if (a ?? false); as if (a);
if let Value::Known(false) = right.as_pure_bool() {
log::debug!(
"Dropping right operand of `??` as it's always false (in bool context)"
);
self.changed = true;
*n = *left.take();
}
}
Expr::Bin(BinExpr {
op: op!("||"),
left,
right,
..
}) => {
// `a || false` => `a` (as it will be casted to boolean anyway)
if let Value::Known(false) = right.as_pure_bool() {
log::debug!("bools: `expr || false` => `expr` (in bool context)");
self.changed = true;
*n = *left.take();
return;
}
}
_ => {
let span = n.span();
let v = n.as_pure_bool();
if let Value::Known(v) = v {
log::debug!("Optimizing expr as {} (in bool context)", v);
*n = make_bool(span, v);
return;
}
}
}
}
/// Rules:
/// - `l > i` => `i < l`
fn can_swap_bin_operands(&mut self, l: &Expr, r: &Expr, is_for_rel: bool) -> bool {
match (l, r) {
(Expr::Member(_), _) if is_for_rel => false,
(Expr::Update(..) | Expr::Assign(..), Expr::Lit(..)) if is_for_rel => false,
(
Expr::Member(..)
| Expr::Call(..)
| Expr::Assign(..)
| Expr::Update(..)
| Expr::Bin(BinExpr {
op: op!("&&") | op!("||"),
..
}),
Expr::Lit(..),
) => true,
(
Expr::Member(..) | Expr::Call(..) | Expr::Assign(..),
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}),
) if match &**arg {
Expr::Lit(..) => true,
_ => false,
} =>
{
true
}
(Expr::Member(..) | Expr::Call(..) | Expr::Assign(..), r) if is_pure_undefined(r) => {
true
}
(Expr::Ident(..), Expr::Lit(..)) if is_for_rel => false,
(Expr::Ident(..), Expr::Ident(..)) => false,
(Expr::Ident(..), Expr::Lit(..))
| (
Expr::Ident(..) | Expr::Member(..),
Expr::Unary(UnaryExpr {
op: op!("void") | op!("!"),
..
}),
)
| (
Expr::This(..),
Expr::Unary(UnaryExpr {
op: op!("void"), ..
}),
)
| (Expr::Unary(..), Expr::Lit(..))
| (Expr::Tpl(..), Expr::Lit(..)) => true,
_ => false,
}
}
fn try_swap_bin(&mut self, op: BinaryOp, left: &mut Expr, right: &mut Expr) -> bool {
fn is_supported(op: BinaryOp) -> bool {
match op {
op!("===")
| op!("!==")
| op!("==")
| op!("!=")
| op!("&")
| op!("^")
| op!("|")
| op!("*") => true,
_ => false,
}
}
if !is_supported(op) {
return false;
}
if self.can_swap_bin_operands(&left, &right, false) {
log::debug!("Swapping operands of binary exprssion");
swap(left, right);
return true;
}
false
}
/// Swap lhs and rhs in certain conditions.
pub(super) fn swap_bin_operands(&mut self, expr: &mut Expr) {
match expr {
Expr::Bin(e @ BinExpr { op: op!("<="), .. })
| Expr::Bin(e @ BinExpr { op: op!("<"), .. }) => {
if self.options.comparisons && self.can_swap_bin_operands(&e.left, &e.right, true) {
self.changed = true;
log::debug!("comparisons: Swapping operands of {}", e.op);
e.op = if e.op == op!("<=") {
op!(">=")
} else {
op!(">")
};
swap(&mut e.left, &mut e.right);
}
}
Expr::Bin(bin) => {
if self.try_swap_bin(bin.op, &mut bin.left, &mut bin.right) {
self.changed = true;
}
}
_ => {}
}
}
///
/// - `a === undefined || a === null` => `a == null`
pub(super) fn optimize_cmp_with_null_or_undefined(&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 Value::Known(lt) = lt {
if let Value::Known(rt) = rt {
match (lt, rt) {
(Type::Undefined, Type::Null) | (Type::Null, Type::Undefined) => {
if op == op!("===") {
log::debug!(
"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::debug!(
"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;
}
}
}
_ => {}
}
}
}
}

View File

@ -0,0 +1,184 @@
use super::Pure;
use crate::{compress::util::negate_cost, debug::dump, util::make_bool};
use std::mem::swap;
use swc_common::{EqIgnoreSpan, Spanned};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Type, Value};
impl Pure<'_> {
///
/// - `foo ? bar : false` => `!!foo && bar`
/// - `!foo ? true : bar` => `!foo || bar`
/// - `foo ? false : bar` => `!foo && bar`
pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
let cond = match e {
Expr::Cond(cond) => cond,
_ => return,
};
let lt = cond.cons.get_type();
if let Value::Known(Type::Bool) = lt {
let lb = cond.cons.as_pure_bool();
if let Value::Known(true) = lb {
log::debug!("conditionals: `foo ? true : bar` => `!!foo || bar`");
// Negate twice to convert `test` to boolean.
self.negate_twice(&mut cond.test);
self.changed = true;
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("||"),
left: cond.test.take(),
right: cond.alt.take(),
});
return;
}
// TODO: Verify this rule.
if let Value::Known(false) = lb {
log::debug!("conditionals: `foo ? false : bar` => `!foo && bar`");
self.changed = true;
self.negate(&mut cond.test, false);
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("&&"),
left: cond.test.take(),
right: cond.alt.take(),
});
return;
}
}
let rt = cond.alt.get_type();
if let Value::Known(Type::Bool) = rt {
let rb = cond.alt.as_pure_bool();
if let Value::Known(false) = rb {
log::debug!("conditionals: `foo ? bar : false` => `!!foo && bar`");
self.changed = true;
// Negate twice to convert `test` to boolean.
self.negate_twice(&mut cond.test);
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("&&"),
left: cond.test.take(),
right: cond.cons.take(),
});
return;
}
if let Value::Known(true) = rb {
log::debug!("conditionals: `foo ? bar : true` => `!foo || bar");
self.changed = true;
self.negate(&mut cond.test, false);
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("||"),
left: cond.test.take(),
right: cond.cons.take(),
});
return;
}
}
}
pub(super) fn compress_cond_with_logical_as_logical(&mut self, e: &mut Expr) {
if !self.options.conditionals {
return;
}
let cond = match e {
Expr::Cond(v) => v,
_ => return,
};
let cons_span = cond.cons.span();
match (&mut *cond.cons, &mut *cond.alt) {
(Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
if (*cons.right).eq_ignore_span(&*alt) =>
{
log::debug!("conditionals: `x ? y || z : z` => `x || y && z`");
self.changed = true;
*e = Expr::Bin(BinExpr {
span: cond.span,
op: op!("||"),
left: Box::new(Expr::Bin(BinExpr {
span: cons_span,
op: op!("&&"),
left: cond.test.take(),
right: cons.left.take(),
})),
right: cons.right.take(),
});
return;
}
_ => {}
}
}
pub(super) fn negate_cond_expr(&mut self, cond: &mut CondExpr) {
if negate_cost(&cond.test, true, false).unwrap_or(isize::MAX) >= 0 {
return;
}
self.changed = true;
log::debug!("conditionals: `a ? foo : bar` => `!a ? bar : foo` (considered cost)");
let start_str = dump(&*cond);
self.negate(&mut cond.test, true);
swap(&mut cond.cons, &mut cond.alt);
if cfg!(feature = "debug") {
log::trace!(
"[Change] Negated cond: `{}` => `{}`",
start_str,
dump(&*cond)
)
}
}
/// Removes useless operands of an logical expressions.
pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
if !self.options.conditionals {
return;
}
let bin = match e {
Expr::Bin(b) => b,
_ => return,
};
if bin.op != op!("||") && bin.op != op!("&&") {
return;
}
if bin.left.may_have_side_effects() {
return;
}
let lt = bin.left.get_type();
let rt = bin.right.get_type();
let _lb = bin.left.as_pure_bool();
let rb = bin.right.as_pure_bool();
if let (Value::Known(Type::Bool), Value::Known(Type::Bool)) = (lt, rt) {
// `!!b || true` => true
if let Value::Known(true) = rb {
self.changed = true;
log::debug!("conditionals: `!!foo || true` => `true`");
*e = make_bool(bin.span, true);
return;
}
}
}
}

View File

@ -0,0 +1,56 @@
use super::Pure;
use std::ops::{Deref, DerefMut};
#[derive(Default, Clone, Copy)]
pub(super) struct Ctx {
pub par_depth: u8,
pub in_delete: bool,
/// `true` if we are in `arg` of `++arg` or `--arg`.
pub is_update_arg: bool,
pub is_callee: bool,
pub in_try_block: bool,
pub is_lhs_of_assign: bool,
}
impl<'b> Pure<'b> {
/// RAII guard to change context temporarically
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b> {
let orig_ctx = self.ctx;
self.ctx = ctx;
WithCtx {
pass: self,
orig_ctx,
}
}
}
pub(super) struct WithCtx<'a, 'b> {
pass: &'a mut Pure<'b>,
orig_ctx: Ctx,
}
impl<'b> Deref for WithCtx<'_, 'b> {
type Target = Pure<'b>;
fn deref(&self) -> &Self::Target {
&self.pass
}
}
impl DerefMut for WithCtx<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.pass
}
}
impl Drop for WithCtx<'_, '_> {
fn drop(&mut self) {
self.pass.ctx = self.orig_ctx;
}
}

View File

@ -0,0 +1,85 @@
use super::Pure;
use swc_atoms::js_word;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::StmtLike;
/// Methods related to option `dead_code`.
impl Pure<'_> {
pub(super) fn drop_useless_blocks<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,
{
fn is_inliable(b: &BlockStmt) -> bool {
b.stmts.iter().all(|s| match s {
Stmt::Decl(Decl::Fn(FnDecl {
ident:
Ident {
sym: js_word!("undefined"),
..
},
..
})) => false,
Stmt::Decl(
Decl::Var(VarDecl {
kind: VarDeclKind::Var,
..
})
| Decl::Fn(..),
) => true,
Stmt::Decl(..) => false,
_ => true,
})
}
if stmts.iter().all(|stmt| match stmt.as_stmt() {
Some(Stmt::Block(b)) if is_inliable(b) => false,
_ => true,
}) {
return;
}
self.changed = true;
log::debug!("Dropping useless block");
let mut new = vec![];
for stmt in stmts.take() {
match stmt.try_into_stmt() {
Ok(v) => match v {
Stmt::Block(v) if is_inliable(&v) => {
new.extend(v.stmts.into_iter().map(T::from_stmt));
}
_ => new.push(T::from_stmt(v)),
},
Err(v) => {
new.push(v);
}
}
}
*stmts = new;
}
pub(super) fn drop_unused_stmt_at_end_of_fn(&mut self, s: &mut Stmt) {
match s {
Stmt::Return(r) => match r.arg.as_deref_mut() {
Some(Expr::Unary(UnaryExpr {
span,
op: op!("void"),
arg,
})) => {
log::debug!("unused: Removing `return void` in end of a function");
self.changed = true;
*s = Stmt::Expr(ExprStmt {
span: *span,
expr: arg.take(),
});
return;
}
_ => {}
},
_ => {}
}
}
}

View File

@ -0,0 +1,465 @@
use super::Pure;
use crate::compress::util::{eval_as_number, is_pure_undefined_or_null};
use swc_atoms::js_word;
use swc_common::{Spanned, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{undefined, ExprExt, Value};
impl Pure<'_> {
pub(super) fn eval_array_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
if self.ctx.in_delete || 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::debug!("evaluate: Dropping array.slice call");
*e = *obj.take();
return;
}
1 => {
if let Value::Known(start) = call.args[0].expr.as_number() {
let start = start.floor() as usize;
self.changed = true;
log::debug!("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 Value::Known(start) = start {
let start = start.floor() as usize;
if let Value::Known(end) = end {
let end = end.floor() as usize;
self.changed = true;
log::debug!(
"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;
}
}
}
}
}
_ => {}
}
}
_ => {}
}
}
pub(super) fn eval_fn_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
if self.ctx.in_delete || 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 {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
if obj.may_have_side_effects() {
return;
}
let _f = match &mut **obj {
Expr::Fn(v) => v,
_ => return,
};
let method_name = match &**prop {
Expr::Ident(i) => i,
_ => return,
};
match &*method_name.sym {
"valueOf" => {
if has_spread {
return;
}
self.changed = true;
log::debug!(
"evaludate: Reduced `funtion.valueOf()` into a function expression"
);
*e = *obj.take();
return;
}
_ => {}
}
}
_ => {}
}
}
/// unsafely evaulate call to `Number`.
pub(super) fn eval_number_call(&mut self, e: &mut Expr) {
if self.options.unsafe_passes && self.options.unsafe_math {
match e {
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
args,
..
}) => {
if args.len() == 1 && args[0].spread.is_none() {
match &**callee {
Expr::Ident(Ident {
sym: js_word!("Number"),
..
}) => {
self.changed = true;
log::debug!(
"evaluate: Reducing a call to `Number` into an unary operation"
);
*e = Expr::Unary(UnaryExpr {
span: *span,
op: op!(unary, "+"),
arg: args.take().into_iter().next().unwrap().expr,
});
}
_ => {}
}
}
}
_ => {}
}
}
}
/// Evaluates method calls of a numeric constant.
pub(super) fn eval_number_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
let (num, method, args) = match e {
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
args,
..
}) => match &mut **callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => match &mut **obj {
Expr::Lit(Lit::Num(obj)) => match &mut **prop {
Expr::Ident(prop) => (obj, prop, args),
_ => return,
},
_ => return,
},
_ => return,
},
_ => return,
};
if args.iter().any(|arg| arg.expr.may_have_side_effects()) {
return;
}
match &*method.sym {
"toFixed" => {
if let Some(precision) = eval_as_number(&args[0].expr) {
let precision = precision.floor() as usize;
let value = num_to_fixed(num.value, precision + 1);
self.changed = true;
log::debug!(
"evaluate: Evaluating `{}.toFixed({})` as `{}`",
num,
precision,
value
);
*e = Expr::Lit(Lit::Str(Str {
span: e.span(),
value: value.into(),
has_escape: false,
kind: Default::default(),
}))
}
}
_ => {}
}
}
pub(super) fn eval_opt_chain(&mut self, e: &mut Expr) {
let opt = match e {
Expr::OptChain(e) => e,
_ => return,
};
match &mut *opt.expr {
Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(obj),
..
}) => {
//
if is_pure_undefined_or_null(&obj) {
self.changed = true;
log::debug!(
"evaluate: Reduced an optioanl chaining operation because object is \
always null or undefined"
);
*e = *undefined(*span);
return;
}
}
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
..
}) => {
if is_pure_undefined_or_null(&callee) {
self.changed = true;
log::debug!(
"evaluate: Reduced a call expression with optioanl chaining operation \
because object is always null or undefined"
);
*e = *undefined(*span);
return;
}
}
_ => {}
}
}
}
/// Evaluation of strings.
impl Pure<'_> {
/// Handle calls on string literals, like `'foo'.toUpperCase()`.
pub(super) fn eval_str_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
}
if self.ctx.in_delete || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
return;
}
let call = match e {
Expr::Call(v) => v,
_ => return,
};
let (s, method) = match &call.callee {
ExprOrSuper::Super(_) => return,
ExprOrSuper::Expr(callee) => match &**callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => match (&**obj, &**prop) {
(Expr::Lit(Lit::Str(s)), Expr::Ident(prop)) => (s.clone(), prop.sym.clone()),
_ => return,
},
_ => return,
},
};
let new_val = match &*method {
"toLowerCase" => s.value.to_lowercase(),
"toUpperCase" => s.value.to_uppercase(),
"charCodeAt" => {
if call.args.len() != 1 {
return;
}
if let Expr::Lit(Lit::Num(Number { value, .. })) = &*call.args[0].expr {
if value.fract() != 0.0 {
return;
}
let idx = value.round() as i64 as usize;
let c = s.value.chars().nth(idx);
match c {
Some(v) => {
self.changed = true;
log::debug!(
"evaluate: Evaluated `charCodeAt` of a string literal as `{}`",
v
);
*e = Expr::Lit(Lit::Num(Number {
span: call.span,
value: v as usize as f64,
}))
}
None => {
self.changed = true;
log::debug!(
"evaluate: Evaluated `charCodeAt` of a string literal as `NaN`",
);
*e = Expr::Ident(Ident::new(
js_word!("NaN"),
e.span().with_ctxt(SyntaxContext::empty()),
))
}
}
}
return;
}
_ => return,
};
self.changed = true;
log::debug!("evaluate: Evaluated `{}` of a string literal", method);
*e = Expr::Lit(Lit::Str(Str {
value: new_val.into(),
..s
}));
}
}
/// https://stackoverflow.com/questions/60497397/how-do-you-format-a-float-to-the-first-significant-decimal-and-with-specified-pr
fn num_to_fixed(float: f64, precision: usize) -> String {
// compute absolute value
let a = float.abs();
// if abs value is greater than 1, then precision becomes less than "standard"
let precision = if a >= 1. {
// reduce by number of digits, minimum 0
let n = (1. + a.log10().floor()) as usize;
if n <= precision {
precision - n
} else {
0
}
// if precision is less than 1 (but non-zero), then precision becomes
// greater than "standard"
} else if a > 0. {
// increase number of digits
let n = -(1. + a.log10().floor()) as usize;
precision + n
// special case for 0
} else {
0
};
// format with the given computed precision
format!("{0:.1$}", float, precision)
}

View File

@ -0,0 +1,110 @@
use super::Pure;
use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Value};
impl Pure<'_> {
///
/// - `while(test);` => `for(;;test);
/// - `do; while(true)` => `for(;;);
pub(super) fn loop_to_for_stmt(&mut self, s: &mut Stmt) {
if !self.options.loops {
return;
}
match s {
Stmt::While(stmt) => {
self.changed = true;
log::debug!("loops: Converting a while loop to a for loop");
*s = Stmt::For(ForStmt {
span: stmt.span,
init: None,
test: Some(stmt.test.take()),
update: None,
body: stmt.body.take(),
});
}
Stmt::DoWhile(stmt) => {
let val = stmt.test.as_pure_bool();
if let Value::Known(true) = val {
self.changed = true;
log::debug!("loops: Converting an always-true do-while loop to a for loop");
*s = Stmt::For(ForStmt {
span: stmt.span,
init: None,
test: Some(stmt.test.take()),
update: None,
body: stmt.body.take(),
});
}
}
_ => {}
}
}
/// # Input
///
/// ```js
/// for(; size--;)
/// if (!(result = eq(a[size], b[size], aStack, bStack)))
/// break;
/// ```
///
///
/// # Output
///
/// ```js
/// for (; size-- && (result = eq(a[size], b[size], aStack, bStack)););
/// ```
pub(super) fn merge_for_if_break(&mut self, s: &mut ForStmt) {
match &mut *s.body {
Stmt::If(IfStmt {
test,
cons,
alt: None,
..
}) => {
match &**cons {
Stmt::Break(BreakStmt { label: None, .. }) => {
// We only care about instant breaks.
//
// Note: As the minifier of swc is very fast, we don't
// care about block statements with a single break as a
// body.
//
// If it's optimizable, other pass for if statements
// will remove block and with the next pass we can apply
// this pass.
self.changed = true;
log::debug!("loops: Compressing for-if-break into a for statement");
// We negate because this `test` is used as a condition for `break`.
self.negate(test, true);
match s.test.take() {
Some(left) => {
s.test = Some(Box::new(Expr::Bin(BinExpr {
span: s.test.span(),
op: op!("&&"),
left,
right: test.take(),
})));
}
None => {
s.test = Some(test.take());
}
}
// Remove body
s.body.take();
}
_ => {}
}
}
_ => {}
}
}
}

View File

@ -0,0 +1,26 @@
use super::Pure;
use crate::compress::util::is_pure_undefined;
use swc_ecma_ast::*;
impl Pure<'_> {
pub(super) fn drop_undefined_from_return_arg(&mut self, s: &mut ReturnStmt) {
match s.arg.as_deref() {
Some(e) => {
if is_pure_undefined(e) {
self.changed = true;
log::debug!("Dropped `undefined` from `return undefined`");
s.arg.take();
}
}
None => {}
}
}
pub(super) fn remove_useless_return(&mut self, stmts: &mut Vec<Stmt>) {
if let Some(Stmt::Return(ReturnStmt { arg: None, .. })) = stmts.last() {
self.changed = true;
log::debug!("misc: Removing useless return");
stmts.pop();
}
}
}

View File

@ -0,0 +1,420 @@
use self::ctx::Ctx;
use crate::{marks::Marks, option::CompressOptions, util::MoudleItemExt, MAX_PAR_DEPTH};
use rayon::prelude::*;
use swc_common::{pass::Repeated, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
mod arrows;
mod bools;
mod conds;
mod ctx;
mod dead_code;
mod evaluate;
mod loops;
mod misc;
mod numbers;
mod properties;
mod sequences;
mod strings;
mod unsafes;
mod vars;
pub(super) fn pure_optimizer<'a>(
options: &'a CompressOptions,
marks: Marks,
) -> impl 'a + VisitMut + Repeated {
Pure {
options,
marks,
ctx: Default::default(),
changed: Default::default(),
}
}
struct Pure<'a> {
options: &'a CompressOptions,
marks: Marks,
ctx: Ctx,
changed: bool,
}
impl Repeated for Pure<'_> {
fn changed(&self) -> bool {
self.changed
}
fn reset(&mut self) {
self.ctx = Default::default();
self.changed = false;
}
}
impl Pure<'_> {
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: MoudleItemExt,
Vec<T>: VisitWith<self::vars::VarWithOutInitCounter>
+ VisitMutWith<self::vars::VarPrepender>
+ VisitMutWith<self::vars::VarMover>,
{
self.drop_useless_blocks(stmts);
self.collapse_vars_without_init(stmts);
}
/// Visit `nodes`, maybe in parallel.
fn visit_par<N>(&mut self, nodes: &mut Vec<N>)
where
N: for<'aa> VisitMutWith<Pure<'aa>> + Send + Sync,
{
if self.ctx.par_depth >= MAX_PAR_DEPTH * 2 || cfg!(target_arch = "wasm32") {
for node in nodes {
let mut v = Pure {
options: self.options,
marks: self.marks,
ctx: self.ctx,
changed: false,
};
node.visit_mut_with(&mut v);
self.changed |= v.changed;
}
} else {
let results = nodes
.par_iter_mut()
.map(|node| {
let mut v = Pure {
options: self.options,
marks: self.marks,
ctx: Ctx {
par_depth: self.ctx.par_depth + 1,
..self.ctx
},
changed: false,
};
node.visit_mut_with(&mut v);
v.changed
})
.collect::<Vec<_>>();
for res in results {
self.changed |= res;
}
}
}
}
impl VisitMut for Pure<'_> {
noop_visit_mut_type!();
fn visit_mut_assign_expr(&mut self, e: &mut AssignExpr) {
{
let ctx = Ctx {
is_lhs_of_assign: true,
..self.ctx
};
e.left.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
e.right.visit_mut_with(self);
}
fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
e.visit_mut_children_with(self);
self.compress_cmp_of_typeof_with_lit(e);
self.optimize_cmp_with_null_or_undefined(e);
if e.op == op!(bin, "+") {
self.concat_tpl(&mut e.left, &mut e.right);
}
}
fn visit_mut_block_stmt_or_expr(&mut self, body: &mut BlockStmtOrExpr) {
body.visit_mut_children_with(self);
self.optimize_arrow_body(body);
}
fn visit_mut_call_expr(&mut self, e: &mut CallExpr) {
{
let ctx = Ctx {
is_callee: true,
..self.ctx
};
e.callee.visit_mut_with(&mut *self.with_ctx(ctx));
}
e.args.visit_mut_with(self);
self.drop_arguemtns_of_symbol_call(e);
}
fn visit_mut_cond_expr(&mut self, e: &mut CondExpr) {
e.visit_mut_children_with(self);
self.optimize_expr_in_bool_ctx(&mut e.test);
self.negate_cond_expr(e);
}
fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);
match e {
Expr::Seq(seq) => {
if seq.exprs.is_empty() {
*e = Expr::Invalid(Invalid { span: DUMMY_SP });
return;
}
}
_ => {}
}
self.eval_opt_chain(e);
self.eval_number_call(e);
self.eval_number_method_call(e);
self.swap_bin_operands(e);
self.handle_property_access(e);
self.optimize_bools(e);
self.drop_logical_operands(e);
self.lift_minus(e);
self.convert_tpl_to_str(e);
self.drop_useless_addition_of_str(e);
self.compress_useless_deletes(e);
self.remove_useless_logical_rhs(e);
self.handle_negated_seq(e);
self.concat_str(e);
self.eval_array_method_call(e);
self.eval_fn_method_call(e);
self.eval_str_method_call(e);
self.compress_conds_as_logical(e);
self.compress_cond_with_logical_as_logical(e);
self.lift_seqs_of_bin(e);
self.lift_seqs_of_cond_assign(e);
}
fn visit_mut_exprs(&mut self, exprs: &mut Vec<Box<Expr>>) {
self.visit_par(exprs);
}
fn visit_mut_for_stmt(&mut self, s: &mut ForStmt) {
s.visit_mut_children_with(self);
self.merge_for_if_break(s);
if let Some(test) = &mut s.test {
self.optimize_expr_in_bool_ctx(&mut **test);
}
}
fn visit_mut_function(&mut self, f: &mut Function) {
{
let ctx = Ctx {
in_try_block: false,
..self.ctx
};
f.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
if let Some(body) = &mut f.body {
self.remove_useless_return(&mut body.stmts);
if let Some(last) = body.stmts.last_mut() {
self.drop_unused_stmt_at_end_of_fn(last);
}
}
}
fn visit_mut_if_stmt(&mut self, s: &mut IfStmt) {
s.visit_mut_children_with(self);
self.optimize_expr_in_bool_ctx(&mut s.test);
}
fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
e.obj.visit_mut_with(self);
if e.computed {
e.prop.visit_mut_with(self);
}
self.optimize_property_of_member_expr(e);
self.handle_known_computed_member_expr(e);
}
fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
self.visit_par(items);
self.handle_stmt_likes(items);
}
fn visit_mut_new_expr(&mut self, e: &mut NewExpr) {
{
let ctx = Ctx {
is_callee: true,
..self.ctx
};
e.callee.visit_mut_with(&mut *self.with_ctx(ctx));
}
e.args.visit_mut_with(self);
}
fn visit_mut_prop(&mut self, p: &mut Prop) {
p.visit_mut_children_with(self);
self.optimize_arrow_method_prop(p);
}
fn visit_mut_prop_name(&mut self, p: &mut PropName) {
p.visit_mut_children_with(self);
self.optimize_computed_prop_name_as_normal(p);
self.optimize_prop_name(p);
}
fn visit_mut_prop_or_spreads(&mut self, exprs: &mut Vec<PropOrSpread>) {
self.visit_par(exprs);
}
fn visit_mut_return_stmt(&mut self, s: &mut ReturnStmt) {
s.visit_mut_children_with(self);
self.drop_undefined_from_return_arg(s);
}
fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
e.visit_mut_children_with(self);
self.drop_useless_ident_ref_in_seq(e);
self.merge_seq_call(e);
}
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
{
let ctx = Ctx {
is_update_arg: false,
is_callee: false,
in_delete: false,
..self.ctx
};
s.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
if self.options.drop_debugger {
match s {
Stmt::Debugger(..) => {
self.changed = true;
*s = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
log::debug!("drop_debugger: Dropped a debugger statement");
return;
}
_ => {}
}
}
self.loop_to_for_stmt(s);
}
fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
self.visit_par(items);
self.handle_stmt_likes(items);
items.retain(|s| match s {
Stmt::Empty(..) => false,
_ => true,
});
}
/// 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_tpl(&mut self, n: &mut Tpl) {
n.visit_mut_children_with(self);
debug_assert_eq!(n.exprs.len() + 1, n.quasis.len());
self.compress_tpl(n);
debug_assert_eq!(
n.exprs.len() + 1,
n.quasis.len(),
"tagged template literal compressor created an invalid template literal"
);
}
fn visit_mut_try_stmt(&mut self, n: &mut TryStmt) {
let ctx = Ctx {
in_try_block: true,
..self.ctx
};
n.block.visit_mut_with(&mut *self.with_ctx(ctx));
n.handler.visit_mut_with(self);
n.finalizer.visit_mut_with(self);
}
fn visit_mut_unary_expr(&mut self, e: &mut UnaryExpr) {
{
let ctx = Ctx {
in_delete: e.op == op!("delete"),
..self.ctx
};
e.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
match e.op {
op!("!") => {
self.optimize_expr_in_bool_ctx(&mut e.arg);
}
op!(unary, "+") | op!(unary, "-") => {
self.optimize_expr_in_num_ctx(&mut e.arg);
}
_ => {}
}
}
fn visit_mut_update_expr(&mut self, e: &mut UpdateExpr) {
let ctx = Ctx {
is_update_arg: true,
..self.ctx
};
e.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
fn visit_mut_while_stmt(&mut self, s: &mut WhileStmt) {
s.visit_mut_children_with(self);
self.optimize_expr_in_bool_ctx(&mut s.test);
}
}

View File

@ -1,8 +1,8 @@
use crate::compress::optimize::Optimizer;
use super::Pure;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
impl Optimizer<'_> {
impl Pure<'_> {
pub(super) fn optimize_expr_in_num_ctx(&mut self, e: &mut Expr) {
match e {
Expr::Lit(Lit::Str(Str { span, value, .. })) => {

View File

@ -1,5 +1,5 @@
use super::util::is_valid_identifier;
use crate::compress::optimize::Optimizer;
use super::Pure;
use crate::compress::util::is_valid_identifier;
use crate::util::deeply_contains_this_expr;
use swc_atoms::js_word;
use swc_common::SyntaxContext;
@ -7,7 +7,7 @@ use swc_ecma_ast::*;
use swc_ecma_utils::prop_name_eq;
use swc_ecma_utils::ExprExt;
impl Optimizer<'_> {
impl Pure<'_> {
pub(super) fn optimize_property_of_member_expr(&mut self, e: &mut MemberExpr) {
if !e.computed {
return;
@ -47,6 +47,63 @@ impl Optimizer<'_> {
}
}
/// If a key of is `'str'` (like `{ 'str': 1 }`) change it to [Ident] like
/// (`{ str: 1, }`)
pub(super) fn optimize_computed_prop_name_as_normal(&mut self, p: &mut PropName) {
if !self.options.computed_props {
return;
}
match p {
PropName::Computed(c) => match &mut *c.expr {
Expr::Lit(Lit::Str(s)) => {
if s.value == *"constructor" || s.value == *"__proto__" {
return;
}
if s.value.is_empty() || s.value.starts_with(|c: char| c.is_numeric()) {
*p = PropName::Str(s.clone());
} else {
*p = PropName::Ident(Ident::new(
s.value.clone(),
s.span.with_ctxt(SyntaxContext::empty()),
));
}
return;
}
Expr::Lit(Lit::Num(n)) => {
*p = PropName::Num(n.clone());
return;
}
_ => {}
},
_ => {}
}
}
pub(super) fn optimize_prop_name(&mut self, name: &mut PropName) {
match name {
PropName::Str(s) => {
if s.value.is_reserved() || s.value.is_reserved_in_es3() {
return;
}
if is_valid_identifier(&s.value, false) {
self.changed = true;
log::debug!("misc: Optimizing string property name");
*name = PropName::Ident(Ident {
span: s.span,
sym: s.value.clone(),
optional: false,
});
return;
}
}
_ => {}
}
}
/// Converts `{ a: 1 }.a` into `1`.
pub(super) fn handle_property_access(&mut self, e: &mut Expr) {
if !self.options.props {
@ -57,6 +114,10 @@ impl Optimizer<'_> {
return;
}
if self.ctx.is_callee {
return;
}
let me = match e {
Expr::Member(m) => m,
_ => return,
@ -100,10 +161,6 @@ impl Optimizer<'_> {
return;
}
if self.ctx.is_callee {
return;
}
if obj.props.iter().any(|prop| match prop {
PropOrSpread::Spread(_) => false,
PropOrSpread::Prop(p) => match &**p {

View File

@ -0,0 +1,218 @@
use crate::compress::util::get_lhs_ident;
use super::Pure;
use swc_common::{SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, ExprFactory};
impl Pure<'_> {
pub(super) fn drop_useless_ident_ref_in_seq(&mut self, seq: &mut SeqExpr) {
if !self.options.collapse_vars {
return;
}
if seq.exprs.len() < 2 {
return;
}
match (
&*seq.exprs[seq.exprs.len() - 2],
&*seq.exprs[seq.exprs.len() - 1],
) {
(Expr::Assign(assign), Expr::Ident(ident)) => {
// Check if lhs is same as `ident`.
match &assign.left {
PatOrExpr::Expr(_) => {}
PatOrExpr::Pat(left) => match &**left {
Pat::Ident(left) => {
if left.id.sym == ident.sym && left.id.span.ctxt == ident.span.ctxt {
seq.exprs.pop();
}
}
_ => {}
},
}
}
_ => {}
}
}
///
/// - `(a, b, c) && d` => `a, b, c && d`
pub(super) fn lift_seqs_of_bin(&mut self, e: &mut Expr) {
let bin = match e {
Expr::Bin(b) => b,
_ => return,
};
match &mut *bin.left {
Expr::Seq(left) => {
if left.exprs.is_empty() {
return;
}
self.changed = true;
log::debug!("sequences: Lifting sequence in a binary expression");
let left_last = left.exprs.pop().unwrap();
let mut exprs = left.exprs.take();
exprs.push(Box::new(Expr::Bin(BinExpr {
span: left.span,
op: bin.op,
left: left_last,
right: bin.right.take(),
})));
*e = Expr::Seq(SeqExpr {
span: bin.span,
exprs,
})
}
_ => {}
}
}
///
/// - `x = (foo(), bar(), baz()) ? 10 : 20` => `foo(), bar(), x = baz() ? 10
/// : 20;`
pub(super) fn lift_seqs_of_cond_assign(&mut self, e: &mut Expr) {
if !self.options.sequences() {
return;
}
let assign = match e {
Expr::Assign(v) => v,
_ => return,
};
let cond = match &mut *assign.right {
Expr::Cond(v) => v,
_ => return,
};
match &mut *cond.test {
Expr::Seq(test) => {
//
if test.exprs.len() >= 2 {
let mut new_seq = vec![];
new_seq.extend(test.exprs.drain(..test.exprs.len() - 1));
self.changed = true;
log::debug!("sequences: Lifting sequences in a assignment with cond expr");
let new_cond = CondExpr {
span: cond.span,
test: test.exprs.pop().unwrap(),
cons: cond.cons.take(),
alt: cond.alt.take(),
};
new_seq.push(Box::new(Expr::Assign(AssignExpr {
span: assign.span,
op: assign.op,
left: assign.left.take(),
right: Box::new(Expr::Cond(new_cond)),
})));
*e = Expr::Seq(SeqExpr {
span: assign.span,
exprs: new_seq,
});
return;
}
}
_ => {}
}
}
/// `(a = foo, a.apply())` => `(a = foo).apply()`
///
/// This is useful for outputs of swc/babel
pub(super) fn merge_seq_call(&mut self, e: &mut SeqExpr) {
if !self.options.sequences() {
return;
}
for idx in 0..e.exprs.len() {
let (e1, e2) = e.exprs.split_at_mut(idx);
let a = match e1.last_mut() {
Some(v) => &mut **v,
None => continue,
};
let b = match e2.first_mut() {
Some(v) => &mut **v,
None => continue,
};
match (&mut *a, &mut *b) {
(
Expr::Assign(a_assign),
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(b_callee),
args,
..
}),
) => {
let var_name = get_lhs_ident(&a_assign.left);
let var_name = match var_name {
Some(v) => v,
None => continue,
};
match &mut **b_callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(b_callee_obj),
computed: false,
prop,
..
}) => {
//
if !b_callee_obj.is_ident_ref_to(var_name.sym.clone()) {
continue;
}
match &**prop {
Expr::Ident(Ident { sym, .. }) => match &**sym {
"apply" | "call" => {}
_ => continue,
},
_ => {}
}
let span = a_assign.span.with_ctxt(SyntaxContext::empty());
let obj = a.take();
let new = Expr::Call(CallExpr {
span,
callee: MemberExpr {
span: DUMMY_SP,
obj: obj.as_obj(),
prop: prop.take(),
computed: false,
}
.as_callee(),
args: args.take(),
type_args: Default::default(),
});
b.take();
self.changed = true;
log::debug!(
"sequences: Reducing `(a = foo, a.call())` to `((a = foo).call())`"
);
*a = new;
}
_ => {}
};
}
_ => {}
}
}
}
}

View File

@ -0,0 +1,291 @@
use std::mem::take;
use super::Pure;
use swc_atoms::{js_word, JsWord};
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Type, Value};
impl Pure<'_> {
/// Converts template literals to string if `exprs` of [Tpl] is empty.
pub(super) fn convert_tpl_to_str(&mut self, e: &mut Expr) {
match e {
Expr::Tpl(t) if t.quasis.len() == 1 && t.exprs.is_empty() => {
if let Some(c) = &t.quasis[0].cooked {
if c.value.chars().all(|c| match c {
'\u{0020}'..='\u{007e}' => true,
_ => false,
}) {
*e = Expr::Lit(Lit::Str(c.clone()));
}
}
}
_ => {}
}
}
/// 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::debug!(
"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::debug!(
"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::debug!("strings: Merged to template literals");
}
_ => {}
}
}
///
/// - `a + 'foo' + 'bar'` => `a + 'foobar'`
pub(super) fn concat_str(&mut self, e: &mut Expr) {
match e {
Expr::Bin(
bin @ BinExpr {
op: op!(bin, "+"), ..
},
) => match &mut *bin.left {
Expr::Bin(
left
@ BinExpr {
op: op!(bin, "+"), ..
},
) => {
let type_of_second = left.right.get_type();
let type_of_third = bin.right.get_type();
if let Value::Known(Type::Str) = type_of_second {
if let Value::Known(Type::Str) = type_of_third {
if let Value::Known(second_str) = left.right.as_string() {
if let Value::Known(third_str) = bin.right.as_string() {
let new_str = format!("{}{}", second_str, third_str);
let left_span = left.span;
self.changed = true;
log::debug!(
"strings: Concatting `{} + {}` to `{}`",
second_str,
third_str,
new_str
);
*e = Expr::Bin(BinExpr {
span: bin.span,
op: op!(bin, "+"),
left: left.left.take(),
right: Box::new(Expr::Lit(Lit::Str(Str {
span: left_span,
value: new_str.into(),
has_escape: false,
kind: Default::default(),
}))),
});
return;
}
}
}
}
}
_ => {}
},
_ => {}
}
}
pub(super) fn drop_useless_addition_of_str(&mut self, e: &mut Expr) {
match e {
Expr::Bin(BinExpr {
op: op!(bin, "+"),
left,
right,
..
}) => {
let lt = left.get_type();
let rt = right.get_type();
if let Value::Known(Type::Str) = lt {
if let Value::Known(Type::Str) = rt {
match &**left {
Expr::Lit(Lit::Str(Str {
value: js_word!(""),
..
})) => {
self.changed = true;
log::debug!(
"string: Dropping empty string literal (in lhs) because it \
does not changes type"
);
*e = *right.take();
return;
}
_ => {}
}
match &**right {
Expr::Lit(Lit::Str(Str {
value: js_word!(""),
..
})) => {
self.changed = true;
log::debug!(
"string: Dropping empty string literal (in rhs) because it \
does not changes type"
);
*e = *left.take();
return;
}
_ => {}
}
}
}
}
_ => {}
}
}
}

View File

@ -1,11 +1,11 @@
use super::Optimizer;
use super::Pure;
use swc_atoms::js_word;
use swc_ecma_ast::*;
use swc_ecma_utils::ExprExt;
impl Optimizer<'_> {
impl Pure<'_> {
/// Drop arguments of `Symbol()` call.
pub(super) fn optimize_symbol_call_unsafely(&mut self, e: &mut CallExpr) {
pub(super) fn drop_arguemtns_of_symbol_call(&mut self, e: &mut CallExpr) {
if !self.options.unsafe_symbols {
return;
}

View File

@ -0,0 +1,291 @@
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{prepend, StmtLike};
use swc_ecma_visit::{
noop_visit_mut_type, noop_visit_type, Node, Visit, VisitMut, VisitMutWith, VisitWith,
};
use super::Pure;
impl Pure<'_> {
/// Collapse single-use non-constant variables, side effects permitting.
///
/// This merges all variables to first variable declartion with an
/// initializer. If such variable declaration is not found, variables are
/// prepended to `stmts`.
pub(super) fn collapse_vars_without_init<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,
Vec<T>:
VisitWith<VarWithOutInitCounter> + VisitMutWith<VarMover> + VisitMutWith<VarPrepender>,
{
if !self.options.collapse_vars {
return;
}
{
// let mut found_other = false;
// let mut need_work = false;
// for stmt in &*stmts {
// match stmt.as_stmt() {
// Some(Stmt::Decl(Decl::Var(
// v
// @
// VarDecl {
// kind: VarDeclKind::Var,
// ..
// },
// ))) => {
// if v.decls.iter().any(|v| v.init.is_none()) {
// if found_other {
// need_work = true;
// }
// } else {
// found_other = true;
// }
// }
// _ => {
// found_other = true;
// }
// }
// }
// Check for nested variable declartions.
let mut v = VarWithOutInitCounter::default();
stmts.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
if !v.need_work {
return;
}
}
self.changed = true;
log::debug!("collapse_vars: Collapsing variables without an initializer");
let vars = {
let mut v = VarMover {
vars: Default::default(),
var_decl_kind: Default::default(),
};
stmts.visit_mut_with(&mut v);
v.vars
};
// Prepend vars
let mut prepender = VarPrepender { vars };
stmts.visit_mut_with(&mut prepender);
if !prepender.vars.is_empty() {
prepend(
stmts,
T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: Default::default(),
decls: prepender.vars,
}))),
);
}
}
}
/// See if there's two [VarDecl] which has [VarDeclarator] without the
/// initializer.
#[derive(Default)]
pub(super) struct VarWithOutInitCounter {
need_work: bool,
found_var_without_init: bool,
found_var_with_init: bool,
}
impl Visit for VarWithOutInitCounter {
noop_visit_type!();
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &dyn Node) {}
fn visit_constructor(&mut self, _: &Constructor, _: &dyn Node) {}
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
fn visit_var_decl(&mut self, v: &VarDecl, _: &dyn Node) {
v.visit_children_with(self);
if v.kind != VarDeclKind::Var {
return;
}
let mut found_init = false;
for d in &v.decls {
if d.init.is_some() {
found_init = true;
} else {
if found_init {
self.need_work = true;
return;
}
}
}
if v.decls.iter().all(|v| v.init.is_none()) {
if self.found_var_without_init || self.found_var_with_init {
self.need_work = true;
}
self.found_var_without_init = true;
} else {
self.found_var_with_init = true;
}
}
fn visit_var_decl_or_pat(&mut self, _: &VarDeclOrPat, _: &dyn Node) {}
}
/// Moves all varaible without initializer.
pub(super) struct VarMover {
vars: Vec<VarDeclarator>,
var_decl_kind: Option<VarDeclKind>,
}
impl VisitMut for VarMover {
noop_visit_mut_type!();
/// Noop
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
/// Noop
fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
/// Noop
fn visit_mut_function(&mut self, _: &mut Function) {}
fn visit_mut_module_item(&mut self, s: &mut ModuleItem) {
s.visit_mut_children_with(self);
match s {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Var(d),
..
})) if d.decls.is_empty() => {
s.take();
return;
}
_ => {}
}
}
fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
n.visit_mut_children_with(self);
match n {
Some(VarDeclOrExpr::VarDecl(var)) => {
if var.decls.is_empty() {
*n = None;
}
}
_ => {}
}
}
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
s.visit_mut_children_with(self);
match s {
Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
s.take();
return;
}
_ => {}
}
}
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
let old = self.var_decl_kind.take();
self.var_decl_kind = Some(v.kind);
v.visit_mut_children_with(self);
self.var_decl_kind = old;
}
fn visit_mut_var_decl_or_pat(&mut self, _: &mut VarDeclOrPat) {}
fn visit_mut_var_declarators(&mut self, d: &mut Vec<VarDeclarator>) {
d.visit_mut_children_with(self);
if self.var_decl_kind.unwrap() != VarDeclKind::Var {
return;
}
if d.iter().all(|v| v.init.is_some()) {
return;
}
let has_init = d.iter().any(|v| v.init.is_some());
if has_init {
let mut new = vec![];
for v in d.take() {
if v.init.is_some() {
new.push(v)
} else {
self.vars.push(v)
}
}
*d = new;
}
let mut new = vec![];
if has_init {
new.extend(self.vars.drain(..));
}
for v in d.take() {
if v.init.is_some() {
new.push(v)
} else {
self.vars.push(v)
}
}
*d = new;
}
}
pub(super) struct VarPrepender {
vars: Vec<VarDeclarator>,
}
impl VisitMut for VarPrepender {
noop_visit_mut_type!();
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
if self.vars.is_empty() {
return;
}
if v.kind != VarDeclKind::Var {
return;
}
if v.decls.iter().any(|d| d.init.is_some()) {
let mut decls = self.vars.take();
decls.extend(v.decls.take());
v.decls = decls;
}
}
/// Noop
fn visit_mut_function(&mut self, _: &mut Function) {}
/// Noop
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
/// Noop
fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
}

View File

@ -0,0 +1,588 @@
use crate::debug::dump;
use std::f64;
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Value};
use unicode_xid::UnicodeXID;
/// Creates `!e` where e is the expression passed as an argument.
///
/// # Note
///
/// This method returns `!e` if `!!e` is given as a argument.
///
/// TODO: Handle special cases like !1 or !0
pub(super) fn negate(e: &mut Expr, in_bool_ctx: bool) {
let start_str = dump(&*e);
match e {
Expr::Bin(bin @ BinExpr { op: op!("=="), .. })
| Expr::Bin(bin @ BinExpr { op: op!("!="), .. })
| Expr::Bin(bin @ BinExpr { op: op!("==="), .. })
| Expr::Bin(bin @ BinExpr { op: op!("!=="), .. }) => {
bin.op = match bin.op {
op!("==") => {
op!("!=")
}
op!("!=") => {
op!("==")
}
op!("===") => {
op!("!==")
}
op!("!==") => {
op!("===")
}
_ => {
unreachable!()
}
};
log::debug!("negate: binary");
return;
}
Expr::Bin(BinExpr {
left,
right,
op: op @ op!("&&"),
..
}) if is_ok_to_negate_rhs(&right) => {
log::debug!("negate: a && b => !a || !b");
negate(&mut **left, in_bool_ctx);
negate(&mut **right, in_bool_ctx);
*op = op!("||");
return;
}
Expr::Bin(BinExpr {
left,
right,
op: op @ op!("||"),
..
}) if is_ok_to_negate_rhs(&right) => {
log::debug!("negate: a || b => !a && !b");
negate(&mut **left, in_bool_ctx);
negate(&mut **right, in_bool_ctx);
*op = op!("&&");
return;
}
Expr::Cond(CondExpr { cons, alt, .. })
if is_ok_to_negate_for_cond(&cons) && is_ok_to_negate_for_cond(&alt) =>
{
log::debug!("negate: cond");
negate(&mut **cons, in_bool_ctx);
negate(&mut **alt, in_bool_ctx);
return;
}
Expr::Seq(SeqExpr { exprs, .. }) => {
if let Some(last) = exprs.last_mut() {
log::debug!("negate: seq");
negate(&mut **last, in_bool_ctx);
return;
}
}
_ => {}
}
let mut arg = Box::new(e.take());
match &mut *arg {
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => match &mut **arg {
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => {
log::debug!("negate: !!bool => !bool");
*e = *arg.take();
return;
}
Expr::Bin(BinExpr { op: op!("in"), .. })
| Expr::Bin(BinExpr {
op: op!("instanceof"),
..
}) => {
log::debug!("negate: !bool => bool");
*e = *arg.take();
return;
}
_ => {
if in_bool_ctx {
log::debug!("negate: !expr => expr (in bool context)");
*e = *arg.take();
return;
}
}
},
_ => {}
}
log::debug!("negate: e => !e");
*e = Expr::Unary(UnaryExpr {
span: DUMMY_SP,
op: op!("!"),
arg,
});
if cfg!(feature = "debug") {
log::trace!("[Change] Negated `{}` as `{}`", start_str, dump(&*e));
}
}
pub(crate) fn is_ok_to_negate_for_cond(e: &Expr) -> bool {
match e {
Expr::Update(..) => false,
_ => true,
}
}
pub(crate) fn is_ok_to_negate_rhs(rhs: &Expr) -> bool {
match rhs {
Expr::Member(..) => true,
Expr::Bin(BinExpr {
op: op!("===") | op!("!==") | op!("==") | op!("!="),
..
}) => true,
Expr::Call(..) | Expr::New(..) => false,
Expr::Update(..) => false,
Expr::Bin(BinExpr {
op: op!("&&") | op!("||"),
left,
right,
..
}) => is_ok_to_negate_rhs(&left) && is_ok_to_negate_rhs(&right),
Expr::Bin(BinExpr { left, right, .. }) => {
is_ok_to_negate_rhs(&left) && is_ok_to_negate_rhs(&right)
}
Expr::Assign(e) => is_ok_to_negate_rhs(&e.right),
Expr::Unary(UnaryExpr {
op: op!("!") | op!("delete"),
..
}) => true,
Expr::Seq(e) => {
if let Some(last) = e.exprs.last() {
is_ok_to_negate_rhs(&last)
} else {
true
}
}
Expr::Cond(e) => is_ok_to_negate_rhs(&e.cons) && is_ok_to_negate_rhs(&e.alt),
_ => {
if !rhs.may_have_side_effects() {
return true;
}
if cfg!(feature = "debug") {
log::warn!("unimplemented: is_ok_to_negate_rhs: `{}`", dump(&*rhs));
}
false
}
}
}
/// A negative value means that it's efficient to negate the expression.
pub(crate) fn negate_cost(e: &Expr, in_bool_ctx: bool, is_ret_val_ignored: bool) -> Option<isize> {
fn cost(
e: &Expr,
in_bool_ctx: bool,
bin_op: Option<BinaryOp>,
is_ret_val_ignored: bool,
) -> isize {
match e {
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => {
// TODO: Check if this argument is actually start of line.
match &**arg {
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
..
}) => match &**callee {
Expr::Fn(..) => return 0,
_ => {}
},
_ => {}
}
if in_bool_ctx {
let c = -cost(arg, true, None, is_ret_val_ignored);
return c.min(-1);
}
match &**arg {
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => -1,
_ => 1,
}
}
Expr::Bin(BinExpr {
op: op!("===") | op!("!==") | op!("==") | op!("!="),
..
}) => 0,
Expr::Bin(BinExpr {
op: op @ op!("||") | op @ op!("&&"),
left,
right,
..
}) => {
let l_cost = cost(&left, in_bool_ctx, Some(*op), false);
if !is_ret_val_ignored && !is_ok_to_negate_rhs(&right) {
return l_cost + 3;
}
l_cost + cost(&right, in_bool_ctx, Some(*op), is_ret_val_ignored)
}
Expr::Cond(CondExpr { cons, alt, .. })
if is_ok_to_negate_for_cond(&cons) && is_ok_to_negate_for_cond(&alt) =>
{
cost(&cons, in_bool_ctx, bin_op, is_ret_val_ignored)
+ cost(&alt, in_bool_ctx, bin_op, is_ret_val_ignored)
}
Expr::Cond(..)
| Expr::Update(..)
| Expr::Bin(BinExpr {
op: op!("in") | op!("instanceof"),
..
}) => 3,
Expr::Assign(..) => {
if is_ret_val_ignored {
0
} else {
3
}
}
Expr::Seq(e) => {
if let Some(last) = e.exprs.last() {
return cost(&last, in_bool_ctx, bin_op, is_ret_val_ignored);
}
if is_ret_val_ignored {
0
} else {
1
}
}
_ => {
if is_ret_val_ignored {
0
} else {
1
}
}
}
}
let cost = cost(e, in_bool_ctx, None, is_ret_val_ignored);
if cfg!(feature = "debug") {
log::trace!("negation cost of `{}` = {}", dump(&*e), cost);
}
Some(cost)
}
pub(crate) fn is_pure_undefined(e: &Expr) -> bool {
match e {
Expr::Ident(Ident {
sym: js_word!("undefined"),
..
}) => true,
Expr::Unary(UnaryExpr {
op: UnaryOp::Void,
arg,
..
}) if !arg.may_have_side_effects() => true,
_ => false,
}
}
pub(crate) fn is_valid_identifier(s: &str, ascii_only: bool) -> bool {
if ascii_only {
if s.chars().any(|c| !c.is_ascii()) {
return false;
}
}
s.starts_with(|c: char| c.is_xid_start())
&& s.chars().all(|c: char| c.is_xid_continue())
&& !s.contains("𝒶")
&& !s.is_reserved()
}
pub(crate) fn get_lhs_ident(e: &PatOrExpr) -> Option<&Ident> {
match e {
PatOrExpr::Expr(v) => match &**v {
Expr::Ident(i) => Some(i),
_ => None,
},
PatOrExpr::Pat(v) => match &**v {
Pat::Ident(i) => Some(&i.id),
Pat::Expr(v) => match &**v {
Expr::Ident(i) => Some(i),
_ => None,
},
_ => None,
},
}
}
pub(crate) fn get_lhs_ident_mut(e: &mut PatOrExpr) -> Option<&mut Ident> {
match e {
PatOrExpr::Expr(v) => match &mut **v {
Expr::Ident(i) => Some(i),
_ => None,
},
PatOrExpr::Pat(v) => match &mut **v {
Pat::Ident(i) => Some(&mut i.id),
Pat::Expr(v) => match &mut **v {
Expr::Ident(i) => Some(i),
_ => None,
},
_ => None,
},
}
}
pub(crate) fn is_directive(e: &Stmt) -> bool {
match e {
Stmt::Expr(s) => match &*s.expr {
Expr::Lit(Lit::Str(Str { value, .. })) => value.starts_with("use "),
_ => false,
},
_ => false,
}
}
pub(crate) fn is_pure_undefined_or_null(e: &Expr) -> bool {
is_pure_undefined(e)
|| match e {
Expr::Lit(Lit::Null(..)) => true,
_ => false,
}
}
/// This method does **not** modifies `e`.
///
/// This method is used to test if a whole call can be replaced, while
/// preserving standalone constants.
pub(crate) fn eval_as_number(e: &Expr) -> Option<f64> {
match e {
Expr::Bin(BinExpr {
op: op!(bin, "-"),
left,
right,
..
}) => {
let l = eval_as_number(&left)?;
let r = eval_as_number(&right)?;
return Some(l - r);
}
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
args,
..
}) => {
for arg in &*args {
if arg.spread.is_some() || arg.expr.may_have_side_effects() {
return None;
}
}
match &**callee {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
let prop = match &**prop {
Expr::Ident(i) => i,
_ => return None,
};
match &**obj {
Expr::Ident(obj) if &*obj.sym == "Math" => match &*prop.sym {
"cos" => {
let v = eval_as_number(&args.first()?.expr)?;
return Some(v.cos());
}
"sin" => {
let v = eval_as_number(&args.first()?.expr)?;
return Some(v.sin());
}
"max" => {
let mut numbers = vec![];
for arg in args {
let v = eval_as_number(&arg.expr)?;
if v.is_infinite() || v.is_nan() {
return None;
}
numbers.push(v);
}
return Some(
numbers
.into_iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f64::NEG_INFINITY),
);
}
"min" => {
let mut numbers = vec![];
for arg in args {
let v = eval_as_number(&arg.expr)?;
if v.is_infinite() || v.is_nan() {
return None;
}
numbers.push(v);
}
return Some(
numbers
.into_iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f64::INFINITY),
);
}
"pow" => {
if args.len() != 2 {
return None;
}
let first = eval_as_number(&args[0].expr)?;
let second = eval_as_number(&args[1].expr)?;
return Some(first.powf(second));
}
_ => {}
},
_ => {}
}
}
_ => {}
}
}
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
let prop = match &**prop {
Expr::Ident(i) => i,
_ => return None,
};
match &**obj {
Expr::Ident(obj) if &*obj.sym == "Math" => match &*prop.sym {
"PI" => return Some(f64::consts::PI),
"E" => return Some(f64::consts::E),
"LN10" => return Some(f64::consts::LN_10),
_ => {}
},
_ => {}
}
}
_ => {
if let Value::Known(v) = e.as_number() {
return Some(v);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::negate_cost;
use swc_common::{input::SourceFileInput, FileName};
use swc_ecma_parser::{lexer::Lexer, Parser};
fn assert_negate_cost(s: &str, in_bool_ctx: bool, is_ret_val_ignored: bool, expected: isize) {
testing::run_test2(false, |cm, _| {
let fm = cm.new_source_file(FileName::Anon, s.to_string());
let lexer = Lexer::new(
Default::default(),
swc_ecma_ast::EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let e = parser
.parse_expr()
.expect("failed to parse input as an expression");
let actual = negate_cost(&e, in_bool_ctx, is_ret_val_ignored).unwrap();
assert_eq!(
actual, expected,
"Expected negation cost of {} to be {}, but got {}",
s, expected, actual,
);
Ok(())
})
.unwrap();
}
#[test]
fn logical_1() {
assert_negate_cost(
"this[key] && !this.hasOwnProperty(key) || (this[key] = value)",
false,
true,
2,
);
}
#[test]
#[ignore]
fn logical_2() {
assert_negate_cost(
"(!this[key] || this.hasOwnProperty(key)) && (this[key] = value)",
false,
true,
-2,
);
}
}

View File

@ -1,135 +0,0 @@
use crate::marks::Marks;
use swc_common::comments::Comment;
use swc_common::comments::CommentKind;
use swc_common::comments::Comments;
use swc_common::Mark;
use swc_common::Span;
use swc_common::SyntaxContext;
use swc_ecma_ast::*;
use swc_ecma_visit::noop_visit_mut_type;
use swc_ecma_visit::VisitMut;
use swc_ecma_visit::VisitMutWith;
/// This pass analyzes the comment
///
/// - Makes all nodes except identifiers unique in aspect of span hygiene.
/// - Convert annottatinos into [Mark].
pub(crate) fn info_marker<'a>(
comments: Option<&'a dyn Comments>,
marks: Marks,
) -> impl 'a + VisitMut {
InfoMarker { comments, marks }
}
struct InfoMarker<'a> {
comments: Option<&'a dyn Comments>,
marks: Marks,
}
impl InfoMarker<'_> {
fn make_unique(&self, span: &mut Span) {
debug_assert_eq!(
span.ctxt,
SyntaxContext::empty(),
"Expected empty syntax context"
);
span.ctxt = span.ctxt.apply_mark(Mark::fresh(Mark::root()));
}
/// Check for `/** @const */`.
pub(super) fn has_const_ann(&self, span: Span) -> bool {
self.find_comment(span, |c| {
if c.kind == CommentKind::Block {
if !c.text.starts_with('*') {
return false;
}
let t = c.text[1..].trim();
//
if t.starts_with("@const") {
return true;
}
}
false
})
}
/// Check for `/*#__NOINLINE__*/`
pub(super) fn has_noinline(&self, span: Span) -> bool {
self.has_flag(span, "NOINLINE")
}
fn find_comment<F>(&self, span: Span, mut op: F) -> bool
where
F: FnMut(&Comment) -> bool,
{
let mut found = false;
if let Some(comments) = self.comments {
let cs = comments.get_leading(span.lo);
if let Some(cs) = cs {
for c in &cs {
found |= op(&c);
if found {
break;
}
}
}
}
found
}
fn has_flag(&self, span: Span, text: &'static str) -> bool {
self.find_comment(span, |c| {
if c.kind == CommentKind::Block {
//
if c.text.len() == (text.len() + 5)
&& c.text.starts_with("#__")
&& c.text.ends_with("__")
&& text == &c.text[3..c.text.len() - 2]
{
return true;
}
}
false
})
}
}
impl VisitMut for InfoMarker<'_> {
noop_visit_mut_type!();
fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
n.visit_mut_children_with(self);
self.make_unique(&mut n.span);
}
fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
n.visit_mut_children_with(self);
if self.has_noinline(n.span) {
n.span = n.span.apply_mark(self.marks.noinline);
}
}
fn visit_mut_function(&mut self, n: &mut Function) {
n.visit_mut_children_with(self);
self.make_unique(&mut n.span);
}
fn visit_mut_ident(&mut self, _: &mut Ident) {}
fn visit_mut_lit(&mut self, _: &mut Lit) {}
fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
n.visit_mut_children_with(self);
if self.has_const_ann(n.span) {
n.span = n.span.apply_mark(self.marks.const_ann);
}
}
}

View File

@ -13,8 +13,8 @@
//! `visit_mut_module_items`.
use crate::compress::compressor;
use crate::hygiene::info_marker;
use crate::marks::Marks;
use crate::metadata::info_marker;
use crate::option::ExtraOptions;
use crate::option::MinifyOptions;
use crate::pass::compute_char_freq::compute_char_freq;
@ -32,6 +32,7 @@ use std::time::Instant;
use swc_common::comments::Comments;
use swc_common::sync::Lrc;
use swc_common::SourceMap;
use swc_common::GLOBALS;
use swc_ecma_ast::Module;
use swc_ecma_visit::FoldWith;
use swc_ecma_visit::VisitMutWith;
@ -40,19 +41,20 @@ use timing::Timings;
mod analyzer;
mod compress;
mod debug;
mod hygiene;
mod marks;
pub mod marks;
mod metadata;
pub mod option;
mod pass;
pub mod timing;
mod util;
const DISABLE_BUGGY_PASSES: bool = true;
const MAX_PAR_DEPTH: u8 = 3;
#[inline]
pub fn optimize(
mut m: Module,
cm: Lrc<SourceMap>,
_cm: Lrc<SourceMap>,
comments: Option<&dyn Comments>,
mut timings: Option<&mut Timings>,
options: &MinifyOptions,
@ -85,7 +87,7 @@ pub fn optimize(
}
}
m.visit_mut_with(&mut info_marker(comments, marks));
m.visit_mut_with(&mut info_marker(comments, marks, extra.top_level_mark));
if options.wrap {
// TODO: wrap_common_js
@ -116,7 +118,7 @@ pub fn optimize(
}
if let Some(options) = &options.compress {
let start = now();
m = m.fold_with(&mut compressor(cm.clone(), marks, &options));
m = GLOBALS.with(|globals| m.fold_with(&mut compressor(globals, marks, &options)));
if let Some(start) = start {
log::info!("compressor took {:?}", Instant::now() - start);
}
@ -148,7 +150,7 @@ pub fn optimize(
}
if let Some(property_mangle_options) = options.mangle.as_ref().and_then(|o| o.props.as_ref()) {
mangle_properties(&mut m, property_mangle_options.clone(), marks);
mangle_properties(&mut m, property_mangle_options.clone());
}
if let Some(ref mut t) = timings {
@ -156,7 +158,7 @@ pub fn optimize(
}
{
let data = analyze(&m, marks);
let data = analyze(&m, None);
m.visit_mut_with(&mut hygiene_optimizer(data, extra.top_level_mark));
}

View File

@ -1,22 +1,35 @@
use swc_common::Mark;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Marks {
pub struct Marks {
/// [Mark] applied to non-top level varaibles which is injected while
/// inlining.
///
/// In other words, AST nodes marked with this mark will not be treated as a
/// top level item, even if it's in the top level scope.
pub(crate) non_top_level: Mark,
/// Treat this function as a top level module.
///
/// If this mark is applied, the function will be treated as a black box. It
/// will not be analyzed by usage analyzer.
///
/// # Note
///
/// Standalone functions should not depend on any other declarations in the
/// outer scope.
///
/// This is only applied to [swc_ecma_ast::Function] and it should not be
/// nested.
pub(crate) standalone: Mark,
//// Appied to [swc_ecma_ast::Module].
pub(crate) bundle_of_standalones: Mark,
/// Optimization is finished. This mark is only applied if both of the
/// stateful optimizer and the pure optimizer cannot optimize anymore.
pub(crate) done: Mark,
/// Temporary mark, used to mark nodes which cannot be optimized by the pure
/// optimizer.
///
/// The stateful optimizer removes this mark if it modified the node, so
/// that the pure optimizer can try again to optimize the node.
pub(crate) pure_done: Mark,
/// `/** @const */`.
pub(crate) const_ann: Mark,
@ -28,15 +41,16 @@ pub(crate) struct Marks {
}
impl Marks {
pub fn new() -> Marks {
pub fn new() -> Self {
fn m() -> Mark {
Mark::fresh(Mark::root())
}
Marks {
non_top_level: m(),
standalone: m(),
bundle_of_standalones: m(),
done: m(),
pure_done: m(),
const_ann: m(),
noinline: m(),
pure: m(),

View File

@ -0,0 +1,452 @@
use crate::marks::Marks;
use swc_common::comments::Comment;
use swc_common::comments::CommentKind;
use swc_common::comments::Comments;
use swc_common::Mark;
use swc_common::Span;
use swc_common::SyntaxContext;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_utils::find_ids;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::Id;
use swc_ecma_visit::noop_visit_mut_type;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::VisitMut;
use swc_ecma_visit::VisitMutWith;
use swc_ecma_visit::VisitWith;
#[cfg(test)]
mod tests;
/// This pass analyzes the comment
///
/// - Makes all nodes except identifiers unique in aspect of span hygiene.
/// - Convert annottatinos into [Mark].
pub(crate) fn info_marker<'a>(
comments: Option<&'a dyn Comments>,
marks: Marks,
top_level_mark: Mark,
) -> impl 'a + VisitMut {
InfoMarker {
comments,
marks,
top_level_mark,
state: Default::default(),
top_level_bindings: Default::default(),
}
}
#[derive(Default)]
struct State {
is_bundle: bool,
is_in_export: bool,
}
struct InfoMarker<'a> {
comments: Option<&'a dyn Comments>,
marks: Marks,
top_level_mark: Mark,
state: State,
top_level_bindings: Vec<Id>,
}
impl InfoMarker<'_> {
fn make_unique(&self, span: &mut Span) {
debug_assert_eq!(
span.ctxt,
SyntaxContext::empty(),
"Expected empty syntax context"
);
span.ctxt = span.ctxt.apply_mark(Mark::fresh(Mark::root()));
}
/// Check for `/** @const */`.
pub(super) fn has_const_ann(&self, span: Span) -> bool {
self.find_comment(span, |c| {
if c.kind == CommentKind::Block {
if !c.text.starts_with('*') {
return false;
}
let t = c.text[1..].trim();
//
if t.starts_with("@const") {
return true;
}
}
false
})
}
/// Check for `/*#__NOINLINE__*/`
pub(super) fn has_noinline(&self, span: Span) -> bool {
self.has_flag(span, "NOINLINE")
}
fn find_comment<F>(&self, span: Span, mut op: F) -> bool
where
F: FnMut(&Comment) -> bool,
{
let mut found = false;
if let Some(comments) = self.comments {
let cs = comments.get_leading(span.lo);
if let Some(cs) = cs {
for c in &cs {
found |= op(&c);
if found {
break;
}
}
}
}
found
}
fn has_flag(&self, span: Span, text: &'static str) -> bool {
self.find_comment(span, |c| {
if c.kind == CommentKind::Block {
//
if c.text.len() == (text.len() + 5)
&& c.text.starts_with("#__")
&& c.text.ends_with("__")
&& text == &c.text[3..c.text.len() - 2]
{
return true;
}
}
false
})
}
}
impl VisitMut for InfoMarker<'_> {
noop_visit_mut_type!();
fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
n.visit_mut_children_with(self);
self.make_unique(&mut n.span);
}
fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
n.visit_mut_children_with(self);
if self.has_noinline(n.span) {
n.span = n.span.apply_mark(self.marks.noinline);
}
}
fn visit_mut_export_default_decl(&mut self, e: &mut ExportDefaultDecl) {
self.state.is_in_export = true;
e.visit_mut_children_with(self);
self.state.is_in_export = false;
}
fn visit_mut_export_default_expr(&mut self, e: &mut ExportDefaultExpr) {
self.state.is_in_export = true;
e.visit_mut_children_with(self);
self.state.is_in_export = false;
}
fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
n.visit_mut_children_with(self);
if !self.state.is_in_export
&& n.function
.params
.iter()
.any(|p| is_param_one_of(p, &["module"]))
&& n.function.params.iter().any(|p| {
is_param_one_of(
p,
&["exports", "__webpack_require__", "__webpack_exports__"],
)
})
{
if is_standalone(
&mut n.function,
self.top_level_mark,
&self.top_level_bindings,
) {
self.state.is_bundle = true;
n.function.span = n.function.span.apply_mark(self.marks.standalone);
return;
}
}
}
fn visit_mut_function(&mut self, n: &mut Function) {
n.visit_mut_children_with(self);
self.make_unique(&mut n.span);
}
fn visit_mut_ident(&mut self, _: &mut Ident) {}
fn visit_mut_lit(&mut self, _: &mut Lit) {}
fn visit_mut_module(&mut self, m: &mut Module) {
self.top_level_bindings = {
let mut v = TopLevelBindingCollector {
top_level_ctxt: SyntaxContext::empty().apply_mark(self.top_level_mark),
bindings: Default::default(),
};
m.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
v.bindings
};
m.visit_mut_children_with(self);
if self.state.is_bundle {
log::info!("Running minifier in the bundle mode");
m.span = m.span.apply_mark(self.marks.bundle_of_standalones);
} else {
log::info!("Running minifier in the normal mode");
}
}
fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
n.visit_mut_children_with(self);
if self.has_const_ann(n.span) {
n.span = n.span.apply_mark(self.marks.const_ann);
}
}
}
fn is_param_one_of(p: &Param, allowed: &[&str]) -> bool {
match &p.pat {
Pat::Ident(i) => allowed.contains(&&*i.id.sym),
_ => false,
}
}
struct TopLevelBindingCollector {
top_level_ctxt: SyntaxContext,
bindings: Vec<Id>,
}
impl TopLevelBindingCollector {
fn add(&mut self, id: Id) {
if id.1 != self.top_level_ctxt {
return;
}
self.bindings.push(id);
}
}
impl Visit for TopLevelBindingCollector {
noop_visit_type!();
fn visit_class_decl(&mut self, v: &ClassDecl, _: &dyn Node) {
self.add(v.ident.to_id());
}
fn visit_fn_decl(&mut self, v: &FnDecl, _: &dyn Node) {
self.add(v.ident.to_id());
}
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
fn visit_var_decl(&mut self, v: &VarDecl, _: &dyn Node) {
v.visit_children_with(self);
let ids: Vec<Id> = find_ids(&v.decls);
for id in ids {
self.add(id)
}
}
}
fn is_standalone<N>(n: &mut N, top_level_mark: Mark, top_level_bindings: &[Id]) -> bool
where
N: VisitMutWith<IdentCollector>,
{
let top_level_ctxt = SyntaxContext::empty().apply_mark(top_level_mark);
let bindings = {
let mut v = IdentCollector {
ids: Default::default(),
for_binding: true,
is_pat_decl: false,
};
n.visit_mut_with(&mut v);
v.ids
};
let used = {
let mut v = IdentCollector {
ids: Default::default(),
for_binding: false,
is_pat_decl: false,
};
n.visit_mut_with(&mut v);
v.ids
};
for used_id in &used {
if used_id.0.starts_with("__WEBPACK_EXTERNAL_MODULE_") {
continue;
}
match &*used_id.0 {
"__webpack_require__" | "exports" => continue,
_ => {}
}
if used_id.1 == top_level_ctxt {
if top_level_bindings.contains(&used_id) {
if cfg!(feature = "debug") {
log::debug!(
"Due to {}{:?} (top-level), it's not a bundle",
used_id.0,
used_id.1
);
}
return false;
}
continue;
}
if bindings.contains(used_id) {
continue;
}
if cfg!(feature = "debug") {
log::debug!("Due to {}{:?}, it's not a bundle", used_id.0, used_id.1);
}
return false;
}
true
}
struct IdentCollector {
ids: Vec<Id>,
for_binding: bool,
is_pat_decl: bool,
}
impl IdentCollector {
fn add(&mut self, i: &Ident) {
let id = i.to_id();
self.ids.push(id);
}
}
impl VisitMut for IdentCollector {
noop_visit_mut_type!();
fn visit_mut_catch_clause(&mut self, c: &mut CatchClause) {
let old = self.is_pat_decl;
self.is_pat_decl = true;
c.param.visit_mut_children_with(self);
self.is_pat_decl = old;
self.is_pat_decl = false;
c.body.visit_mut_with(self);
self.is_pat_decl = old;
}
fn visit_mut_class_decl(&mut self, e: &mut ClassDecl) {
if self.for_binding {
e.ident.visit_mut_with(self);
}
e.class.visit_mut_with(self);
}
fn visit_mut_class_expr(&mut self, e: &mut ClassExpr) {
e.class.visit_mut_with(self);
}
fn visit_mut_expr(&mut self, e: &mut Expr) {
match e {
Expr::Ident(..) if self.for_binding => {}
_ => {
e.visit_mut_children_with(self);
}
}
}
fn visit_mut_fn_decl(&mut self, e: &mut FnDecl) {
if self.for_binding {
e.ident.visit_mut_with(self);
}
e.function.visit_mut_with(self);
}
fn visit_mut_fn_expr(&mut self, e: &mut FnExpr) {
if self.for_binding {
e.ident.visit_mut_with(self);
}
e.function.visit_mut_with(self);
}
fn visit_mut_ident(&mut self, i: &mut Ident) {
self.add(&*i);
}
fn visit_mut_labeled_stmt(&mut self, s: &mut LabeledStmt) {
s.body.visit_mut_with(self);
}
fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
e.obj.visit_mut_with(self);
if e.computed {
e.prop.visit_mut_with(self);
}
}
fn visit_mut_param(&mut self, p: &mut Param) {
let old = self.is_pat_decl;
self.is_pat_decl = true;
p.visit_mut_children_with(self);
self.is_pat_decl = old;
}
fn visit_mut_pat(&mut self, p: &mut Pat) {
match p {
Pat::Ident(..) if self.for_binding && !self.is_pat_decl => {}
_ => {
p.visit_mut_children_with(self);
}
}
}
fn visit_mut_prop_name(&mut self, p: &mut PropName) {
match p {
PropName::Computed(..) => {
p.visit_mut_children_with(self);
}
_ => {}
}
}
fn visit_mut_var_declarator(&mut self, d: &mut VarDeclarator) {
let old = self.is_pat_decl;
self.is_pat_decl = true;
d.name.visit_mut_with(self);
self.is_pat_decl = false;
d.init.visit_mut_with(self);
self.is_pat_decl = old;
}
}

View File

@ -0,0 +1,160 @@
use swc_common::{input::SourceFileInput, FileName, Mark, Span, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_parser::{lexer::Lexer, Parser};
use swc_ecma_transforms::resolver_with_mark;
use swc_ecma_visit::{Node, Visit, VisitMutWith, VisitWith};
use crate::marks::Marks;
use super::info_marker;
fn assert_standalone(src: &str, expected: usize) {
testing::run_test(false, |cm, _handler| {
let marks = Marks::new();
let top_level_mark = Mark::fresh(Mark::root());
let fm = cm.new_source_file(FileName::Anon, src.to_string());
let lexer = Lexer::new(
Default::default(),
EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let mut m = parser.parse_module().expect("failed to parse");
m.visit_mut_with(&mut resolver_with_mark(top_level_mark));
m.visit_mut_with(&mut info_marker(None, marks, top_level_mark));
eprintln!("Expected: {} modules in bundle", expected);
let actual = {
let mut counter = MarkCounter {
mark: marks.standalone,
count: 0,
};
m.visit_with(&Invalid { span: DUMMY_SP }, &mut counter);
counter.count
};
eprintln!("Actual: {} modules in bundle", actual);
assert_eq!(expected, actual);
if expected != 0 {
assert!(
m.span.has_mark(marks.bundle_of_standalones),
"Expected module to be marked as a bundle"
);
} else {
assert!(
!m.span.has_mark(marks.bundle_of_standalones),
"Expected module to be not marked as a bundle"
);
}
Ok(())
})
.unwrap();
}
struct MarkCounter {
mark: Mark,
count: usize,
}
impl Visit for MarkCounter {
fn visit_span(&mut self, span: &Span, _: &dyn Node) {
if span.has_mark(self.mark) {
self.count += 1;
}
}
}
#[test]
fn standalone_base() {
assert_standalone("function foo() {}", 0);
}
#[test]
fn standalone_no_usage() {
assert_standalone(
"function foo() {
declare(function (module, exports) {
}, function (module, exports) {
});
}",
2,
);
}
#[test]
fn usage_of_var_1() {
assert_standalone(
"function foo() {
var bar = 2;
declare(function (module, exports) {
bar = 1;
}, function (module, exports) {
});
}",
1,
);
}
#[test]
fn usage_of_class_1() {
assert_standalone(
"function foo() {
class Foo {
}
declare(function (module, exports) {
const bar = new Foo();
}, function (module, exports) {
});
}",
1,
);
}
#[test]
fn usage_of_fn_1() {
assert_standalone(
"function foo() {
function bar() {
}
declare(function (module, exports) {
const baz = new bar();
}, function (module, exports) {
});
}",
1,
);
}
#[test]
fn usage_of_var_2() {
assert_standalone(
"var C = 1;
var obj = {
bar: function (module, exports) {
return C + C;
},
};
console.log(obj.bar());
",
0,
);
}
#[test]
fn export_default_fn_1() {
assert_standalone("export default function f(module, exports) {}", 0);
}

View File

@ -1,6 +1,5 @@
use crate::analyzer::analyze;
use crate::analyzer::ProgramData;
use crate::marks::Marks;
use crate::pass::hygiene::analyzer::HygieneAnalyzer;
use crate::pass::hygiene::analyzer::HygieneData;
use crate::util::now;
@ -21,7 +20,7 @@ mod analyzer;
///
/// Requires [swc_common::GLOBALS].
pub fn optimize_hygiene(m: &mut Module, top_level_mark: Mark) {
let data = analyze(&*m, Marks::new());
let data = analyze(&*m, None);
m.visit_mut_with(&mut hygiene_optimizer(data, top_level_mark))
}

View File

@ -167,7 +167,7 @@ impl VisitMut for Mangler {
}
fn visit_mut_module(&mut self, n: &mut Module) {
let data = analyze(&*n, self.marks);
let data = analyze(&*n, None);
self.data = Some(data);
self.preserved = idents_to_preserve(self.options.clone(), n);
self.preserved_symbols = self.preserved.iter().map(|v| v.0.clone()).collect();
@ -200,7 +200,7 @@ impl VisitMut for Mangler {
}
fn visit_mut_script(&mut self, n: &mut Script) {
let data = analyze(&*n, self.marks);
let data = analyze(&*n, None);
self.data = Some(data);
self.preserved = idents_to_preserve(self.options.clone(), n);
self.preserved_symbols = self.preserved.iter().map(|v| v.0.clone()).collect();

View File

@ -1,6 +1,5 @@
use crate::analyzer::analyze;
use crate::analyzer::ProgramData;
use crate::marks::Marks;
use crate::option::ManglePropertiesOptions;
use crate::util::base54::incr_base54;
use once_cell::sync::Lazy;
@ -106,17 +105,13 @@ impl ManglePropertiesState {
}
}
pub(crate) fn mangle_properties<'a>(
m: &mut Module,
options: ManglePropertiesOptions,
marks: Marks,
) {
pub(crate) fn mangle_properties<'a>(m: &mut Module, options: ManglePropertiesOptions) {
let mut state = ManglePropertiesState {
options,
..Default::default()
};
let data = analyze(&*m, marks);
let data = analyze(&*m, None);
m.visit_mut_with(&mut PropertyCollector {
state: &mut state,
data,

View File

@ -60,7 +60,7 @@ impl PrecompressOptimizer<'_> {
});
if has_decl {
let data = Some(analyze(&*stmts, self.marks));
let data = Some(analyze(&*stmts, Some(self.marks)));
stmts.visit_mut_children_with(&mut PrecompressOptimizer {
options: self.options,

View File

@ -21,6 +21,7 @@ use swc_ecma_visit::VisitWith;
pub(crate) mod base54;
pub(crate) mod sort;
pub(crate) mod unit;
///
pub(crate) fn make_number(span: Span, value: f64) -> Expr {

View File

@ -0,0 +1,63 @@
use crate::debug::dump;
use swc_common::Mark;
use swc_ecma_ast::*;
use swc_ecma_transforms::fixer;
use swc_ecma_utils::DropSpan;
use swc_ecma_visit::{FoldWith, VisitMut, VisitMutWith};
/// Inidicates a unit of minifaction.
pub(crate) trait CompileUnit:
swc_ecma_codegen::Node + Clone + VisitMutWith<DropSpan> + VisitMutWith<crate::debug::Debugger>
{
fn is_module() -> bool;
fn dump(&self) -> String;
fn apply<V>(&mut self, visitor: &mut V)
where
V: VisitMut;
fn remove_mark(&mut self) -> Mark;
}
impl CompileUnit for Module {
fn is_module() -> bool {
true
}
fn dump(&self) -> String {
dump(&self.clone().fold_with(&mut fixer(None)))
}
fn apply<V>(&mut self, visitor: &mut V)
where
V: VisitMut,
{
self.visit_mut_with(&mut *visitor)
}
fn remove_mark(&mut self) -> Mark {
Mark::root()
}
}
impl CompileUnit for FnExpr {
fn is_module() -> bool {
false
}
fn dump(&self) -> String {
dump(&self.clone().fold_with(&mut fixer(None)))
}
fn apply<V>(&mut self, visitor: &mut V)
where
V: VisitMut,
{
self.visit_mut_with(&mut *visitor)
}
fn remove_mark(&mut self) -> Mark {
self.function.span.remove_mark()
}
}

View File

@ -116,6 +116,10 @@ fn run(
config: &str,
mangle: Option<TestMangleOptions>,
) -> Option<Module> {
let _ = rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("rayon-{}", i + 1))
.build_global();
let (_module, config) = parse_compressor_config(cm.clone(), &config);
let fm = cm.load_file(&input).expect("failed to load input.js");

View File

@ -2,11 +2,11 @@ export const E = {
set: function(models, options) {
(options = _.defaults({
}, options, setOptions)).parse && (models = this.parse(models, options));
var singular = !_.isArray(models);
var i, l, id, model, attrs, existing, sort, singular = !_.isArray(models);
models = singular ? models ? [
models
] : [] : _.clone(models);
var i, l, id, model, attrs, existing, sort, at = options.at, targetModel = this.model, sortable = this.comparator && null == at && !1 !== options.sort, sortAttr = _.isString(this.comparator) ? this.comparator : null, toAdd = [], toRemove = [], modelMap = {
var at = options.at, targetModel = this.model, sortable = this.comparator && null == at && !1 !== options.sort, sortAttr = _.isString(this.comparator) ? this.comparator : null, toAdd = [], toRemove = [], modelMap = {
}, add = options.add, merge = options.merge, remove = options.remove, order = !sortable && !!add && !!remove && [];
for(i = 0, l = models.length; i < l; i++){
if (id = (attrs = models[i]) instanceof Model ? model = attrs : attrs[targetModel.prototype.idAttribute], existing = this.get(id)) remove && (modelMap[existing.cid] = !0), merge && (attrs = attrs === model ? model.attributes : attrs, options.parse && (attrs = existing.parse(attrs, options)), existing.set(attrs, options), sortable && !sort && existing.hasChanged(sortAttr) && (sort = !0)), models[i] = existing;

View File

@ -25,14 +25,14 @@ function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) {
} else null != mappedChild && (isValidElement(mappedChild) && (mappedChild = cloneAndReplaceKey(mappedChild, escapedPrefix + (mappedChild.key && (!_child || _child.key !== mappedChild.key) ? escapeUserProvidedKey("" + mappedChild.key) + "/" : "") + childKey)), array.push(mappedChild));
return 1;
}
var child, subtreeCount = 0, nextNamePrefix = "" === nameSoFar ? "." : nameSoFar + SUBSEPARATOR;
var subtreeCount = 0, nextNamePrefix = "" === nameSoFar ? "." : nameSoFar + SUBSEPARATOR;
if (Array.isArray(children)) for(var i = 0; i < children.length; i++)subtreeCount += mapIntoArray(child, array, escapedPrefix, nextNamePrefix + getElementKey(child = children[i], i), callback);
else {
var iteratorFn = getIteratorFn(children);
if ("function" == typeof iteratorFn) {
var iterableChildren = children;
var child, step, iterableChildren = children;
iteratorFn === iterableChildren.entries && (didWarnAboutMaps || warn("Using Maps as children is not supported. Use an array of keyed ReactElements instead."), didWarnAboutMaps = !0);
for(var step, iterator = iteratorFn.call(iterableChildren), ii = 0; !(step = iterator.next()).done;)subtreeCount += mapIntoArray(child, array, escapedPrefix, nextNamePrefix + getElementKey(child = step.value, ii++), callback);
for(var iterator = iteratorFn.call(iterableChildren), ii = 0; !(step = iterator.next()).done;)subtreeCount += mapIntoArray(child, array, escapedPrefix, nextNamePrefix + getElementKey(child = step.value, ii++), callback);
} else if ("object" === type) {
var childrenString = "" + children;
throw Error("Objects are not valid as a React child (found: " + ("[object Object]" === childrenString ? "object with keys {" + Object.keys(children).join(", ") + "}" : childrenString) + "). If you meant to render a collection of children, use an array instead.");

View File

@ -1,2 +1,2 @@
(x() || 0) && y();
x() && y();
x();

View File

@ -10,7 +10,7 @@
return new Error(message);
};
}
var msie, jqLite, jQuery, angularModule, nodeName_, lowercase = function(string) {
var promiseWarning, msie, jqLite, jQuery, angularModule, nodeName_, lowercase = function(string) {
return isString(string) ? string.toLowerCase() : string;
}, uppercase = function(string) {
return isString(string) ? string.toUpperCase() : string;
@ -1092,7 +1092,7 @@
},
remove: function(key) {
var lruEntry = lruHash[key];
lruEntry && (lruEntry == freshEnd && (freshEnd = lruEntry.p), lruEntry == staleEnd && (staleEnd = lruEntry.n), link(lruEntry.n, lruEntry.p), delete lruHash[key], delete data[key], size--);
!!lruEntry && (lruEntry == freshEnd && (freshEnd = lruEntry.p), lruEntry == staleEnd && (staleEnd = lruEntry.n), link(lruEntry.n, lruEntry.p), delete lruHash[key], delete data[key], size--);
},
removeAll: function() {
data = {
@ -2109,7 +2109,7 @@
return this.$$replace = !0, this;
}
};
var promiseWarning, $parseMinErr = minErr("$parse"), promiseWarningCache = {
var $parseMinErr = minErr("$parse"), promiseWarningCache = {
};
function ensureSafeMemberName(name, fullExpression) {
if ("constructor" === name) throw $parseMinErr("isecfld", "Referencing \"constructor\" field in Angular expressions is disallowed! Expression: {0}", fullExpression);
@ -2604,9 +2604,9 @@
promise.$$v = val;
})), pathVal = pathVal.$$v), key3 && null !== pathVal && pathVal !== undefined && ((pathVal = pathVal[key3]) && pathVal.then && (promiseWarning(fullExp), "$$v" in pathVal || ((promise = pathVal).$$v = undefined, promise.then(function(val) {
promise.$$v = val;
})), pathVal = pathVal.$$v), key4 && null !== pathVal && pathVal !== undefined))) && (pathVal = pathVal[key4]) && pathVal.then && (promiseWarning(fullExp), "$$v" in pathVal || ((promise = pathVal).$$v = undefined, promise.then(function(val) {
})), pathVal = pathVal.$$v), key4 && null !== pathVal && pathVal !== undefined && (pathVal = pathVal[key4]) && pathVal.then && (promiseWarning(fullExp), "$$v" in pathVal || ((promise = pathVal).$$v = undefined, promise.then(function(val) {
promise.$$v = val;
})), pathVal = pathVal.$$v)), pathVal;
})), pathVal = pathVal.$$v))))), pathVal;
} : function(scope, locals) {
var pathVal = locals && locals.hasOwnProperty(key0) ? locals : scope;
return null === pathVal || pathVal === undefined ? pathVal : (pathVal = pathVal[key0], key1 && null !== pathVal && pathVal !== undefined && (pathVal = pathVal[key1], key2 && null !== pathVal && pathVal !== undefined && (pathVal = pathVal[key2], key3 && null !== pathVal && pathVal !== undefined && (pathVal = pathVal[key3], key4 && null !== pathVal && pathVal !== undefined)))) ? pathVal = pathVal[key4] : pathVal;
@ -3560,7 +3560,7 @@
priority: 99,
link: function(scope, element, attr) {
attr.$observe(normalized, function(value) {
value && (attr.$set(attrName, value), msie && element.prop(attrName, attr[attrName]));
!!value && (attr.$set(attrName, value), msie && element.prop(attrName, attr[attrName]));
});
}
};
@ -3725,7 +3725,7 @@
};
if ($sniffer.hasEvent("input")) element.on("input", listener);
else {
var timeout, deferListener = function() {
var deferListener = function() {
timeout || (timeout = $browser.defer(function() {
listener(), timeout = null;
}));
@ -3738,7 +3738,7 @@
element.on("change", listener), ctrl.$render = function() {
element.val(ctrl.$isEmpty(ctrl.$viewValue) ? "" : ctrl.$viewValue);
};
var patternValidator, match, pattern = attr.ngPattern, validate = function(regexp, value) {
var timeout, patternValidator, match, pattern = attr.ngPattern, validate = function(regexp, value) {
return ctrl.$isEmpty(value) || regexp.test(value) ? (ctrl.$setValidity("pattern", !0), value) : void ctrl.$setValidity("pattern", !1);
};
if (pattern && (patternValidator = (match = pattern.match(/^\/(.*)\/([gim]*)$/)) ? function(value) {

View File

@ -305,11 +305,11 @@
set: function(models, options) {
(options = _.defaults({
}, options, setOptions)).parse && (models = this.parse(models, options));
var singular = !_.isArray(models);
var i, l, id, model, attrs, existing, sort, singular = !_.isArray(models);
models = singular ? models ? [
models
] : [] : _.clone(models);
var i, l, id, model, attrs, existing, sort, at = options.at, targetModel = this.model, sortable = this.comparator && null == at && !1 !== options.sort, sortAttr = _.isString(this.comparator) ? this.comparator : null, toAdd = [], toRemove = [], modelMap = {
var at = options.at, targetModel = this.model, sortable = this.comparator && null == at && !1 !== options.sort, sortAttr = _.isString(this.comparator) ? this.comparator : null, toAdd = [], toRemove = [], modelMap = {
}, add = options.add, merge = options.merge, remove = options.remove, order = !sortable && !!add && !!remove && [];
for(i = 0, l = models.length; i < l; i++){
if (id = (attrs = models[i]) instanceof Model ? model = attrs : attrs[targetModel.prototype.idAttribute], existing = this.get(id)) remove && (modelMap[existing.cid] = !0), merge && (attrs = attrs === model ? model.attributes : attrs, options.parse && (attrs = existing.parse(attrs, options)), existing.set(attrs, options), sortable && !sort && existing.hasChanged(sortAttr) && (sort = !0)), models[i] = existing;

View File

@ -2043,8 +2043,8 @@
])[1].toLowerCase()]) {
value = value.replace(rxhtmlTag, "<$1></$2>");
try {
for(; i < l; i++)1 === (elem = this[i] || {
}).nodeType && (jQuery.cleanData(getAll(elem, !1)), elem.innerHTML = value);
for(; i < l; i++)elem = this[i] || {
}, 1 === elem.nodeType && (jQuery.cleanData(getAll(elem, !1)), elem.innerHTML = value);
elem = 0;
} catch (e) {
}
@ -2155,8 +2155,8 @@
return elem = el || elem, "none" === jQuery.css(elem, "display") || !jQuery.contains(elem.ownerDocument, elem);
}
function showHide(elements, show) {
for(var display, elem, hidden, values = [], index = 0, length = elements.length; index < length; index++)(elem = elements[index]).style && (values[index] = jQuery._data(elem, "olddisplay"), display = elem.style.display, show ? (values[index] || "none" !== display || (elem.style.display = ""), "" === elem.style.display && isHidden(elem) && (values[index] = jQuery._data(elem, "olddisplay", css_defaultDisplay(elem.nodeName)))) : values[index] || (hidden = isHidden(elem), (display && "none" !== display || !hidden) && jQuery._data(elem, "olddisplay", hidden ? display : jQuery.css(elem, "display"))));
for(index = 0; index < length; index++)(elem = elements[index]).style && (show && "none" !== elem.style.display && "" !== elem.style.display || (elem.style.display = show ? values[index] || "" : "none"));
for(var display, elem, hidden, values = [], index = 0, length = elements.length; index < length; index++)!!(elem = elements[index]).style && (values[index] = jQuery._data(elem, "olddisplay"), display = elem.style.display, show ? (values[index] || "none" !== display || (elem.style.display = ""), "" === elem.style.display && isHidden(elem) && (values[index] = jQuery._data(elem, "olddisplay", css_defaultDisplay(elem.nodeName)))) : values[index] || (hidden = isHidden(elem), (display && "none" !== display || !hidden) && jQuery._data(elem, "olddisplay", hidden ? display : jQuery.css(elem, "display"))));
for(index = 0; index < length; index++)!!(elem = elements[index]).style && (show && "none" !== elem.style.display && "" !== elem.style.display || (elem.style.display = show ? values[index] || "" : "none"));
return elements;
}
function setPositiveNumber(elem, value, subtract) {

View File

@ -624,7 +624,7 @@
var v, uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), props = (prop + " " + vendors.join(uc_prop + " ") + uc_prop).split(" ");
for(v in props)if (undefined !== fbCSS[props[v]]) return !0;
}
var w, ua, platform, wkmatch, wkversion, ffmatch, ffversion, operammobilematch, omversion, fauxBase, base, supports, element, documentElement, getComputedStyle, ua1, nokiaLTE7_3, fakeBody = $17("<body>").prependTo("html"), fbCSS = fakeBody[0].style, vendors = [
var nokiaLTE7_3, w, ua, platform, wkmatch, wkversion, ffmatch, ffversion, operammobilematch, omversion, fauxBase, base, supports, element, documentElement, getComputedStyle, ua1, fakeBody = $17("<body>").prependTo("html"), fbCSS = fakeBody[0].style, vendors = [
"Webkit",
"Moz",
"O"
@ -1207,7 +1207,7 @@
$17.event.special.swipe.eventInProgress = !0;
var stop, start = $17.event.special.swipe.start(event), origTarget = event.target, emitted = !1;
context.move = function(event) {
start && (stop = $17.event.special.swipe.stop(event), !emitted && (emitted = $17.event.special.swipe.handleSwipe(start, stop, thisObject, origTarget)) && ($17.event.special.swipe.eventInProgress = !1), Math.abs(start.coords[0] - stop.coords[0]) > $17.event.special.swipe.scrollSupressionThreshold && event.preventDefault());
!!start && (stop = $17.event.special.swipe.stop(event), !emitted && (emitted = $17.event.special.swipe.handleSwipe(start, stop, thisObject, origTarget)) && ($17.event.special.swipe.eventInProgress = !1), Math.abs(start.coords[0] - stop.coords[0]) > $17.event.special.swipe.scrollSupressionThreshold && event.preventDefault());
}, context.stop = function() {
emitted = !0, $17.event.special.swipe.eventInProgress = !1, $document.off(touchMoveEvent, context.move), context.move = null;
}, $document.on(touchMoveEvent, context.move).one(touchStopEvent, context.stop);
@ -1282,7 +1282,7 @@
}).prependTo($7("head")),
linkSelector: "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]",
set: function(href) {
$7.mobile.dynamicBaseEnabled && $7.support.dynamicBaseTag && base.element.attr("href", $7.mobile.path.makeUrlAbsolute(href, $7.mobile.path.documentBase));
!!$7.mobile.dynamicBaseEnabled && $7.support.dynamicBaseTag && base.element.attr("href", $7.mobile.path.makeUrlAbsolute(href, $7.mobile.path.documentBase));
},
rewrite: function(href, page) {
var newPath = $7.mobile.path.get(href);
@ -1728,7 +1728,7 @@
}), $17.mobile.document.bind("vclick", function(event) {
var $btn, btnEls, target = event.target, needClosest = !1;
if (!(event.which > 1) && $17.mobile.linkBindingEnabled) {
if ($lastVClicked = $17(target), $17.data(target, "mobile-button")) getAjaxFormData($17(target).closest("form"), !0) && target.parentNode && (target = target.parentNode);
if ($lastVClicked = $17(target), $17.data(target, "mobile-button")) !!getAjaxFormData($17(target).closest("form"), !0) && target.parentNode && (target = target.parentNode);
else {
if (!((target = findClosestLink(target)) && "#" !== $17.mobile.path.parseUrl(target.getAttribute("href") || "#").hash)) return;
if (!$17(target).jqmHijackable().length) return;
@ -3522,7 +3522,7 @@
});
},
close: function() {
!this.options.disabled && this.isOpen && ("page" === this.menuType ? (this.menuPage.dialog("close"), this.list.appendTo(this.listbox)) : this.listbox.popup("close"), this._focusButton(), this.isOpen = !1);
this.options.disabled || !this.isOpen || ("page" === this.menuType ? (this.menuPage.dialog("close"), this.list.appendTo(this.listbox)) : this.listbox.popup("close"), this._focusButton(), this.isOpen = !1);
},
open: function() {
this.button.click();

View File

@ -1898,7 +1898,7 @@ Elements.prototype = {
return document.id(Slick.find(this, expression));
}
});
var set, translations, contains = {
var set, translations, types, search, find, match, pseudos, addSlickPseudos, contains = {
contains: function(element) {
return Slick.contains(this, element);
}
@ -2081,7 +2081,7 @@ Elements.prototype = {
var value = node.value;
node.type = type, node.value = value;
}), input = null;
var types, search, find, match, pseudos, addSlickPseudos, div, pollutesGetAttribute = ((div = document.createElement("div")).random = "attribute", "attribute" == div.getAttribute("random"));
var div, pollutesGetAttribute = ((div = document.createElement("div")).random = "attribute", "attribute" == div.getAttribute("random"));
Element1.implement({
setProperty: function(name, value) {
var setter = propertySetters[name.toLowerCase()];

View File

@ -5,7 +5,7 @@
});
}(this, function(exports) {
"use strict";
var REACT_ELEMENT_TYPE = 60103, REACT_PORTAL_TYPE = 60106;
var specialPropKeyWarningShown, specialPropRefWarningShown, didWarnAboutStringRefs, prevLog, prevInfo, prevWarn, prevError, prevGroup, prevGroupCollapsed, prevGroupEnd, prefix, componentFrameCache, propTypesMisspellWarningShown, requestHostCallback, requestHostTimeout, cancelHostTimeout, shouldYieldToHost, requestPaint, getCurrentTime, forceFrameRate, REACT_ELEMENT_TYPE = 60103, REACT_PORTAL_TYPE = 60106;
exports.Fragment = 60107, exports.StrictMode = 60108, exports.Profiler = 60114;
var REACT_PROVIDER_TYPE = 60109, REACT_CONTEXT_TYPE = 60110, REACT_FORWARD_REF_TYPE = 60112;
exports.Suspense = 60113;
@ -178,7 +178,7 @@
return null;
}
pureComponentPrototype.constructor = PureComponent, assign(pureComponentPrototype, Component.prototype), pureComponentPrototype.isPureReactComponent = !0;
var specialPropKeyWarningShown, specialPropRefWarningShown, didWarnAboutStringRefs, hasOwnProperty$1 = Object.prototype.hasOwnProperty, RESERVED_PROPS = {
var hasOwnProperty$1 = Object.prototype.hasOwnProperty, RESERVED_PROPS = {
key: !0,
ref: !0,
__self: !0,
@ -276,7 +276,7 @@
}
function cloneElement(element, config, children) {
if (!(null != element)) throw Error("React.cloneElement(...): The argument must be a React element, but you passed " + element + ".");
var defaultProps, propName, props = assign({
var propName, defaultProps, props = assign({
}, element.props), key = element.key, ref = element.ref, self = element._self, source = element._source, owner = element._owner;
if (null != config) for(propName in hasValidRef(config) && (ref = config.ref, owner = ReactCurrentOwner.current), hasValidKey(config) && (key = "" + config.key), element.type && element.type.defaultProps && (defaultProps = element.type.defaultProps), config)hasOwnProperty$1.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) && (void 0 === config[propName] && void 0 !== defaultProps ? props[propName] = defaultProps[propName] : props[propName] = config[propName]);
var childrenLength = arguments.length - 2;
@ -333,14 +333,14 @@
} else null != mappedChild && (isValidElement(mappedChild) && (mappedChild = cloneAndReplaceKey(mappedChild, escapedPrefix + (mappedChild.key && (!_child || _child.key !== mappedChild.key) ? escapeUserProvidedKey("" + mappedChild.key) + "/" : "") + childKey)), array.push(mappedChild));
return 1;
}
var child, subtreeCount = 0, nextNamePrefix = "" === nameSoFar ? "." : nameSoFar + ":";
var subtreeCount = 0, nextNamePrefix = "" === nameSoFar ? "." : nameSoFar + ":";
if (Array.isArray(children)) for(var i = 0; i < children.length; i++)subtreeCount += mapIntoArray(child, array, escapedPrefix, nextNamePrefix + getElementKey(child = children[i], i), callback);
else {
var iteratorFn = getIteratorFn(children);
if ("function" == typeof iteratorFn) {
var iterableChildren = children;
var child, step, iterableChildren = children;
iteratorFn === iterableChildren.entries && (didWarnAboutMaps || warn("Using Maps as children is not supported. Use an array of keyed ReactElements instead."), didWarnAboutMaps = !0);
for(var step, iterator = iteratorFn.call(iterableChildren), ii = 0; !(step = iterator.next()).done;)subtreeCount += mapIntoArray(child, array, escapedPrefix, nextNamePrefix + getElementKey(child = step.value, ii++), callback);
for(var iterator = iteratorFn.call(iterableChildren), ii = 0; !(step = iterator.next()).done;)subtreeCount += mapIntoArray(child, array, escapedPrefix, nextNamePrefix + getElementKey(child = step.value, ii++), callback);
} else if ("object" === type) {
var childrenString = "" + children;
throw Error("Objects are not valid as a React child (found: " + ("[object Object]" === childrenString ? "object with keys {" + Object.keys(children).join(", ") + "}" : childrenString) + "). If you meant to render a collection of children, use an array instead.");
@ -384,7 +384,7 @@
if (!(null !== dispatcher)) throw Error("Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.");
return dispatcher;
}
var prevLog, prevInfo, prevWarn, prevError, prevGroup, prevGroupCollapsed, prevGroupEnd, disabledDepth = 0;
var disabledDepth = 0;
function disabledLog() {
}
function disableLogs() {
@ -449,7 +449,7 @@
disabledDepth < 0 && error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
disabledLog.__reactDisabledLog = !0;
var prefix, ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher;
var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher;
function describeBuiltInComponentFrame(name, source, ownerFn) {
if (void 0 === prefix) try {
throw Error();
@ -459,7 +459,7 @@
}
return "\n" + prefix + name;
}
var componentFrameCache, reentry = !1;
var reentry = !1;
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) return "";
var control, frame = componentFrameCache.get(fn);
@ -667,7 +667,7 @@
if (validType) for(var i = 2; i < arguments.length; i++)validateChildKeys(arguments[i], type);
return type === exports.Fragment ? validateFragmentProps(element) : validatePropTypes(element), element;
}
var propTypesMisspellWarningShown, requestHostCallback, requestHostTimeout, cancelHostTimeout, shouldYieldToHost, requestPaint, getCurrentTime, forceFrameRate, didWarnAboutDeprecatedCreateFactory = !1, enableSchedulerDebugging = !1, enableProfiling = !1;
var didWarnAboutDeprecatedCreateFactory = !1, enableSchedulerDebugging = !1, enableProfiling = !1;
if ("object" == typeof performance && "function" == typeof performance.now) {
var localPerformance = performance;
getCurrentTime = function() {
@ -869,7 +869,7 @@
}
},
unstable_scheduleCallback: function(priorityLevel, callback, options) {
var timeout, startTime, currentTime = getCurrentTime();
var startTime, timeout, currentTime = getCurrentTime();
if ("object" == typeof options && null !== options) {
var delay = options.delay;
startTime = "number" == typeof delay && delay > 0 ? currentTime + delay : currentTime;
@ -1038,14 +1038,14 @@
return ++threadIDCounter;
},
unstable_trace: function(name, timestamp, callback) {
var threadID = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : 0, interaction = {
var returnValue, threadID = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : 0, interaction = {
__count: 1,
id: interactionIDCounter++,
name: name,
timestamp: timestamp
}, prevInteractions = interactionsRef.current, interactions = new Set(prevInteractions);
interactions.add(interaction), interactionsRef.current = interactions;
var returnValue, subscriber = subscriberRef.current;
var subscriber = subscriberRef.current;
try {
null !== subscriber && subscriber.onInteractionTraced(interaction);
} finally{

View File

@ -6,7 +6,7 @@
}, global.React);
}(this, function(exports, React) {
"use strict";
var devToolsConfig, ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
var devToolsConfig, prevLog, prevInfo, prevWarn, prevError, prevGroup, prevGroupCollapsed, prevGroupEnd, prefix, componentFrameCache, didWarnValueDefaultValue$1, reusableSVGContainer, attemptUserBlockingHydration, attemptContinuousHydration, attemptHydrationAtCurrentPriority, attemptHydrationAtPriority, lastMovementX, lastMovementY, lastMouseEvent, warnedUnknownTags, suppressHydrationWarning, validatePropertiesInDevelopment, warnForTextDifference, warnForPropDifference, warnForExtraAttributes, warnForInvalidEventListener, canDiffStyleForHydrationWarning, normalizeMarkupForTextOrAttribute, normalizeHTML, SUPPRESS_HYDRATION_WARNING$1, fiberStack, warnedAboutMissingGetChildContext, rendererSigil, didWarnUpdateInsideUpdate, currentlyProcessingQueue, didWarnAboutStateAssignmentForComponent, didWarnAboutUninitializedState, didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate, didWarnAboutLegacyLifecyclesAndDerivedState, didWarnAboutUndefinedDerivedState, warnOnUndefinedDerivedState, warnOnInvalidCallback, didWarnAboutDirectlyAssigningPropsToState, didWarnAboutContextTypeAndContextTypes, didWarnAboutInvalidateContextType, didWarnAboutMaps, didWarnAboutGenerators, didWarnAboutStringRefs, ownerHasKeyUseWarning, ownerHasFunctionTypeWarning, rendererSigil$1, didWarnAboutMismatchedHooksForComponent, didWarnAboutUseOpaqueIdentifier, didWarnAboutBadClass, didWarnAboutModulePatternComponent, didWarnAboutContextTypeOnFunctionComponent, didWarnAboutGetDerivedStateOnFunctionComponent, didWarnAboutFunctionRefs, didWarnAboutReassigningProps, didWarnAboutRevealOrder, didWarnAboutTailOptions, appendAllChildren, updateHostContainer, updateHostComponent$1, updateHostText$1, beginWork$1, didWarnAboutUpdateInRenderForAnotherComponent, hasBadMapPolyfill, didWarnAboutNestedUpdates, didWarnAboutFindNodeInStrictMode, topLevelUpdateWarnings, ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function warn(format) {
for(var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++)args[_key - 1] = arguments[_key];
printWarning("warn", format, args);
@ -36,7 +36,7 @@
registrationNameDependencies[registrationName] && error("EventRegistry: More than one plugin attempted to publish the same registration name, `%s`.", registrationName), registrationNameDependencies[registrationName] = dependencies, possibleRegistrationNames[registrationName.toLowerCase()] = registrationName, "onDoubleClick" === registrationName && (possibleRegistrationNames.ondblclick = registrationName);
for(var i = 0; i < dependencies.length; i++)allNativeEvents.add(dependencies[i]);
}
var canUseDOM = !!("undefined" != typeof window && void 0 !== window.document && void 0 !== window.document.createElement), VALID_ATTRIBUTE_NAME_REGEX = /^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, hasOwnProperty = Object.prototype.hasOwnProperty, illegalAttributeNameCache = {
var canUseDOM = !!("undefined" != typeof window && void 0 !== window.document && void 0 !== window.document.createElement), ATTRIBUTE_NAME_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040", VALID_ATTRIBUTE_NAME_REGEX = new RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][" + ATTRIBUTE_NAME_CHAR + "]*$"), hasOwnProperty = Object.prototype.hasOwnProperty, illegalAttributeNameCache = {
}, validatedAttributeNameCache = {
};
function isAttributeNameSafe(attributeName) {
@ -351,7 +351,7 @@
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable["@@iterator"];
return "function" == typeof maybeIterator ? maybeIterator : null;
}
var prevLog, prevInfo, prevWarn, prevError, prevGroup, prevGroupCollapsed, prevGroupEnd, disabledDepth = 0;
var disabledDepth = 0;
function disabledLog() {
}
function disableLogs() {
@ -416,7 +416,7 @@
disabledDepth < 0 && error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
disabledLog.__reactDisabledLog = !0;
var prefix, ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
function describeBuiltInComponentFrame(name, source, ownerFn) {
if (void 0 === prefix) try {
throw Error();
@ -426,7 +426,7 @@
}
return "\n" + prefix + name;
}
var componentFrameCache, reentry = !1;
var reentry = !1;
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) return "";
var control, frame = componentFrameCache.get(fn);
@ -919,7 +919,7 @@
function getChildNamespace(parentNamespace, type) {
return null == parentNamespace || "http://www.w3.org/1999/xhtml" === parentNamespace ? getIntrinsicNamespace(type) : "http://www.w3.org/2000/svg" === parentNamespace && "foreignObject" === type ? "http://www.w3.org/1999/xhtml" : parentNamespace;
}
var didWarnValueDefaultValue$1, reusableSVGContainer, setInnerHTML = function(func) {
var setInnerHTML = function(func) {
return "undefined" != typeof MSApp && MSApp.execUnsafeLocalFunction ? function(arg0, arg1, arg2, arg3) {
MSApp.execUnsafeLocalFunction(function() {
return func(arg0, arg1, arg2, arg3);
@ -1926,7 +1926,7 @@
"aria-rowspan": 0,
"aria-setsize": 0
}, warnedProperties = {
}, rARIA = /^(aria)-[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, rARIACamel = /^(aria)[A-Z][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, hasOwnProperty$1 = Object.prototype.hasOwnProperty;
}, rARIA = new RegExp("^(aria)-[" + ATTRIBUTE_NAME_CHAR + "]*$"), rARIACamel = new RegExp("^(aria)[A-Z][" + ATTRIBUTE_NAME_CHAR + "]*$"), hasOwnProperty$1 = Object.prototype.hasOwnProperty;
function validateProperty(tagName, name) {
if (hasOwnProperty$1.call(warnedProperties, name) && warnedProperties[name]) return !0;
if (rARIACamel.test(name)) {
@ -1956,8 +1956,8 @@
}
var validateProperty$1 = function() {
}, warnedProperties$1 = {
}, _hasOwnProperty = Object.prototype.hasOwnProperty, EVENT_NAME_REGEX = /^on./, INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/, rARIA$1 = /^(aria)-[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, rARIACamel$1 = /^(aria)[A-Z][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, warnUnknownProperties = function(type, props, eventRegistry) {
var unknownProps = [];
}, _hasOwnProperty = Object.prototype.hasOwnProperty, EVENT_NAME_REGEX = /^on./, INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/, rARIA$1 = new RegExp("^(aria)-[" + ATTRIBUTE_NAME_CHAR + "]*$"), rARIACamel$1 = new RegExp("^(aria)[A-Z][" + ATTRIBUTE_NAME_CHAR + "]*$"), warnUnknownProperties = function(type, props, eventRegistry) {
var isValid, unknownProps = [];
for(var key in props)(validateProperty$1 = function(tagName, name, value, eventRegistry) {
if (_hasOwnProperty.call(warnedProperties$1, name) && warnedProperties$1[name]) return !0;
var lowerCasedName = name.toLowerCase();
@ -2114,7 +2114,7 @@
var fakeNode = document.createElement("react");
invokeGuardedCallbackImpl = function(name, func, context, a, b, c, d, e, f) {
if (!("undefined" != typeof document)) throw Error("The `document` global was defined when React was initialized, but is not defined anymore. This can happen in a test environment if a component schedules an update from an asynchronous callback, but the test has already finished running. To solve this, you can either unmount the component at the end of your test (and ensure that any asynchronous operations get canceled in `componentWillUnmount`), or you can change the test itself to be asynchronous.");
var evt = document.createEvent("Event"), didCall = !1, didError = !0, windowEvent = window.event, windowEventDescriptor = Object.getOwnPropertyDescriptor(window, "event");
var error, evt = document.createEvent("Event"), didCall = !1, didError = !0, windowEvent = window.event, windowEventDescriptor = Object.getOwnPropertyDescriptor(window, "event");
function restoreAfterDispatch() {
fakeNode.removeEventListener(evtType, callCallback, !1), void 0 !== window.event && window.hasOwnProperty("event") && (window.event = windowEvent);
}
@ -2122,7 +2122,7 @@
function callCallback() {
didCall = !0, restoreAfterDispatch(), func.apply(context, funcArgs), didError = !1;
}
var error, didSetError = !1, isCrossOriginError = !1;
var didSetError = !1, isCrossOriginError = !1;
function handleWindowError(event) {
if (didSetError = !0, null === (error = event.error) && 0 === event.colno && 0 === event.lineno && (isCrossOriginError = !0), event.defaultPrevented && null != error && "object" == typeof error) try {
error._suppressLogging = !0;
@ -2529,7 +2529,7 @@
var priority = eventPriorities.get(domEventName);
return void 0 === priority ? 2 : priority;
}
var attemptUserBlockingHydration, attemptContinuousHydration, attemptHydrationAtCurrentPriority, attemptHydrationAtPriority, _ReactInternals$Sched$1 = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing, __interactionsRef = _ReactInternals$Sched$1.__interactionsRef, __subscriberRef = _ReactInternals$Sched$1.__subscriberRef, unstable_getThreadID = _ReactInternals$Sched$1.unstable_getThreadID, unstable_wrap = _ReactInternals$Sched$1.unstable_wrap;
var _ReactInternals$Sched$1 = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing, __interactionsRef = _ReactInternals$Sched$1.__interactionsRef, __subscriberRef = _ReactInternals$Sched$1.__subscriberRef, unstable_getThreadID = _ReactInternals$Sched$1.unstable_getThreadID, unstable_wrap = _ReactInternals$Sched$1.unstable_wrap;
if (_ReactInternals$Sched$1.unstable_clear, _ReactInternals$Sched$1.unstable_getCurrent, _ReactInternals$Sched$1.unstable_subscribe, _ReactInternals$Sched$1.unstable_trace, _ReactInternals$Sched$1.unstable_unsubscribe, !(null != __interactionsRef && null != __interactionsRef.current)) throw Error("It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `scheduler/tracing` module with `scheduler/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at https://reactjs.org/link/profiling");
unstable_now();
var NoLanes = 0, NoLane = 0, SyncLane = 1;
@ -2781,7 +2781,7 @@
entanglements[index] |= entangledLanes, lanes &= ~lane;
}
}
var clz32 = Math.clz32 ? Math.clz32 : function(lanes) {
var clz32 = Math.clz32 ? Math.clz32 : function clz32Fallback(lanes) {
return 0 === lanes ? 32 : 31 - (log(lanes) / LN2 | 0) | 0;
}, log = Math.log, LN2 = Math.LN2, UserBlockingPriority$1 = unstable_UserBlockingPriority, runWithPriority = unstable_runWithPriority, _enabled = !0;
function setEnabled(enabled) {
@ -2912,7 +2912,7 @@
isPersistent: functionThatReturnsTrue
}), SyntheticBaseEvent;
}
var lastMovementX, lastMovementY, lastMouseEvent, EventInterface = {
var EventInterface = {
eventPhase: 0,
bubbles: 0,
cancelable: 0,
@ -3039,7 +3039,7 @@
}
var SyntheticKeyboardEvent = createSyntheticEvent(_assign({
}, UIEventInterface, {
key: function(nativeEvent) {
key: function getEventKey(nativeEvent) {
if (nativeEvent.key) {
var key = normalizeKey[nativeEvent.key] || nativeEvent.key;
if ("Unidentified" !== key) return key;
@ -3152,10 +3152,9 @@
}
var isComposing = !1;
function extractCompositionEvent(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget) {
var eventType, fallbackData;
if (canUseCompositionEvent ? eventType = getCompositionEventType(domEventName) : isComposing ? isFallbackCompositionEnd(domEventName, nativeEvent) && (eventType = "onCompositionEnd") : isFallbackCompositionStart(domEventName, nativeEvent) && (eventType = "onCompositionStart"), !eventType) return null;
useFallbackCompositionData && !isUsingKoreanIME(nativeEvent) && (isComposing || "onCompositionStart" !== eventType ? "onCompositionEnd" === eventType && isComposing && (fallbackData = getData()) : isComposing = initialize(nativeEventTarget));
var listeners = accumulateTwoPhaseListeners(targetInst, eventType);
var eventType, fallbackData, listeners = accumulateTwoPhaseListeners(targetInst, eventType);
if (listeners.length > 0) {
var event = new SyntheticCompositionEvent(eventType, domEventName, null, nativeEvent, nativeEventTarget);
if (dispatchQueue.push({
@ -3310,7 +3309,7 @@
handleEventFunc && handleEventFunc(domEventName, targetNode, targetInst), "focusout" === domEventName && handleControlledInputBlur(targetNode);
}
function extractEvents$2(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
var isOverEvent = "mouseover" === domEventName || "pointerover" === domEventName, isOutEvent = "mouseout" === domEventName || "pointerout" === domEventName;
var win, from, to, isOverEvent = "mouseover" === domEventName || "pointerover" === domEventName, isOutEvent = "mouseout" === domEventName || "pointerout" === domEventName;
if (isOverEvent && (16 & eventSystemFlags) == 0) {
var related = nativeEvent.relatedTarget || nativeEvent.fromElement;
if (related && (getClosestInstanceFromNode(related) || isContainerMarkedAsRoot(related))) return;
@ -3322,7 +3321,7 @@
win = doc ? doc.defaultView || doc.parentWindow : window;
}
if (isOutEvent) {
var win, from, to, _related = nativeEvent.relatedTarget || nativeEvent.toElement;
var _related = nativeEvent.relatedTarget || nativeEvent.toElement;
from = targetInst, null !== (to = _related ? getClosestInstanceFromNode(_related) : null) && (to !== getNearestMountedFiber(to) || 5 !== to.tag && 6 !== to.tag) && (to = null);
} else from = null, to = targetInst;
if (from !== to) {
@ -3348,7 +3347,7 @@
}
return isSupported;
})("input") && (!document.documentMode || document.documentMode > 9));
var objectIs = "function" == typeof Object.is ? Object.is : function(x, y) {
var objectIs = "function" == typeof Object.is ? Object.is : function is(x, y) {
return x === y && (0 !== x || 1 / x == 1 / y) || x != x && y != y;
}, hasOwnProperty$2 = Object.prototype.hasOwnProperty;
function shallowEqual(objA, objB) {
@ -4175,7 +4174,7 @@
}
function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
validatePropertiesInDevelopment(tag, nextRawProps);
var lastProps, nextProps, updatePayload = null;
var lastProps, nextProps, propKey, styleName, updatePayload = null;
switch(tag){
case "input":
lastProps = getHostProps(domElement, lastRawProps), nextProps = getHostProps(domElement, nextRawProps), updatePayload = [];
@ -4194,7 +4193,7 @@
break;
}
assertValidProps(tag, nextProps);
var propKey, styleName, styleUpdates = null;
var styleUpdates = null;
for(propKey in lastProps)if (!nextProps.hasOwnProperty(propKey) && lastProps.hasOwnProperty(propKey) && null != lastProps[propKey]) if ("style" === propKey) {
var lastStyle = lastProps[propKey];
for(styleName in lastStyle)lastStyle.hasOwnProperty(styleName) && (styleUpdates || (styleUpdates = {
@ -4235,7 +4234,6 @@
return possibleStandardNames.hasOwnProperty(lowerCasedName) ? possibleStandardNames[lowerCasedName] || null : null;
}
function diffHydratedProperties(domElement, tag, rawProps, parentNamespace, rootContainerElement) {
var isCustomComponentTag, extraAttributeNames;
switch(suppressHydrationWarning = !0 === rawProps.suppressHydrationWarning, isCustomComponentTag = isCustomComponent(tag, rawProps), validatePropertiesInDevelopment(tag, rawProps), tag){
case "dialog":
listenToNonDelegatedEvent("cancel", domElement), listenToNonDelegatedEvent("close", domElement);
@ -4247,7 +4245,7 @@
break;
case "video":
case "audio":
for(var i = 0; i < mediaEventTypes.length; i++)listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
for(var isCustomComponentTag, extraAttributeNames, i = 0; i < mediaEventTypes.length; i++)listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
break;
case "source":
listenToNonDelegatedEvent("error", domElement);
@ -4693,7 +4691,7 @@
}
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var hostContextDev = hostContext;
validateDOMNesting(type, null, hostContextDev.ancestorInfo), ("string" == typeof props.children || "number" == typeof props.children) && validateDOMNesting(null, "" + props.children, updatedAncestorInfo(hostContextDev.ancestorInfo, type)), hostContextDev.namespace;
validateDOMNesting(type, null, hostContextDev.ancestorInfo), ("string" == typeof props.children || "number" == typeof props.children) && validateDOMNesting(null, "" + props.children, updatedAncestorInfo(hostContextDev.ancestorInfo, type));
var domElement = createElement(type, props, rootContainerInstance, hostContextDev.namespace);
return precacheFiberNode(internalInstanceHandle, domElement), updateFiberProps(domElement, props), domElement;
}
@ -4955,7 +4953,7 @@
!error$1 || error$1 instanceof Error || (setCurrentlyValidatingElement(element), error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1), setCurrentlyValidatingElement(null)), error$1 instanceof Error && !(error$1.message in loggedTypeFailures) && (loggedTypeFailures[error$1.message] = !0, setCurrentlyValidatingElement(element), error("Failed %s type: %s", location, error$1.message), setCurrentlyValidatingElement(null));
}
}
var warnedUnknownTags, suppressHydrationWarning, validatePropertiesInDevelopment, warnForTextDifference, warnForPropDifference, warnForExtraAttributes, warnForInvalidEventListener, canDiffStyleForHydrationWarning, normalizeMarkupForTextOrAttribute, normalizeHTML, SUPPRESS_HYDRATION_WARNING$1, fiberStack, valueStack = [];
var valueStack = [];
fiberStack = [];
var index = -1;
function createCursor(defaultValue) {
@ -5238,7 +5236,7 @@
}, ReactStrictModeWarnings.discardPendingWarnings = function() {
pendingComponentWillMountWarnings = [], pendingUNSAFE_ComponentWillMountWarnings = [], pendingComponentWillReceivePropsWarnings = [], pendingUNSAFE_ComponentWillReceivePropsWarnings = [], pendingComponentWillUpdateWarnings = [], pendingUNSAFE_ComponentWillUpdateWarnings = [], pendingLegacyContextWarning = new Map();
};
var warnedAboutMissingGetChildContext, rendererSigil, valueCursor = createCursor(null);
var valueCursor = createCursor(null);
rendererSigil = {
};
var currentlyRenderingFiber = null, lastContextDependency = null, lastContextWithAllBitsObserved = null, isDisallowedContextReadInDEV = !1;
@ -5336,7 +5334,7 @@
}
return context._currentValue;
}
var didWarnUpdateInsideUpdate, currentlyProcessingQueue, ForceUpdate = 2, hasForceUpdate = !1;
var ForceUpdate = 2, hasForceUpdate = !1;
function initializeUpdateQueue(fiber) {
var queue = {
baseState: fiber.memoizedState,
@ -5530,7 +5528,7 @@
}
}
didWarnUpdateInsideUpdate = !1, currentlyProcessingQueue = null;
var didWarnAboutStateAssignmentForComponent, didWarnAboutUninitializedState, didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate, didWarnAboutLegacyLifecyclesAndDerivedState, didWarnAboutUndefinedDerivedState, warnOnUndefinedDerivedState, warnOnInvalidCallback, didWarnAboutDirectlyAssigningPropsToState, didWarnAboutContextTypeAndContextTypes, didWarnAboutInvalidateContextType, fakeInternalInstance = {
var fakeInternalInstance = {
}, isArray = Array.isArray, emptyRefsObject = new React.Component().refs;
didWarnAboutStateAssignmentForComponent = new Set(), didWarnAboutUninitializedState = new Set(), didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set(), didWarnAboutLegacyLifecyclesAndDerivedState = new Set(), didWarnAboutDirectlyAssigningPropsToState = new Set(), didWarnAboutUndefinedDerivedState = new Set(), didWarnAboutContextTypeAndContextTypes = new Set(), didWarnAboutInvalidateContextType = new Set();
var didWarnOnInvalidCallback = new Set();
@ -5710,7 +5708,7 @@
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);
return shouldUpdate ? (hasNewLifecycles || "function" != typeof instance.UNSAFE_componentWillUpdate && "function" != typeof instance.componentWillUpdate || ("function" == typeof instance.componentWillUpdate && instance.componentWillUpdate(newProps, newState, nextContext), "function" == typeof instance.UNSAFE_componentWillUpdate && instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext)), "function" == typeof instance.componentDidUpdate && (workInProgress.flags |= Update), "function" == typeof instance.getSnapshotBeforeUpdate && (workInProgress.flags |= 256)) : ("function" == typeof instance.componentDidUpdate && (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) && (workInProgress.flags |= Update), "function" == typeof instance.getSnapshotBeforeUpdate && (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) && (workInProgress.flags |= 256), workInProgress.memoizedProps = newProps, workInProgress.memoizedState = newState), instance.props = newProps, instance.state = newState, instance.context = nextContext, shouldUpdate;
}
var didWarnAboutMaps, didWarnAboutGenerators, didWarnAboutStringRefs, ownerHasKeyUseWarning, ownerHasFunctionTypeWarning, warnForMissingKey = function(child, returnFiber) {
var warnForMissingKey = function(child, returnFiber) {
};
didWarnAboutMaps = !1, didWarnAboutGenerators = !1, didWarnAboutStringRefs = {
}, ownerHasKeyUseWarning = {
@ -6253,7 +6251,7 @@
function getIsHydrating() {
return isHydrating;
}
var rendererSigil$1, workInProgressSources = [];
var workInProgressSources = [];
function markSourceAsDirty(mutableSource) {
workInProgressSources.push(mutableSource);
}
@ -6272,7 +6270,7 @@
}
rendererSigil$1 = {
};
var didWarnAboutMismatchedHooksForComponent, didWarnAboutUseOpaqueIdentifier, ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher, ReactCurrentBatchConfig$1 = ReactSharedInternals.ReactCurrentBatchConfig;
var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher, ReactCurrentBatchConfig$1 = ReactSharedInternals.ReactCurrentBatchConfig;
didWarnAboutUseOpaqueIdentifier = {
}, didWarnAboutMismatchedHooksForComponent = new Set();
var renderLanes = NoLanes, currentlyRenderingFiber$1 = null, currentHook = null, workInProgressHook = null, didScheduleRenderPhaseUpdate = !1, didScheduleRenderPhaseUpdateDuringThisPass = !1, currentHookNameInDev = null, hookTypesDev = null, hookTypesUpdateIndexDev = -1, ignorePreviousDependencies = !1;
@ -7317,7 +7315,7 @@
function transferActualDuration(fiber) {
for(var child = fiber.child; child;)fiber.actualDuration += child.actualDuration, child = child.sibling;
}
var didWarnAboutBadClass, didWarnAboutModulePatternComponent, didWarnAboutContextTypeOnFunctionComponent, didWarnAboutGetDerivedStateOnFunctionComponent, didWarnAboutFunctionRefs, didWarnAboutReassigningProps, didWarnAboutRevealOrder, didWarnAboutTailOptions, ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner, didReceiveUpdate = !1;
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner, didReceiveUpdate = !1;
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
null === current ? workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes) : workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
@ -7326,10 +7324,10 @@
}
function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) {
if (workInProgress.type !== workInProgress.elementType) {
var innerPropTypes = Component.propTypes;
var nextChildren, innerPropTypes = Component.propTypes;
innerPropTypes && checkPropTypes(innerPropTypes, nextProps, "prop", getComponentName(Component));
}
var nextChildren, render = Component.render, ref = workInProgress.ref;
var render = Component.render, ref = workInProgress.ref;
if (prepareToReadContext(workInProgress, renderLanes), ReactCurrentOwner$1.current = workInProgress, setIsRendering(!0), nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes), 1 & workInProgress.mode) {
disableLogs();
try {
@ -7386,7 +7384,7 @@
function updateOffscreenComponent(current, workInProgress, renderLanes) {
var _subtreeRenderLanes, nextProps = workInProgress.pendingProps, nextChildren = nextProps.children, prevState = null !== current ? current.memoizedState : null;
if ("hidden" === nextProps.mode || "unstable-defer-without-hiding" === nextProps.mode) if ((4 & workInProgress.mode) == 0) {
var nextState = {
var nextBaseLanes, nextState = {
baseLanes: NoLanes
};
workInProgress.memoizedState = nextState, pushRenderLanes(workInProgress, renderLanes);
@ -7397,7 +7395,7 @@
workInProgress.memoizedState = _nextState2, pushRenderLanes(workInProgress, null !== prevState ? prevState.baseLanes : renderLanes);
} else {
nextBaseLanes = null !== prevState ? mergeLanes(prevState.baseLanes, renderLanes) : renderLanes, markSpawnedWork(1073741824), workInProgress.lanes = workInProgress.childLanes = laneToLanes(1073741824);
var nextBaseLanes, _nextState = {
var _nextState = {
baseLanes: nextBaseLanes
};
return workInProgress.memoizedState = _nextState, pushRenderLanes(workInProgress, nextBaseLanes), null;
@ -7530,7 +7528,7 @@
}
function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
null !== _current && (_current.alternate = null, workInProgress.alternate = null, workInProgress.flags |= Placement);
var value, context, props = workInProgress.pendingProps;
var context, value, props = workInProgress.pendingProps;
if (context = getMaskedContext(workInProgress, getUnmaskedContext(workInProgress, Component, !1)), prepareToReadContext(workInProgress, renderLanes), Component.prototype && "function" == typeof Component.prototype.render) {
var componentName = getComponentName(Component) || "Unknown";
didWarnAboutBadClass[componentName] || (error("The <%s /> component appears to have a render method, but doesn't extend React.Component. This is likely to cause errors. Change %s to extend React.Component instead.", componentName, componentName), didWarnAboutBadClass[componentName] = !0);
@ -7648,7 +7646,7 @@
return (2 & workInProgress.mode) == 0 && (primaryChildFragment.lanes = renderLanes), primaryChildFragment.return = workInProgress, primaryChildFragment.sibling = null, null !== currentFallbackChildFragment && (currentFallbackChildFragment.nextEffect = null, currentFallbackChildFragment.flags = Deletion, workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment), workInProgress.child = primaryChildFragment, primaryChildFragment;
}
function updateSuspenseFallbackChildren(current, workInProgress, primaryChildren, fallbackChildren, renderLanes) {
var fallbackChildFragment, primaryChildFragment, mode = workInProgress.mode, currentPrimaryChildFragment = current.child, currentFallbackChildFragment = currentPrimaryChildFragment.sibling, primaryChildProps = {
var primaryChildFragment, fallbackChildFragment, mode = workInProgress.mode, currentPrimaryChildFragment = current.child, currentFallbackChildFragment = currentPrimaryChildFragment.sibling, primaryChildProps = {
mode: "hidden",
children: primaryChildren
};
@ -7794,11 +7792,11 @@
}
var hasWarnedAboutUsingContextAsConsumer = !1;
function updateContextConsumer(current, workInProgress, renderLanes) {
var context = workInProgress.type;
var newChildren, context = workInProgress.type;
void 0 === context._context ? context === context.Consumer || hasWarnedAboutUsingContextAsConsumer || (hasWarnedAboutUsingContextAsConsumer = !0, error("Rendering <Context> directly is not supported and will be removed in a future major release. Did you mean to render <Context.Consumer> instead?")) : context = context._context;
var newProps = workInProgress.pendingProps, render = newProps.children;
"function" != typeof render && error("A context consumer was rendered with multiple children, or a child that isn't a function. A context consumer expects a single child that is a function. If you did pass a function, make sure there is no trailing or leading whitespace around it."), prepareToReadContext(workInProgress, renderLanes);
var newChildren, newValue = readContext(context, newProps.unstable_observedBits);
var newValue = readContext(context, newProps.unstable_observedBits);
return ReactCurrentOwner$1.current = workInProgress, setIsRendering(!0), newChildren = render(newValue), setIsRendering(!1), workInProgress.flags |= 1, reconcileChildren(current, workInProgress, newChildren, renderLanes), workInProgress.child;
}
function markWorkInProgressReceivedUpdate() {
@ -8145,16 +8143,17 @@
}
function logCapturedError(boundary, errorInfo) {
try {
if (!1 === showErrorDialog(boundary, errorInfo)) return;
var logError = showErrorDialog(boundary, errorInfo);
if (!1 === logError) return;
var error = errorInfo.value;
if (0) console.error(error);
else {
var source = errorInfo.source, stack = errorInfo.stack, componentStack = null !== stack ? stack : "";
var errorBoundaryMessage, source = errorInfo.source, stack = errorInfo.stack, componentStack = null !== stack ? stack : "";
if (null != error && error._suppressLogging) {
if (1 === boundary.tag) return;
console.error(error);
}
var errorBoundaryMessage, componentName = source ? getComponentName(source.type) : null, componentNameMessage = componentName ? "The above error occurred in the <" + componentName + "> component:" : "The above error occurred in one of your React components:", errorBoundaryName = getComponentName(boundary.type);
var componentName = source ? getComponentName(source.type) : null, componentNameMessage = componentName ? "The above error occurred in the <" + componentName + "> component:" : "The above error occurred in one of your React components:", errorBoundaryName = getComponentName(boundary.type);
errorBoundaryMessage = errorBoundaryName ? "React will try to recreate this component tree from scratch " + ("using the error boundary you provided, " + errorBoundaryName + ".") : "Consider adding an error boundary to your tree to customize error handling behavior.\nVisit https://reactjs.org/link/error-boundaries to learn more about error boundaries.";
var combinedMessage = componentNameMessage + "\n" + componentStack + "\n\n" + ("" + errorBoundaryMessage);
console.error(combinedMessage);
@ -8832,9 +8831,8 @@
}
function performSyncWorkOnRoot(root) {
if ((48 & executionContext) != 0) throw Error("Should not already be working.");
var lanes, exitStatus;
if (flushPassiveEffects(), root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes) ? (exitStatus = renderRootSync(root, lanes = workInProgressRootRenderLanes), includesSomeLane(workInProgressRootIncludedLanes, workInProgressRootUpdatedLanes) && (exitStatus = renderRootSync(root, lanes = getNextLanes(root, lanes)))) : exitStatus = renderRootSync(root, lanes = getNextLanes(root, NoLanes)), 0 !== root.tag && 2 === exitStatus && (executionContext |= 64, root.hydrate && (root.hydrate = !1, clearContainer(root.containerInfo)), (lanes = getLanesToRetrySynchronouslyOnError(root)) !== NoLanes && (exitStatus = renderRootSync(root, lanes))), 1 === exitStatus) {
var fatalError = workInProgressRootFatalError;
var lanes, exitStatus, fatalError = workInProgressRootFatalError;
throw prepareFreshStack(root, NoLanes), markRootSuspended$1(root, lanes), ensureRootIsScheduled(root, now()), fatalError;
}
var finishedWork = root.current.alternate;
@ -9023,13 +9021,13 @@
do flushPassiveEffects();
while (null !== rootWithPendingPassiveEffects)
if (flushRenderPhaseStrictModeWarningsInDEV(), (48 & executionContext) != 0) throw Error("Should not already be working.");
var finishedWork = root.finishedWork, lanes = root.finishedLanes;
var firstEffect, finishedWork = root.finishedWork, lanes = root.finishedLanes;
if (null === finishedWork) return null;
if (root.finishedWork = null, root.finishedLanes = NoLanes, !(finishedWork !== root.current)) throw Error("Cannot commit the same tree as before. This error is likely caused by a bug in React. Please file an issue.");
root.callbackNode = null;
var remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
if (markRootFinished(root, remainingLanes), null !== rootsWithPendingDiscreteUpdates && !hasDiscreteLanes(remainingLanes) && rootsWithPendingDiscreteUpdates.has(root) && rootsWithPendingDiscreteUpdates.delete(root), root === workInProgressRoot && (workInProgressRoot = null, workInProgress = null, workInProgressRootRenderLanes = NoLanes), finishedWork.flags > 1 ? null !== finishedWork.lastEffect ? (finishedWork.lastEffect.nextEffect = finishedWork, firstEffect = finishedWork.firstEffect) : firstEffect = finishedWork : firstEffect = finishedWork.firstEffect, null !== firstEffect) {
var firstEffect, prevExecutionContext = executionContext;
var prevExecutionContext = executionContext;
executionContext |= 32;
var prevInteractions = pushInteractions(root);
ReactCurrentOwner$2.current = null, focusedInstanceHandle = prepareForCommit(root.containerInfo), shouldFireAfterActiveInstanceBlur = !1, nextEffect = firstEffect;
@ -9300,7 +9298,7 @@
throw originalError;
}
};
var appendAllChildren, updateHostContainer, updateHostComponent$1, updateHostText$1, beginWork$1, didWarnAboutUpdateInRenderForAnotherComponent, didWarnAboutUpdateInRender = !1;
var didWarnAboutUpdateInRender = !1;
function warnAboutRenderPhaseUpdatesInDEV(fiber) {
if (isRendering && (16 & executionContext) !== 0 && !getIsUpdatingOpaqueValueInRenderPhaseInDEV()) switch(fiber.tag){
case 0:
@ -9393,7 +9391,7 @@
function finishPendingInteractions(root, committedLanes) {
var subscriber, remainingLanesAfterCommit = root.pendingLanes;
try {
if (null !== (subscriber = __subscriberRef.current) && root.memoizedInteractions.size > 0) {
if (subscriber = __subscriberRef.current, null !== subscriber && root.memoizedInteractions.size > 0) {
var threadID = computeThreadID(root, committedLanes);
subscriber.onWorkStopped(root.memoizedInteractions, threadID);
}
@ -9924,7 +9922,7 @@
unmarkContainerAsRoot(container);
});
};
var hasBadMapPolyfill, didWarnAboutNestedUpdates, didWarnAboutFindNodeInStrictMode, topLevelUpdateWarnings, ReactCurrentOwner$3 = ReactSharedInternals.ReactCurrentOwner, warnedAboutHydrateAPI = !1;
var ReactCurrentOwner$3 = ReactSharedInternals.ReactCurrentOwner, warnedAboutHydrateAPI = !1;
function getReactRootElementInContainer(container) {
return container ? 9 === container.nodeType ? container.documentElement : container.firstChild : null;
}
@ -10075,12 +10073,7 @@
var hostFiber = findCurrentHostFiber(fiber);
return null === hostFiber ? null : hostFiber.stateNode;
},
findFiberByHostInstance: (devToolsConfig = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 1,
version: "17.0.2",
rendererPackageName: "react-dom"
}).findFiberByHostInstance || function(instance) {
findFiberByHostInstance: devToolsConfig.findFiberByHostInstance || function(instance) {
return null;
},
findHostInstancesForRefresh: function(root, families) {

View File

@ -1296,6 +1296,7 @@ var YUI = function() {
"yui-base"
]
}), YUI.add("loader-base", function(Y, NAME) {
var VERSION, CDN_BASE, COMBO_BASE, META, groups, yui2Update, galleryUpdate;
VERSION = Y.version, COMBO_BASE = (CDN_BASE = Y.Env.base) + "combo?", groups = (META = {
version: VERSION,
root: VERSION + "/",
@ -1356,7 +1357,7 @@ var YUI = function() {
"groups",
"skin"
], 0, !0), YUI.Env[VERSION] = META;
var VERSION, CDN_BASE, COMBO_BASE, META, groups, yui2Update, galleryUpdate, modulekey, NOT_FOUND = {
var modulekey, NOT_FOUND = {
}, NO_REQUIREMENTS = [], GLOBAL_ENV = YUI.Env, GLOBAL_LOADED = GLOBAL_ENV._loaded, VERSION1 = Y.version, YObject = Y.Object, oeach = YObject.each, yArray = Y.Array, _queue = GLOBAL_ENV._loaderQueue, META1 = GLOBAL_ENV[VERSION1], L = Y.Lang, ON_PAGE = GLOBAL_ENV.mods, _path = function(dir, file, type, nomin) {
var path = dir + "/" + file;
return nomin || (path += "-min"), path += "." + (type || "css");

View File

@ -1,3 +1,5 @@
function x() {
if (FOO) var var1, var2;
if (FOO) {
let let1, let2;
}
}