mirror of
https://github.com/swc-project/swc.git
synced 2024-12-28 16:13:34 +03:00
feat(es/minifier): Implement more rules (#1766)
swc_ecma_minifier: - Implement more rules. swc_ecma_transforms_base: - `resolver`: Fix syntax context of catch block. swc_ecma_transforms_optimization: - `expr_simplifier`: Fix a bug related to `this` of call expressions. - `dead_branch_remover`: Don't reduce switch cases if test is now known. - `dead_branch_remover`: Don't break `&&`.
This commit is contained in:
parent
f8a3df8cc3
commit
33a43f85b1
@ -6,7 +6,7 @@ edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_ecmascript"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@ -31,7 +31,7 @@ typescript = ["swc_ecma_transforms/typescript"]
|
||||
swc_ecma_ast = {version = "0.47.0", path = "./ast"}
|
||||
swc_ecma_codegen = {version = "0.60.0", path = "./codegen", optional = true}
|
||||
swc_ecma_dep_graph = {version = "0.29.0", path = "./dep-graph", optional = true}
|
||||
swc_ecma_minifier = {version = "0.9.0", path = "./minifier", optional = true}
|
||||
swc_ecma_minifier = {version = "0.10.0", path = "./minifier", optional = true}
|
||||
swc_ecma_parser = {version = "0.61.0", path = "./parser", optional = true}
|
||||
swc_ecma_transforms = {version = "0.57.0", path = "./transforms", optional = true}
|
||||
swc_ecma_utils = {version = "0.38.0", path = "./utils", optional = true}
|
||||
|
@ -3,12 +3,13 @@ use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::text_writer::omit_trailing_semi;
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
fmt::Debug,
|
||||
io::Write,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use swc_common::{comments::SingleThreadedComments, FileName, SourceMap};
|
||||
use swc_ecma_parser;
|
||||
use testing::DebugUsingDisplay;
|
||||
|
||||
struct Builder {
|
||||
cfg: Config,
|
||||
@ -649,12 +650,3 @@ impl Write for Buf {
|
||||
self.0.write().unwrap().flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct DebugUsingDisplay<'a>(&'a str);
|
||||
|
||||
impl<'a> Debug for DebugUsingDisplay<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self.0, f)
|
||||
}
|
||||
}
|
||||
|
@ -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.9.0"
|
||||
version = "0.10.0"
|
||||
|
||||
[features]
|
||||
debug = []
|
||||
|
@ -4,7 +4,10 @@
|
||||
# Note that this is append-only.
|
||||
set -eu
|
||||
|
||||
export SWC_RUN=0
|
||||
|
||||
cargo test --test compress --all-features \
|
||||
| grep 'terser__compress' \
|
||||
| grep 'js .\.\. ok$' \
|
||||
| sed -e 's!test fixture_terser__compress__!!' \
|
||||
| sed -e 's! ... ok!!' \
|
||||
|
8
ecmascript/minifier/scripts/base.sh
Executable file
8
ecmascript/minifier/scripts/base.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
export RUST_LOG=swc_ecma_minifier=trace
|
||||
|
||||
# To prevent regression, we run base test before real tests.
|
||||
touch tests/compress.rs
|
||||
cargo test --test compress ${1-base_exec} --all-features
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
export SWC_RUN=0
|
||||
|
||||
cargo test --test compress --all-features ${1-''} \
|
||||
| grep 'terser__compress' \
|
||||
| grep 'js .\.\. FAILED$' \
|
||||
|
@ -10,6 +10,7 @@ find tests/compress/exec -name output.js | xargs -L 1 rm
|
||||
find tests/compress/exec -name expected.stdout | xargs -L 1 rm
|
||||
find tests/compress/exec -name output.terser.js | xargs -L 1 rm
|
||||
find tests/compress/exec -name mangle.json | xargs -L 1 rm
|
||||
find tests/compress/exec -empty -type d -delete
|
||||
|
||||
cargo test --test compress --all-features ${1-''} \
|
||||
| grep 'terser__compress' \
|
||||
@ -18,6 +19,10 @@ cargo test --test compress --all-features ${1-''} \
|
||||
| sed -e 's! ... FAILED!!' \
|
||||
| sed -e 's!__!/!g' \
|
||||
| sed -e 's!_js!.js!' \
|
||||
>> tests/postponed.txt
|
||||
>> tests/postponed_candidates.txt
|
||||
|
||||
|
||||
comm -23 tests/postponed_candidates.txt tests/golden.txt >> tests/postponed.txt
|
||||
rm tests/postponed_candidates.txt
|
||||
|
||||
./scripts/sort.sh
|
@ -15,7 +15,8 @@ export RUST_LOG=swc_ecma_minifier=trace
|
||||
|
||||
# To prevent regression, we run base test before real tests.
|
||||
touch tests/compress.rs
|
||||
cargo test --test compress base_exec --all-features
|
||||
UPDATE=1 ./scripts/base.sh base_fixture
|
||||
./scripts/base.sh base_exec
|
||||
|
||||
if [ -z "$@" ]; then
|
||||
./scripts/sort.sh
|
||||
|
@ -5,6 +5,7 @@ find tests/compress/exec -name output.js | xargs -L 1 rm
|
||||
find tests/compress/exec -name expected.stdout | xargs -L 1 rm
|
||||
find tests/compress/exec -name output.terser.js | xargs -L 1 rm
|
||||
find tests/compress/exec -name mangle.json | xargs -L 1 rm
|
||||
find tests/compress/exec -empty -type d -delete
|
||||
|
||||
./scripts/run.sh \
|
||||
| grep 'base_exec_compress__exec__terser__' \
|
||||
|
@ -1,8 +1,7 @@
|
||||
use swc_ecma_ast::VarDeclKind;
|
||||
|
||||
use super::UsageAnalyzer;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use swc_ecma_ast::VarDeclKind;
|
||||
|
||||
impl UsageAnalyzer {
|
||||
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx {
|
||||
@ -22,6 +21,15 @@ pub(super) struct Ctx {
|
||||
pub in_pat_of_var_decl_with_init: bool,
|
||||
pub in_pat_of_param: bool,
|
||||
pub in_catch_param: bool,
|
||||
/// `true` for `foo.bar` and `false` for `foo` in `foo.bar++`
|
||||
pub is_exact_reassignment: bool,
|
||||
|
||||
/// `true` for arugments of [swc_ecma_ast::Expr::Call] or
|
||||
/// [swc_ecma_ast::Expr::New]
|
||||
pub in_call_arg: bool,
|
||||
|
||||
/// `false` for `array` in `array.length.
|
||||
pub is_exact_arg: bool,
|
||||
|
||||
pub in_left_of_for_loop: bool,
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
use self::ctx::Ctx;
|
||||
use crate::util::can_end_conditionally;
|
||||
use crate::util::idents_used_by;
|
||||
use fxhash::FxHashMap;
|
||||
use fxhash::FxHashSet;
|
||||
use std::collections::hash_map::Entry;
|
||||
@ -6,6 +8,7 @@ use swc_atoms::JsWord;
|
||||
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_type;
|
||||
@ -28,14 +31,14 @@ where
|
||||
};
|
||||
n.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||
let top_scope = v.scope;
|
||||
v.data.top.merge(top_scope);
|
||||
v.data.top.merge(top_scope, false);
|
||||
|
||||
v.data
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct VarUsageInfo {
|
||||
/// # of reference to this identifier.
|
||||
/// The number of reference to this identifier.
|
||||
pub ref_count: usize,
|
||||
|
||||
/// `true` if a varaible is conditionally initialized.
|
||||
@ -43,14 +46,19 @@ pub(crate) struct VarUsageInfo {
|
||||
|
||||
/// `false` if it's only used.
|
||||
pub declared: bool,
|
||||
pub declared_count: usize,
|
||||
|
||||
/// `true` if the enclosing function defines this variable as a parameter.
|
||||
pub declared_as_fn_param: bool,
|
||||
|
||||
pub assign_count: usize,
|
||||
pub mutation_by_call_count: usize,
|
||||
pub usage_count: usize,
|
||||
|
||||
/// The variable itself is modified.
|
||||
pub reassigned: bool,
|
||||
/// The variable itself or a property of it is modified.
|
||||
pub mutated: bool,
|
||||
|
||||
pub has_property_access: bool,
|
||||
pub accessed_props: FxHashSet<JsWord>,
|
||||
@ -66,6 +74,7 @@ pub(crate) struct VarUsageInfo {
|
||||
pub used_in_loop: bool,
|
||||
|
||||
pub var_kind: Option<VarDeclKind>,
|
||||
pub var_initialized: bool,
|
||||
|
||||
pub declared_as_catch_param: bool,
|
||||
|
||||
@ -78,6 +87,12 @@ pub(crate) struct VarUsageInfo {
|
||||
infects: Vec<Id>,
|
||||
}
|
||||
|
||||
impl VarUsageInfo {
|
||||
pub fn is_mutated_only_by_one_call(&self) -> bool {
|
||||
self.assign_count == 0 && self.mutation_by_call_count == 1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ScopeKind {
|
||||
Fn,
|
||||
@ -88,12 +103,21 @@ enum ScopeKind {
|
||||
pub(crate) struct ScopeData {
|
||||
pub has_with_stmt: bool,
|
||||
pub has_eval_call: bool,
|
||||
|
||||
/// Variables declared in the scope.
|
||||
pub declared_symbols: FxHashMap<JsWord, FxHashSet<SyntaxContext>>,
|
||||
}
|
||||
|
||||
impl ScopeData {
|
||||
fn merge(&mut self, other: ScopeData) {
|
||||
fn merge(&mut self, other: ScopeData, is_child: bool) {
|
||||
self.has_with_stmt |= other.has_with_stmt;
|
||||
self.has_eval_call |= other.has_eval_call;
|
||||
|
||||
if !is_child {
|
||||
for (k, v) in other.declared_symbols {
|
||||
self.declared_symbols.entry(k).or_default().extend(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,15 +129,18 @@ pub(crate) struct ProgramData {
|
||||
pub top: ScopeData,
|
||||
|
||||
pub scopes: FxHashMap<SyntaxContext, ScopeData>,
|
||||
|
||||
/// { dependant: [dependendcies] }
|
||||
var_deps: FxHashMap<Id, FxHashSet<Id>>,
|
||||
}
|
||||
|
||||
impl ProgramData {
|
||||
fn merge(&mut self, kind: ScopeKind, child: ProgramData) {
|
||||
for (ctxt, scope) in child.scopes {
|
||||
let to = self.scopes.entry(ctxt).or_default();
|
||||
self.top.merge(scope.clone());
|
||||
self.top.merge(scope.clone(), true);
|
||||
|
||||
to.merge(scope);
|
||||
to.merge(scope, false);
|
||||
}
|
||||
|
||||
for (id, var_info) in child.vars {
|
||||
@ -121,11 +148,15 @@ impl ProgramData {
|
||||
Entry::Occupied(mut e) => {
|
||||
e.get_mut().ref_count += var_info.ref_count;
|
||||
e.get_mut().cond_init |= var_info.cond_init;
|
||||
|
||||
e.get_mut().reassigned |= var_info.reassigned;
|
||||
e.get_mut().mutated |= var_info.mutated;
|
||||
|
||||
e.get_mut().has_property_access |= var_info.has_property_access;
|
||||
e.get_mut().exported |= var_info.exported;
|
||||
|
||||
e.get_mut().declared |= var_info.declared;
|
||||
e.get_mut().declared_count += var_info.declared_count;
|
||||
e.get_mut().declared_as_fn_param |= var_info.declared_as_fn_param;
|
||||
|
||||
// If a var is registered at a parent scope, it means that it's delcared before
|
||||
@ -134,6 +165,7 @@ impl ProgramData {
|
||||
// e.get_mut().used_above_decl |= var_info.used_above_decl;
|
||||
e.get_mut().used_in_loop |= var_info.used_in_loop;
|
||||
e.get_mut().assign_count += var_info.assign_count;
|
||||
e.get_mut().mutation_by_call_count += var_info.mutation_by_call_count;
|
||||
e.get_mut().usage_count += var_info.usage_count;
|
||||
|
||||
e.get_mut().declared_as_catch_param |= var_info.declared_as_catch_param;
|
||||
@ -153,6 +185,12 @@ impl ProgramData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: Make this recursive
|
||||
#[allow(unused)]
|
||||
pub fn deps(&self, id: &Id) -> FxHashSet<Id> {
|
||||
self.var_deps.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// This assumes there are no two variable with same name and same span hygiene.
|
||||
@ -175,42 +213,52 @@ impl UsageAnalyzer {
|
||||
};
|
||||
|
||||
let ret = op(&mut child);
|
||||
{
|
||||
let child_scope = child.data.scopes.entry(child_ctxt).or_default();
|
||||
|
||||
child
|
||||
.data
|
||||
.scopes
|
||||
.entry(child_ctxt)
|
||||
.or_default()
|
||||
.merge(child.scope);
|
||||
child_scope.merge(child.scope, false);
|
||||
}
|
||||
|
||||
self.data.merge(kind, child.data);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn report(&mut self, i: Id, is_assign: bool) {
|
||||
fn report(&mut self, i: Id, is_modify: bool, dejavu: &mut FxHashSet<Id>) {
|
||||
let is_first = dejavu.is_empty();
|
||||
|
||||
if !dejavu.insert(i.clone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let e = self.data.vars.entry(i).or_insert_with(|| VarUsageInfo {
|
||||
used_above_decl: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
e.ref_count += 1;
|
||||
e.reassigned |= is_assign;
|
||||
e.reassigned |= is_first && is_modify && self.ctx.is_exact_reassignment;
|
||||
// Passing object as a argument is possibly modification.
|
||||
e.mutated |= is_modify || (self.ctx.in_call_arg && self.ctx.is_exact_arg);
|
||||
e.used_in_loop |= self.ctx.in_loop;
|
||||
|
||||
if is_assign {
|
||||
if is_modify && self.ctx.is_exact_reassignment {
|
||||
e.assign_count += 1;
|
||||
|
||||
for other in e.infects.clone() {
|
||||
self.report(other, true)
|
||||
self.report(other, true, dejavu)
|
||||
}
|
||||
} else {
|
||||
if self.ctx.in_call_arg && self.ctx.is_exact_arg {
|
||||
e.mutation_by_call_count += 1;
|
||||
}
|
||||
|
||||
e.usage_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn report_usage(&mut self, i: &Ident, is_assign: bool) {
|
||||
self.report(i.to_id(), is_assign)
|
||||
self.report(i.to_id(), is_assign, &mut Default::default())
|
||||
}
|
||||
|
||||
fn declare_decl(
|
||||
@ -223,12 +271,26 @@ impl UsageAnalyzer {
|
||||
.data
|
||||
.vars
|
||||
.entry(i.to_id())
|
||||
.and_modify(|v| {
|
||||
if has_init {
|
||||
v.mutated = true;
|
||||
v.reassigned = true;
|
||||
v.assign_count += 1;
|
||||
}
|
||||
})
|
||||
.or_insert_with(|| VarUsageInfo {
|
||||
is_fn_local: true,
|
||||
var_kind: kind,
|
||||
var_initialized: has_init,
|
||||
..Default::default()
|
||||
});
|
||||
self.scope
|
||||
.declared_symbols
|
||||
.entry(i.sym.clone())
|
||||
.or_default()
|
||||
.insert(i.span.ctxt);
|
||||
|
||||
v.declared_count += 1;
|
||||
v.declared = true;
|
||||
if self.ctx.in_cond && has_init {
|
||||
v.cond_init = true;
|
||||
@ -268,6 +330,7 @@ impl Visit for UsageAnalyzer {
|
||||
fn visit_assign_expr(&mut self, n: &AssignExpr, _: &dyn Node) {
|
||||
let ctx = Ctx {
|
||||
in_assign_lhs: true,
|
||||
is_exact_reassignment: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.left.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
@ -282,7 +345,15 @@ impl Visit for UsageAnalyzer {
|
||||
}
|
||||
|
||||
fn visit_call_expr(&mut self, n: &CallExpr, _: &dyn Node) {
|
||||
n.visit_children_with(self);
|
||||
{
|
||||
n.callee.visit_with(n, self);
|
||||
let ctx = Ctx {
|
||||
in_call_arg: true,
|
||||
is_exact_arg: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.args.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
match &n.callee {
|
||||
ExprOrSuper::Expr(callee) => match &**callee {
|
||||
@ -323,6 +394,7 @@ impl Visit for UsageAnalyzer {
|
||||
fn visit_do_while_stmt(&mut self, n: &DoWhileStmt, _: &dyn Node) {
|
||||
let ctx = Ctx {
|
||||
in_loop: true,
|
||||
in_cond: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.visit_children_with(&mut *self.with_ctx(ctx));
|
||||
@ -355,12 +427,14 @@ impl Visit for UsageAnalyzer {
|
||||
self.with_child(n.span.ctxt, ScopeKind::Block, |child| {
|
||||
let ctx = Ctx {
|
||||
in_left_of_for_loop: true,
|
||||
is_exact_reassignment: true,
|
||||
..child.ctx
|
||||
};
|
||||
n.left.visit_with(n, &mut *child.with_ctx(ctx));
|
||||
|
||||
let ctx = Ctx {
|
||||
in_loop: true,
|
||||
in_cond: true,
|
||||
..child.ctx
|
||||
};
|
||||
n.body.visit_with(n, &mut *child.with_ctx(ctx))
|
||||
@ -373,12 +447,14 @@ impl Visit for UsageAnalyzer {
|
||||
self.with_child(n.span.ctxt, ScopeKind::Block, |child| {
|
||||
let ctx = Ctx {
|
||||
in_left_of_for_loop: true,
|
||||
is_exact_reassignment: true,
|
||||
..child.ctx
|
||||
};
|
||||
n.left.visit_with(n, &mut *child.with_ctx(ctx));
|
||||
|
||||
let ctx = Ctx {
|
||||
in_loop: true,
|
||||
in_cond: true,
|
||||
..child.ctx
|
||||
};
|
||||
n.body.visit_with(n, &mut *child.with_ctx(ctx))
|
||||
@ -390,6 +466,7 @@ impl Visit for UsageAnalyzer {
|
||||
|
||||
let ctx = Ctx {
|
||||
in_loop: true,
|
||||
in_cond: true,
|
||||
..self.ctx
|
||||
};
|
||||
|
||||
@ -427,10 +504,24 @@ impl Visit for UsageAnalyzer {
|
||||
}
|
||||
|
||||
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
|
||||
e.obj.visit_with(&Invalid { span: DUMMY_SP }, self);
|
||||
{
|
||||
let ctx = Ctx {
|
||||
is_exact_arg: false,
|
||||
is_exact_reassignment: false,
|
||||
..self.ctx
|
||||
};
|
||||
e.obj
|
||||
.visit_with(&Invalid { span: DUMMY_SP }, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
if e.computed {
|
||||
e.prop.visit_with(&Invalid { span: DUMMY_SP }, self);
|
||||
let ctx = Ctx {
|
||||
is_exact_arg: false,
|
||||
is_exact_reassignment: false,
|
||||
..self.ctx
|
||||
};
|
||||
e.prop
|
||||
.visit_with(&Invalid { span: DUMMY_SP }, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
match &e.obj {
|
||||
@ -460,6 +551,18 @@ impl Visit for UsageAnalyzer {
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_new_expr(&mut self, n: &NewExpr, _: &dyn Node) {
|
||||
{
|
||||
n.callee.visit_with(n, self);
|
||||
let ctx = Ctx {
|
||||
in_call_arg: true,
|
||||
is_exact_arg: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.args.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_param(&mut self, n: &Param, _: &dyn Node) {
|
||||
let ctx = Ctx {
|
||||
in_pat_of_param: false,
|
||||
@ -469,6 +572,7 @@ impl Visit for UsageAnalyzer {
|
||||
|
||||
let ctx = Ctx {
|
||||
in_pat_of_param: true,
|
||||
var_decl_kind_of_pat: None,
|
||||
..self.ctx
|
||||
};
|
||||
n.pat.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
@ -501,6 +605,7 @@ impl Visit for UsageAnalyzer {
|
||||
|
||||
if in_left_of_for_loop {
|
||||
v.reassigned = true;
|
||||
v.mutated = true;
|
||||
}
|
||||
} else {
|
||||
self.report_usage(&i.id, true);
|
||||
@ -526,16 +631,18 @@ impl Visit for UsageAnalyzer {
|
||||
}
|
||||
|
||||
fn visit_setter_prop(&mut self, n: &SetterProp, _: &dyn Node) {
|
||||
n.key.visit_with(n, self);
|
||||
{
|
||||
let ctx = Ctx {
|
||||
in_pat_of_param: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.param.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
self.with_child(n.span.ctxt, ScopeKind::Fn, |a| {
|
||||
n.key.visit_with(n, a);
|
||||
{
|
||||
let ctx = Ctx {
|
||||
in_pat_of_param: true,
|
||||
..a.ctx
|
||||
};
|
||||
n.param.visit_with(n, &mut *a.with_ctx(ctx));
|
||||
}
|
||||
|
||||
n.body.visit_with(n, self);
|
||||
n.body.visit_with(n, a);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, n: &Stmt, _: &dyn Node) {
|
||||
@ -546,6 +653,33 @@ impl Visit for UsageAnalyzer {
|
||||
n.visit_children_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
fn visit_stmts(&mut self, stmts: &[Stmt], _: &dyn Node) {
|
||||
let mut had_cond = false;
|
||||
|
||||
for n in stmts {
|
||||
let ctx = Ctx {
|
||||
in_cond: self.ctx.in_cond || had_cond,
|
||||
..self.ctx
|
||||
};
|
||||
|
||||
n.visit_with(&Invalid { span: DUMMY_SP }, &mut *self.with_ctx(ctx));
|
||||
|
||||
had_cond |= can_end_conditionally(n);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_switch_case(&mut self, n: &SwitchCase, _: &dyn Node) {
|
||||
n.test.visit_with(n, self);
|
||||
|
||||
{
|
||||
let ctx = Ctx {
|
||||
in_cond: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.cons.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_try_stmt(&mut self, n: &TryStmt, _: &dyn Node) {
|
||||
let ctx = Ctx {
|
||||
in_cond: true,
|
||||
@ -558,6 +692,7 @@ impl Visit for UsageAnalyzer {
|
||||
fn visit_update_expr(&mut self, n: &UpdateExpr, _: &dyn Node) {
|
||||
let ctx = Ctx {
|
||||
in_update_arg: true,
|
||||
is_exact_reassignment: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.visit_children_with(&mut *self.with_ctx(ctx));
|
||||
@ -572,13 +707,24 @@ impl Visit for UsageAnalyzer {
|
||||
|
||||
for decl in &n.decls {
|
||||
match (&decl.name, decl.init.as_deref()) {
|
||||
(Pat::Ident(var), Some(Expr::Ident(init))) => {
|
||||
self.data
|
||||
.vars
|
||||
.entry(init.to_id())
|
||||
.or_default()
|
||||
.infects
|
||||
.push(var.to_id());
|
||||
(Pat::Ident(var), Some(init)) => {
|
||||
let used_idents = idents_used_by(init);
|
||||
|
||||
for id in used_idents {
|
||||
self.data
|
||||
.vars
|
||||
.entry(id.clone())
|
||||
.or_default()
|
||||
.infects
|
||||
.push(var.to_id());
|
||||
|
||||
self.data
|
||||
.vars
|
||||
.entry(var.to_id())
|
||||
.or_default()
|
||||
.infects
|
||||
.push(id);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -593,12 +739,24 @@ impl Visit for UsageAnalyzer {
|
||||
};
|
||||
e.name.visit_with(e, &mut *self.with_ctx(ctx));
|
||||
|
||||
let declared_names: Vec<Id> = find_ids(&e.name);
|
||||
let used_names = idents_used_by(&e.init);
|
||||
|
||||
for name in declared_names {
|
||||
self.data
|
||||
.var_deps
|
||||
.entry(name)
|
||||
.or_default()
|
||||
.extend(used_names.clone());
|
||||
}
|
||||
|
||||
e.init.visit_with(e, self);
|
||||
}
|
||||
|
||||
fn visit_while_stmt(&mut self, n: &WhileStmt, _: &dyn Node) {
|
||||
let ctx = Ctx {
|
||||
in_loop: true,
|
||||
in_cond: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.visit_children_with(&mut *self.with_ctx(ctx));
|
||||
|
@ -136,7 +136,7 @@ impl VisitMut for Compressor<'_> {
|
||||
panic!("Infinite loop detected")
|
||||
}
|
||||
|
||||
let start = if cfg!(debug_assertions) {
|
||||
let start = if cfg!(feature = "debug") {
|
||||
let start = dump(&*n);
|
||||
log::trace!("===== Start =====\n{}", start);
|
||||
start
|
||||
@ -150,10 +150,12 @@ impl VisitMut for Compressor<'_> {
|
||||
self.changed |= visitor.changed();
|
||||
if visitor.changed() {
|
||||
log::trace!("compressor: Simplified expressions");
|
||||
log::trace!("===== Simplified =====\n{}", dump(&*n));
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("===== Simplified =====\n{}", dump(&*n));
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) && !visitor.changed() {
|
||||
if cfg!(feature = "debug") && !visitor.changed() {
|
||||
let simplified = dump(&*n);
|
||||
if start != simplified {
|
||||
assert_eq!(
|
||||
@ -176,8 +178,27 @@ impl VisitMut for Compressor<'_> {
|
||||
}
|
||||
|
||||
if self.options.conditionals || self.options.dead_code {
|
||||
let start = if cfg!(feature = "debug") {
|
||||
dump(&*n)
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
|
||||
let mut v = dead_branch_remover();
|
||||
n.map_with_mut(|n| n.fold_with(&mut v));
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
let simplified = dump(&*n);
|
||||
|
||||
if start != simplified {
|
||||
log::trace!(
|
||||
"===== Removed dead branches =====\n{}\n==== ===== ===== ===== ======\n{}",
|
||||
start,
|
||||
simplified
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.changed |= v.changed();
|
||||
}
|
||||
|
||||
@ -223,7 +244,7 @@ impl VisitMut for Compressor<'_> {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct DebugUsingDisplay<'a>(&'a str);
|
||||
struct DebugUsingDisplay<'a>(pub &'a str);
|
||||
|
||||
impl<'a> Debug for DebugUsingDisplay<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
@ -1,11 +1,11 @@
|
||||
use std::iter::repeat_with;
|
||||
|
||||
use crate::analyzer::analyze;
|
||||
|
||||
use super::Optimizer;
|
||||
use crate::analyzer::analyze;
|
||||
use crate::compress::optimize::is_left_access_to_arguments;
|
||||
use std::iter::repeat_with;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::private_ident;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
use swc_ecma_visit::VisitMut;
|
||||
@ -53,7 +53,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if f.params.iter().any(|param| match param.pat {
|
||||
if f.params.iter().any(|param| match ¶m.pat {
|
||||
Pat::Ident(BindingIdent {
|
||||
id:
|
||||
Ident {
|
||||
@ -62,7 +62,12 @@ impl Optimizer<'_> {
|
||||
},
|
||||
..
|
||||
}) => true,
|
||||
Pat::Ident(..) => false,
|
||||
Pat::Ident(i) => self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|v| v.vars.get(&i.id.to_id()))
|
||||
.map(|v| v.declared_count >= 2)
|
||||
.unwrap_or(false),
|
||||
_ => true,
|
||||
}) {
|
||||
return;
|
||||
@ -82,6 +87,7 @@ impl Optimizer<'_> {
|
||||
params: &mut f.params,
|
||||
changed: false,
|
||||
keep_fargs: self.options.keep_fargs,
|
||||
prevent: false,
|
||||
};
|
||||
|
||||
// We visit body two time, to use simpler logic in `inject_params_if_required`
|
||||
@ -96,6 +102,7 @@ struct ArgReplacer<'a> {
|
||||
params: &'a mut Vec<Param>,
|
||||
changed: bool,
|
||||
keep_fargs: bool,
|
||||
prevent: bool,
|
||||
}
|
||||
|
||||
impl ArgReplacer<'_> {
|
||||
@ -126,7 +133,19 @@ impl ArgReplacer<'_> {
|
||||
impl VisitMut for ArgReplacer<'_> {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
if is_left_access_to_arguments(&n.left) {
|
||||
self.prevent = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_expr(&mut self, n: &mut Expr) {
|
||||
if self.prevent {
|
||||
return;
|
||||
}
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
match n {
|
||||
@ -197,6 +216,10 @@ impl VisitMut for ArgReplacer<'_> {
|
||||
}
|
||||
|
||||
fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
|
||||
if self.prevent {
|
||||
return;
|
||||
}
|
||||
|
||||
n.obj.visit_mut_with(self);
|
||||
|
||||
if n.computed {
|
||||
|
@ -339,6 +339,22 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
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::trace!("bools: `expr || false` => `expr` (in bool context)");
|
||||
self.changed = true;
|
||||
*n = *left.take();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let span = n.span();
|
||||
let v = n.as_pure_bool();
|
||||
|
@ -157,7 +157,7 @@ impl Optimizer<'_> {
|
||||
.and_then(|data| data.vars.get(&name.to_id()))
|
||||
{
|
||||
if var.usage_count != 1
|
||||
|| var.reassigned
|
||||
|| var.mutated
|
||||
|| !SimpleUsageFinder::find(&name.id, r)
|
||||
{
|
||||
return None;
|
||||
|
@ -311,7 +311,7 @@ impl Optimizer<'_> {
|
||||
op: op!("&&"),
|
||||
right: Box::new(alt.take()),
|
||||
}));
|
||||
self.compress_logical_exprs_as_bang_bang(&mut expr);
|
||||
self.compress_logical_exprs_as_bang_bang(&mut expr, true);
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: stmt.span,
|
||||
expr,
|
||||
@ -326,7 +326,7 @@ impl Optimizer<'_> {
|
||||
op: op!("||"),
|
||||
right: Box::new(alt.take()),
|
||||
}));
|
||||
self.compress_logical_exprs_as_bang_bang(&mut expr);
|
||||
self.compress_logical_exprs_as_bang_bang(&mut expr, false);
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: stmt.span,
|
||||
expr,
|
||||
|
@ -39,7 +39,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -320,7 +320,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -434,7 +434,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -755,7 +755,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -886,7 +886,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.is_delete_arg || self.ctx.is_update_arg || self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ impl Optimizer<'_> {
|
||||
if !self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.vars.get(&name.to_id()).map(|v| !v.reassigned))
|
||||
.and_then(|data| data.vars.get(&name.to_id()).map(|v| !v.mutated))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
|
@ -7,6 +7,7 @@ use fxhash::FxHashMap;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::replace;
|
||||
use std::mem::swap;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::pass::Either;
|
||||
use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
@ -159,12 +160,33 @@ impl Optimizer<'_> {
|
||||
for (idx, param) in params.iter().enumerate() {
|
||||
let arg = e.args.get(idx).map(|v| &v.expr);
|
||||
if let Pat::Ident(param) = ¶m {
|
||||
if let Some(usage) = self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.vars.get(¶m.to_id()))
|
||||
{
|
||||
if usage.reassigned {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(arg) = arg {
|
||||
let should_be_inlined = self.can_be_inlined_for_iife(arg);
|
||||
if should_be_inlined {
|
||||
log::trace!(
|
||||
"iife: Trying to inline {}{:?}",
|
||||
param.id.sym,
|
||||
param.id.span.ctxt
|
||||
);
|
||||
vars.insert(param.to_id(), arg.clone());
|
||||
}
|
||||
} else {
|
||||
log::trace!(
|
||||
"iife: Trying to inline {}{:?} (undefined)",
|
||||
param.id.sym,
|
||||
param.id.span.ctxt
|
||||
);
|
||||
|
||||
vars.insert(param.to_id(), undefined(param.span()));
|
||||
}
|
||||
}
|
||||
@ -172,9 +194,11 @@ impl Optimizer<'_> {
|
||||
|
||||
match find_body(callee) {
|
||||
Some(Either::Left(body)) => {
|
||||
log::debug!("inline: Inlining arguments");
|
||||
self.inline_vars_in_node(body, vars);
|
||||
}
|
||||
Some(Either::Right(body)) => {
|
||||
log::debug!("inline: Inlining arguments");
|
||||
self.inline_vars_in_node(body, vars);
|
||||
}
|
||||
_ => {}
|
||||
@ -328,6 +352,12 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(i) = &f.ident {
|
||||
if idents_used_by(&f.function.body).contains(&i.to_id()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if is_param_used_by_body(&f.function.params, &f.function.body) {
|
||||
return;
|
||||
}
|
||||
@ -354,8 +384,12 @@ impl Optimizer<'_> {
|
||||
|
||||
fn inline_fn_like(&mut self, body: &mut BlockStmt) -> Option<Expr> {
|
||||
if !body.stmts.iter().all(|stmt| match stmt {
|
||||
Stmt::Expr(e) if e.expr.is_await_expr() => false,
|
||||
|
||||
Stmt::Expr(..) => true,
|
||||
Stmt::Return(ReturnStmt { arg, .. }) => match arg.as_deref() {
|
||||
Some(Expr::Await(..)) => false,
|
||||
|
||||
Some(Expr::Lit(Lit::Num(..))) => {
|
||||
if self.ctx.in_obj_of_non_computed_member {
|
||||
false
|
||||
@ -497,5 +531,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
for (sym, _) in used {
|
||||
if sym == js_word!("arguments") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
use crate::compress::optimize::util::class_has_side_effect;
|
||||
use crate::compress::optimize::util::is_valid_for_lhs;
|
||||
|
||||
use super::Optimizer;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::Spanned;
|
||||
@ -21,6 +24,15 @@ impl Optimizer<'_> {
|
||||
let should_preserve = (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
&& self.ctx.in_top_level();
|
||||
|
||||
if self
|
||||
.data
|
||||
.as_ref()
|
||||
.map(|v| v.top.has_eval_call)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Check for side effect between original decl position and inlined
|
||||
// position
|
||||
|
||||
@ -52,7 +64,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.options.reduce_vars && self.options.typeofs && !usage.reassigned {
|
||||
if !usage.reassigned {
|
||||
match &**init {
|
||||
Expr::Fn(..) | Expr::Arrow(..) => {
|
||||
self.typeofs.insert(i.to_id(), js_word!("function"));
|
||||
@ -78,8 +90,9 @@ impl Optimizer<'_> {
|
||||
|| self.options.collapse_vars
|
||||
|| self.options.inline != 0;
|
||||
|
||||
// Mutation of properties are ok
|
||||
if is_inline_enabled
|
||||
&& !usage.reassigned
|
||||
&& (!usage.mutated || (usage.assign_count == 0 && !usage.reassigned))
|
||||
&& match &**init {
|
||||
Expr::Lit(lit) => match lit {
|
||||
Lit::Str(_)
|
||||
@ -123,7 +136,7 @@ impl Optimizer<'_> {
|
||||
// Single use => inlined
|
||||
if is_inline_enabled
|
||||
&& !should_preserve
|
||||
&& !usage.reassigned
|
||||
&& (!usage.mutated || usage.is_mutated_only_by_one_call())
|
||||
&& usage.ref_count == 1
|
||||
{
|
||||
match &**init {
|
||||
@ -146,11 +159,25 @@ impl Optimizer<'_> {
|
||||
}
|
||||
match &**init {
|
||||
Expr::Lit(Lit::Regex(..)) => return,
|
||||
|
||||
Expr::This(..) => {
|
||||
// Don't inline this if it passes function boundaries.
|
||||
if !usage.is_fn_local {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if init.may_have_side_effects() {
|
||||
// TODO: Inline partially
|
||||
return;
|
||||
if !self
|
||||
.vars_accessible_without_side_effect
|
||||
.contains(&i.to_id())
|
||||
{
|
||||
// TODO: Inline partially
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
@ -192,10 +219,6 @@ impl Optimizer<'_> {
|
||||
|
||||
/// Stores `typeof` of [ClassDecl] and [FnDecl].
|
||||
pub(super) fn store_typeofs(&mut self, decl: &mut Decl) {
|
||||
if !self.options.reduce_vars || !self.options.typeofs {
|
||||
return;
|
||||
}
|
||||
|
||||
let i = match &*decl {
|
||||
Decl::Class(v) => v.ident.clone(),
|
||||
Decl::Fn(f) => f.ident.clone(),
|
||||
@ -259,6 +282,10 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_exported {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(usage) = self
|
||||
.data
|
||||
.as_ref()
|
||||
@ -268,6 +295,10 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if usage.reassigned {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inline very simple functions.
|
||||
match decl {
|
||||
Decl::Fn(f) if self.options.inline >= 2 && f.ident.sym != *"arguments" => {
|
||||
@ -309,15 +340,8 @@ impl Optimizer<'_> {
|
||||
&& !usage.used_in_loop
|
||||
{
|
||||
match decl {
|
||||
Decl::Class(ClassDecl {
|
||||
class:
|
||||
Class {
|
||||
super_class: Some(super_class),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) => {
|
||||
if super_class.may_have_side_effects() {
|
||||
Decl::Class(ClassDecl { class, .. }) => {
|
||||
if class_has_side_effect(&class) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -378,7 +402,7 @@ impl Optimizer<'_> {
|
||||
if let Some(value) = self.lits.get(&i.to_id()).cloned() {
|
||||
match &*value {
|
||||
Expr::Lit(Lit::Num(..)) => {
|
||||
if self.ctx.is_lhs_of_assign {
|
||||
if self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -389,7 +413,11 @@ impl Optimizer<'_> {
|
||||
log::trace!("inline: Replacing a variable with cheap expression");
|
||||
|
||||
*e = *value;
|
||||
} else if let Some(value) = self.vars_for_inlining.remove(&i.to_id()) {
|
||||
} else if let Some(value) = self.vars_for_inlining.get(&i.to_id()) {
|
||||
if self.ctx.is_exact_lhs_of_assign && !is_valid_for_lhs(&value) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
"inline: Replacing '{}{:?}' with an expression",
|
||||
@ -397,7 +425,7 @@ impl Optimizer<'_> {
|
||||
i.span.ctxt
|
||||
);
|
||||
|
||||
*e = *value;
|
||||
*e = *value.clone();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -60,8 +60,22 @@ impl Optimizer<'_> {
|
||||
};
|
||||
|
||||
// We only care about instant breaks.
|
||||
if !f.body.is_break_stmt() {
|
||||
return;
|
||||
match &*f.body {
|
||||
Stmt::Break(BreakStmt { label: None, .. }) => {}
|
||||
Stmt::Break(BreakStmt {
|
||||
label: Some(label), ..
|
||||
}) => {
|
||||
if let Some(closest_label) = self.label.clone() {
|
||||
if closest_label.0 != label.sym {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
|
@ -4,6 +4,7 @@ use crate::analyzer::UsageAnalyzer;
|
||||
use crate::option::CompressOptions;
|
||||
use crate::util::contains_leaping_yield;
|
||||
use fxhash::FxHashMap;
|
||||
use fxhash::FxHashSet;
|
||||
use retain_mut::RetainMut;
|
||||
use std::fmt::Write;
|
||||
use std::mem::take;
|
||||
@ -86,6 +87,8 @@ pub(super) fn optimizer<'a>(
|
||||
ctx: Default::default(),
|
||||
done,
|
||||
done_ctxt,
|
||||
vars_accessible_without_side_effect: Default::default(),
|
||||
label: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +117,9 @@ struct Ctx {
|
||||
is_delete_arg: bool,
|
||||
/// `true` if we are in `arg` of `++arg` or `--arg`.
|
||||
is_update_arg: bool,
|
||||
is_lhs_of_assign: bool,
|
||||
in_lhs_of_assign: bool,
|
||||
/// `false` for `d` in `d[0] = foo`.
|
||||
is_exact_lhs_of_assign: bool,
|
||||
|
||||
/// `true` for loop bodies and conditions of loops.
|
||||
executed_multiple_time: bool,
|
||||
@ -134,6 +139,8 @@ struct Ctx {
|
||||
/// `true` while we are in a function or something simillar.
|
||||
in_fn_like: bool,
|
||||
|
||||
in_block: bool,
|
||||
|
||||
in_obj_of_non_computed_member: bool,
|
||||
|
||||
in_tpl_expr: bool,
|
||||
@ -141,11 +148,24 @@ struct Ctx {
|
||||
/// True while handling callee, except an arrow expression in callee.
|
||||
is_this_aware_callee: bool,
|
||||
|
||||
can_inline_arguments: bool,
|
||||
|
||||
/// Current scope.
|
||||
scope: SyntaxContext,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
pub fn is_top_level_for_block_level_vars(self) -> bool {
|
||||
if self.top_level {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.in_fn_like || self.in_block {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn in_top_level(self) -> bool {
|
||||
self.top_level || !self.in_fn_like
|
||||
}
|
||||
@ -181,6 +201,11 @@ struct Optimizer<'a> {
|
||||
/// In future: This will be used to `mark` node as done.
|
||||
done: Mark,
|
||||
done_ctxt: SyntaxContext,
|
||||
|
||||
vars_accessible_without_side_effect: FxHashSet<Id>,
|
||||
|
||||
/// Closest label.
|
||||
label: Option<Id>,
|
||||
}
|
||||
|
||||
impl Repeated for Optimizer<'_> {
|
||||
@ -570,18 +595,41 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_invalid(&mut self, e: &mut Expr) {
|
||||
match e {
|
||||
Expr::Bin(BinExpr { left, right, .. }) => {
|
||||
self.remove_invalid(left);
|
||||
self.remove_invalid(right);
|
||||
|
||||
if left.is_invalid() {
|
||||
*e = *right.take();
|
||||
self.remove_invalid(e);
|
||||
return;
|
||||
} else if right.is_invalid() {
|
||||
*e = *left.take();
|
||||
self.remove_invalid(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [None] if expression is side-effect-free.
|
||||
/// If an expression has a side effect, only side effects are returned.
|
||||
fn ignore_return_value(&mut self, e: &mut Expr) -> Option<Expr> {
|
||||
match e {
|
||||
Expr::Ident(..) | Expr::This(_) | Expr::Invalid(_) | Expr::Lit(..) => {
|
||||
log::trace!("Dropping unused expr");
|
||||
log::trace!("ignore_return_value: Dropping unused expr");
|
||||
self.changed = true;
|
||||
return None;
|
||||
}
|
||||
// Function expression cannot have a side effect.
|
||||
Expr::Fn(_) => {
|
||||
log::trace!("Dropping unused fn expr as it does not have any side effect");
|
||||
log::trace!(
|
||||
"ignore_return_value: Dropping unused fn expr as it does not have any side \
|
||||
effect"
|
||||
);
|
||||
self.changed = true;
|
||||
return None;
|
||||
}
|
||||
@ -606,10 +654,20 @@ impl Optimizer<'_> {
|
||||
},
|
||||
ClassMember::ClassProp(ClassProp {
|
||||
key,
|
||||
computed: true,
|
||||
computed,
|
||||
is_static,
|
||||
value,
|
||||
..
|
||||
}) => {
|
||||
exprs.extend(self.ignore_return_value(key).map(Box::new));
|
||||
if *computed {
|
||||
exprs.extend(self.ignore_return_value(key).map(Box::new));
|
||||
}
|
||||
|
||||
if *is_static {
|
||||
if let Some(v) = value {
|
||||
exprs.extend(self.ignore_return_value(v).map(Box::new));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
@ -628,6 +686,24 @@ impl Optimizer<'_> {
|
||||
|
||||
Expr::Paren(e) => return self.ignore_return_value(&mut e.expr),
|
||||
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("&&") | op!("||"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) => {
|
||||
let new_r = self.ignore_return_value(right);
|
||||
|
||||
match new_r {
|
||||
Some(r) => {
|
||||
*right = Box::new(r);
|
||||
}
|
||||
None => return self.ignore_return_value(left),
|
||||
}
|
||||
|
||||
return Some(e.take());
|
||||
}
|
||||
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("delete"), ..
|
||||
}) => return Some(e.take()),
|
||||
@ -641,8 +717,8 @@ impl Optimizer<'_> {
|
||||
//
|
||||
// Div is exlcuded because of zero.
|
||||
// TOOD: Handle
|
||||
Expr::Bin(BinExpr { op, .. })
|
||||
if match op {
|
||||
Expr::Bin(BinExpr {
|
||||
op:
|
||||
BinaryOp::EqEq
|
||||
| BinaryOp::NotEq
|
||||
| BinaryOp::EqEqEq
|
||||
@ -664,12 +740,9 @@ impl Optimizer<'_> {
|
||||
| BinaryOp::BitAnd
|
||||
| BinaryOp::In
|
||||
| BinaryOp::InstanceOf
|
||||
| BinaryOp::Exp => false,
|
||||
_ => true,
|
||||
} =>
|
||||
{
|
||||
return Some(e.take())
|
||||
}
|
||||
| BinaryOp::Exp,
|
||||
..
|
||||
}) => return Some(e.take()),
|
||||
|
||||
// Pure calls can be removed
|
||||
Expr::Call(CallExpr {
|
||||
@ -1246,18 +1319,34 @@ impl Optimizer<'_> {
|
||||
impl VisitMut for Optimizer<'_> {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_arrow_expr(&mut self, n: &mut ArrowExpr) {
|
||||
let ctx = Ctx {
|
||||
can_inline_arguments: true,
|
||||
..self.ctx
|
||||
};
|
||||
|
||||
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
fn visit_mut_assign_expr(&mut self, e: &mut AssignExpr) {
|
||||
{
|
||||
let ctx = Ctx {
|
||||
is_lhs_of_assign: true,
|
||||
in_lhs_of_assign: true,
|
||||
is_exact_lhs_of_assign: true,
|
||||
..self.ctx
|
||||
};
|
||||
e.left.visit_mut_with(&mut *self.with_ctx(ctx));
|
||||
|
||||
if is_left_access_to_arguments(&e.left) {
|
||||
// self.ctx.can_inline_arguments = false;
|
||||
}
|
||||
}
|
||||
e.right.visit_mut_with(self);
|
||||
|
||||
self.compress_bin_assignment_to_left(e);
|
||||
self.compress_bin_assignment_to_right(e);
|
||||
|
||||
self.vars_accessible_without_side_effect.clear();
|
||||
}
|
||||
|
||||
fn visit_mut_assign_pat_prop(&mut self, n: &mut AssignPatProp) {
|
||||
@ -1300,8 +1389,9 @@ impl VisitMut for Optimizer<'_> {
|
||||
fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
|
||||
let ctx = Ctx {
|
||||
stmt_lablled: false,
|
||||
scope: n.span.ctxt,
|
||||
top_level: false,
|
||||
in_block: true,
|
||||
scope: n.span.ctxt,
|
||||
..self.ctx
|
||||
};
|
||||
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
@ -1325,6 +1415,15 @@ impl VisitMut for Optimizer<'_> {
|
||||
e.callee.visit_mut_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
match &e.callee {
|
||||
ExprOrSuper::Super(_) => {}
|
||||
ExprOrSuper::Expr(e) => {
|
||||
if e.may_have_side_effects() {
|
||||
self.vars_accessible_without_side_effect.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let ctx = Ctx {
|
||||
is_this_aware_callee: false,
|
||||
@ -1337,6 +1436,8 @@ impl VisitMut for Optimizer<'_> {
|
||||
self.optimize_symbol_call_unsafely(e);
|
||||
|
||||
self.inline_args_of_iife(e);
|
||||
|
||||
self.vars_accessible_without_side_effect.clear();
|
||||
}
|
||||
|
||||
fn visit_mut_class(&mut self, n: &mut Class) {
|
||||
@ -1386,9 +1487,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
DefaultDecl::Class(_) => {}
|
||||
DefaultDecl::Fn(f) => {
|
||||
if !self.options.keep_fargs && self.options.evaluate && self.options.unused {
|
||||
f.function.params.iter_mut().for_each(|param| {
|
||||
self.take_pat_if_unused(&mut param.pat, None);
|
||||
})
|
||||
self.drop_unused_params(&mut f.function.params);
|
||||
}
|
||||
}
|
||||
DefaultDecl::TsInterfaceDecl(_) => {}
|
||||
@ -1403,9 +1502,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
// I don't know why, but terser removes parameters from an exported function if
|
||||
// `unused` is true, regardless of keep_fargs or others.
|
||||
if self.options.unused {
|
||||
f.function.params.iter_mut().for_each(|param| {
|
||||
self.take_pat_if_unused(&mut param.pat, None);
|
||||
})
|
||||
self.drop_unused_params(&mut f.function.params);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -1433,16 +1530,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
};
|
||||
e.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
|
||||
match e {
|
||||
Expr::Bin(BinExpr { left, right, .. }) => {
|
||||
if left.is_invalid() {
|
||||
*e = *right.take();
|
||||
} else if right.is_invalid() {
|
||||
*e = *left.take();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.remove_invalid(e);
|
||||
|
||||
self.concat_str(e);
|
||||
|
||||
@ -1479,7 +1567,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
|
||||
self.optimize_nullish_coalescing(e);
|
||||
|
||||
self.compress_logical_exprs_as_bang_bang(e);
|
||||
self.compress_logical_exprs_as_bang_bang(e, false);
|
||||
|
||||
self.compress_useless_cond_expr(e);
|
||||
|
||||
@ -1573,9 +1661,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
|
||||
fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
|
||||
if !self.options.keep_fargs && self.options.evaluate && self.options.unused {
|
||||
f.function.params.iter_mut().for_each(|param| {
|
||||
self.take_pat_if_unused(&mut param.pat, None);
|
||||
})
|
||||
self.drop_unused_params(&mut f.function.params);
|
||||
}
|
||||
|
||||
f.visit_mut_children_with(self);
|
||||
@ -1626,6 +1712,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
}
|
||||
|
||||
fn visit_mut_function(&mut self, n: &mut Function) {
|
||||
let old_vars = take(&mut self.vars_accessible_without_side_effect);
|
||||
{
|
||||
let ctx = Ctx {
|
||||
stmt_lablled: false,
|
||||
@ -1639,6 +1726,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
stmt_lablled: false,
|
||||
in_fn_like: true,
|
||||
scope: n.span.ctxt,
|
||||
can_inline_arguments: true,
|
||||
..self.ctx
|
||||
};
|
||||
let optimizer = &mut *self.with_ctx(ctx);
|
||||
@ -1657,11 +1745,19 @@ impl VisitMut for Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
self.optimize_usage_of_arguments(n);
|
||||
{
|
||||
let ctx = Ctx {
|
||||
can_inline_arguments: true,
|
||||
..self.ctx
|
||||
};
|
||||
self.with_ctx(ctx).optimize_usage_of_arguments(n);
|
||||
}
|
||||
|
||||
if let Some(body) = &mut n.body {
|
||||
self.merge_if_returns(&mut body.stmts);
|
||||
}
|
||||
|
||||
self.vars_accessible_without_side_effect = old_vars;
|
||||
}
|
||||
|
||||
fn visit_mut_if_stmt(&mut self, n: &mut IfStmt) {
|
||||
@ -1684,19 +1780,27 @@ impl VisitMut for Optimizer<'_> {
|
||||
stmt_lablled: true,
|
||||
..self.ctx
|
||||
};
|
||||
let old_label = self.label.take();
|
||||
self.label = Some(n.label.to_id());
|
||||
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
self.label = old_label;
|
||||
}
|
||||
|
||||
fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
|
||||
{
|
||||
let ctx = Ctx {
|
||||
in_obj_of_non_computed_member: !n.computed,
|
||||
is_exact_lhs_of_assign: false,
|
||||
..self.ctx
|
||||
};
|
||||
n.obj.visit_mut_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
if n.computed {
|
||||
n.prop.visit_mut_with(self);
|
||||
let ctx = Ctx {
|
||||
is_exact_lhs_of_assign: false,
|
||||
..self.ctx
|
||||
};
|
||||
n.prop.visit_mut_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
self.handle_known_computed_member_expr(n);
|
||||
@ -1715,6 +1819,20 @@ impl VisitMut for Optimizer<'_> {
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_mut_new_expr(&mut self, n: &mut NewExpr) {
|
||||
n.callee.visit_mut_with(self);
|
||||
|
||||
if n.callee.may_have_side_effects() {
|
||||
self.vars_accessible_without_side_effect.clear();
|
||||
}
|
||||
|
||||
{
|
||||
n.args.visit_mut_with(self);
|
||||
}
|
||||
|
||||
self.vars_accessible_without_side_effect.clear();
|
||||
}
|
||||
|
||||
fn visit_mut_param(&mut self, n: &mut Param) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
@ -1789,7 +1907,7 @@ impl VisitMut for Optimizer<'_> {
|
||||
is_delete_arg: false,
|
||||
is_exported: false,
|
||||
is_update_arg: false,
|
||||
is_lhs_of_assign: false,
|
||||
in_lhs_of_assign: false,
|
||||
in_obj_of_non_computed_member: false,
|
||||
..self.ctx
|
||||
};
|
||||
@ -1992,6 +2110,8 @@ impl VisitMut for Optimizer<'_> {
|
||||
};
|
||||
|
||||
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
|
||||
self.vars_accessible_without_side_effect.clear();
|
||||
}
|
||||
|
||||
fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
|
||||
@ -2039,9 +2159,13 @@ impl VisitMut for Optimizer<'_> {
|
||||
..self.ctx
|
||||
};
|
||||
|
||||
var.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
var.name.visit_mut_with(&mut *self.with_ctx(ctx));
|
||||
|
||||
var.init.visit_mut_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
self.remove_duplicate_names(var);
|
||||
|
||||
self.store_var_for_inining(var);
|
||||
self.store_var_for_prop_hoisting(var);
|
||||
|
||||
@ -2144,3 +2268,29 @@ fn is_callee_this_aware(callee: &Expr) -> bool {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn is_expr_access_to_arguments(l: &Expr) -> bool {
|
||||
match l {
|
||||
Expr::Member(MemberExpr {
|
||||
obj: ExprOrSuper::Expr(obj),
|
||||
..
|
||||
}) => match &**obj {
|
||||
Expr::Ident(Ident {
|
||||
sym: js_word!("arguments"),
|
||||
..
|
||||
}) => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_left_access_to_arguments(l: &PatOrExpr) -> bool {
|
||||
match l {
|
||||
PatOrExpr::Expr(e) => is_expr_access_to_arguments(&e),
|
||||
PatOrExpr::Pat(pat) => match &**pat {
|
||||
Pat::Expr(e) => is_expr_access_to_arguments(&e),
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::Optimizer;
|
||||
use crate::util::make_bool;
|
||||
use crate::util::ValueExt;
|
||||
use std::mem::swap;
|
||||
use swc_atoms::js_word;
|
||||
@ -140,18 +141,39 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if e.op == op!("===") || e.op == op!("==") || e.op == op!("!=") || e.op == op!("!==") {
|
||||
if e.left.is_ident() && e.left.eq_ignore_span(&e.right) {
|
||||
match e.op {
|
||||
op!("===") | op!("==") | op!("!==") | op!("!=") => {
|
||||
if e.left.is_ident() && e.left.eq_ignore_span(&e.right) {
|
||||
let id: Ident = e.left.clone().ident().unwrap();
|
||||
if let Some(t) = self.typeofs.get(&id.to_id()) {
|
||||
match *t {
|
||||
js_word!("object") | js_word!("function") => {
|
||||
e.left = Box::new(make_bool(
|
||||
e.span,
|
||||
e.op == op!("===") || e.op == op!("=="),
|
||||
));
|
||||
e.right.take();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if e.op == op!("===") || e.op == op!("!==") {
|
||||
if (e.left.is_ident() || e.left.is_member()) && e.left.eq_ignore_span(&e.right) {
|
||||
self.changed = true;
|
||||
log::trace!("Reducing comparison of same variable ({})", e.op);
|
||||
|
||||
// TODO(kdy1): Create another method and assign to `Expr` instead of using a
|
||||
// hack based on take.
|
||||
e.left = Box::new(Expr::Lit(Lit::Bool(Bool {
|
||||
span: e.span,
|
||||
value: e.op == op!("===") || e.op == op!("==="),
|
||||
})));
|
||||
e.right.take();
|
||||
e.op = if e.op == op!("===") {
|
||||
op!("==")
|
||||
} else {
|
||||
op!("!=")
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -225,8 +247,12 @@ impl Optimizer<'_> {
|
||||
| Expr::Bin(BinExpr { op: op!("<"), .. })
|
||||
| Expr::Bin(BinExpr { op: op!(">="), .. })
|
||||
| Expr::Bin(BinExpr { op: op!(">"), .. }) => {
|
||||
log::trace!("Optimizing: `!!expr` => `expr`");
|
||||
*e = *arg.take();
|
||||
if let Known(Type::Bool) = arg.get_type() {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: `!!expr` => `expr`");
|
||||
*e = *arg.take();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -408,8 +434,32 @@ impl Optimizer<'_> {
|
||||
e.right = left.take();
|
||||
}
|
||||
|
||||
/// Swap lhs and rhs in certain conditions.
|
||||
pub(super) fn swap_bin_operands(&mut self, expr: &mut Expr) {
|
||||
fn can_swap_bin_operands(&mut self, l: &Expr, r: &Expr, is_for_rel: bool) -> bool {
|
||||
match (l, r) {
|
||||
(Expr::Ident(l), Expr::Ident(r)) => {
|
||||
self.options.comparisons && (is_for_rel || l.sym > r.sym)
|
||||
}
|
||||
|
||||
(Expr::Ident(..), Expr::Lit(..))
|
||||
| (
|
||||
Expr::Ident(..),
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("void"), ..
|
||||
}),
|
||||
)
|
||||
| (
|
||||
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!("===")
|
||||
@ -424,40 +474,40 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize(op: BinaryOp, left: &mut Expr, right: &mut Expr) -> bool {
|
||||
if !is_supported(op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match (&*left, &*right) {
|
||||
(Expr::Ident(..), Expr::Lit(..))
|
||||
| (
|
||||
Expr::Ident(..),
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("void"), ..
|
||||
}),
|
||||
)
|
||||
| (
|
||||
Expr::This(..),
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("void"), ..
|
||||
}),
|
||||
)
|
||||
| (Expr::Unary(..), Expr::Lit(..))
|
||||
| (Expr::Tpl(..), Expr::Lit(..)) => {
|
||||
log::trace!("Swapping operands of binary exprssion");
|
||||
swap(left, right);
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
if !is_supported(op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.can_swap_bin_operands(&left, &right, false) {
|
||||
log::trace!("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::trace!("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 optimize(bin.op, &mut bin.left, &mut bin.right) {
|
||||
if self.try_swap_bin(bin.op, &mut bin.left, &mut bin.right) {
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
@ -467,11 +517,14 @@ impl Optimizer<'_> {
|
||||
|
||||
/// Remove meaningless literals in a binary expressions.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `in_bool_ctx`: True for expressions casted to bool.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// - `x() && true` => `!!x()`
|
||||
pub(super) fn compress_logical_exprs_as_bang_bang(&mut self, e: &mut Expr) {
|
||||
pub(super) fn compress_logical_exprs_as_bang_bang(&mut self, e: &mut Expr, in_bool_ctx: bool) {
|
||||
if !self.options.conditionals && !self.options.reduce_vars {
|
||||
return;
|
||||
}
|
||||
@ -482,27 +535,65 @@ impl Optimizer<'_> {
|
||||
};
|
||||
|
||||
match bin.op {
|
||||
op!("&&") => {
|
||||
let rt = bin.right.get_type();
|
||||
match rt {
|
||||
Known(Type::Bool) => {}
|
||||
_ => return,
|
||||
}
|
||||
op!("&&") | op!("||") => {
|
||||
self.compress_logical_exprs_as_bang_bang(&mut bin.left, true);
|
||||
self.compress_logical_exprs_as_bang_bang(&mut bin.right, true);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let lt = bin.left.get_type();
|
||||
if !in_bool_ctx {
|
||||
match lt {
|
||||
// Don't change type
|
||||
Known(Type::Bool) => {}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
let rt = bin.right.get_type();
|
||||
match rt {
|
||||
Known(Type::Bool) => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
match bin.op {
|
||||
op!("&&") => {
|
||||
let rb = bin.right.as_pure_bool();
|
||||
let rb = match rb {
|
||||
Value::Known(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
log::trace!("Optimizing: e && true => !!e");
|
||||
|
||||
if rb {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: e && true => !!e");
|
||||
|
||||
self.negate_twice(&mut bin.left);
|
||||
*e = *bin.left.take();
|
||||
} else {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: e && false => e");
|
||||
|
||||
*e = *bin.left.take();
|
||||
}
|
||||
}
|
||||
op!("||") => {
|
||||
let rb = bin.right.as_pure_bool();
|
||||
let rb = match rb {
|
||||
Value::Known(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !rb {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: e || false => !!e");
|
||||
|
||||
self.negate_twice(&mut bin.left);
|
||||
*e = *bin.left.take();
|
||||
}
|
||||
}
|
||||
op!("||") => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::compress::optimize::util::class_has_side_effect;
|
||||
use crate::option::PureGetterOption;
|
||||
|
||||
use super::Optimizer;
|
||||
@ -5,6 +6,7 @@ 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::contains_ident_ref;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
use swc_ecma_visit::VisitMut;
|
||||
@ -94,10 +96,21 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
&& self.ctx.in_top_level()
|
||||
{
|
||||
return;
|
||||
// Top-level
|
||||
match self.ctx.var_kind {
|
||||
Some(VarDeclKind::Var) => {
|
||||
if (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
&& self.ctx.in_top_level()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(VarDeclKind::Let) | Some(VarDeclKind::Const) => {
|
||||
if !self.options.top_level() && self.ctx.is_top_level_for_block_level_vars() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
if let Some(scope) = self
|
||||
@ -117,6 +130,16 @@ impl Optimizer<'_> {
|
||||
self.take_pat_if_unused(name, init);
|
||||
}
|
||||
|
||||
pub(super) fn drop_unused_params(&mut self, params: &mut Vec<Param>) {
|
||||
for param in params.iter_mut().rev() {
|
||||
self.take_pat_if_unused(&mut param.pat, None);
|
||||
|
||||
if !param.pat.is_invalid() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn take_pat_if_unused(&mut self, name: &mut Pat, mut init: Option<&mut Expr>) {
|
||||
let had_value = init.is_some();
|
||||
let can_drop_children = had_value;
|
||||
@ -237,7 +260,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.options.top_level() && (self.ctx.top_level || !self.ctx.in_fn_like) {
|
||||
if !self.options.top_level() && self.ctx.is_top_level_for_block_level_vars() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -255,6 +278,15 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
match decl {
|
||||
Decl::Class(c) => {
|
||||
if class_has_side_effect(&c.class) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match decl {
|
||||
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
|
||||
// We should skip if the name of decl is arguments.
|
||||
@ -379,6 +411,34 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `var Parser = function Parser() {};` => `var Parser = function () {}`
|
||||
pub(super) fn remove_duplicate_names(&mut self, v: &mut VarDeclarator) {
|
||||
if !self.options.unused {
|
||||
return;
|
||||
}
|
||||
|
||||
match v.init.as_deref_mut() {
|
||||
Some(Expr::Fn(f)) => {
|
||||
if f.ident.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if contains_ident_ref(&f.function.body, f.ident.as_ref().unwrap()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
"unused: Removing the name of a function expression because it's not used by \
|
||||
it'"
|
||||
);
|
||||
f.ident = None;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -9,6 +9,7 @@ use swc_common::Mark;
|
||||
use swc_common::Span;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::prop_name_eq;
|
||||
use swc_ecma_utils::ExprExt;
|
||||
|
||||
impl<'b> Optimizer<'b> {
|
||||
pub(super) fn access_property<'e>(
|
||||
@ -179,3 +180,56 @@ impl Drop for WithCtx<'_, '_> {
|
||||
self.reducer.ctx = self.orig_ctx;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn class_has_side_effect(c: &Class) -> bool {
|
||||
if let Some(e) = &c.super_class {
|
||||
if e.may_have_side_effects() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for m in &c.body {
|
||||
match m {
|
||||
ClassMember::Method(p) => {
|
||||
if let PropName::Computed(key) = &p.key {
|
||||
if key.expr.may_have_side_effects() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassMember::ClassProp(p) => {
|
||||
if p.computed {
|
||||
if p.key.may_have_side_effects() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(v) = &p.value {
|
||||
if v.may_have_side_effects() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClassMember::PrivateProp(p) => {
|
||||
if let Some(v) = &p.value {
|
||||
if v.may_have_side_effects() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {
|
||||
match e {
|
||||
Expr::Lit(..) => return false,
|
||||
Expr::Unary(..) => return false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ use crate::pass::compute_char_freq::compute_char_freq;
|
||||
use crate::pass::expand_names::name_expander;
|
||||
use crate::pass::global_defs;
|
||||
use crate::pass::hygiene::hygiene_optimizer;
|
||||
pub use crate::pass::hygiene::optimize_hygiene;
|
||||
use crate::pass::mangle_names::name_mangler;
|
||||
use crate::pass::mangle_props::mangle_properties;
|
||||
use crate::pass::remove_parens::remove_parens;
|
||||
use crate::pass::single::single_pass_optimizer;
|
||||
use analyzer::analyze;
|
||||
use swc_common::comments::Comments;
|
||||
use swc_ecma_ast::Module;
|
||||
@ -47,8 +48,6 @@ pub fn optimize(
|
||||
options: &MinifyOptions,
|
||||
extra: &ExtraOptions,
|
||||
) -> Module {
|
||||
m.visit_mut_with(&mut remove_parens());
|
||||
|
||||
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
|
||||
// Apply global defs.
|
||||
//
|
||||
@ -62,6 +61,10 @@ pub fn optimize(
|
||||
}
|
||||
}
|
||||
|
||||
m.visit_mut_with(&mut single_pass_optimizer(
|
||||
options.compress.clone().unwrap_or_default(),
|
||||
));
|
||||
|
||||
m.visit_mut_with(&mut unique_marker());
|
||||
|
||||
if options.wrap {
|
||||
@ -124,7 +127,7 @@ pub fn optimize(
|
||||
|
||||
{
|
||||
let data = analyze(&m);
|
||||
m.visit_mut_with(&mut hygiene_optimizer(data));
|
||||
m.visit_mut_with(&mut hygiene_optimizer(data, extra.top_level_mark));
|
||||
}
|
||||
|
||||
if let Some(ref mut t) = timings {
|
||||
|
121
ecmascript/minifier/src/pass/hygiene/analyzer.rs
Normal file
121
ecmascript/minifier/src/pass/hygiene/analyzer.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use crate::{
|
||||
analyzer::{ProgramData, ScopeData},
|
||||
util::has_mark,
|
||||
};
|
||||
use fxhash::FxHashSet;
|
||||
use swc_common::{Mark, SyntaxContext};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::{ident::IdentLike, Id};
|
||||
use swc_ecma_visit::{noop_visit_type, Node, Visit, VisitWith};
|
||||
|
||||
pub(super) struct HygieneAnalyzer<'a> {
|
||||
pub data: &'a ProgramData,
|
||||
pub hygiene: HygieneData,
|
||||
pub top_level_mark: Mark,
|
||||
pub cur_scope: Option<SyntaxContext>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct HygieneData {
|
||||
/// Has higher precedence over `modified`.
|
||||
pub preserved: FxHashSet<Id>,
|
||||
|
||||
pub modified: FxHashSet<Id>,
|
||||
}
|
||||
|
||||
impl HygieneAnalyzer<'_> {
|
||||
fn scope(&self) -> &ScopeData {
|
||||
match self.cur_scope {
|
||||
Some(v) => self.data.scopes.get(&v).expect("failed to get scope"),
|
||||
None => &self.data.top,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_scope<F, Ret>(&mut self, scope_ctxt: SyntaxContext, op: F) -> Ret
|
||||
where
|
||||
F: FnOnce(&mut HygieneAnalyzer) -> Ret,
|
||||
{
|
||||
let old = self.cur_scope;
|
||||
self.cur_scope = Some(scope_ctxt);
|
||||
|
||||
let ret = op(self);
|
||||
|
||||
self.cur_scope = old;
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Registers a binding ident. This is treated as [PatMode::OtherDecl].
|
||||
///
|
||||
/// If it conflicts
|
||||
#[allow(unused)]
|
||||
fn register_binding_ident(&mut self, i: &mut Ident) {}
|
||||
}
|
||||
|
||||
impl Visit for HygieneAnalyzer<'_> {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_function(&mut self, n: &Function, _: &dyn Node) {
|
||||
self.with_scope(n.span.ctxt, |v| {
|
||||
n.visit_children_with(v);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
|
||||
log::trace!("hygiene: Handling ({}{:?})", i.sym, i.span.ctxt);
|
||||
|
||||
if i.span.ctxt == SyntaxContext::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if has_mark(i.span, self.top_level_mark) {
|
||||
return;
|
||||
}
|
||||
|
||||
let info = self.data.vars.get(&i.to_id());
|
||||
// Ignore labels.
|
||||
let info = match info {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
log::trace!("hygiene: No such var: {}{:?}", i.sym, i.span.ctxt);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// If multiple variables with same name is declared, skip it.
|
||||
if let Some(decls) = self.scope().declared_symbols.get(&i.sym) {
|
||||
if decls.len() >= 2 {
|
||||
self.hygiene.preserved.insert(i.to_id());
|
||||
log::trace!(
|
||||
"hygiene: Preserving hygiene of {}{:?} because it's declared multiple times",
|
||||
i.sym,
|
||||
i.span.ctxt
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if info.is_fn_local {
|
||||
log::trace!(
|
||||
"hygiene: Optimization candidate: {}{:?}",
|
||||
i.sym,
|
||||
i.span.ctxt
|
||||
);
|
||||
self.hygiene.modified.insert(i.to_id());
|
||||
} else {
|
||||
log::trace!(
|
||||
"hygiene: Preserving {}{:?} as it is not fn-local",
|
||||
i.sym,
|
||||
i.span.ctxt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_member_expr(&mut self, n: &MemberExpr, _: &dyn Node) {
|
||||
n.obj.visit_with(n, self);
|
||||
|
||||
if n.computed {
|
||||
n.prop.visit_with(n, self);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +1,63 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::analyzer::analyze;
|
||||
use crate::analyzer::ProgramData;
|
||||
use swc_common::Span;
|
||||
use crate::pass::hygiene::analyzer::HygieneAnalyzer;
|
||||
use crate::pass::hygiene::analyzer::HygieneData;
|
||||
use swc_common::Mark;
|
||||
use swc_common::SyntaxContext;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
use swc_ecma_visit::VisitMut;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
use swc_ecma_visit::VisitWith;
|
||||
|
||||
mod analyzer;
|
||||
|
||||
pub fn optimize_hygiene(m: &mut Module, top_level_mark: Mark) {
|
||||
let data = analyze(&*m);
|
||||
m.visit_mut_with(&mut hygiene_optimizer(data, top_level_mark))
|
||||
}
|
||||
|
||||
/// Create a hygiene optimizer.
|
||||
///
|
||||
/// Hygiene optimizer removes span hygiene without renaming if it's ok to do so.
|
||||
pub(crate) fn hygiene_optimizer(data: ProgramData) -> impl 'static + VisitMut {
|
||||
Optimizer { data }
|
||||
pub(crate) fn hygiene_optimizer(
|
||||
data: ProgramData,
|
||||
top_level_mark: Mark,
|
||||
) -> impl 'static + VisitMut {
|
||||
Optimizer {
|
||||
data,
|
||||
hygiene: Default::default(),
|
||||
top_level_mark,
|
||||
}
|
||||
}
|
||||
|
||||
struct Optimizer {
|
||||
data: ProgramData,
|
||||
hygiene: HygieneData,
|
||||
top_level_mark: Mark,
|
||||
}
|
||||
|
||||
impl Optimizer {
|
||||
/// Registers a binding ident. This is treated as [PatMode::OtherDecl].
|
||||
///
|
||||
/// If it conflicts
|
||||
#[allow(unused)]
|
||||
fn register_binding_ident(&mut self, i: &mut Ident) {}
|
||||
}
|
||||
impl Optimizer {}
|
||||
|
||||
impl VisitMut for Optimizer {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_span(&mut self, span: &mut Span) {
|
||||
span.ctxt = SyntaxContext::empty();
|
||||
}
|
||||
|
||||
fn visit_mut_ident(&mut self, i: &mut Ident) {
|
||||
if i.span.ctxt == SyntaxContext::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let info = self.data.vars.get(&i.to_id());
|
||||
// Ignore labels.
|
||||
let info = match info {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if info.is_fn_local {
|
||||
i.span.ctxt = SyntaxContext::empty();
|
||||
if self.hygiene.preserved.contains(&i.to_id())
|
||||
|| !self.hygiene.modified.contains(&i.to_id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
i.span.ctxt = SyntaxContext::empty().apply_mark(self.top_level_mark);
|
||||
}
|
||||
|
||||
fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
|
||||
@ -57,4 +67,27 @@ impl VisitMut for Optimizer {
|
||||
n.prop.visit_mut_with(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_module(&mut self, n: &mut Module) {
|
||||
log::debug!("hygiene: Analyzing span hygiene");
|
||||
let start = Instant::now();
|
||||
|
||||
let mut analyzer = HygieneAnalyzer {
|
||||
data: &self.data,
|
||||
hygiene: Default::default(),
|
||||
top_level_mark: self.top_level_mark,
|
||||
cur_scope: None,
|
||||
};
|
||||
n.visit_with(&Invalid { span: DUMMY_SP }, &mut analyzer);
|
||||
self.hygiene = analyzer.hygiene;
|
||||
|
||||
let end = Instant::now();
|
||||
log::debug!("hygiene: Span hygiene analysis took {:?}", end - start);
|
||||
let start = end;
|
||||
|
||||
log::debug!("hygiene: Optimizing span hygiene");
|
||||
n.visit_mut_children_with(self);
|
||||
let end = Instant::now();
|
||||
log::debug!("hygiene: Span hygiene optimiation took {:?}", end - start);
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,4 @@ pub mod global_defs;
|
||||
pub mod hygiene;
|
||||
pub mod mangle_names;
|
||||
pub mod mangle_props;
|
||||
pub mod remove_parens;
|
||||
pub mod single;
|
||||
|
@ -1,26 +0,0 @@
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
use swc_ecma_visit::VisitMut;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
|
||||
pub fn remove_parens() -> impl 'static + VisitMut {
|
||||
RemoveParens
|
||||
}
|
||||
|
||||
struct RemoveParens;
|
||||
|
||||
impl VisitMut for RemoveParens {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_expr(&mut self, e: &mut Expr) {
|
||||
e.visit_mut_children_with(self);
|
||||
|
||||
match e {
|
||||
Expr::Paren(p) => {
|
||||
*e = *p.expr.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
153
ecmascript/minifier/src/pass/single.rs
Normal file
153
ecmascript/minifier/src/pass/single.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use crate::{
|
||||
analyzer::{analyze, ProgramData},
|
||||
option::CompressOptions,
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use swc_atoms::js_word;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::{ident::IdentLike, Id};
|
||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
|
||||
|
||||
///
|
||||
/// - Remove parens.
|
||||
pub fn single_pass_optimizer(options: CompressOptions) -> impl VisitMut {
|
||||
SinglePassOptimizer {
|
||||
options,
|
||||
data: Default::default(),
|
||||
fn_decl_count: Default::default(),
|
||||
ctx: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct SinglePassOptimizer {
|
||||
options: CompressOptions,
|
||||
data: ProgramData,
|
||||
fn_decl_count: FxHashMap<Id, usize>,
|
||||
ctx: Ctx,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct Ctx {
|
||||
in_var_pat: bool,
|
||||
}
|
||||
|
||||
impl VisitMut for SinglePassOptimizer {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_decl(&mut self, n: &mut Decl) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
match n {
|
||||
Decl::Fn(FnDecl { ident, .. }) => {
|
||||
if ident.sym == js_word!("") {
|
||||
n.take();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_expr(&mut self, e: &mut Expr) {
|
||||
e.visit_mut_children_with(self);
|
||||
|
||||
match e {
|
||||
Expr::Paren(p) => {
|
||||
*e = *p.expr.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
if self.options.dead_code || self.options.unused {
|
||||
if let Some(usage) = self.data.vars.get(&n.ident.to_id()) {
|
||||
// Remove if variable with same name exists.
|
||||
if usage.var_kind.is_some() && usage.var_initialized {
|
||||
n.ident.take();
|
||||
return;
|
||||
}
|
||||
|
||||
if usage.assign_count > 1 {
|
||||
let v = self.fn_decl_count.entry(n.ident.to_id()).or_default();
|
||||
*v += 1;
|
||||
|
||||
if *v == usage.assign_count {
|
||||
n.ident.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_module(&mut self, n: &mut Module) {
|
||||
let data = analyze(&*n);
|
||||
self.data = data;
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_module_item(&mut self, n: &mut ModuleItem) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
match n {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl: Decl::Var(var),
|
||||
..
|
||||
})) if var.decls.is_empty() => {
|
||||
n.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
n.retain(|s| match s {
|
||||
ModuleItem::Stmt(Stmt::Empty(..)) => false,
|
||||
_ => true,
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_mut_script(&mut self, n: &mut Script) {
|
||||
let data = analyze(&*n);
|
||||
self.data = data;
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, n: &mut Stmt) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
match n {
|
||||
Stmt::Decl(Decl::Var(var)) if var.decls.is_empty() => {
|
||||
n.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
n.retain(|s| match s {
|
||||
Stmt::Empty(..) => false,
|
||||
_ => true,
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
|
||||
let old = self.ctx;
|
||||
self.ctx.in_var_pat = true;
|
||||
n.name.visit_mut_with(self);
|
||||
|
||||
self.ctx.in_var_pat = false;
|
||||
n.init.visit_mut_with(self);
|
||||
|
||||
self.ctx = old;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ use swc_common::pass::CompilerPass;
|
||||
use swc_common::pass::Repeated;
|
||||
use swc_common::Mark;
|
||||
use swc_common::Span;
|
||||
use swc_common::SyntaxContext;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
@ -304,3 +305,62 @@ where
|
||||
n.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||
v.ids
|
||||
}
|
||||
|
||||
pub(crate) fn ctxt_has_mark(mut ctxt: SyntaxContext, mark: Mark) -> bool {
|
||||
debug_assert_ne!(mark, Mark::root());
|
||||
|
||||
loop {
|
||||
if ctxt == SyntaxContext::empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let m = ctxt.remove_mark();
|
||||
if m == mark {
|
||||
return true;
|
||||
}
|
||||
if m == Mark::root() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_mark(span: Span, mark: Mark) -> bool {
|
||||
ctxt_has_mark(span.ctxt, mark)
|
||||
}
|
||||
|
||||
pub(crate) fn can_end_conditionally(s: &Stmt) -> bool {
|
||||
///
|
||||
///`ignore_always`: If true, [Stmt::Return] will be ignored.
|
||||
fn can_end(s: &Stmt, ignore_always: bool) -> bool {
|
||||
match s {
|
||||
Stmt::If(s) => {
|
||||
can_end(&s.cons, false)
|
||||
|| s.alt
|
||||
.as_deref()
|
||||
.map(|s| can_end(s, false))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
Stmt::Switch(s) => s
|
||||
.cases
|
||||
.iter()
|
||||
.any(|case| case.cons.iter().any(|s| can_end(&s, false))),
|
||||
|
||||
Stmt::DoWhile(s) => can_end(&s.body, false),
|
||||
|
||||
Stmt::While(s) => can_end(&s.body, false),
|
||||
|
||||
Stmt::For(s) => can_end(&s.body, false),
|
||||
|
||||
Stmt::ForOf(s) => can_end(&s.body, false),
|
||||
|
||||
Stmt::ForIn(s) => can_end(&s.body, false),
|
||||
|
||||
Stmt::Return(..) | Stmt::Break(..) | Stmt::Continue(..) => !ignore_always,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
can_end(s, true)
|
||||
}
|
||||
|
@ -5,10 +5,7 @@ use anyhow::Error;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::fs::read_to_string;
|
||||
use std::panic::catch_unwind;
|
||||
use std::path::Path;
|
||||
@ -17,6 +14,7 @@ use std::process::Command;
|
||||
use swc_common::comments::SingleThreadedComments;
|
||||
use swc_common::errors::Handler;
|
||||
use swc_common::sync::Lrc;
|
||||
use swc_common::EqIgnoreSpan;
|
||||
use swc_common::FileName;
|
||||
use swc_common::Mark;
|
||||
use swc_common::SourceMap;
|
||||
@ -35,8 +33,10 @@ use swc_ecma_parser::Parser;
|
||||
use swc_ecma_transforms::fixer;
|
||||
use swc_ecma_transforms::hygiene;
|
||||
use swc_ecma_transforms::resolver_with_mark;
|
||||
use swc_ecma_utils::drop_span;
|
||||
use swc_ecma_visit::FoldWith;
|
||||
use testing::assert_eq;
|
||||
use testing::DebugUsingDisplay;
|
||||
use testing::NormalizedOutput;
|
||||
|
||||
fn load_txt(filename: &str) -> Vec<String> {
|
||||
@ -110,6 +110,26 @@ fn run(
|
||||
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Input"), fm.src);
|
||||
|
||||
if env::var("SWC_RUN").unwrap_or_default() == "1" {
|
||||
let stdout = stdout_of(&fm.src);
|
||||
match stdout {
|
||||
Ok(stdout) => {
|
||||
eprintln!(
|
||||
"---- {} -----\n{}",
|
||||
Color::Green.paint("Stdout (expected)"),
|
||||
stdout
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"---- {} -----\n{:?}",
|
||||
Color::Green.paint("Error (of orignal source code)"),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let top_level_mark = Mark::fresh(Mark::root());
|
||||
|
||||
let lexer = Lexer::new(
|
||||
@ -179,6 +199,35 @@ fn stdout_of(code: &str) -> Result<String, Error> {
|
||||
Ok(String::from_utf8_lossy(&actual_output.stdout).to_string())
|
||||
}
|
||||
|
||||
#[testing::fixture("compress/fixture/**/input.js")]
|
||||
fn base_fixture(input: PathBuf) {
|
||||
let dir = input.parent().unwrap();
|
||||
let config = dir.join("config.json");
|
||||
let config = read_to_string(&config).expect("failed to read config.json");
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Config"), config);
|
||||
|
||||
testing::run_test2(false, |cm, handler| {
|
||||
let output = run(cm.clone(), &handler, &input, &config, None);
|
||||
let output_module = match output {
|
||||
Some(v) => v,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let output = print(cm.clone(), &[output_module.clone()]);
|
||||
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||
|
||||
println!("{}", input.display());
|
||||
|
||||
NormalizedOutput::from(output)
|
||||
.compare_to_file(dir.join("output.js"))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Tests used to prevent regressions.
|
||||
#[testing::fixture("compress/exec/**/input.js")]
|
||||
fn base_exec(input: PathBuf) {
|
||||
@ -206,7 +255,10 @@ fn base_exec(input: PathBuf) {
|
||||
let actual_output = stdout_of(&output).expect("failed to execute the optimized code");
|
||||
assert_ne!(actual_output, "");
|
||||
|
||||
assert_eq!(actual_output, expected_output);
|
||||
assert_eq!(
|
||||
DebugUsingDisplay(&actual_output),
|
||||
DebugUsingDisplay(&*expected_output)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@ -240,11 +292,12 @@ fn fixture(input: PathBuf) {
|
||||
mangle.map(|s| serde_json::from_str(&s).expect("failed to deserialize mangle.json"));
|
||||
|
||||
let output = run(cm.clone(), &handler, &input, &config, mangle);
|
||||
let output = match output {
|
||||
let output_module = match output {
|
||||
Some(v) => v,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let output = print(cm.clone(), &[output]);
|
||||
|
||||
let output = print(cm.clone(), &[output_module.clone()]);
|
||||
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||
|
||||
@ -262,6 +315,14 @@ fn fixture(input: PathBuf) {
|
||||
err.into_diagnostic(&handler).emit();
|
||||
})?;
|
||||
let mut expected = expected.fold_with(&mut fixer(None));
|
||||
expected = drop_span(expected);
|
||||
|
||||
if output_module.eq_ignore_span(&expected)
|
||||
|| drop_span(output_module.clone()) == expected
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
expected.body.retain(|s| match s {
|
||||
ModuleItem::Stmt(Stmt::Empty(..)) => false,
|
||||
_ => true,
|
||||
@ -307,7 +368,9 @@ fn fixture(input: PathBuf) {
|
||||
});
|
||||
}
|
||||
|
||||
assert_eq!(DebugUsingDisplay(&output), DebugUsingDisplay(&expected));
|
||||
let output_str = print(cm.clone(), &[drop_span(output_module.clone())]);
|
||||
|
||||
assert_eq!(DebugUsingDisplay(&output_str), DebugUsingDisplay(&expected));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@ -332,12 +395,3 @@ fn print<N: swc_ecma_codegen::Node>(cm: Lrc<SourceMap>, nodes: &[N]) -> String {
|
||||
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct DebugUsingDisplay<'a>(&'a str);
|
||||
|
||||
impl<'a> Debug for DebugUsingDisplay<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self.0, f)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"booleans": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"side_effects": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
function x() {
|
||||
}
|
||||
function y() {
|
||||
return "foo";
|
||||
}
|
||||
console.log(x() && true && y());
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"booleans": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"side_effects": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
function x() {
|
||||
}
|
||||
function y() {
|
||||
return "foo";
|
||||
}
|
||||
console.log(y() && true && x());
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"booleans": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"side_effects": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
function x() {
|
||||
}
|
||||
function y() {
|
||||
return "foo";
|
||||
}
|
||||
console.log(x() || false || y());
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"booleans": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"side_effects": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
function x() {
|
||||
}
|
||||
function y() {
|
||||
return "foo";
|
||||
}
|
||||
console.log(y() || false || x());
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"booleans": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"side_effects": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
function x() {
|
||||
}
|
||||
function y() {
|
||||
return "foo";
|
||||
}
|
||||
console.log((x() || false) && y());
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"booleans": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"side_effects": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
function x() {
|
||||
}
|
||||
function y() {
|
||||
return "foo";
|
||||
}
|
||||
console.log((y() || false) && x());
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"inline": true,
|
||||
"passes": 3,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
console.log(function c() {
|
||||
c = 6;
|
||||
return c;
|
||||
}())
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"arguments": true,
|
||||
"defaults": true
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
(function (a, a) {
|
||||
console.log((a = "foo"), arguments[0]);
|
||||
})("baz", "Bar");
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"defaults": true,
|
||||
"toplevel": true
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
function shouldBePure() {
|
||||
return arguments.length;
|
||||
}
|
||||
console.log(shouldBePure())
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"arguments": true
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
(function (a, b) {
|
||||
var c = arguments[0];
|
||||
var d = arguments[1];
|
||||
var a = "foo";
|
||||
b++;
|
||||
arguments[0] = "moo";
|
||||
arguments[1] *= 2;
|
||||
console.log(a, b, c, d, arguments[0], arguments[1]);
|
||||
})("bar", 42);
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"arguments": true,
|
||||
"reduce_vars": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
(function (a, b) {
|
||||
var c = arguments[0];
|
||||
var d = arguments[1];
|
||||
var a = "foo";
|
||||
b++;
|
||||
arguments[0] = "moo";
|
||||
arguments[1] *= 2;
|
||||
console.log(a, b, c, d, arguments[0], arguments[1]);
|
||||
})("bar", 42);
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"collapse_vars": true,
|
||||
"conditionals": true,
|
||||
"evaluate": true,
|
||||
"inline": true,
|
||||
"negate_iife": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"toplevel": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
(async function () {
|
||||
return await 3;
|
||||
})();
|
||||
(async function (x) {
|
||||
await console.log(x);
|
||||
})(4);
|
||||
function invoke(x, y) {
|
||||
return x(y);
|
||||
}
|
||||
invoke(async function () {
|
||||
return await 1;
|
||||
});
|
||||
invoke(async function (x) {
|
||||
await console.log(x);
|
||||
}, 2);
|
||||
function top() {
|
||||
console.log("top");
|
||||
}
|
||||
top();
|
||||
async function async_top() {
|
||||
console.log("async_top");
|
||||
}
|
||||
async_top();
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"toplevel": true,
|
||||
"reduce_vars": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
let x = 0;
|
||||
class NoProps {}
|
||||
class WithProps {
|
||||
prop = (x = x === 1 ? "PASS" : "FAIL");
|
||||
}
|
||||
class WithStaticProps {
|
||||
static prop = (x = x === 0 ? 1 : "FAIL");
|
||||
}
|
||||
new NoProps();
|
||||
new WithProps();
|
||||
new WithStaticProps();
|
||||
console.log(x);
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"dead_code": true,
|
||||
"loops": true
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
const a = 9,
|
||||
b = 0;
|
||||
for (const a = 1; a < 3; ++b) break;
|
||||
console.log(a, b);
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"dead_code": true,
|
||||
"loops": true
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
let a = 9,
|
||||
b = 0;
|
||||
for (const a = 1; a < 3; ++b) break;
|
||||
console.log(a, b);
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"dead_code": true,
|
||||
"loops": true
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
var a = 9,
|
||||
b = 0;
|
||||
for (const a = 1; a < 3; ++b) break;
|
||||
console.log(a, b);
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
(function () {
|
||||
var x = function f() {
|
||||
return f;
|
||||
};
|
||||
console.log(x() === eval("x"));
|
||||
})();
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"unsafe": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 1 };
|
||||
o.p++;
|
||||
console.log(o.p);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 2 };
|
||||
--o.p;
|
||||
console.log(o.p);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 3 };
|
||||
o.p += "";
|
||||
console.log(o.p);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 4 };
|
||||
o = {};
|
||||
console.log(o.p);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 5 };
|
||||
o.p = -9;
|
||||
console.log(o.p);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
function inc() {
|
||||
this.p++;
|
||||
}
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 6 };
|
||||
inc.call(o);
|
||||
console.log(o.p);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 7 };
|
||||
console.log([o][0].p++);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
||||
console.log(
|
||||
(function () {
|
||||
var o = { p: 8 };
|
||||
console.log({ q: o }.q.p++);
|
||||
return o.p;
|
||||
})()
|
||||
);
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"inline": true,
|
||||
"passes": 3,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
function f() {
|
||||
return g(2);
|
||||
function g(b) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(f())
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"inline": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
function f() {
|
||||
function g() {
|
||||
return 1;
|
||||
}
|
||||
function h() {
|
||||
return 2;
|
||||
}
|
||||
g = function () {
|
||||
return 3;
|
||||
};
|
||||
return g() + h();
|
||||
}
|
||||
|
||||
console.log(f())
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"inline": true,
|
||||
"passes": 2,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"toplevel": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
var a = 42;
|
||||
!(function (a) {
|
||||
console.log(a());
|
||||
})(function () {
|
||||
return a;
|
||||
});
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"inline": true,
|
||||
"passes": 2,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"toplevel": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
var a = 42;
|
||||
!(function (a) {
|
||||
console.log(a());
|
||||
})(function (a) {
|
||||
return a;
|
||||
});
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"inline": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
function f(a) {
|
||||
function a() {
|
||||
return 1;
|
||||
}
|
||||
function b() {
|
||||
return 2;
|
||||
}
|
||||
function c() {
|
||||
return 3;
|
||||
}
|
||||
b.inject = [];
|
||||
c = function () {
|
||||
return 4;
|
||||
};
|
||||
return a() + b() + c();
|
||||
}
|
||||
|
||||
console.log(f(1423796))
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
!(function () {
|
||||
var a = 1;
|
||||
for (var b = 1; --b; ) var a = 2;
|
||||
console.log(a);
|
||||
})();
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
function f(a) {
|
||||
l: {
|
||||
if (a) break l;
|
||||
var t = 1;
|
||||
}
|
||||
console.log(t);
|
||||
}
|
||||
|
||||
f(123123)
|
||||
f(0)
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"inline": true,
|
||||
"passes": 3,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
console.log(
|
||||
(function () {
|
||||
var a = 1;
|
||||
return a;
|
||||
})(),
|
||||
(function () {
|
||||
var b;
|
||||
b = 2;
|
||||
return b;
|
||||
})(),
|
||||
(function () {
|
||||
c = 3;
|
||||
return c;
|
||||
var c;
|
||||
})(),
|
||||
(function (c) {
|
||||
c = 4;
|
||||
return c;
|
||||
})(),
|
||||
(function (c) {
|
||||
c = 5;
|
||||
return c;
|
||||
var c;
|
||||
})(),
|
||||
(function c() {
|
||||
c = 6;
|
||||
return typeof c;
|
||||
})(),
|
||||
(function c() {
|
||||
c = 7;
|
||||
return c;
|
||||
var c;
|
||||
})(),
|
||||
(function () {
|
||||
c = 8;
|
||||
return c;
|
||||
var c = "foo";
|
||||
})()
|
||||
);
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"comparisons": true,
|
||||
"conditionals": true,
|
||||
"dead_code": true,
|
||||
"evaluate": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"switches": true,
|
||||
"typeofs": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
(function f() {
|
||||
switch (1) {
|
||||
case 0:
|
||||
var a = true;
|
||||
break;
|
||||
default:
|
||||
if (typeof a === "undefined") console.log("PASS");
|
||||
else console.log("FAIL");
|
||||
}
|
||||
})();
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"conditionals": true,
|
||||
"dead_code": true,
|
||||
"evaluate": true,
|
||||
"passes": 2,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"switches": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
(function f() {
|
||||
switch (1) {
|
||||
case 0:
|
||||
var a = true;
|
||||
break;
|
||||
default:
|
||||
if (typeof a === "undefined") console.log("PASS");
|
||||
else console.log("FAIL");
|
||||
}
|
||||
})();
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"comparisons": true,
|
||||
"conditionals": true,
|
||||
"dead_code": true,
|
||||
"evaluate": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"switches": true,
|
||||
"typeofs": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
(function f() {
|
||||
switch (1) {
|
||||
case 0:
|
||||
var a = true;
|
||||
break;
|
||||
case 1:
|
||||
if (typeof a === "undefined") console.log("PASS");
|
||||
else console.log("FAIL");
|
||||
}
|
||||
})();
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"conditionals": true,
|
||||
"dead_code": true,
|
||||
"evaluate": true,
|
||||
"passes": 2,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"switches": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
(function f() {
|
||||
switch (1) {
|
||||
case 0:
|
||||
var a = true;
|
||||
break;
|
||||
case 1:
|
||||
if (typeof a === "undefined") console.log("PASS");
|
||||
else console.log("FAIL");
|
||||
}
|
||||
})();
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"dead_code": true,
|
||||
"evaluate": true,
|
||||
"keep_fargs": false,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"switches": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
(function (a) {
|
||||
switch (1) {
|
||||
case a:
|
||||
console.log(a);
|
||||
break;
|
||||
default:
|
||||
console.log(2);
|
||||
break;
|
||||
}
|
||||
})(1);
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"dead_code": true,
|
||||
"evaluate": true,
|
||||
"keep_fargs": false,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"side_effects": true,
|
||||
"switches": true,
|
||||
"unused": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
(function (a) {
|
||||
switch (1) {
|
||||
case (a = 1):
|
||||
console.log(a);
|
||||
break;
|
||||
default:
|
||||
console.log(2);
|
||||
break;
|
||||
}
|
||||
})(1);
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"evaluate": true,
|
||||
"reduce_funcs": true,
|
||||
"reduce_vars": true,
|
||||
"unsafe": true
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
function f(some) {
|
||||
some.thing = false;
|
||||
}
|
||||
console.log(
|
||||
(function () {
|
||||
var some = { thing: true };
|
||||
f(some);
|
||||
return some.thing;
|
||||
})()
|
||||
);
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"reduce_vars": true,
|
||||
"toplevel": true,
|
||||
"unused": true
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user