mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 21:54:36 +03:00
feat(es/minifier): Implement more rules (#1871)
swc_ecma_codegen: - Fix codegen of `U+0028` and `U+0029`. swc_ecma_minifier: - Implement MVP. swc_ecma_transforms_base: - `fixer`: Handle seq/await in rhs of for-of.
This commit is contained in:
parent
d64aa6f80d
commit
b02e189d07
4
.github/workflows/cargo.yml
vendored
4
.github/workflows/cargo.yml
vendored
@ -160,7 +160,7 @@ jobs:
|
||||
# Source map format
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
node-version: "16"
|
||||
|
||||
# We explicitly do this to cache properly.
|
||||
- name: Install Rust
|
||||
@ -170,7 +170,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 16
|
||||
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
|
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2430,7 +2430,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_codegen"
|
||||
version = "0.64.0"
|
||||
version = "0.64.2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"num-bigint",
|
||||
@ -2500,11 +2500,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_minifier"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"anyhow",
|
||||
"fxhash",
|
||||
"indexmap",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pretty_assertions 0.6.1",
|
||||
@ -2522,7 +2523,9 @@ dependencies = [
|
||||
"swc_ecma_transforms_base",
|
||||
"swc_ecma_utils",
|
||||
"swc_ecma_visit",
|
||||
"swc_node_base",
|
||||
"testing",
|
||||
"unicode-xid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@ -2605,7 +2608,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_transforms_base"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"fxhash",
|
||||
"once_cell",
|
||||
@ -2788,7 +2791,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_transforms_typescript"
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"fxhash",
|
||||
"serde",
|
||||
@ -2835,7 +2838,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecmascript"
|
||||
version = "0.48.0"
|
||||
version = "0.49.0"
|
||||
dependencies = [
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_codegen",
|
||||
@ -2940,7 +2943,7 @@ dependencies = [
|
||||
"env_logger 0.7.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pretty_assertions 0.6.1",
|
||||
"pretty_assertions 0.7.2",
|
||||
"regex",
|
||||
"swc_common",
|
||||
"testing_macros",
|
||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_ecmascript"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.48.0"
|
||||
version = "0.49.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@ -34,7 +34,7 @@ typescript = ["typescript-parser", "swc_ecma_transforms/typescript"]
|
||||
swc_ecma_ast = {version = "0.49.0", path = "./ast"}
|
||||
swc_ecma_codegen = {version = "0.64.0", path = "./codegen", optional = true}
|
||||
swc_ecma_dep_graph = {version = "0.33.0", path = "./dep-graph", optional = true}
|
||||
swc_ecma_minifier = {version = "0.14.0", path = "./minifier", optional = true}
|
||||
swc_ecma_minifier = {version = "0.15.0", path = "./minifier", optional = true}
|
||||
swc_ecma_parser = {version = "0.65.0", path = "./parser", optional = true, default-features = false}
|
||||
swc_ecma_transforms = {version = "0.61.0", path = "./transforms", optional = true}
|
||||
swc_ecma_utils = {version = "0.41.0", path = "./utils", optional = true}
|
||||
|
@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_ecma_codegen"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.64.1"
|
||||
version = "0.64.2"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1"
|
||||
|
@ -2543,6 +2543,13 @@ fn escape_without_source(v: &str, target: JscTarget, single_quote: bool) -> Stri
|
||||
let _ = write!(buf, "\\x{:x}", c as u8);
|
||||
}
|
||||
|
||||
'\u{2028}' => {
|
||||
buf.push_str("\\u2028");
|
||||
}
|
||||
'\u{2029}' => {
|
||||
buf.push_str("\\u2029");
|
||||
}
|
||||
|
||||
_ => {
|
||||
buf.push(c);
|
||||
}
|
||||
|
@ -560,7 +560,7 @@ fn test_escape_without_source() {
|
||||
es2020("abcde", "abcde");
|
||||
es2020(
|
||||
"\x00\r\n\u{85}\u{2028}\u{2029};",
|
||||
"\\x00\\r\\n\\x85\u{2028}\u{2029};",
|
||||
"\\x00\\r\\n\\x85\\u2028\\u2029;",
|
||||
);
|
||||
|
||||
es2020("\n", "\\n");
|
||||
|
47
ecmascript/minifier/.eslintrc
Normal file
47
ecmascript/minifier/.eslintrc
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"space-unary-ops": 2,
|
||||
"no-undef": "off",
|
||||
"consistent-return": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-cond-assign": "off",
|
||||
"multiline-ternary": [
|
||||
"error",
|
||||
"always-multiline"
|
||||
],
|
||||
"object-property-newline": [
|
||||
"error",
|
||||
{
|
||||
"allowAllPropertiesOnSameLine": false
|
||||
}
|
||||
],
|
||||
"function-call-argument-newline": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"object-curly-newline": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"function-paren-newline": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-trailing-spaces": [
|
||||
"error"
|
||||
]
|
||||
}
|
||||
}
|
2
ecmascript/minifier/.gitignore
vendored
Normal file
2
ecmascript/minifier/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Used to see ideal output using terser
|
||||
lab.js
|
@ -7,13 +7,14 @@ 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.14.0"
|
||||
version = "0.15.0"
|
||||
|
||||
[features]
|
||||
debug = []
|
||||
|
||||
[dependencies]
|
||||
fxhash = "0.2.1"
|
||||
indexmap = "1.7.0"
|
||||
log = "0.4"
|
||||
once_cell = "1.5.2"
|
||||
pretty_assertions = {version = "0.6.1", optional = true}
|
||||
@ -31,10 +32,12 @@ swc_ecma_transforms = {version = "0.61.0", path = "../transforms/", features = [
|
||||
swc_ecma_transforms_base = {version = "0.24.0", path = "../transforms/base"}
|
||||
swc_ecma_utils = {version = "0.41.0", path = "../utils"}
|
||||
swc_ecma_visit = {version = "0.35.0", path = "../visit"}
|
||||
unicode-xid = "0.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
anyhow = "1"
|
||||
pretty_assertions = "0.6.1"
|
||||
swc_node_base = {version = "0.2.0", path = "../../node/base"}
|
||||
testing = {version = "0.12.0", path = "../../testing"}
|
||||
walkdir = "2.3.1"
|
||||
|
@ -8,6 +8,26 @@ Currently name mangler is very simple. To focus on creating a MVP, I (kdy1) will
|
||||
|
||||
## Testing
|
||||
|
||||
### Real-library tests
|
||||
|
||||
You can use vscode to see diffs. Select a file from `tests/projects/output` and select the corresponding file from `tests/projects/refs`. Then you can right click to see an option to open diff.
|
||||
|
||||
#### Splitting a library test
|
||||
|
||||
Extracting only subset from a library test makes development much easier.
|
||||
|
||||
Split status:
|
||||
|
||||
- [ ] angular-1.2.5
|
||||
- [x] backbone-1.1.0
|
||||
- [ ] jquery-1.9.1
|
||||
- [ ] jquery.mobile-1.4.2
|
||||
- [ ] mootools-1.4.5
|
||||
- [x] underscore-1.5.2
|
||||
- [ ] yui-3.12.0
|
||||
|
||||
### Tests from terser
|
||||
|
||||
Tests ported from terser has
|
||||
|
||||
- input.js
|
||||
@ -16,6 +36,6 @@ Tests ported from terser has
|
||||
- output.js (if exists and can be modified if our one is better or due to lack of frequency-aware base54)
|
||||
- output.terser.js (if exists)
|
||||
|
||||
## Copying tests
|
||||
#### Copying tests
|
||||
|
||||
Replace the content of `terser/test/compress.js` with it of `scripts/compress.js` and run `npm run test:compress`
|
||||
|
20
ecmascript/minifier/package.json
Normal file
20
ecmascript/minifier/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "minifier",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "EcmaScript minifier for the swc project. This is basically a port of terser.",
|
||||
"main": "lab.js",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.29.0"
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ set -eu
|
||||
|
||||
export SWC_RUN=0
|
||||
|
||||
cargo test --test compress --all-features \
|
||||
| grep 'terser__compress' \
|
||||
cargo test --test compress --all-features fixture_tests__terser__compress__ \
|
||||
| grep 'fixture_tests__terser__compress__' \
|
||||
| grep 'js .\.\. ok$' \
|
||||
| sed -e 's!test fixture_terser__compress__!!' \
|
||||
| sed -e 's!test fixture_tests__terser__compress__!!' \
|
||||
| sed -e 's! ... ok!!' \
|
||||
| sed -e 's!__!/!g' \
|
||||
| sed -e 's!_js!.js!' \
|
||||
|
19
ecmascript/minifier/scripts/apply.sh
Executable file
19
ecmascript/minifier/scripts/apply.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eux
|
||||
|
||||
|
||||
# Prevent regression
|
||||
./scripts/run.sh
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
export RUST_LOG=swc_ecma_minifier=trace
|
||||
touch tests/compress.rs
|
||||
|
||||
UPDATE=1 cargo test --test compress files__projects --all-features || true
|
||||
|
||||
# Make it easier to compare
|
||||
prettier --write tests/projects/output
|
||||
yarn run eslint --fix ./tests/projects/output/
|
||||
|
||||
ls -al ./tests/projects/output
|
||||
ls -al ./tests/projects/refs
|
@ -5,4 +5,4 @@ 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
|
||||
cargo test --test compress ${1-base_exec} -q --all-features
|
||||
|
9
ecmascript/minifier/scripts/ideal.sh
Executable file
9
ecmascript/minifier/scripts/ideal.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# This scripts print the ideal output of lab.js
|
||||
#
|
||||
|
||||
set -eux
|
||||
terser --compress --format beautify -- lab.js
|
||||
node lab.js
|
@ -3,10 +3,10 @@ set -eu
|
||||
|
||||
export SWC_RUN=0
|
||||
|
||||
cargo test --test compress --all-features ${1-''} \
|
||||
| grep 'terser__compress' \
|
||||
cargo test --test compress --all-features ${1-'fixture_tests__terser__compress__'} \
|
||||
| grep 'fixture_tests__terser__compress__' \
|
||||
| grep 'js .\.\. FAILED$' \
|
||||
| sed -e 's!test fixture_terser__compress__!!' \
|
||||
| sed -e 's!test fixture_tests__terser__compress__!!' \
|
||||
| sed -e 's! ... FAILED!!' \
|
||||
| sed -e 's!__!/!g' \
|
||||
| sed -e 's!_js!.js!' \
|
||||
|
22
ecmascript/minifier/scripts/project-ref.sh
Executable file
22
ecmascript/minifier/scripts/project-ref.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
run() {
|
||||
terser --compress -- ./tests/projects/files/$1 > ./tests/projects/refs/$1
|
||||
}
|
||||
|
||||
|
||||
mkdir -p ./tests/projects/refs
|
||||
|
||||
run angular-1.2.5.js
|
||||
run backbone-1.1.0.js
|
||||
run jquery-1.9.1.js
|
||||
run jquery.mobile-1.4.2.js
|
||||
run mootools-1.4.5.js
|
||||
run underscore-1.5.2.js
|
||||
run yui-3.12.0.js
|
||||
run react-17.0.2.js
|
||||
run react-dom-17.0.2.js
|
||||
|
||||
prettier --write tests/projects/refs/
|
||||
yarn run eslint --fix ./tests/projects/refs/
|
@ -13,6 +13,9 @@ set -eu
|
||||
|
||||
export RUST_LOG=swc_ecma_minifier=trace
|
||||
|
||||
# Run unit tests.
|
||||
cargo test --all-features --lib
|
||||
|
||||
# To prevent regression, we run base test before real tests.
|
||||
touch tests/compress.rs
|
||||
UPDATE=1 ./scripts/base.sh base_fixture
|
||||
@ -21,7 +24,7 @@ UPDATE=1 ./scripts/base.sh base_fixture
|
||||
if [ -z "$@" ]; then
|
||||
./scripts/sort.sh
|
||||
|
||||
SWC_RUN=0 GOLDEN_ONLY=1 cargo test --test compress --all-features
|
||||
SWC_RUN=0 GOLDEN_ONLY=1 cargo test -q --test compress --all-features fixture_tests__terser__compress__
|
||||
fi
|
||||
|
||||
SKIP_GOLDEN=1 cargo test --test compress --all-features $@
|
||||
|
@ -12,4 +12,4 @@ set -eu
|
||||
echo '' > tests/ignored.txt
|
||||
|
||||
scripts/add-golden.sh
|
||||
scripts/ignore.sh ''
|
||||
scripts/ignore.sh 'fixture_tests__terser__compress__'
|
@ -17,6 +17,9 @@ impl UsageAnalyzer {
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub(super) struct Ctx {
|
||||
pub var_decl_kind_of_pat: Option<VarDeclKind>,
|
||||
|
||||
pub in_var_decl_with_no_side_effect_for_member_access: bool,
|
||||
|
||||
pub in_pat_of_var_decl: bool,
|
||||
pub in_pat_of_var_decl_with_init: bool,
|
||||
pub in_pat_of_param: bool,
|
||||
@ -38,6 +41,8 @@ pub(super) struct Ctx {
|
||||
pub in_update_arg: bool,
|
||||
pub in_assign_lhs: bool,
|
||||
pub in_cond: bool,
|
||||
|
||||
pub inline_prevented: bool,
|
||||
}
|
||||
|
||||
pub(super) struct WithCtx<'a> {
|
||||
|
@ -4,6 +4,7 @@ use crate::util::idents_used_by;
|
||||
use fxhash::FxHashMap;
|
||||
use fxhash::FxHashSet;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::time::Instant;
|
||||
use swc_atoms::JsWord;
|
||||
use swc_common::SyntaxContext;
|
||||
use swc_common::DUMMY_SP;
|
||||
@ -24,6 +25,8 @@ pub(crate) fn analyze<N>(n: &N) -> ProgramData
|
||||
where
|
||||
N: VisitWith<UsageAnalyzer>,
|
||||
{
|
||||
let start_time = Instant::now();
|
||||
|
||||
let mut v = UsageAnalyzer {
|
||||
data: Default::default(),
|
||||
scope: Default::default(),
|
||||
@ -33,11 +36,17 @@ where
|
||||
let top_scope = v.scope;
|
||||
v.data.top.merge(top_scope, false);
|
||||
|
||||
let end_time = Instant::now();
|
||||
|
||||
log::debug!("Scope analysis took {:?}", end_time - start_time);
|
||||
|
||||
v.data
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct VarUsageInfo {
|
||||
pub inline_prevented: bool,
|
||||
|
||||
/// The number of reference to this identifier.
|
||||
pub ref_count: usize,
|
||||
|
||||
@ -51,6 +60,8 @@ pub(crate) struct VarUsageInfo {
|
||||
/// `true` if the enclosing function defines this variable as a parameter.
|
||||
pub declared_as_fn_param: bool,
|
||||
|
||||
pub declared_as_fn_expr: bool,
|
||||
|
||||
pub assign_count: usize,
|
||||
pub mutation_by_call_count: usize,
|
||||
pub usage_count: usize,
|
||||
@ -71,6 +82,8 @@ pub(crate) struct VarUsageInfo {
|
||||
/// functions.
|
||||
pub is_fn_local: bool,
|
||||
|
||||
used_by_nested_fn: bool,
|
||||
|
||||
pub used_in_loop: bool,
|
||||
|
||||
pub var_kind: Option<VarDeclKind>,
|
||||
@ -83,6 +96,8 @@ pub(crate) struct VarUsageInfo {
|
||||
/// Indicates a variable or function is overrided without using it.
|
||||
pub overriden_without_used: bool,
|
||||
|
||||
pub no_side_effect_for_member_access: bool,
|
||||
|
||||
/// In `c = b`, `b` inffects `c`.
|
||||
infects: Vec<Id>,
|
||||
}
|
||||
@ -143,9 +158,12 @@ impl ProgramData {
|
||||
to.merge(scope, false);
|
||||
}
|
||||
|
||||
for (id, var_info) in child.vars {
|
||||
for (id, mut var_info) in child.vars {
|
||||
// log::trace!("merge({:?},{}{:?})", kind, id.0, id.1);
|
||||
match self.vars.entry(id) {
|
||||
Entry::Occupied(mut e) => {
|
||||
e.get_mut().inline_prevented |= var_info.inline_prevented;
|
||||
|
||||
e.get_mut().ref_count += var_info.ref_count;
|
||||
e.get_mut().cond_init |= var_info.cond_init;
|
||||
|
||||
@ -158,6 +176,7 @@ impl ProgramData {
|
||||
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;
|
||||
e.get_mut().declared_as_fn_expr |= var_info.declared_as_fn_expr;
|
||||
|
||||
// If a var is registered at a parent scope, it means that it's delcared before
|
||||
// usages.
|
||||
@ -172,14 +191,30 @@ impl ProgramData {
|
||||
|
||||
e.get_mut().infects.extend(var_info.infects);
|
||||
|
||||
e.get_mut().no_side_effect_for_member_access =
|
||||
e.get_mut().no_side_effect_for_member_access
|
||||
&& var_info.no_side_effect_for_member_access;
|
||||
|
||||
match kind {
|
||||
ScopeKind::Fn => {
|
||||
e.get_mut().is_fn_local = false;
|
||||
e.get_mut().used_by_nested_fn = true;
|
||||
}
|
||||
ScopeKind::Block => {
|
||||
if var_info.used_by_nested_fn {
|
||||
e.get_mut().is_fn_local = false;
|
||||
e.get_mut().used_by_nested_fn = true;
|
||||
}
|
||||
}
|
||||
ScopeKind::Block => {}
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
match kind {
|
||||
ScopeKind::Fn => {
|
||||
var_info.used_by_nested_fn = true;
|
||||
}
|
||||
ScopeKind::Block => {}
|
||||
}
|
||||
e.insert(var_info);
|
||||
}
|
||||
}
|
||||
@ -225,17 +260,25 @@ impl UsageAnalyzer {
|
||||
}
|
||||
|
||||
fn report(&mut self, i: Id, is_modify: bool, dejavu: &mut FxHashSet<Id>) {
|
||||
// log::trace!("report({}{:?})", i.0, i.1);
|
||||
|
||||
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()
|
||||
let e = self.data.vars.entry(i.clone()).or_insert_with(|| {
|
||||
// log::trace!("insert({}{:?})", i.0, i.1);
|
||||
|
||||
VarUsageInfo {
|
||||
used_above_decl: true,
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
e.inline_prevented |= self.ctx.inline_prevented;
|
||||
|
||||
e.ref_count += 1;
|
||||
e.reassigned |= is_first && is_modify && self.ctx.is_exact_reassignment;
|
||||
// Passing object as a argument is possibly modification.
|
||||
@ -258,7 +301,7 @@ impl UsageAnalyzer {
|
||||
}
|
||||
|
||||
fn report_usage(&mut self, i: &Ident, is_assign: bool) {
|
||||
self.report(i.to_id(), is_assign, &mut Default::default())
|
||||
self.report(i.to_id(), is_assign, &mut Default::default());
|
||||
}
|
||||
|
||||
fn declare_decl(
|
||||
@ -266,7 +309,12 @@ impl UsageAnalyzer {
|
||||
i: &Ident,
|
||||
has_init: bool,
|
||||
kind: Option<VarDeclKind>,
|
||||
is_fn_decl: bool,
|
||||
) -> &mut VarUsageInfo {
|
||||
// log::trace!("declare_decl({}{:?})", i.sym, i.span.ctxt);
|
||||
|
||||
let ctx = self.ctx;
|
||||
|
||||
let v = self
|
||||
.data
|
||||
.vars
|
||||
@ -282,6 +330,9 @@ impl UsageAnalyzer {
|
||||
is_fn_local: true,
|
||||
var_kind: kind,
|
||||
var_initialized: has_init,
|
||||
no_side_effect_for_member_access: ctx
|
||||
.in_var_decl_with_no_side_effect_for_member_access,
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
self.scope
|
||||
@ -297,6 +348,8 @@ impl UsageAnalyzer {
|
||||
}
|
||||
v.declared_as_catch_param |= self.ctx.in_catch_param;
|
||||
|
||||
v.declared_as_fn_expr |= is_fn_decl;
|
||||
|
||||
v
|
||||
}
|
||||
}
|
||||
@ -385,8 +438,22 @@ impl Visit for UsageAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_class(&mut self, n: &Class, _: &dyn Node) {
|
||||
n.decorators.visit_with(n, self);
|
||||
|
||||
{
|
||||
let ctx = Ctx {
|
||||
inline_prevented: true,
|
||||
..self.ctx
|
||||
};
|
||||
n.super_class.visit_with(n, &mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
n.body.visit_with(n, self);
|
||||
}
|
||||
|
||||
fn visit_class_decl(&mut self, n: &ClassDecl, _: &dyn Node) {
|
||||
self.declare_decl(&n.ident, true, None);
|
||||
self.declare_decl(&n.ident, true, None, false);
|
||||
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
@ -415,8 +482,20 @@ impl Visit for UsageAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_fn_expr(&mut self, n: &FnExpr, _: &dyn Node) {
|
||||
n.visit_children_with(self);
|
||||
|
||||
if let Some(id) = &n.ident {
|
||||
self.data
|
||||
.vars
|
||||
.entry(id.to_id())
|
||||
.or_default()
|
||||
.declared_as_fn_expr = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_fn_decl(&mut self, n: &FnDecl, _: &dyn Node) {
|
||||
self.declare_decl(&n.ident, true, None);
|
||||
self.declare_decl(&n.ident, true, None, true);
|
||||
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
@ -432,12 +511,14 @@ impl Visit for UsageAnalyzer {
|
||||
};
|
||||
n.left.visit_with(n, &mut *child.with_ctx(ctx));
|
||||
|
||||
n.right.visit_with(n, child);
|
||||
|
||||
let ctx = Ctx {
|
||||
in_loop: true,
|
||||
in_cond: true,
|
||||
..child.ctx
|
||||
};
|
||||
n.body.visit_with(n, &mut *child.with_ctx(ctx))
|
||||
n.body.visit_with(n, &mut *child.with_ctx(ctx));
|
||||
});
|
||||
}
|
||||
|
||||
@ -597,6 +678,7 @@ impl Visit for UsageAnalyzer {
|
||||
&i.id,
|
||||
self.ctx.in_pat_of_var_decl_with_init,
|
||||
self.ctx.var_decl_kind_of_pat,
|
||||
false,
|
||||
);
|
||||
|
||||
if in_pat_of_param {
|
||||
@ -735,6 +817,10 @@ impl Visit for UsageAnalyzer {
|
||||
let ctx = Ctx {
|
||||
in_pat_of_var_decl: true,
|
||||
in_pat_of_var_decl_with_init: e.init.is_some(),
|
||||
in_var_decl_with_no_side_effect_for_member_access: match e.init.as_deref() {
|
||||
Some(Expr::Array(..) | Expr::Lit(..)) => true,
|
||||
_ => false,
|
||||
},
|
||||
..self.ctx
|
||||
};
|
||||
e.name.visit_with(e, &mut *self.with_ctx(ctx));
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::analyzer::analyze;
|
||||
use crate::analyzer::ProgramData;
|
||||
use crate::analyzer::UsageAnalyzer;
|
||||
use crate::util::is_hoisted_var_decl_without_init;
|
||||
@ -24,21 +23,21 @@ pub(super) struct DeclHoisterConfig {
|
||||
pub top_level: bool,
|
||||
}
|
||||
|
||||
pub(super) fn decl_hoister(config: DeclHoisterConfig) -> Hoister {
|
||||
pub(super) fn decl_hoister(config: DeclHoisterConfig, data: &ProgramData) -> Hoister {
|
||||
Hoister {
|
||||
config,
|
||||
changed: false,
|
||||
data: None,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Hoister {
|
||||
pub(super) struct Hoister<'a> {
|
||||
config: DeclHoisterConfig,
|
||||
changed: bool,
|
||||
data: Option<ProgramData>,
|
||||
data: &'a ProgramData,
|
||||
}
|
||||
|
||||
impl Repeated for Hoister {
|
||||
impl Repeated for Hoister<'_> {
|
||||
fn changed(&self) -> bool {
|
||||
self.changed
|
||||
}
|
||||
@ -48,40 +47,30 @@ impl Repeated for Hoister {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hoister {
|
||||
impl Hoister<'_> {
|
||||
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: StmtLike + IsModuleItem,
|
||||
Vec<T>: VisitMutWith<Self> + VisitWith<UsageAnalyzer>,
|
||||
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer>,
|
||||
{
|
||||
match self.data {
|
||||
None => {
|
||||
self.data = Some(analyze(stmts));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
stmts.visit_mut_children_with(self);
|
||||
|
||||
let should_hoist = !is_sorted_by_key(stmts.iter(), |stmt| match stmt.as_stmt() {
|
||||
Some(stmt) => match stmt {
|
||||
Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1,
|
||||
Stmt::Decl(Decl::Var(var)) if self.config.hoist_vars => {
|
||||
if let Some(data) = &self.data {
|
||||
let ids: Vec<Id> = find_ids(&var.decls);
|
||||
let ids: Vec<Id> = find_ids(&var.decls);
|
||||
|
||||
if ids.iter().any(|id| {
|
||||
data.vars
|
||||
.get(id)
|
||||
.map(|v| !v.used_above_decl)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
}
|
||||
} else {
|
||||
if ids.iter().any(|id| {
|
||||
self.data
|
||||
.vars
|
||||
.get(id)
|
||||
.map(|v| !v.used_above_decl)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
_ => 3,
|
||||
@ -133,8 +122,8 @@ impl Hoister {
|
||||
if decl.init.is_none()
|
||||
&& self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|v| v.vars.get(&id.to_id()))
|
||||
.vars
|
||||
.get(&id.to_id())
|
||||
.map(|v| v.declared_as_fn_param)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -207,8 +196,8 @@ impl Hoister {
|
||||
if decl.init.is_none()
|
||||
&& self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|v| v.vars.get(&name.to_id()))
|
||||
.vars
|
||||
.get(&name.to_id())
|
||||
.map(|v| v.declared_as_fn_param)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -258,7 +247,7 @@ impl Hoister {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitMut for Hoister {
|
||||
impl VisitMut for Hoister<'_> {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
|
||||
|
@ -1,9 +1,12 @@
|
||||
use self::drop_console::drop_console;
|
||||
use self::hoist_decls::DeclHoisterConfig;
|
||||
use self::optimize::optimizer;
|
||||
use crate::analyzer::analyze;
|
||||
use crate::analyzer::ProgramData;
|
||||
use crate::compress::hoist_decls::decl_hoister;
|
||||
use crate::debug::dump;
|
||||
use crate::debug::invoke;
|
||||
use crate::marks::Marks;
|
||||
use crate::option::CompressOptions;
|
||||
use crate::util::Optional;
|
||||
#[cfg(feature = "pretty_assertions")]
|
||||
@ -13,12 +16,15 @@ use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use swc_common::chain;
|
||||
use std::time::Instant;
|
||||
use swc_common::comments::Comments;
|
||||
use swc_common::pass::CompilerPass;
|
||||
use swc_common::pass::Repeat;
|
||||
use swc_common::pass::Repeated;
|
||||
use swc_common::sync::Lrc;
|
||||
use swc_common::{chain, SourceMap};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms::fixer;
|
||||
use swc_ecma_transforms::optimization::simplify::dead_branch_remover;
|
||||
use swc_ecma_transforms::optimization::simplify::expr_simplifier;
|
||||
use swc_ecma_transforms::pass::JsPass;
|
||||
@ -34,7 +40,9 @@ mod drop_console;
|
||||
mod hoist_decls;
|
||||
mod optimize;
|
||||
|
||||
pub fn compressor<'a>(
|
||||
pub(crate) fn compressor<'a>(
|
||||
cm: Lrc<SourceMap>,
|
||||
marks: Marks,
|
||||
options: &'a CompressOptions,
|
||||
comments: Option<&'a dyn Comments>,
|
||||
) -> impl 'a + JsPass {
|
||||
@ -43,10 +51,13 @@ pub fn compressor<'a>(
|
||||
visitor: drop_console(),
|
||||
};
|
||||
let compressor = Compressor {
|
||||
comments,
|
||||
cm,
|
||||
marks,
|
||||
options,
|
||||
pass: 0,
|
||||
comments,
|
||||
changed: false,
|
||||
pass: 0,
|
||||
data: None,
|
||||
};
|
||||
|
||||
chain!(
|
||||
@ -57,10 +68,13 @@ pub fn compressor<'a>(
|
||||
}
|
||||
|
||||
struct Compressor<'a> {
|
||||
cm: Lrc<SourceMap>,
|
||||
marks: Marks,
|
||||
options: &'a CompressOptions,
|
||||
comments: Option<&'a dyn Comments>,
|
||||
changed: bool,
|
||||
pass: usize,
|
||||
data: Option<ProgramData>,
|
||||
}
|
||||
|
||||
impl CompilerPass for Compressor<'_> {
|
||||
@ -77,6 +91,7 @@ impl Repeated for Compressor<'_> {
|
||||
fn reset(&mut self) {
|
||||
self.changed = false;
|
||||
self.pass += 1;
|
||||
self.data = None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +99,7 @@ impl Compressor<'_> {
|
||||
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: StmtLike,
|
||||
Vec<T>: VisitMutWith<Self> + VisitMutWith<hoist_decls::Hoister>,
|
||||
Vec<T>: VisitMutWith<Self> + for<'aa> VisitMutWith<hoist_decls::Hoister<'aa>>,
|
||||
{
|
||||
// Skip if `use asm` exists.
|
||||
if stmts.iter().any(|stmt| match stmt.as_stmt() {
|
||||
@ -104,17 +119,6 @@ impl Compressor<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let mut v = decl_hoister(DeclHoisterConfig {
|
||||
hoist_fns: self.options.hoist_fns,
|
||||
hoist_vars: self.options.hoist_vars,
|
||||
top_level: self.options.top_level(),
|
||||
});
|
||||
stmts.visit_mut_with(&mut v);
|
||||
self.changed |= v.changed();
|
||||
}
|
||||
// TODO: Hoist decls
|
||||
|
||||
stmts.visit_mut_children_with(self);
|
||||
|
||||
// TODO: drop unused
|
||||
@ -125,38 +129,56 @@ impl VisitMut for Compressor<'_> {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_module(&mut self, n: &mut Module) {
|
||||
debug_assert!(self.data.is_none());
|
||||
self.data = Some(analyze(&*n));
|
||||
|
||||
if self.options.passes != 0 && self.options.passes + 1 <= self.pass {
|
||||
let done = dump(&*n);
|
||||
log::trace!("===== Done =====\n{}", done);
|
||||
log::debug!("===== Done =====\n{}", done);
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporary
|
||||
if self.pass > 10 {
|
||||
panic!("Infinite loop detected")
|
||||
if self.pass > 30 {
|
||||
panic!("Infinite loop detected (current pass = {})", self.pass)
|
||||
}
|
||||
|
||||
let start = if cfg!(feature = "debug") {
|
||||
let start = dump(&*n);
|
||||
log::trace!("===== Start =====\n{}", start);
|
||||
let start = dump(&n.clone().fold_with(&mut fixer(None)));
|
||||
log::debug!("===== Start =====\n{}", start);
|
||||
start
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
{
|
||||
log::info!(
|
||||
"compress: Running expression simplifier (pass = {})",
|
||||
self.pass
|
||||
);
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let mut visitor = expr_simplifier();
|
||||
n.map_with_mut(|m| m.fold_with(&mut visitor));
|
||||
n.visit_mut_with(&mut visitor);
|
||||
self.changed |= visitor.changed();
|
||||
if visitor.changed() {
|
||||
log::trace!("compressor: Simplified expressions");
|
||||
log::debug!("compressor: Simplified expressions");
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("===== Simplified =====\n{}", dump(&*n));
|
||||
log::debug!("===== Simplified =====\n{}", dump(&*n));
|
||||
}
|
||||
}
|
||||
|
||||
let end_time = Instant::now();
|
||||
|
||||
log::info!(
|
||||
"compress: expr_simplifier took {:?} (pass = {})",
|
||||
end_time - start_time,
|
||||
self.pass
|
||||
);
|
||||
|
||||
if cfg!(feature = "debug") && !visitor.changed() {
|
||||
let simplified = dump(&*n);
|
||||
let simplified = dump(&n.clone().fold_with(&mut fixer(None)));
|
||||
if start != simplified {
|
||||
assert_eq!(
|
||||
DebugUsingDisplay(&start),
|
||||
@ -169,12 +191,31 @@ impl VisitMut for Compressor<'_> {
|
||||
}
|
||||
|
||||
{
|
||||
log::info!("compress: Running optimizer (pass = {})", self.pass);
|
||||
|
||||
let start_time = Instant::now();
|
||||
// TODO: reset_opt_flags
|
||||
//
|
||||
// This is swc version of `node.optimize(this);`.
|
||||
let mut visitor = optimizer(self.options.clone(), self.comments);
|
||||
let mut visitor = optimizer(
|
||||
self.cm.clone(),
|
||||
self.marks,
|
||||
self.options,
|
||||
self.comments,
|
||||
self.data.as_ref().unwrap(),
|
||||
);
|
||||
n.visit_mut_with(&mut visitor);
|
||||
self.changed |= visitor.changed();
|
||||
|
||||
let end_time = Instant::now();
|
||||
|
||||
log::info!(
|
||||
"compress: Optimizer took {:?} (pass = {})",
|
||||
end_time - start_time,
|
||||
self.pass
|
||||
);
|
||||
// let done = dump(&*n);
|
||||
// log::debug!("===== Result =====\n{}", done);
|
||||
}
|
||||
|
||||
if self.options.conditionals || self.options.dead_code {
|
||||
@ -184,14 +225,18 @@ impl VisitMut for Compressor<'_> {
|
||||
"".into()
|
||||
};
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let mut v = dead_branch_remover();
|
||||
n.map_with_mut(|n| n.fold_with(&mut v));
|
||||
|
||||
let end_time = Instant::now();
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
let simplified = dump(&*n);
|
||||
|
||||
if start != simplified {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"===== Removed dead branches =====\n{}\n==== ===== ===== ===== ======\n{}",
|
||||
start,
|
||||
simplified
|
||||
@ -199,6 +244,12 @@ impl VisitMut for Compressor<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"compress: dead_branch_remover took {:?} (pass = {})",
|
||||
end_time - start_time,
|
||||
self.pass
|
||||
);
|
||||
|
||||
self.changed |= v.changed();
|
||||
}
|
||||
|
||||
@ -207,14 +258,20 @@ impl VisitMut for Compressor<'_> {
|
||||
invoke(&*n);
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, n: &mut Stmt) {
|
||||
// TODO: Skip if node is already optimized.
|
||||
// if (has_flag(node, SQUEEZED)) return node;
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
|
||||
{
|
||||
let mut v = decl_hoister(
|
||||
DeclHoisterConfig {
|
||||
hoist_fns: self.options.hoist_fns,
|
||||
hoist_vars: self.options.hoist_vars,
|
||||
top_level: self.options.top_level(),
|
||||
},
|
||||
self.data.as_ref().unwrap(),
|
||||
);
|
||||
stmts.visit_mut_with(&mut v);
|
||||
self.changed |= v.changed();
|
||||
}
|
||||
|
||||
self.handle_stmt_likes(stmts);
|
||||
|
||||
stmts.retain(|stmt| match stmt {
|
||||
@ -232,6 +289,26 @@ impl VisitMut for Compressor<'_> {
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_mut_script(&mut self, n: &mut Script) {
|
||||
debug_assert!(self.data.is_none());
|
||||
self.data = Some(analyze(&*n));
|
||||
|
||||
{
|
||||
let mut v = decl_hoister(
|
||||
DeclHoisterConfig {
|
||||
hoist_fns: self.options.hoist_fns,
|
||||
hoist_vars: self.options.hoist_vars,
|
||||
top_level: self.options.top_level(),
|
||||
},
|
||||
self.data.as_ref().unwrap(),
|
||||
);
|
||||
n.body.visit_mut_with(&mut v);
|
||||
self.changed |= v.changed();
|
||||
}
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
|
||||
self.handle_stmt_likes(stmts);
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
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::find_ids;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::private_ident;
|
||||
use swc_ecma_utils::Id;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
use swc_ecma_visit::VisitMut;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
@ -36,7 +37,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("arguments: Optimizing computed access to arguments");
|
||||
log::debug!("arguments: Optimizing computed access to arguments");
|
||||
member.computed = false;
|
||||
member.prop = Box::new(Expr::Ident(Ident {
|
||||
span: prop.span,
|
||||
@ -75,9 +76,9 @@ impl Optimizer<'_> {
|
||||
|
||||
{
|
||||
// If a function has a variable named `arguments`, we abort.
|
||||
let data = analyze(&f.body);
|
||||
for (id, var) in &data.vars {
|
||||
if id.0 == js_word!("arguments") && var.declared {
|
||||
let data: Vec<Id> = find_ids(&f.body);
|
||||
for id in &data {
|
||||
if id.0 == js_word!("arguments") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -113,7 +114,7 @@ impl ArgReplacer<'_> {
|
||||
let new_args = idx + 1 - self.params.len();
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("arguments: Injecting {} parameters", new_args);
|
||||
log::debug!("arguments: Injecting {} parameters", new_args);
|
||||
let mut start = self.params.len();
|
||||
self.params.extend(
|
||||
repeat_with(|| {
|
||||
@ -191,7 +192,7 @@ impl VisitMut for ArgReplacer<'_> {
|
||||
if let Some(param) = self.params.get(idx) {
|
||||
match ¶m.pat {
|
||||
Pat::Ident(i) => {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"arguments: Replacing access to arguments to \
|
||||
normal reference",
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ impl Optimizer<'_> {
|
||||
if s.stmts.len() == 1 {
|
||||
if let Stmt::Return(s) = &mut s.stmts[0] {
|
||||
if let Some(arg) = &mut s.arg {
|
||||
log::trace!("arrows: Optimizing the body of an arrow");
|
||||
log::debug!("arrows: Optimizing the body of an arrow");
|
||||
*b = BlockStmtOrExpr::Expr(arg.take());
|
||||
return;
|
||||
}
|
||||
|
@ -1,71 +1,20 @@
|
||||
use super::Optimizer;
|
||||
use crate::compress::optimize::is_pure_undefined;
|
||||
use crate::compress::optimize::{is_pure_undefined, Ctx};
|
||||
use crate::debug::dump;
|
||||
use crate::util::make_bool;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::ExprExt;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::Type;
|
||||
use swc_ecma_utils::Value;
|
||||
use swc_ecma_utils::Value::Known;
|
||||
use swc_ecma_utils::Value::Unknown;
|
||||
use swc_ecma_utils::{undefined, ExprExt};
|
||||
|
||||
/// Methods related to the options `bools` and `bool_as_ints`.
|
||||
impl Optimizer<'_> {
|
||||
/// Disabled because it can change semantics.
|
||||
///
|
||||
/// - `!foo || bar();` => `foo && bar();`
|
||||
/// - `!foo && bar();` => `foo || bar();`
|
||||
pub(super) fn compress_logical_exprs_with_negated_lhs(&mut self, e: &mut Expr) {
|
||||
if !self.options.bools || true {
|
||||
return;
|
||||
}
|
||||
|
||||
match e {
|
||||
Expr::Bin(BinExpr {
|
||||
span,
|
||||
op: op @ op!("||"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
})
|
||||
| Expr::Bin(BinExpr {
|
||||
span,
|
||||
op: op @ op!("&&"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) => match &mut **left {
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) => {
|
||||
if *op == op!("&&") {
|
||||
log::trace!("booleans: Compressing `!foo && bar` as `foo || bar`");
|
||||
} else {
|
||||
log::trace!("booleans: Compressing `!foo || bar` as `foo && bar`");
|
||||
}
|
||||
self.changed = true;
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: *span,
|
||||
left: arg.take(),
|
||||
op: if *op == op!("&&") {
|
||||
op!("||")
|
||||
} else {
|
||||
op!("&&")
|
||||
},
|
||||
right: right.take(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// - `!condition() || !-3.5` => `!condition()`
|
||||
///
|
||||
@ -104,9 +53,9 @@ impl Optimizer<'_> {
|
||||
|
||||
if can_remove {
|
||||
if *op == op!("&&") {
|
||||
log::trace!("booleans: Compressing `!foo && true` as `!foo`");
|
||||
log::debug!("booleans: Compressing `!foo && true` as `!foo`");
|
||||
} else {
|
||||
log::trace!("booleans: Compressing `!foo || false` as `!foo`");
|
||||
log::debug!("booleans: Compressing `!foo || false` as `!foo`");
|
||||
}
|
||||
self.changed = true;
|
||||
*e = *left.take();
|
||||
@ -128,10 +77,7 @@ impl Optimizer<'_> {
|
||||
|
||||
match e {
|
||||
Expr::Unary(UnaryExpr {
|
||||
span,
|
||||
op: op!("!"),
|
||||
arg,
|
||||
..
|
||||
op: op!("!"), arg, ..
|
||||
}) => match &mut **arg {
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("&&"),
|
||||
@ -139,30 +85,123 @@ impl Optimizer<'_> {
|
||||
right,
|
||||
..
|
||||
}) => {
|
||||
log::trace!("Optimizing ``!(a && b)` as `!a || !b`");
|
||||
if negate_cost(&left, self.ctx.in_bool_ctx, false).unwrap_or(isize::MAX) >= 0
|
||||
|| negate_cost(&right, self.ctx.in_bool_ctx, false).unwrap_or(isize::MAX)
|
||||
>= 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
log::debug!("Optimizing `!(a && b)` as `!a || !b`");
|
||||
self.changed = true;
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: *span,
|
||||
op: op!("||"),
|
||||
left: Box::new(Expr::Unary(UnaryExpr {
|
||||
span: DUMMY_SP,
|
||||
op: op!("!"),
|
||||
arg: left.take(),
|
||||
})),
|
||||
right: Box::new(Expr::Unary(UnaryExpr {
|
||||
span: DUMMY_SP,
|
||||
op: op!("!"),
|
||||
arg: right.take(),
|
||||
})),
|
||||
});
|
||||
self.negate(arg);
|
||||
*e = *arg.take();
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"),
|
||||
arg: arg_of_arg,
|
||||
..
|
||||
}) => match &mut **arg_of_arg {
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("||"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) => {
|
||||
if negate_cost(&left, self.ctx.in_bool_ctx, false).unwrap_or(isize::MAX) > 0
|
||||
&& negate_cost(&right, self.ctx.in_bool_ctx, false)
|
||||
.unwrap_or(isize::MAX)
|
||||
> 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
log::debug!("Optimizing `!!(a || b)` as `!a && !b`");
|
||||
self.changed = true;
|
||||
self.negate(arg_of_arg);
|
||||
*e = *arg.take();
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// **This negates bool**.
|
||||
///
|
||||
/// Returns true if it's negated.
|
||||
pub(super) fn optimize_bang_within_logical_ops(
|
||||
&mut self,
|
||||
expr: &mut Expr,
|
||||
is_ret_val_ignored: bool,
|
||||
) -> bool {
|
||||
if negate_cost(&expr, is_ret_val_ignored, is_ret_val_ignored).unwrap_or(isize::MAX) >= 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let e = match expr {
|
||||
Expr::Bin(b) => b,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
match e.op {
|
||||
op!("&&") | op!("||") => {}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
if !is_ret_val_ignored {
|
||||
if let Known(Type::Bool) = e.left.get_type() {
|
||||
} else {
|
||||
// Don't change type.
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Known(Type::Bool) = e.right.get_type() {
|
||||
} else {
|
||||
// Don't change type.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// `!_ && 'undefined' !== typeof require`
|
||||
//
|
||||
// =>
|
||||
//
|
||||
// `_ || 'undefined' == typeof require`
|
||||
log::debug!(
|
||||
"bools({}): Negating: (!a && !b) => !(a || b) (because both expression are good for \
|
||||
negation)",
|
||||
self.line_col(e.span)
|
||||
);
|
||||
let start = dump(&*e);
|
||||
|
||||
e.op = if e.op == op!("&&") {
|
||||
op!("||")
|
||||
} else {
|
||||
op!("&&")
|
||||
};
|
||||
|
||||
let ctx = Ctx {
|
||||
in_bool_ctx: true,
|
||||
..self.ctx
|
||||
};
|
||||
|
||||
self.changed = true;
|
||||
self.with_ctx(ctx).negate(&mut e.left);
|
||||
self.with_ctx(ctx).negate(&mut e.right);
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("[Change] {} => {}", start, dump(&*e));
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn compress_useless_deletes(&mut self, e: &mut Expr) {
|
||||
if !self.options.bools {
|
||||
return;
|
||||
@ -226,7 +265,7 @@ impl Optimizer<'_> {
|
||||
} else {
|
||||
self.changed = true;
|
||||
let span = delete.arg.span();
|
||||
log::trace!("booleans: Compressing `delete` as sequence expression");
|
||||
log::debug!("booleans: Compressing `delete` as sequence expression");
|
||||
*e = Expr::Seq(SeqExpr {
|
||||
span,
|
||||
exprs: vec![delete.arg.take(), Box::new(make_bool(span, true))],
|
||||
@ -241,26 +280,84 @@ impl Optimizer<'_> {
|
||||
if convert_to_true {
|
||||
self.changed = true;
|
||||
let span = delete.arg.span();
|
||||
log::trace!("booleans: Compressing `delete` => true");
|
||||
log::debug!("booleans: Compressing `delete` => true");
|
||||
*e = make_bool(span, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compress_comparsion_of_typeof(&mut self, e: &mut BinExpr) {
|
||||
fn should_optimize(l: &Expr, r: &Expr) -> bool {
|
||||
match (l, r) {
|
||||
(
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("typeof"), ..
|
||||
}),
|
||||
Expr::Lit(..),
|
||||
) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
match e.op {
|
||||
op!("===") | op!("!==") => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
if should_optimize(&e.left, &e.right) || should_optimize(&e.right, &e.left) {
|
||||
log::debug!("bools: Compressing comparison of `typeof` with literal");
|
||||
self.changed = true;
|
||||
e.op = match e.op {
|
||||
op!("===") => {
|
||||
op!("==")
|
||||
}
|
||||
op!("!==") => {
|
||||
op!("!=")
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This method converts `!1` to `0`.
|
||||
pub(super) fn optimize_expr_in_bool_ctx(&mut self, n: &mut Expr) {
|
||||
if !self.options.bools {
|
||||
return;
|
||||
}
|
||||
|
||||
match n {
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("&&") | op!("||"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) => {
|
||||
// Regardless if it's truthy or falsy, we can optimize it because it will be
|
||||
// casted as bool anyway.
|
||||
self.optimize_expr_in_bool_ctx(&mut **left);
|
||||
self.optimize_expr_in_bool_ctx(&mut **right);
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Seq(e) => {
|
||||
if let Some(last) = e.exprs.last_mut() {
|
||||
self.optimize_expr_in_bool_ctx(&mut **last);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match n {
|
||||
Expr::Unary(UnaryExpr {
|
||||
span,
|
||||
op: op!("!"),
|
||||
arg,
|
||||
}) => match &**arg {
|
||||
}) => match &mut **arg {
|
||||
Expr::Lit(Lit::Num(Number { value, .. })) => {
|
||||
log::trace!("Optimizing: number => number (in bool context)");
|
||||
log::debug!("Optimizing: number => number (in bool context)");
|
||||
|
||||
self.changed = true;
|
||||
*n = Expr::Lit(Lit::Num(Number {
|
||||
@ -268,6 +365,15 @@ impl Optimizer<'_> {
|
||||
value: if *value == 0.0 { 1.0 } else { 0.0 },
|
||||
}))
|
||||
}
|
||||
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) => {
|
||||
log::debug!("bools: !!expr => expr (in bool ctx)");
|
||||
self.changed = true;
|
||||
*n = *arg.take();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
@ -276,7 +382,7 @@ impl Optimizer<'_> {
|
||||
op: op!("typeof"),
|
||||
arg,
|
||||
}) => {
|
||||
log::trace!("Optimizing: typeof => true (in bool context)");
|
||||
log::debug!("Optimizing: typeof => true (in bool context)");
|
||||
self.changed = true;
|
||||
|
||||
match &**arg {
|
||||
@ -301,7 +407,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
Expr::Lit(Lit::Str(s)) => {
|
||||
log::trace!("Converting string as boolean expressions");
|
||||
log::debug!("Converting string as boolean expressions");
|
||||
self.changed = true;
|
||||
*n = Expr::Lit(Lit::Num(Number {
|
||||
span: s.span,
|
||||
@ -314,7 +420,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
if self.options.bools {
|
||||
log::trace!("booleans: Converting number as boolean expressions");
|
||||
log::debug!("booleans: Converting number as boolean expressions");
|
||||
self.changed = true;
|
||||
*n = Expr::Lit(Lit::Num(Number {
|
||||
span: num.span,
|
||||
@ -331,7 +437,7 @@ impl Optimizer<'_> {
|
||||
}) => {
|
||||
// Optimize if (a ?? false); as if (a);
|
||||
if let Value::Known(false) = right.as_pure_bool() {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"Dropping right operand of `??` as it's always false (in bool context)"
|
||||
);
|
||||
self.changed = true;
|
||||
@ -348,7 +454,7 @@ impl Optimizer<'_> {
|
||||
// `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)");
|
||||
log::debug!("bools: `expr || false` => `expr` (in bool context)");
|
||||
self.changed = true;
|
||||
*n = *left.take();
|
||||
return;
|
||||
@ -359,8 +465,9 @@ impl Optimizer<'_> {
|
||||
let span = n.span();
|
||||
let v = n.as_pure_bool();
|
||||
if let Known(v) = v {
|
||||
log::trace!("Optimizing expr as {} (in bool context)", v);
|
||||
log::debug!("Optimizing expr as {} (in bool context)", v);
|
||||
*n = make_bool(span, v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -381,7 +488,7 @@ impl Optimizer<'_> {
|
||||
None => match &mut *stmt.cons {
|
||||
Stmt::Expr(cons) => {
|
||||
self.changed = true;
|
||||
log::trace!("conditionals: `if (foo) bar;` => `foo && bar`");
|
||||
log::debug!("conditionals: `if (foo) bar;` => `foo && bar`");
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: stmt.span,
|
||||
expr: Box::new(Expr::Bin(BinExpr {
|
||||
@ -397,4 +504,286 @@ impl Optimizer<'_> {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// - `"undefined" == typeof value;` => `void 0 === value`
|
||||
pub(super) fn compress_typeof_undefined(&mut self, e: &mut BinExpr) {
|
||||
fn opt(o: &mut Optimizer, l: &mut Expr, r: &mut Expr) -> bool {
|
||||
match (&mut *l, &mut *r) {
|
||||
(
|
||||
Expr::Lit(Lit::Str(Str {
|
||||
value: js_word!("undefined"),
|
||||
..
|
||||
})),
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("typeof"),
|
||||
arg,
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
// TODO?
|
||||
match &**arg {
|
||||
Expr::Ident(arg) => {
|
||||
if let Some(usage) =
|
||||
o.data.as_ref().and_then(|data| data.vars.get(&arg.to_id()))
|
||||
{
|
||||
if !usage.declared {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
*l = *undefined(l.span());
|
||||
*r = *arg.take();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
match e.op {
|
||||
op!("==") | op!("!=") | op!("===") | op!("!==") => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
if opt(self, &mut e.left, &mut e.right) || opt(self, &mut e.right, &mut e.left) {
|
||||
e.op = match e.op {
|
||||
op!("==") => {
|
||||
op!("===")
|
||||
}
|
||||
op!("!=") => {
|
||||
op!("!==")
|
||||
}
|
||||
_ => e.op,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_ok_to_negate_for_cond(e: &Expr) -> bool {
|
||||
match e {
|
||||
Expr::Update(..) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_ok_to_negate_rhs(rhs: &Expr) -> bool {
|
||||
match rhs {
|
||||
Expr::Member(..) => true,
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("===") | op!("!==") | op!("==") | op!("!="),
|
||||
..
|
||||
}) => true,
|
||||
|
||||
Expr::Call(..) | Expr::New(..) => false,
|
||||
|
||||
Expr::Update(..) => false,
|
||||
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("&&") | op!("||"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) => is_ok_to_negate_rhs(&left) && is_ok_to_negate_rhs(&right),
|
||||
|
||||
Expr::Bin(BinExpr { left, right, .. }) => {
|
||||
is_ok_to_negate_rhs(&left) && is_ok_to_negate_rhs(&right)
|
||||
}
|
||||
|
||||
Expr::Assign(e) => is_ok_to_negate_rhs(&e.right),
|
||||
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!") | op!("delete"),
|
||||
..
|
||||
}) => true,
|
||||
|
||||
Expr::Seq(e) => {
|
||||
if let Some(last) = e.exprs.last() {
|
||||
is_ok_to_negate_rhs(&last)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Cond(e) => is_ok_to_negate_rhs(&e.cons) && is_ok_to_negate_rhs(&e.alt),
|
||||
|
||||
_ => {
|
||||
if !rhs.may_have_side_effects() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::warn!("unimplemented: is_ok_to_negate_rhs: `{}`", dump(&*rhs));
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A negative value means that it's efficient to negate the expression.
|
||||
pub(crate) fn negate_cost(e: &Expr, in_bool_ctx: bool, is_ret_val_ignored: bool) -> Option<isize> {
|
||||
fn cost(
|
||||
e: &Expr,
|
||||
in_bool_ctx: bool,
|
||||
bin_op: Option<BinaryOp>,
|
||||
is_ret_val_ignored: bool,
|
||||
) -> isize {
|
||||
match e {
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) => {
|
||||
// TODO: Check if this argument is actually start of line.
|
||||
match &**arg {
|
||||
Expr::Call(CallExpr {
|
||||
callee: ExprOrSuper::Expr(callee),
|
||||
..
|
||||
}) => match &**callee {
|
||||
Expr::Fn(..) => return 0,
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if in_bool_ctx {
|
||||
let c = -cost(arg, true, None, is_ret_val_ignored);
|
||||
return c.min(-1);
|
||||
}
|
||||
|
||||
match &**arg {
|
||||
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => -1,
|
||||
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
Expr::Bin(BinExpr {
|
||||
op: op!("===") | op!("!==") | op!("==") | op!("!="),
|
||||
..
|
||||
}) => 0,
|
||||
|
||||
Expr::Bin(BinExpr {
|
||||
op: op @ op!("||") | op @ op!("&&"),
|
||||
left,
|
||||
right,
|
||||
..
|
||||
}) => {
|
||||
let l_cost = cost(&left, in_bool_ctx, Some(*op), false);
|
||||
|
||||
if !is_ret_val_ignored && !is_ok_to_negate_rhs(&right) {
|
||||
return l_cost + 3;
|
||||
}
|
||||
l_cost + cost(&right, in_bool_ctx, Some(*op), is_ret_val_ignored)
|
||||
}
|
||||
|
||||
Expr::Cond(CondExpr { cons, alt, .. })
|
||||
if is_ok_to_negate_for_cond(&cons) && is_ok_to_negate_for_cond(&alt) =>
|
||||
{
|
||||
cost(&cons, in_bool_ctx, bin_op, is_ret_val_ignored)
|
||||
+ cost(&alt, in_bool_ctx, bin_op, is_ret_val_ignored)
|
||||
}
|
||||
|
||||
Expr::Cond(..)
|
||||
| Expr::Update(..)
|
||||
| Expr::Bin(BinExpr {
|
||||
op: op!("in") | op!("instanceof"),
|
||||
..
|
||||
}) => 3,
|
||||
|
||||
Expr::Assign(..) => {
|
||||
if is_ret_val_ignored {
|
||||
0
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Seq(e) => {
|
||||
if let Some(last) = e.exprs.last() {
|
||||
return cost(&last, in_bool_ctx, bin_op, is_ret_val_ignored);
|
||||
}
|
||||
|
||||
if is_ret_val_ignored {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
if is_ret_val_ignored {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cost = cost(e, in_bool_ctx, None, is_ret_val_ignored);
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("negation cost of `{}` = {}", dump(&*e), cost);
|
||||
}
|
||||
|
||||
Some(cost)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::negate_cost;
|
||||
use swc_common::{input::SourceFileInput, FileName};
|
||||
use swc_ecma_parser::{lexer::Lexer, Parser};
|
||||
|
||||
fn assert_negate_cost(s: &str, in_bool_ctx: bool, is_ret_val_ignored: bool, expected: isize) {
|
||||
testing::run_test2(false, |cm, _| {
|
||||
let fm = cm.new_source_file(FileName::Anon, s.to_string());
|
||||
|
||||
let lexer = Lexer::new(
|
||||
Default::default(),
|
||||
swc_ecma_ast::EsVersion::latest(),
|
||||
SourceFileInput::from(&*fm),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut parser = Parser::new_from(lexer);
|
||||
|
||||
let e = parser
|
||||
.parse_expr()
|
||||
.expect("failed to parse input as an expression");
|
||||
|
||||
let actual = negate_cost(&e, in_bool_ctx, is_ret_val_ignored).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"Expected negation cost of {} to be {}, but got {}",
|
||||
s, expected, actual,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logical_1() {
|
||||
assert_negate_cost(
|
||||
"this[key] && !this.hasOwnProperty(key) || (this[key] = value)",
|
||||
false,
|
||||
true,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn logical_2() {
|
||||
assert_negate_cost(
|
||||
"(!this[key] || this.hasOwnProperty(key)) && (this[key] = value)",
|
||||
false,
|
||||
true,
|
||||
-2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::prepend;
|
||||
use swc_ecma_utils::Id;
|
||||
use swc_ecma_utils::StmtLike;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
@ -57,7 +58,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.in_try_block || self.ctx.executed_multiple_time {
|
||||
if self.ctx.in_try_block || self.ctx.executed_multiple_time || self.ctx.in_cond {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -84,6 +85,20 @@ impl Optimizer<'_> {
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if usage.used_in_loop {
|
||||
match &*assign.right {
|
||||
Expr::Lit(..) | Expr::Ident(..) => {}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
if usage.usage_count >= 2 {
|
||||
match &*assign.right {
|
||||
Expr::Lit(..) => {}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value = match &*assign.right {
|
||||
@ -94,6 +109,12 @@ impl Optimizer<'_> {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"collpase_vars: Decided to inline {}{:?}",
|
||||
left.id.sym,
|
||||
left.id.span.ctxt
|
||||
);
|
||||
|
||||
self.lits.insert(left.to_id(), value);
|
||||
}
|
||||
_ => {}
|
||||
@ -101,139 +122,284 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
/// Collapse single-use non-constant variables, side effects permitting.
|
||||
pub(super) fn collapse_consequtive_vars<T>(&mut self, stmts: &mut Vec<T>)
|
||||
///
|
||||
/// This merges all variables to first variable declartion with an
|
||||
/// initializer. If such variable declaration is not found, variables are
|
||||
/// prepended to `stmts`.
|
||||
pub(super) fn collapse_vars_without_init<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: StmtLike,
|
||||
Vec<T>:
|
||||
VisitWith<VarWithOutInitCounter> + VisitMutWith<VarMover> + VisitMutWith<VarPrepender>,
|
||||
{
|
||||
if !self.options.collapse_vars {
|
||||
return;
|
||||
}
|
||||
|
||||
let indexes = stmts
|
||||
.windows(2)
|
||||
.enumerate()
|
||||
.filter_map(|(i, stmts)| {
|
||||
match (stmts[0].as_stmt(), stmts[1].as_stmt()) {
|
||||
(Some(Stmt::Decl(Decl::Var(v))), Some(r)) => {
|
||||
// Should be handled by other passes.
|
||||
if v.kind == VarDeclKind::Const {
|
||||
return None;
|
||||
}
|
||||
{
|
||||
// let mut found_other = false;
|
||||
// let mut need_work = false;
|
||||
|
||||
// We only inline for some subset of statements.
|
||||
match r {
|
||||
Stmt::Expr(..) | Stmt::Return(..) => {}
|
||||
_ => return None,
|
||||
}
|
||||
// for stmt in &*stmts {
|
||||
// match stmt.as_stmt() {
|
||||
// Some(Stmt::Decl(Decl::Var(
|
||||
// v
|
||||
// @
|
||||
// VarDecl {
|
||||
// kind: VarDeclKind::Var,
|
||||
// ..
|
||||
// },
|
||||
// ))) => {
|
||||
// if v.decls.iter().any(|v| v.init.is_none()) {
|
||||
// if found_other {
|
||||
// need_work = true;
|
||||
// }
|
||||
// } else {
|
||||
// found_other = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// We only touch last variable delcarator.
|
||||
if let Some(last) = v.decls.last() {
|
||||
match last.init.as_deref()? {
|
||||
Expr::Object(..) => {
|
||||
// Objects are handled by other passes.
|
||||
return None;
|
||||
}
|
||||
Expr::Arrow(..) | Expr::Fn(..) => {
|
||||
// Handled by other passes
|
||||
return None;
|
||||
}
|
||||
// _ => {
|
||||
// found_other = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
_ => {
|
||||
let mut chekcer = InlinabiltyChecker { can_inline: true };
|
||||
last.init
|
||||
.visit_with(&Invalid { span: DUMMY_SP }, &mut chekcer);
|
||||
if !chekcer.can_inline {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &last.name {
|
||||
Pat::Ident(name) => {
|
||||
//
|
||||
if let Some(var) = self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.vars.get(&name.to_id()))
|
||||
{
|
||||
if var.usage_count != 1
|
||||
|| var.mutated
|
||||
|| !SimpleUsageFinder::find(&name.id, r)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(i);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if indexes.is_empty() {
|
||||
return;
|
||||
// Check for nested variable declartions.
|
||||
let mut v = VarWithOutInitCounter::default();
|
||||
stmts.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||
if !v.need_work {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("collapse_vars: Collapsing {:?}", indexes);
|
||||
log::debug!("collapse_vars: Collapsing variables without an initializer");
|
||||
|
||||
let mut new = Vec::with_capacity(stmts.len());
|
||||
let mut values = FxHashMap::default();
|
||||
for (idx, stmt) in stmts.take().into_iter().enumerate() {
|
||||
match stmt.try_into_stmt() {
|
||||
Ok(mut stmt) => match stmt {
|
||||
Stmt::Decl(Decl::Var(mut v)) if indexes.contains(&idx) => {
|
||||
if let Some(last) = v.decls.pop() {
|
||||
match last.name {
|
||||
Pat::Ident(name) => {
|
||||
values.insert(name.to_id(), last.init);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
stmt.visit_mut_with(&mut Inliner {
|
||||
values: &mut values,
|
||||
});
|
||||
let vars = {
|
||||
let mut v = VarMover {
|
||||
vars: Default::default(),
|
||||
var_decl_kind: Default::default(),
|
||||
};
|
||||
stmts.visit_mut_with(&mut v);
|
||||
|
||||
new.push(T::from_stmt(stmt));
|
||||
}
|
||||
},
|
||||
Err(item) => {
|
||||
new.push(item);
|
||||
v.vars
|
||||
};
|
||||
|
||||
// Prepend vars
|
||||
|
||||
let mut prepender = VarPrepender { vars };
|
||||
stmts.visit_mut_with(&mut prepender);
|
||||
|
||||
if !prepender.vars.is_empty() {
|
||||
prepend(
|
||||
stmts,
|
||||
T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Var,
|
||||
declare: Default::default(),
|
||||
decls: prepender.vars,
|
||||
}))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// See if there's two [VarDecl] which has [VarDeclarator] without the
|
||||
/// initializer.
|
||||
#[derive(Default)]
|
||||
pub(super) struct VarWithOutInitCounter {
|
||||
need_work: bool,
|
||||
found_var_without_init: bool,
|
||||
found_var_with_init: bool,
|
||||
}
|
||||
|
||||
impl Visit for VarWithOutInitCounter {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &dyn Node) {}
|
||||
|
||||
fn visit_constructor(&mut self, _: &Constructor, _: &dyn Node) {}
|
||||
|
||||
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
|
||||
|
||||
fn visit_var_decl(&mut self, v: &VarDecl, _: &dyn Node) {
|
||||
v.visit_children_with(self);
|
||||
|
||||
if v.kind != VarDeclKind::Var {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut found_init = false;
|
||||
for d in &v.decls {
|
||||
if d.init.is_some() {
|
||||
found_init = true;
|
||||
} else {
|
||||
if found_init {
|
||||
self.need_work = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*stmts = new;
|
||||
if v.decls.iter().all(|v| v.init.is_none()) {
|
||||
if self.found_var_without_init || self.found_var_with_init {
|
||||
self.need_work = true;
|
||||
}
|
||||
self.found_var_without_init = true;
|
||||
} else {
|
||||
self.found_var_with_init = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
fn visit_var_decl_or_pat(&mut self, _: &VarDeclOrPat, _: &dyn Node) {}
|
||||
}
|
||||
|
||||
/// Moves all varaible without initializer.
|
||||
pub(super) struct VarMover {
|
||||
vars: Vec<VarDeclarator>,
|
||||
var_decl_kind: Option<VarDeclKind>,
|
||||
}
|
||||
|
||||
impl VisitMut for VarMover {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
/// Noop
|
||||
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
|
||||
|
||||
/// Noop
|
||||
fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
|
||||
|
||||
/// Noop
|
||||
fn visit_mut_function(&mut self, _: &mut Function) {}
|
||||
|
||||
fn visit_mut_module_item(&mut self, s: &mut ModuleItem) {
|
||||
s.visit_mut_children_with(self);
|
||||
|
||||
match s {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl: Decl::Var(d),
|
||||
..
|
||||
})) if d.decls.is_empty() => {
|
||||
s.take();
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
match n {
|
||||
Some(VarDeclOrExpr::VarDecl(var)) => {
|
||||
if var.decls.is_empty() {
|
||||
*n = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
|
||||
s.visit_mut_children_with(self);
|
||||
|
||||
match s {
|
||||
Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
|
||||
s.take();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
|
||||
let old = self.var_decl_kind.take();
|
||||
self.var_decl_kind = Some(v.kind);
|
||||
v.visit_mut_children_with(self);
|
||||
self.var_decl_kind = old;
|
||||
}
|
||||
|
||||
fn visit_mut_var_decl_or_pat(&mut self, _: &mut VarDeclOrPat) {}
|
||||
|
||||
fn visit_mut_var_declarators(&mut self, d: &mut Vec<VarDeclarator>) {
|
||||
d.visit_mut_children_with(self);
|
||||
|
||||
if self.var_decl_kind.unwrap() != VarDeclKind::Var {
|
||||
return;
|
||||
}
|
||||
|
||||
if d.iter().all(|v| v.init.is_some()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let has_init = d.iter().any(|v| v.init.is_some());
|
||||
|
||||
if has_init {
|
||||
let mut new = vec![];
|
||||
|
||||
for v in d.take() {
|
||||
if v.init.is_some() {
|
||||
new.push(v)
|
||||
} else {
|
||||
self.vars.push(v)
|
||||
}
|
||||
}
|
||||
|
||||
*d = new;
|
||||
}
|
||||
|
||||
let mut new = vec![];
|
||||
|
||||
if has_init {
|
||||
new.extend(self.vars.drain(..));
|
||||
}
|
||||
|
||||
for v in d.take() {
|
||||
if v.init.is_some() {
|
||||
new.push(v)
|
||||
} else {
|
||||
self.vars.push(v)
|
||||
}
|
||||
}
|
||||
|
||||
*d = new;
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks inlinabilty of variable initializer.
|
||||
struct InlinabiltyChecker {
|
||||
can_inline: bool,
|
||||
pub(super) struct VarPrepender {
|
||||
vars: Vec<VarDeclarator>,
|
||||
}
|
||||
|
||||
impl Visit for InlinabiltyChecker {
|
||||
noop_visit_type!();
|
||||
impl VisitMut for VarPrepender {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_update_expr(&mut self, _: &UpdateExpr, _: &dyn Node) {
|
||||
self.can_inline = false;
|
||||
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
|
||||
if self.vars.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if v.kind != VarDeclKind::Var {
|
||||
return;
|
||||
}
|
||||
|
||||
if v.decls.iter().any(|d| d.init.is_some()) {
|
||||
let mut decls = self.vars.take();
|
||||
decls.extend(v.decls.take());
|
||||
|
||||
v.decls = decls;
|
||||
}
|
||||
}
|
||||
|
||||
/// Noop
|
||||
fn visit_mut_function(&mut self, _: &mut Function) {}
|
||||
|
||||
/// Noop
|
||||
fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
|
||||
|
||||
/// Noop
|
||||
fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
|
||||
}
|
||||
|
||||
struct Inliner<'a> {
|
||||
@ -249,6 +415,8 @@ impl VisitMut for Inliner<'_> {
|
||||
match e {
|
||||
Expr::Ident(i) => {
|
||||
if let Some(value) = self.values.remove(&i.to_id()) {
|
||||
log::debug!("collapse_vars: Inlining {}{:?}", i.sym, i.span.ctxt);
|
||||
|
||||
*e = *value.expect("should be used only once");
|
||||
}
|
||||
}
|
||||
@ -256,63 +424,3 @@ impl VisitMut for Inliner<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds usage of `ident`, but except assignment to it.
|
||||
struct SimpleUsageFinder<'a> {
|
||||
ident: &'a Ident,
|
||||
found: bool,
|
||||
}
|
||||
|
||||
impl<'a> Visit for SimpleUsageFinder<'a> {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_pat(&mut self, n: &Pat, _: &dyn Node) {
|
||||
match n {
|
||||
Pat::Ident(..) => {}
|
||||
_ => {
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_update_expr(&mut self, _: &UpdateExpr, _: &dyn Node) {}
|
||||
|
||||
fn visit_expr_or_spread(&mut self, n: &ExprOrSpread, _: &dyn Node) {
|
||||
if n.spread.is_some() {
|
||||
return;
|
||||
}
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_assign_expr(&mut self, e: &AssignExpr, _: &dyn Node) {
|
||||
e.right.visit_with(e, self);
|
||||
}
|
||||
|
||||
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
|
||||
if i.span.ctxt() == self.ident.span.ctxt() && i.sym == self.ident.sym {
|
||||
self.found = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
|
||||
e.obj.visit_with(e as _, self);
|
||||
|
||||
if e.computed {
|
||||
e.prop.visit_with(e as _, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SimpleUsageFinder<'a> {
|
||||
pub fn find<N>(ident: &'a Ident, node: &N) -> bool
|
||||
where
|
||||
N: VisitWith<Self>,
|
||||
{
|
||||
let mut v = SimpleUsageFinder {
|
||||
ident,
|
||||
found: false,
|
||||
};
|
||||
node.visit_with(&Invalid { span: DUMMY_SP } as _, &mut v);
|
||||
v.found
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
use super::Optimizer;
|
||||
use crate::compress::optimize::bools::negate_cost;
|
||||
use crate::compress::optimize::Ctx;
|
||||
use crate::compress::optimize::DISABLE_BUGGY_PASSES;
|
||||
use crate::debug::dump;
|
||||
use crate::util::make_bool;
|
||||
use crate::util::SpanExt;
|
||||
use std::mem::swap;
|
||||
use swc_common::EqIgnoreSpan;
|
||||
use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
@ -18,6 +23,104 @@ use swc_ecma_utils::Value::Known;
|
||||
/// Methods related to the option `conditionals`. All methods are noop if
|
||||
/// `conditionals` is false.
|
||||
impl Optimizer<'_> {
|
||||
pub(super) fn negate_cond_expr(&mut self, cond: &mut CondExpr) {
|
||||
if negate_cost(&cond.test, true, false).unwrap_or(isize::MAX) >= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: `a ? foo : bar` => `!a ? bar : foo` (considered cost)");
|
||||
let start_str = dump(&*cond);
|
||||
|
||||
{
|
||||
let ctx = Ctx {
|
||||
in_bool_ctx: true,
|
||||
..self.ctx
|
||||
};
|
||||
self.with_ctx(ctx).negate(&mut cond.test);
|
||||
}
|
||||
swap(&mut cond.cons, &mut cond.alt);
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"[Change] Negated cond: `{}` => `{}`",
|
||||
start_str,
|
||||
dump(&*cond)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Negates the condition of a `if` statement to reduce body size.
|
||||
pub(super) fn negate_if_stmt(&mut self, stmt: &mut IfStmt) {
|
||||
let alt = match stmt.alt.as_deref_mut() {
|
||||
Some(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match &*stmt.cons {
|
||||
Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => return,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if negate_cost(&stmt.test, true, false).unwrap_or(isize::MAX) < 0 {
|
||||
self.changed = true;
|
||||
log::debug!("if_return: Negating `cond` of an if statement which has cons and alt");
|
||||
let ctx = Ctx {
|
||||
in_bool_ctx: true,
|
||||
..self.ctx
|
||||
};
|
||||
self.with_ctx(ctx).negate(&mut stmt.test);
|
||||
swap(alt, &mut *stmt.cons);
|
||||
return;
|
||||
}
|
||||
|
||||
match &*alt {
|
||||
Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => {
|
||||
self.changed = true;
|
||||
log::debug!(
|
||||
"if_return: Negating an if statement because the alt is return / continue"
|
||||
);
|
||||
self.negate(&mut stmt.test);
|
||||
swap(alt, &mut *stmt.cons);
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
/// This method may change return value.
|
||||
///
|
||||
/// - `a ? b : false` => `a && b`
|
||||
pub(super) fn compress_cond_to_logical_ignoring_return_value(&mut self, e: &mut Expr) {
|
||||
let cond = match e {
|
||||
Expr::Cond(cond) => cond,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !cond.cons.may_have_side_effects() {
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: `cond ? useless : alt` => `cond || alt`");
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: cond.span,
|
||||
op: op!("||"),
|
||||
left: cond.test.take(),
|
||||
right: cond.alt.take(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if !cond.alt.may_have_side_effects() {
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: `cond ? cons : useless` => `cond && cons`");
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: cond.span,
|
||||
op: op!("&&"),
|
||||
left: cond.test.take(),
|
||||
right: cond.cons.take(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes useless operands of an logical expressions.
|
||||
pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
|
||||
if !self.options.conditionals {
|
||||
@ -47,7 +150,7 @@ impl Optimizer<'_> {
|
||||
// `!!b || true` => true
|
||||
if let Known(true) = rb {
|
||||
self.changed = true;
|
||||
log::trace!("conditionals: `!!foo || true` => `true`");
|
||||
log::debug!("conditionals: `!!foo || true` => `true`");
|
||||
*e = make_bool(bin.span, true);
|
||||
return;
|
||||
}
|
||||
@ -70,7 +173,7 @@ impl Optimizer<'_> {
|
||||
(Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
|
||||
if (*cons.right).eq_ignore_span(&*alt) =>
|
||||
{
|
||||
log::trace!("conditionals: `x ? y || z : z` => `x || y && z`");
|
||||
log::debug!("conditionals: `x ? y || z : z` => `x || y && z`");
|
||||
self.changed = true;
|
||||
|
||||
*e = Expr::Bin(BinExpr {
|
||||
@ -91,13 +194,10 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
///
|
||||
/// - `foo ? 1 : false` => `!!foo && 1`
|
||||
/// - `!foo ? true : 0` => `!foo || 0`
|
||||
/// - `foo ? bar : false` => `!!foo && bar`
|
||||
/// - `!foo ? true : bar` => `!foo || bar`
|
||||
/// - `foo ? false : bar` => `!foo && bar`
|
||||
pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
|
||||
if !self.options.conditionals {
|
||||
return;
|
||||
}
|
||||
|
||||
let cond = match e {
|
||||
Expr::Cond(cond) => cond,
|
||||
_ => return,
|
||||
@ -107,7 +207,7 @@ impl Optimizer<'_> {
|
||||
if let Known(Type::Bool) = lt {
|
||||
let lb = cond.cons.as_pure_bool();
|
||||
if let Known(true) = lb {
|
||||
log::trace!("conditionals: `foo ? true : bar` => `!!foo || bar`");
|
||||
log::debug!("conditionals: `foo ? true : bar` => `!!foo || bar`");
|
||||
|
||||
// Negate twice to convert `test` to boolean.
|
||||
self.negate_twice(&mut cond.test);
|
||||
@ -123,21 +223,19 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
// TODO: Verify this rule.
|
||||
if false {
|
||||
if let Known(false) = lb {
|
||||
log::trace!("conditionals: `foo ? false : bar` => `!foo && bar`");
|
||||
if let Known(false) = lb {
|
||||
log::debug!("conditionals: `foo ? false : bar` => `!foo && bar`");
|
||||
|
||||
self.changed = true;
|
||||
self.negate(&mut cond.test);
|
||||
self.changed = true;
|
||||
self.negate(&mut cond.test);
|
||||
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: cond.span,
|
||||
op: op!("&&"),
|
||||
left: cond.test.take(),
|
||||
right: cond.alt.take(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: cond.span,
|
||||
op: op!("&&"),
|
||||
left: cond.test.take(),
|
||||
right: cond.alt.take(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +243,7 @@ impl Optimizer<'_> {
|
||||
if let Known(Type::Bool) = rt {
|
||||
let rb = cond.alt.as_pure_bool();
|
||||
if let Known(false) = rb {
|
||||
log::trace!("conditionals: `foo ? 1 : false` => `!!foo && 1`");
|
||||
log::debug!("conditionals: `foo ? bar : false` => `!!foo && bar`");
|
||||
self.changed = true;
|
||||
|
||||
// Negate twice to convert `test` to boolean.
|
||||
@ -159,6 +257,22 @@ impl Optimizer<'_> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if let Known(true) = rb {
|
||||
log::debug!("conditionals: `foo ? bar : true` => `!foo || bar");
|
||||
self.changed = true;
|
||||
|
||||
// Negate twice to convert `test` to boolean.
|
||||
self.negate(&mut cond.test);
|
||||
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: cond.span,
|
||||
op: op!("||"),
|
||||
left: cond.test.take(),
|
||||
right: cond.cons.take(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +313,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("conditionals: Merging if statements with same `cons`");
|
||||
log::debug!("conditionals: Merging if statements with same `cons`");
|
||||
|
||||
let mut cur: Option<IfStmt> = None;
|
||||
let mut new = Vec::with_capacity(stmts.len());
|
||||
@ -272,15 +386,28 @@ impl Optimizer<'_> {
|
||||
/// }
|
||||
/// ```
|
||||
pub(super) fn compress_if_stmt_as_cond(&mut self, s: &mut Stmt) {
|
||||
if !self.options.conditionals {
|
||||
return;
|
||||
}
|
||||
|
||||
let stmt = match s {
|
||||
Stmt::If(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match &*stmt.cons {
|
||||
Stmt::Empty(..) => {
|
||||
if self.options.conditionals || self.options.unused {
|
||||
if stmt.alt.is_none() {
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: stmt.span,
|
||||
expr: stmt.test.take(),
|
||||
});
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: `if (foo);` => `foo` ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// If alt does not exist, an if statement is better than a conditional
|
||||
// expression.
|
||||
let alt = match &mut stmt.alt {
|
||||
@ -303,7 +430,7 @@ impl Optimizer<'_> {
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) => {
|
||||
log::trace!("Optimizing `if (!foo); else bar();` as `foo && bar();`");
|
||||
log::debug!("Optimizing `if (!foo); else bar();` as `foo && bar();`");
|
||||
|
||||
let mut expr = Box::new(Expr::Bin(BinExpr {
|
||||
span: DUMMY_SP,
|
||||
@ -318,7 +445,7 @@ impl Optimizer<'_> {
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
log::trace!("Optimizing `if (foo); else bar();` as `foo || bar();`");
|
||||
log::debug!("Optimizing `if (foo); else bar();` as `foo || bar();`");
|
||||
|
||||
let mut expr = Box::new(Expr::Bin(BinExpr {
|
||||
span: DUMMY_SP,
|
||||
@ -356,29 +483,27 @@ impl Optimizer<'_> {
|
||||
None => {}
|
||||
}
|
||||
|
||||
// if (a) b(); else c(); => a ? b() : c()
|
||||
log::trace!(
|
||||
"Compressing if statement as conditional expression (even though cons and alt is not \
|
||||
compressable)"
|
||||
);
|
||||
self.changed = true;
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: stmt.span.with_mark(self.done),
|
||||
expr: Box::new(Expr::Cond(CondExpr {
|
||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||
test: stmt.test.take(),
|
||||
cons: Box::new(cons.take()),
|
||||
alt: Box::new(alt.take()),
|
||||
})),
|
||||
})
|
||||
if self.options.conditionals || self.options.bools {
|
||||
// if (a) b(); else c(); => a ? b() : c()
|
||||
log::debug!(
|
||||
"Compressing if statement as conditional expression (even though cons and alt is \
|
||||
not compressable)"
|
||||
);
|
||||
self.changed = true;
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: stmt.span.with_mark(self.done),
|
||||
expr: Box::new(Expr::Cond(CondExpr {
|
||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||
test: stmt.test.take(),
|
||||
cons: Box::new(cons.take()),
|
||||
alt: Box::new(alt.take()),
|
||||
})),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Compress a conditional expression if cons and alt is simillar
|
||||
pub(super) fn compress_cond_expr_if_simillar(&mut self, e: &mut Expr) {
|
||||
if !self.options.conditionals {
|
||||
return;
|
||||
}
|
||||
|
||||
let cond = match e {
|
||||
Expr::Cond(expr) => expr,
|
||||
_ => return,
|
||||
@ -398,7 +523,7 @@ impl Optimizer<'_> {
|
||||
|
||||
// x ? x : y => x || y
|
||||
if cond.test.is_ident() && cond.test.eq_ignore_span(&cond.cons) {
|
||||
log::trace!("Compressing `x ? x : y` as `x || y`");
|
||||
log::debug!("Compressing `x ? x : y` as `x || y`");
|
||||
self.changed = true;
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: cond.span,
|
||||
@ -417,43 +542,24 @@ impl Optimizer<'_> {
|
||||
alt: &mut Expr,
|
||||
is_for_if_stmt: bool,
|
||||
) -> Option<Expr> {
|
||||
if cons.eq_ignore_span(alt) {
|
||||
if cons.eq_ignore_span(alt)
|
||||
&& match &*cons {
|
||||
Expr::Yield(..) | Expr::Fn(..) => false,
|
||||
_ => true,
|
||||
}
|
||||
{
|
||||
log::debug!("conditionals: cons is same as alt");
|
||||
return Some(Expr::Seq(SeqExpr {
|
||||
span: DUMMY_SP,
|
||||
exprs: vec![test.take(), Box::new(cons.take())],
|
||||
}));
|
||||
}
|
||||
|
||||
match &**test {
|
||||
Expr::Call(CallExpr {
|
||||
callee: ExprOrSuper::Expr(callee),
|
||||
..
|
||||
}) => match &**callee {
|
||||
Expr::Ident(callee) => {
|
||||
let side_effect_free = self
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.vars.get(&callee.to_id()))
|
||||
.map(|v| v.is_fn_local)
|
||||
.unwrap_or(false);
|
||||
if !side_effect_free {
|
||||
match cons {
|
||||
Expr::Call(..) => {
|
||||
return None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match (cons, alt) {
|
||||
(Expr::Call(cons), Expr::Call(alt)) => {
|
||||
let cons_callee = cons.callee.as_expr().and_then(|e| e.as_ident())?;
|
||||
//
|
||||
|
||||
if !cons.callee.eq_ignore_span(&alt.callee) {
|
||||
return None;
|
||||
}
|
||||
@ -462,7 +568,7 @@ impl Optimizer<'_> {
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.vars.get(&cons_callee.to_id()))
|
||||
.map(|v| v.is_fn_local)
|
||||
.map(|v| v.is_fn_local && v.declared)
|
||||
.unwrap_or(false);
|
||||
|
||||
if side_effect_free
|
||||
@ -476,8 +582,9 @@ impl Optimizer<'_> {
|
||||
.zip(alt.args.iter())
|
||||
.filter(|(cons, alt)| !cons.eq_ignore_span(alt))
|
||||
.count();
|
||||
|
||||
if diff_count == 1 {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"conditionals: Merging cons and alt as only one argument differs"
|
||||
);
|
||||
self.changed = true;
|
||||
@ -543,7 +650,7 @@ impl Optimizer<'_> {
|
||||
})),
|
||||
});
|
||||
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"Compreessing if into cond as there's no side effect and the number of \
|
||||
arguments is 1"
|
||||
);
|
||||
@ -556,7 +663,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
if !side_effect_free && is_for_if_stmt {
|
||||
log::trace!("Compreessing if into cond while preserving side effects");
|
||||
log::debug!("Compreessing if into cond while preserving side effects");
|
||||
return Some(Expr::Cond(CondExpr {
|
||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||
test: test.take(),
|
||||
@ -601,7 +708,7 @@ impl Optimizer<'_> {
|
||||
})),
|
||||
});
|
||||
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"Compreessing if statement into a condiotnal expression of `new` as \
|
||||
there's no side effect and the number of arguments is 1"
|
||||
);
|
||||
@ -623,7 +730,7 @@ impl Optimizer<'_> {
|
||||
&& cons.left.eq_ignore_span(&alt.left)
|
||||
&& is_simple_lhs(&cons.left) =>
|
||||
{
|
||||
log::trace!("Merging assignments in cons and alt of if statement");
|
||||
log::debug!("Merging assignments in cons and alt of if statement");
|
||||
Some(Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP,
|
||||
op: cons.op,
|
||||
@ -639,7 +746,7 @@ impl Optimizer<'_> {
|
||||
|
||||
// a ? b ? c() : d() : d() => a && b ? c() : d()
|
||||
(Expr::Cond(cons), alt) if (*cons.alt).eq_ignore_span(&*alt) => {
|
||||
log::trace!("conditionals: a ? b ? c() : d() : d() => a && b ? c() : d()");
|
||||
log::debug!("conditionals: a ? b ? c() : d() : d() => a && b ? c() : d()");
|
||||
return Some(Expr::Cond(CondExpr {
|
||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||
test: Box::new(Expr::Bin(BinExpr {
|
||||
@ -656,16 +763,19 @@ impl Optimizer<'_> {
|
||||
// z ? "fuji" : (condition(), "fuji");
|
||||
// =>
|
||||
// (z || condition(), "fuji");
|
||||
(cons, Expr::Seq(alt))
|
||||
if alt.exprs.len() == 2 && (**alt.exprs.last().unwrap()).eq_ignore_span(&*cons) =>
|
||||
{
|
||||
log::trace!("conditionals: Reducing seq expr in alt");
|
||||
(cons, Expr::Seq(alt)) if (**alt.exprs.last().unwrap()).eq_ignore_span(&*cons) => {
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: Reducing seq expr in alt");
|
||||
//
|
||||
alt.exprs.pop();
|
||||
let first = Box::new(Expr::Bin(BinExpr {
|
||||
span: DUMMY_SP,
|
||||
left: test.take(),
|
||||
op: op!("||"),
|
||||
right: alt.exprs[0].take(),
|
||||
right: Box::new(Expr::Seq(SeqExpr {
|
||||
span: alt.span,
|
||||
exprs: alt.exprs.take(),
|
||||
})),
|
||||
}));
|
||||
return Some(Expr::Seq(SeqExpr {
|
||||
span: DUMMY_SP,
|
||||
@ -673,9 +783,164 @@ impl Optimizer<'_> {
|
||||
}));
|
||||
}
|
||||
|
||||
// z ? (condition(), "fuji") : "fuji"
|
||||
// =>
|
||||
// (z && condition(), "fuji");
|
||||
(Expr::Seq(cons), alt) if (**cons.exprs.last().unwrap()).eq_ignore_span(&*alt) => {
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: Reducing seq expr in cons");
|
||||
//
|
||||
cons.exprs.pop();
|
||||
let first = Box::new(Expr::Bin(BinExpr {
|
||||
span: DUMMY_SP,
|
||||
left: test.take(),
|
||||
op: op!("&&"),
|
||||
right: Box::new(Expr::Seq(SeqExpr {
|
||||
span: cons.span,
|
||||
exprs: cons.exprs.take(),
|
||||
})),
|
||||
}));
|
||||
return Some(Expr::Seq(SeqExpr {
|
||||
span: DUMMY_SP,
|
||||
exprs: vec![first, Box::new(alt.take())],
|
||||
}));
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Currently disabled.
|
||||
pub(super) fn inject_else(&mut self, stmts: &mut Vec<Stmt>) {
|
||||
if DISABLE_BUGGY_PASSES {
|
||||
return;
|
||||
}
|
||||
|
||||
let len = stmts.len();
|
||||
|
||||
let pos_of_if = stmts.iter().enumerate().rposition(|(idx, s)| {
|
||||
idx != len - 1
|
||||
&& match s {
|
||||
Stmt::If(IfStmt {
|
||||
cons, alt: None, ..
|
||||
}) => match &**cons {
|
||||
Stmt::Block(b) => {
|
||||
b.stmts.len() == 2
|
||||
&& match b.stmts.first() {
|
||||
Some(Stmt::If(..) | Stmt::Expr(..)) => false,
|
||||
_ => true,
|
||||
}
|
||||
&& match b.stmts.last() {
|
||||
Some(Stmt::Return(ReturnStmt { arg: None, .. })) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
let pos_of_if = match pos_of_if {
|
||||
Some(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("if_return: Injecting else because it's shorter");
|
||||
|
||||
let mut new = vec![];
|
||||
new.reserve(pos_of_if + 1);
|
||||
new.extend(stmts.drain(..pos_of_if));
|
||||
let alt = stmts.drain(1..).collect::<Vec<_>>();
|
||||
|
||||
let if_stmt = stmts.take().into_iter().next().unwrap();
|
||||
match if_stmt {
|
||||
Stmt::If(mut s) => {
|
||||
match &mut *s.cons {
|
||||
Stmt::Block(cons) => {
|
||||
cons.stmts.pop();
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(s.alt, None);
|
||||
|
||||
s.alt = Some(if alt.len() == 1 {
|
||||
Box::new(alt.into_iter().next().unwrap())
|
||||
} else {
|
||||
Box::new(Stmt::Block(BlockStmt {
|
||||
span: DUMMY_SP,
|
||||
stmts: alt,
|
||||
}))
|
||||
});
|
||||
|
||||
new.push(Stmt::If(s))
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
*stmts = new;
|
||||
}
|
||||
|
||||
/// if (foo) return bar()
|
||||
/// else baz()
|
||||
///
|
||||
/// `else` token can be removed from the code above.
|
||||
pub(super) fn drop_else_token<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: StmtLike,
|
||||
{
|
||||
// Find an if statement with else token.
|
||||
let need_work = stmts.iter().any(|stmt| match stmt.as_stmt() {
|
||||
Some(Stmt::If(IfStmt {
|
||||
cons,
|
||||
alt: Some(..),
|
||||
..
|
||||
})) => always_terminates(cons),
|
||||
_ => false,
|
||||
});
|
||||
if !need_work {
|
||||
return;
|
||||
}
|
||||
//
|
||||
|
||||
let mut new_stmts = vec![];
|
||||
|
||||
for stmt in stmts.take() {
|
||||
match stmt.try_into_stmt() {
|
||||
Ok(stmt) => match stmt {
|
||||
Stmt::If(IfStmt {
|
||||
span,
|
||||
test,
|
||||
cons,
|
||||
alt: Some(alt),
|
||||
..
|
||||
}) if always_terminates(&cons) => {
|
||||
new_stmts.push(T::from_stmt(Stmt::If(IfStmt {
|
||||
span,
|
||||
test,
|
||||
cons,
|
||||
alt: None,
|
||||
})));
|
||||
new_stmts.push(T::from_stmt(*alt));
|
||||
}
|
||||
_ => {
|
||||
new_stmts.push(T::from_stmt(stmt));
|
||||
}
|
||||
},
|
||||
Err(stmt) => new_stmts.push(stmt),
|
||||
}
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("conditionals: Dropped useless `else` token");
|
||||
*stmts = new_stmts;
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_expr_stmt(s: &mut Stmt) -> Option<&mut Expr> {
|
||||
@ -693,7 +958,23 @@ fn is_simple_lhs(l: &PatOrExpr) -> bool {
|
||||
},
|
||||
PatOrExpr::Pat(l) => match &**l {
|
||||
Pat::Ident(_) => return true,
|
||||
Pat::Expr(e) => match &**e {
|
||||
Expr::Ident(..) => return true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn always_terminates(s: &Stmt) -> bool {
|
||||
match s {
|
||||
Stmt::Return(..) | Stmt::Throw(..) | Stmt::Break(..) | Stmt::Continue(..) => true,
|
||||
Stmt::If(IfStmt { cons, alt, .. }) => {
|
||||
always_terminates(&cons) && alt.as_deref().map(always_terminates).unwrap_or(false)
|
||||
}
|
||||
Stmt::Block(s) => s.stmts.iter().any(always_terminates),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ impl Optimizer<'_> {
|
||||
.map(|var| var.is_fn_local)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"dead_code: Dropping an assigment to a varaible declared in \
|
||||
function because function is being terminated"
|
||||
);
|
||||
|
@ -65,7 +65,7 @@ impl Optimizer<'_> {
|
||||
sym: js_word!("undefined"),
|
||||
..
|
||||
}) => {
|
||||
log::trace!("evaluate: `undefined` -> `void 0`");
|
||||
log::debug!("evaluate: `undefined` -> `void 0`");
|
||||
self.changed = true;
|
||||
*e = *undefined(*span);
|
||||
return;
|
||||
@ -76,7 +76,7 @@ impl Optimizer<'_> {
|
||||
sym: js_word!("Infinity"),
|
||||
..
|
||||
}) => {
|
||||
log::trace!("evaluate: `Infinity` -> `1 / 0`");
|
||||
log::debug!("evaluate: `Infinity` -> `1 / 0`");
|
||||
self.changed = true;
|
||||
*e = Expr::Bin(BinExpr {
|
||||
span: span.with_ctxt(self.done_ctxt),
|
||||
@ -149,7 +149,7 @@ impl Optimizer<'_> {
|
||||
1 => match &*args[0].expr {
|
||||
Expr::Lit(Lit::Str(exp)) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Converting RegExpr call into a regexp literal `/{}/`",
|
||||
exp.value
|
||||
);
|
||||
@ -165,7 +165,7 @@ impl Optimizer<'_> {
|
||||
_ => match (&*args[0].expr, &*args[1].expr) {
|
||||
(Expr::Lit(Lit::Str(exp)), Expr::Lit(Lit::Str(flags))) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Converting RegExpr call into a regexp literal `/{}/{}`",
|
||||
exp.value,
|
||||
flags.value
|
||||
@ -209,7 +209,7 @@ impl Optimizer<'_> {
|
||||
match char::from_u32(v) {
|
||||
Some(v) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evanluate: Evaluated `String.charCodeAt({})` as `{}`",
|
||||
char_code,
|
||||
v
|
||||
@ -362,7 +362,7 @@ impl Optimizer<'_> {
|
||||
match c {
|
||||
Some(v) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Evaluated `charCodeAt` of a string literal as `{}`",
|
||||
v
|
||||
);
|
||||
@ -373,7 +373,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
None => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Evaluated `charCodeAt` of a string literal as `NaN`",
|
||||
);
|
||||
*e = Expr::Ident(Ident::new(
|
||||
@ -389,7 +389,7 @@ impl Optimizer<'_> {
|
||||
};
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: Evaluated `{}` of a string literal", method);
|
||||
log::debug!("evaluate: Evaluated `{}` of a string literal", method);
|
||||
*e = Expr::Lit(Lit::Str(Str {
|
||||
value: new_val.into(),
|
||||
..s
|
||||
@ -412,7 +412,7 @@ impl Optimizer<'_> {
|
||||
..
|
||||
}) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Reducing a call to `Number` into an unary operation"
|
||||
);
|
||||
|
||||
@ -442,7 +442,7 @@ impl Optimizer<'_> {
|
||||
Expr::Call(..) => {
|
||||
if let Some(value) = self.eval_as_number(&e) {
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: Evaluated an expression as `{}`", value);
|
||||
log::debug!("evaluate: Evaluated an expression as `{}`", value);
|
||||
|
||||
*e = Expr::Lit(Lit::Num(Number {
|
||||
span: e.span(),
|
||||
@ -462,7 +462,7 @@ impl Optimizer<'_> {
|
||||
if let Known(l) = l {
|
||||
if let Known(r) = r {
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: Evaulated `{:?} ** {:?}`", l, r);
|
||||
log::debug!("evaluate: Evaulated `{:?} ** {:?}`", l, r);
|
||||
|
||||
let value = l.powf(r);
|
||||
*e = Expr::Lit(Lit::Num(Number {
|
||||
@ -507,7 +507,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: `0 / 0` => `NaN`");
|
||||
log::debug!("evaluate: `0 / 0` => `NaN`");
|
||||
|
||||
// Sign does not matter for NaN
|
||||
*e = Expr::Ident(Ident::new(
|
||||
@ -518,7 +518,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
(FpCategory::Normal, FpCategory::Zero) => {
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: `constant / 0` => `Infinity`");
|
||||
log::debug!("evaluate: `constant / 0` => `Infinity`");
|
||||
|
||||
// Sign does not matter for NaN
|
||||
*e = if ln.is_sign_positive() == rn.is_sign_positive() {
|
||||
@ -590,7 +590,7 @@ impl Optimizer<'_> {
|
||||
let value = num_to_fixed(num.value, precision + 1);
|
||||
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Evaluating `{}.toFixed({})` as `{}`",
|
||||
num,
|
||||
precision,
|
||||
@ -816,7 +816,7 @@ impl Optimizer<'_> {
|
||||
match call.args.len() {
|
||||
0 => {
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: Dropping array.slice call");
|
||||
log::debug!("evaluate: Dropping array.slice call");
|
||||
*e = *obj.take();
|
||||
return;
|
||||
}
|
||||
@ -825,7 +825,7 @@ impl Optimizer<'_> {
|
||||
let start = start.floor() as usize;
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("evaluate: Reducing array.slice({}) call", start);
|
||||
log::debug!("evaluate: Reducing array.slice({}) call", start);
|
||||
|
||||
if start >= arr.elems.len() {
|
||||
*e = Expr::Array(ArrayLit {
|
||||
@ -850,7 +850,7 @@ impl Optimizer<'_> {
|
||||
if let Known(end) = end {
|
||||
let end = end.floor() as usize;
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Reducing array.slice({}, {}) call",
|
||||
start,
|
||||
end
|
||||
@ -936,7 +936,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaludate: Reduced `funtion.valueOf()` into a function expression"
|
||||
);
|
||||
|
||||
@ -977,7 +977,7 @@ impl Optimizer<'_> {
|
||||
// As we used as_pure_bool, we can drop it.
|
||||
if v && e.op == op!("&&") {
|
||||
self.changed = true;
|
||||
log::trace!("Removing `b` from `a && b && c` because b is always truthy");
|
||||
log::debug!("Removing `b` from `a && b && c` because b is always truthy");
|
||||
|
||||
left.right.take();
|
||||
return;
|
||||
@ -985,7 +985,7 @@ impl Optimizer<'_> {
|
||||
|
||||
if !v && e.op == op!("||") {
|
||||
self.changed = true;
|
||||
log::trace!("Removing `b` from `a || b || c` because b is always falsy");
|
||||
log::debug!("Removing `b` from `a || b || c` because b is always falsy");
|
||||
|
||||
left.right.take();
|
||||
return;
|
||||
@ -1011,7 +1011,7 @@ impl Optimizer<'_> {
|
||||
//
|
||||
if is_pure_undefined_or_null(&obj) {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Reduced an optioanl chaining operation because object is \
|
||||
always null or undefined"
|
||||
);
|
||||
@ -1028,7 +1028,7 @@ impl Optimizer<'_> {
|
||||
}) => {
|
||||
if is_pure_undefined_or_null(&callee) {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"evaluate: Reduced a call expression with optioanl chaining operation \
|
||||
because object is always null or undefined"
|
||||
);
|
||||
|
97
ecmascript/minifier/src/compress/optimize/fns.rs
Normal file
97
ecmascript/minifier/src/compress/optimize/fns.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use super::Optimizer;
|
||||
use crate::util::{sort::is_sorted_by, MoudleItemExt};
|
||||
use std::cmp::Ordering;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
|
||||
impl Optimizer<'_> {
|
||||
/// Calls `reorder_stmts_inner` after splitting stmts.
|
||||
pub(super) fn reorder_stmts<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: MoudleItemExt,
|
||||
{
|
||||
if self.ctx.in_asm {
|
||||
return;
|
||||
}
|
||||
|
||||
self.reorder_stmts_inner(stmts);
|
||||
}
|
||||
|
||||
/// Sorts given statements.
|
||||
fn reorder_stmts_inner<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: MoudleItemExt,
|
||||
{
|
||||
if !self.options.join_vars {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by position
|
||||
|
||||
if is_sorted_by(stmts.iter(), |a, b| {
|
||||
// Check for function declarations.
|
||||
|
||||
match (a.as_module_decl(), b.as_module_decl()) {
|
||||
(
|
||||
Err(Stmt::Decl(
|
||||
Decl::Fn(..)
|
||||
| Decl::Var(VarDecl {
|
||||
kind: VarDeclKind::Var,
|
||||
..
|
||||
}),
|
||||
)),
|
||||
Err(Stmt::Decl(
|
||||
Decl::Fn(..)
|
||||
| Decl::Var(VarDecl {
|
||||
kind: VarDeclKind::Var,
|
||||
..
|
||||
}),
|
||||
)),
|
||||
) => Some(Ordering::Less),
|
||||
(_, Err(Stmt::Decl(Decl::Fn(..)))) => Some(Ordering::Greater),
|
||||
_ => Some(Ordering::Less),
|
||||
}
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("fns: Reordering statements");
|
||||
let mut fns = vec![];
|
||||
let mut other = vec![];
|
||||
|
||||
for stmt in stmts.take() {
|
||||
let stmt = stmt.into_module_item();
|
||||
|
||||
match stmt {
|
||||
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(..)))
|
||||
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl: Decl::Fn(..),
|
||||
..
|
||||
}))
|
||||
| ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
kind: VarDeclKind::Var,
|
||||
..
|
||||
})))
|
||||
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl:
|
||||
Decl::Var(VarDecl {
|
||||
kind: VarDeclKind::Var,
|
||||
..
|
||||
}),
|
||||
..
|
||||
})) => {
|
||||
fns.push(T::from_module_item(stmt));
|
||||
}
|
||||
|
||||
_ => {
|
||||
other.push(T::from_module_item(stmt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fns.extend(other);
|
||||
|
||||
*stmts = fns;
|
||||
}
|
||||
}
|
@ -88,7 +88,7 @@ impl Optimizer<'_> {
|
||||
|
||||
match &p.key {
|
||||
PropName::Str(s) => {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"hoist_props: Storing a varaible to inline \
|
||||
properties"
|
||||
);
|
||||
@ -96,7 +96,7 @@ impl Optimizer<'_> {
|
||||
.insert((name.to_id(), s.value.clone()), value);
|
||||
}
|
||||
PropName::Ident(i) => {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"hoist_props: Storing a varaible to inline \
|
||||
properties"
|
||||
);
|
||||
@ -118,9 +118,12 @@ impl Optimizer<'_> {
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| {
|
||||
data.vars
|
||||
.get(&name.to_id())
|
||||
.map(|v| v.ref_count == 1 && v.has_property_access)
|
||||
data.vars.get(&name.to_id()).map(|v| {
|
||||
v.ref_count == 1
|
||||
&& v.has_property_access
|
||||
&& v.is_fn_local
|
||||
&& !v.used_in_loop
|
||||
})
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -132,6 +135,15 @@ impl Optimizer<'_> {
|
||||
None => return,
|
||||
};
|
||||
|
||||
match &*init {
|
||||
Expr::This(..) => {
|
||||
n.init = Some(init);
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.vars_for_prop_hoisting.insert(name.to_id(), init) {
|
||||
Some(prev) => {
|
||||
panic!(
|
||||
@ -140,7 +152,13 @@ impl Optimizer<'_> {
|
||||
prev
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
None => {
|
||||
log::debug!(
|
||||
"hoist_props: Stored {}{:?} to inline property access",
|
||||
name.id.sym,
|
||||
name.id.span.ctxt
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -161,7 +179,7 @@ impl Optimizer<'_> {
|
||||
if let Some(value) = self.vars_for_prop_hoisting.remove(&obj.to_id()) {
|
||||
member.obj = ExprOrSuper::Expr(value);
|
||||
self.changed = true;
|
||||
log::trace!("hoist_props: Inlined a property");
|
||||
log::debug!("hoist_props: Inlined a property");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -174,7 +192,7 @@ impl Optimizer<'_> {
|
||||
if let Some(value) =
|
||||
self.simple_props.get(&(obj.to_id(), prop.sym.clone()))
|
||||
{
|
||||
log::trace!("hoist_props: Inlining `{}.{}`", obj.sym, prop.sym);
|
||||
log::debug!("hoist_props: Inlining `{}.{}`", obj.sym, prop.sym);
|
||||
self.changed = true;
|
||||
*e = *value.clone()
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
use super::Optimizer;
|
||||
use crate::compress::optimize::is_pure_undefined;
|
||||
use crate::debug::dump;
|
||||
use crate::util::ExprOptExt;
|
||||
use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::prepend;
|
||||
use swc_ecma_utils::undefined;
|
||||
use swc_ecma_utils::StmtLike;
|
||||
use swc_ecma_visit::noop_visit_type;
|
||||
@ -14,6 +17,216 @@ use swc_ecma_visit::VisitWith;
|
||||
/// Methods related to the option `if_return`. All methods are noop if
|
||||
/// `if_return` is false.
|
||||
impl Optimizer<'_> {
|
||||
pub(super) fn drop_undefined_from_return_arg(&mut self, s: &mut ReturnStmt) {
|
||||
match s.arg.as_deref() {
|
||||
Some(e) => {
|
||||
if is_pure_undefined(e) {
|
||||
self.changed = true;
|
||||
log::debug!("Dropped `undefined` from `return undefined`");
|
||||
s.arg.take();
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Input
|
||||
///
|
||||
/// ```js
|
||||
/// function f(a, b) {
|
||||
/// if (a) return;
|
||||
/// console.log(b);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Output
|
||||
/// ```js
|
||||
/// function f(a, b) {
|
||||
/// if (!a)
|
||||
/// console.log(b);
|
||||
/// }
|
||||
/// ```
|
||||
pub(super) fn negate_if_terminate(
|
||||
&mut self,
|
||||
stmts: &mut Vec<Stmt>,
|
||||
handle_return: bool,
|
||||
handle_continue: bool,
|
||||
) {
|
||||
if handle_return {
|
||||
if !self.options.if_return || !self.options.bools {
|
||||
return;
|
||||
}
|
||||
|
||||
for s in stmts.iter_mut() {
|
||||
match s {
|
||||
Stmt::If(s) => match &mut *s.cons {
|
||||
Stmt::Block(cons) => {
|
||||
self.negate_if_terminate(&mut cons.stmts, true, false);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let len = stmts.len();
|
||||
|
||||
let pos_of_if = stmts.iter().enumerate().rposition(|(idx, s)| {
|
||||
idx != len - 1
|
||||
&& match s {
|
||||
Stmt::If(IfStmt {
|
||||
cons, alt: None, ..
|
||||
}) => match &**cons {
|
||||
Stmt::Return(ReturnStmt { arg: None, .. }) => handle_return,
|
||||
|
||||
Stmt::Continue(ContinueStmt { label: None, .. }) => handle_continue,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
let pos_of_if = match pos_of_if {
|
||||
Some(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.changed = true;
|
||||
log::debug!(
|
||||
"if_return: Negating `foo` in `if (foo) return; bar()` to make it `if (!foo) bar()`"
|
||||
);
|
||||
|
||||
let mut new = vec![];
|
||||
new.extend(stmts.drain(..pos_of_if));
|
||||
let cons = stmts.drain(1..).collect::<Vec<_>>();
|
||||
|
||||
let if_stmt = stmts.take().into_iter().next().unwrap();
|
||||
match if_stmt {
|
||||
Stmt::If(mut s) => {
|
||||
assert_eq!(s.alt, None);
|
||||
self.negate(&mut s.test);
|
||||
|
||||
s.cons = if cons.len() == 1 {
|
||||
Box::new(cons.into_iter().next().unwrap())
|
||||
} else {
|
||||
Box::new(Stmt::Block(BlockStmt {
|
||||
span: DUMMY_SP,
|
||||
stmts: cons,
|
||||
}))
|
||||
};
|
||||
|
||||
new.push(Stmt::If(s))
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
*stmts = new;
|
||||
}
|
||||
|
||||
pub(super) fn merge_nested_if(&mut self, s: &mut IfStmt) {
|
||||
if !self.options.conditionals && !self.options.bools {
|
||||
return;
|
||||
}
|
||||
|
||||
if s.alt.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
match &mut *s.cons {
|
||||
Stmt::If(IfStmt {
|
||||
test,
|
||||
cons,
|
||||
alt: None,
|
||||
..
|
||||
}) => {
|
||||
self.changed = true;
|
||||
log::debug!("if_return: Merging nested if statements");
|
||||
|
||||
s.test = Box::new(Expr::Bin(BinExpr {
|
||||
span: s.test.span(),
|
||||
op: op!("&&"),
|
||||
left: s.test.take(),
|
||||
right: test.take(),
|
||||
}));
|
||||
s.cons = cons.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn merge_else_if(&mut self, s: &mut IfStmt) {
|
||||
match s.alt.as_deref_mut() {
|
||||
Some(Stmt::If(IfStmt {
|
||||
span: span_of_alt,
|
||||
test: test_of_alt,
|
||||
cons: cons_of_alt,
|
||||
alt: Some(alt_of_alt),
|
||||
..
|
||||
})) => {
|
||||
match &**cons_of_alt {
|
||||
Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
match &mut **alt_of_alt {
|
||||
Stmt::Block(..) => {}
|
||||
Stmt::Expr(..) => {
|
||||
*alt_of_alt = Box::new(Stmt::Block(BlockStmt {
|
||||
span: DUMMY_SP,
|
||||
stmts: vec![*alt_of_alt.take()],
|
||||
}));
|
||||
}
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("if_return: Merging `else if` into `else`");
|
||||
|
||||
match &mut **alt_of_alt {
|
||||
Stmt::Block(alt_of_alt) => {
|
||||
prepend(
|
||||
&mut alt_of_alt.stmts,
|
||||
Stmt::If(IfStmt {
|
||||
span: *span_of_alt,
|
||||
test: test_of_alt.take(),
|
||||
cons: cons_of_alt.take(),
|
||||
alt: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
s.alt = Some(alt_of_alt.take());
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_nested_if_returns(&mut self, s: &mut Stmt) {
|
||||
match s {
|
||||
Stmt::Block(s) => self.merge_if_returns(&mut s.stmts),
|
||||
Stmt::If(s) => {
|
||||
self.merge_nested_if_returns(&mut s.cons);
|
||||
|
||||
if let Some(alt) = &mut s.alt {
|
||||
self.merge_nested_if_returns(&mut **alt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge simple return statements in if statements.
|
||||
///
|
||||
/// # Example
|
||||
@ -34,33 +247,94 @@ impl Optimizer<'_> {
|
||||
/// return a ? foo() : bar();
|
||||
/// }
|
||||
/// ```
|
||||
pub(super) fn merge_if_returns<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: StmtLike,
|
||||
Vec<T>: VisitWith<ReturnFinder>,
|
||||
{
|
||||
if !self.options.if_return || stmts.is_empty() {
|
||||
pub(super) fn merge_if_returns(&mut self, stmts: &mut Vec<Stmt>) {
|
||||
if !self.options.if_return {
|
||||
return;
|
||||
}
|
||||
|
||||
for stmt in stmts.iter_mut() {
|
||||
self.merge_nested_if_returns(stmt)
|
||||
}
|
||||
|
||||
if stmts.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let idx_of_not_mergable = stmts.iter().rposition(|stmt| match stmt.as_stmt() {
|
||||
Some(v) => !self.can_merge_stmt_as_if_return(v),
|
||||
None => true,
|
||||
});
|
||||
let skip = idx_of_not_mergable.map(|v| v + 1).unwrap_or(0);
|
||||
log::trace!("if_return: Skip = {}", skip);
|
||||
|
||||
if stmts.len() <= skip + 1 {
|
||||
log::trace!("if_return: [x] Aborting because of skip");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let return_count = count_leaping_returns(&*stmts);
|
||||
let stmts = &stmts[skip..];
|
||||
let return_count: usize = stmts.iter().map(|v| count_leaping_returns(v)).sum();
|
||||
|
||||
let ends_with_if = stmts
|
||||
.last()
|
||||
.map(|stmt| match stmt.as_stmt() {
|
||||
Some(Stmt::If(..)) => true,
|
||||
// There's no return statment so merging requires injecting unnecessary `void 0`
|
||||
if return_count == 0 {
|
||||
log::trace!("if_return: [x] Aborting because we failed to find return");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the last statement is a return statement and last - 1 is an if statement
|
||||
// is without return, we don't need to fold it as `void 0` is too much for such
|
||||
// cases.
|
||||
|
||||
let if_return_count = stmts
|
||||
.iter()
|
||||
.filter(|s| match s {
|
||||
Stmt::If(IfStmt {
|
||||
cons, alt: None, ..
|
||||
}) => always_terminates_with_return_arg(&cons),
|
||||
_ => false,
|
||||
})
|
||||
.unwrap();
|
||||
if return_count <= 1 && ends_with_if {
|
||||
return;
|
||||
.count();
|
||||
|
||||
if stmts.len() >= 2 {
|
||||
match (
|
||||
&stmts[stmts.len() - 2].as_stmt(),
|
||||
&stmts[stmts.len() - 1].as_stmt(),
|
||||
) {
|
||||
(_, Some(Stmt::If(IfStmt { alt: None, .. }) | Stmt::Expr(..)))
|
||||
if if_return_count <= 1 =>
|
||||
{
|
||||
log::trace!(
|
||||
"if_return: [x] Aborting because last stmt is a not return stmt"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
(
|
||||
Some(Stmt::If(IfStmt {
|
||||
cons, alt: None, ..
|
||||
})),
|
||||
Some(Stmt::Return(..)),
|
||||
) => match &**cons {
|
||||
Stmt::Return(ReturnStmt { arg: Some(..), .. }) => {}
|
||||
_ => {
|
||||
log::trace!(
|
||||
"if_return: [x] Aborting because stmt before last is an if stmt \
|
||||
and cons of it is not a return stmt"
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let start = stmts
|
||||
.iter()
|
||||
.skip(skip)
|
||||
.position(|stmt| match stmt.as_stmt() {
|
||||
Some(v) => self.can_merge_stmt_as_if_return(v),
|
||||
None => false,
|
||||
@ -70,16 +344,16 @@ impl Optimizer<'_> {
|
||||
let ends_with_mergable = stmts
|
||||
.last()
|
||||
.map(|stmt| match stmt.as_stmt() {
|
||||
Some(Stmt::Return(..)) | Some(Stmt::Expr(..)) => true,
|
||||
Some(s) => self.can_merge_stmt_as_if_return(s),
|
||||
_ => false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if stmts.len() - start == 1 && ends_with_mergable {
|
||||
if stmts.len() == start + skip + 1 || !ends_with_mergable {
|
||||
return;
|
||||
}
|
||||
|
||||
let can_merge = stmts.iter().skip(start).all(|stmt| match stmt.as_stmt() {
|
||||
let can_merge = stmts.iter().skip(skip).all(|stmt| match stmt.as_stmt() {
|
||||
Some(s) => self.can_merge_stmt_as_if_return(s),
|
||||
_ => false,
|
||||
});
|
||||
@ -88,28 +362,33 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!("if_return: Merging returns");
|
||||
log::debug!("if_return: Merging returns");
|
||||
if cfg!(feature = "debug") {
|
||||
let block = BlockStmt {
|
||||
span: DUMMY_SP,
|
||||
stmts: stmts.clone(),
|
||||
};
|
||||
log::trace!("if_return: {}", dump(&block))
|
||||
}
|
||||
self.changed = true;
|
||||
|
||||
let mut cur: Option<Box<Expr>> = None;
|
||||
let mut new = Vec::with_capacity(stmts.len());
|
||||
|
||||
for stmt in stmts.take() {
|
||||
let stmt = match stmt.try_into_stmt() {
|
||||
Ok(stmt) => {
|
||||
if !self.can_merge_stmt_as_if_return(&stmt) {
|
||||
debug_assert_eq!(cur, None);
|
||||
new.push(T::from_stmt(stmt));
|
||||
continue;
|
||||
} else {
|
||||
stmt
|
||||
}
|
||||
}
|
||||
Err(item) => {
|
||||
debug_assert_eq!(cur, None);
|
||||
new.push(item);
|
||||
for (idx, stmt) in stmts.take().into_iter().enumerate() {
|
||||
if let Some(not_mergable) = idx_of_not_mergable {
|
||||
if idx < not_mergable {
|
||||
new.push(stmt);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let stmt = if !self.can_merge_stmt_as_if_return(&stmt) {
|
||||
debug_assert_eq!(cur, None);
|
||||
new.push(stmt);
|
||||
continue;
|
||||
} else {
|
||||
stmt
|
||||
};
|
||||
let is_nonconditional_return = match stmt {
|
||||
Stmt::Return(..) => true,
|
||||
@ -203,17 +482,17 @@ impl Optimizer<'_> {
|
||||
let expr = self.ignore_return_value(&mut cur);
|
||||
|
||||
if let Some(cur) = expr {
|
||||
new.push(T::from_stmt(Stmt::Expr(ExprStmt {
|
||||
new.push(Stmt::Expr(ExprStmt {
|
||||
span: DUMMY_SP,
|
||||
expr: Box::new(cur),
|
||||
})))
|
||||
}))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
new.push(T::from_stmt(Stmt::Return(ReturnStmt {
|
||||
new.push(Stmt::Return(ReturnStmt {
|
||||
span: DUMMY_SP,
|
||||
arg: Some(cur),
|
||||
})));
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,6 +506,11 @@ impl Optimizer<'_> {
|
||||
fn merge_if_returns_to(&mut self, stmt: Stmt, mut exprs: Vec<Box<Expr>>) -> Expr {
|
||||
//
|
||||
match stmt {
|
||||
Stmt::Block(s) => {
|
||||
assert_eq!(s.stmts.len(), 1);
|
||||
self.merge_if_returns_to(s.stmts.into_iter().next().unwrap(), exprs)
|
||||
}
|
||||
|
||||
Stmt::If(IfStmt {
|
||||
span,
|
||||
test,
|
||||
@ -276,9 +560,10 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
fn can_merge_stmt_as_if_return(&self, s: &Stmt) -> bool {
|
||||
match s {
|
||||
let res = match s {
|
||||
Stmt::Expr(..) | Stmt::Return(..) => true,
|
||||
Stmt::If(stmt) if self.options.conditionals => {
|
||||
Stmt::Block(s) => s.stmts.len() == 1 && self.can_merge_stmt_as_if_return(&s.stmts[0]),
|
||||
Stmt::If(stmt) => {
|
||||
self.can_merge_stmt_as_if_return(&stmt.cons)
|
||||
&& stmt
|
||||
.alt
|
||||
@ -287,7 +572,12 @@ impl Optimizer<'_> {
|
||||
.unwrap_or(true)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
// if !res {
|
||||
// log::trace!("Cannot merge: {}", dump(s));
|
||||
// }
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,3 +613,19 @@ impl Visit for ReturnFinder {
|
||||
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
|
||||
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &dyn Node) {}
|
||||
}
|
||||
|
||||
fn always_terminates_with_return_arg(s: &Stmt) -> bool {
|
||||
match s {
|
||||
Stmt::Return(ReturnStmt { arg: Some(..), .. }) => true,
|
||||
Stmt::If(IfStmt { cons, alt, .. }) => {
|
||||
always_terminates_with_return_arg(&cons)
|
||||
&& alt
|
||||
.as_deref()
|
||||
.map(always_terminates_with_return_arg)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
Stmt::Block(s) => s.stmts.iter().any(always_terminates_with_return_arg),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use super::Optimizer;
|
||||
use crate::compress::optimize::Ctx;
|
||||
use crate::util::idents_used_by;
|
||||
use crate::util::make_number;
|
||||
use crate::util::IdentUsageCollector;
|
||||
use fxhash::FxHashMap;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::replace;
|
||||
@ -13,20 +12,17 @@ use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::find_ids;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::undefined;
|
||||
use swc_ecma_utils::DestructuringFinder;
|
||||
use swc_ecma_utils::ExprExt;
|
||||
use swc_ecma_utils::ExprFactory;
|
||||
use swc_ecma_utils::Id;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
use swc_ecma_visit::VisitWith;
|
||||
|
||||
/// Methods related to the option `negate_iife`.
|
||||
impl Optimizer<'_> {
|
||||
/// Negates iife, while ignore return value.
|
||||
pub(super) fn negate_iife_ignoring_ret(&mut self, e: &mut Expr) {
|
||||
if !self.options.negate_iife || self.ctx.in_bang_arg {
|
||||
if !self.options.negate_iife || self.ctx.in_bang_arg || self.ctx.dont_use_negated_iife {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -42,7 +38,7 @@ impl Optimizer<'_> {
|
||||
|
||||
match callee {
|
||||
Expr::Fn(..) => {
|
||||
log::trace!("negate_iife: Negating iife");
|
||||
log::debug!("negate_iife: Negating iife");
|
||||
*e = Expr::Unary(UnaryExpr {
|
||||
span: DUMMY_SP,
|
||||
op: op!("!"),
|
||||
@ -54,38 +50,72 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if it did any work.
|
||||
///
|
||||
///
|
||||
/// - `iife ? foo : bar` => `!iife ? bar : foo`
|
||||
pub(super) fn negate_iife_in_cond(&mut self, e: &mut Expr) {
|
||||
pub(super) fn negate_iife_in_cond(&mut self, e: &mut Expr) -> bool {
|
||||
let cond = match e {
|
||||
Expr::Cond(v) => v,
|
||||
_ => return,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let test_call = match &mut *cond.test {
|
||||
Expr::Call(e) => e,
|
||||
_ => return,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let callee = match &mut test_call.callee {
|
||||
ExprOrSuper::Super(_) => return,
|
||||
ExprOrSuper::Super(_) => return false,
|
||||
ExprOrSuper::Expr(e) => &mut **e,
|
||||
};
|
||||
|
||||
match callee {
|
||||
Expr::Fn(..) => {
|
||||
log::trace!("negate_iife: Swapping cons and alt");
|
||||
log::debug!("negate_iife: Swapping cons and alt");
|
||||
cond.test = Box::new(Expr::Unary(UnaryExpr {
|
||||
span: DUMMY_SP,
|
||||
op: op!("!"),
|
||||
arg: cond.test.take(),
|
||||
}));
|
||||
swap(&mut cond.cons, &mut cond.alt);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn restore_negated_iife(&mut self, cond: &mut CondExpr) {
|
||||
if !self.ctx.dont_use_negated_iife {
|
||||
return;
|
||||
}
|
||||
|
||||
match &mut *cond.test {
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) => match &mut **arg {
|
||||
Expr::Call(CallExpr {
|
||||
span: call_span,
|
||||
callee: ExprOrSuper::Expr(callee),
|
||||
args,
|
||||
..
|
||||
}) => match &**callee {
|
||||
Expr::Fn(..) => {
|
||||
cond.test = Box::new(Expr::Call(CallExpr {
|
||||
span: *call_span,
|
||||
callee: callee.take().as_callee(),
|
||||
args: args.take(),
|
||||
type_args: Default::default(),
|
||||
}));
|
||||
swap(&mut cond.cons, &mut cond.alt);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods related to iife.
|
||||
@ -168,21 +198,33 @@ impl Optimizer<'_> {
|
||||
if usage.reassigned {
|
||||
continue;
|
||||
}
|
||||
if usage.ref_count != 1 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(arg) = arg {
|
||||
// NOTE
|
||||
//
|
||||
// This function is misdesigned and should be removed.
|
||||
// This is wrong because the order of execution is not guaranteed.
|
||||
match &**arg {
|
||||
Expr::Ident(..) | Expr::Lit(..) => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let should_be_inlined = self.can_be_inlined_for_iife(arg);
|
||||
if should_be_inlined {
|
||||
log::trace!(
|
||||
"iife: Trying to inline {}{:?}",
|
||||
log::debug!(
|
||||
"iife: Trying to inline argument ({}{:?})",
|
||||
param.id.sym,
|
||||
param.id.span.ctxt
|
||||
);
|
||||
vars.insert(param.to_id(), arg.clone());
|
||||
}
|
||||
} else {
|
||||
log::trace!(
|
||||
"iife: Trying to inline {}{:?} (undefined)",
|
||||
log::debug!(
|
||||
"iife: Trying to inline argument ({}{:?}) (undefined)",
|
||||
param.id.sym,
|
||||
param.id.span.ctxt
|
||||
);
|
||||
@ -206,10 +248,11 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_vars_in_node<N>(&mut self, n: &mut N, vars: FxHashMap<Id, Box<Expr>>)
|
||||
pub(super) fn inline_vars_in_node<N>(&mut self, n: &mut N, vars: FxHashMap<Id, Box<Expr>>)
|
||||
where
|
||||
N: VisitMutWith<Self>,
|
||||
{
|
||||
log::debug!("inline: inline_vars_in_node");
|
||||
let ctx = Ctx {
|
||||
inline_prevented: false,
|
||||
..self.ctx
|
||||
@ -249,10 +292,6 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.ctx.inline_prevented {
|
||||
return;
|
||||
}
|
||||
|
||||
let call = match e {
|
||||
Expr::Call(v) => v,
|
||||
_ => return,
|
||||
@ -267,31 +306,44 @@ impl Optimizer<'_> {
|
||||
ExprOrSuper::Expr(e) => &mut **e,
|
||||
};
|
||||
|
||||
if call.args.iter().any(|arg| match &*arg.expr {
|
||||
Expr::Member(MemberExpr {
|
||||
computed: false, ..
|
||||
}) => false,
|
||||
_ => arg.expr.may_have_side_effects(),
|
||||
}) {
|
||||
if self.ctx.inline_prevented {
|
||||
log::trace!("iife: [x] Inline is prevented");
|
||||
return;
|
||||
}
|
||||
|
||||
match callee {
|
||||
Expr::Arrow(f) => {
|
||||
if self.ctx.in_top_level() && !self.ctx.in_call_arg && self.options.negate_iife {
|
||||
match &f.body {
|
||||
BlockStmtOrExpr::BlockStmt(body) => {
|
||||
let has_decl = body.stmts.iter().any(|stmt| match stmt {
|
||||
Stmt::Decl(..) => true,
|
||||
_ => false,
|
||||
});
|
||||
if has_decl {
|
||||
return;
|
||||
}
|
||||
}
|
||||
BlockStmtOrExpr::Expr(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
if f.params.iter().any(|param| !param.is_ident()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_param_used_by_body(&f.params, &f.body) {
|
||||
return;
|
||||
}
|
||||
let param_ids = f
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| p.clone().ident().unwrap().id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match &mut f.body {
|
||||
BlockStmtOrExpr::BlockStmt(body) => {
|
||||
let new = self.inline_fn_like(body);
|
||||
let new = self.inline_fn_like(¶m_ids, body, &mut call.args);
|
||||
if let Some(new) = new {
|
||||
self.changed = true;
|
||||
log::trace!("inline: Inlining a function call (arrow)");
|
||||
log::debug!("inline: Inlining a function call (arrow)");
|
||||
|
||||
*e = new;
|
||||
}
|
||||
@ -306,23 +358,6 @@ impl Optimizer<'_> {
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
let mut inline_map = HashMap::default();
|
||||
for (idx, param) in f.params.iter().enumerate() {
|
||||
let arg_val = if let Some(arg) = call.args.get(idx) {
|
||||
arg.expr.clone()
|
||||
} else {
|
||||
undefined(DUMMY_SP)
|
||||
};
|
||||
|
||||
match param {
|
||||
Pat::Ident(param) => {
|
||||
inline_map.insert(param.id.to_id(), arg_val);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.inline_vars_in_node(&mut f.body, inline_map);
|
||||
|
||||
match &mut f.body {
|
||||
BlockStmtOrExpr::BlockStmt(_) => {
|
||||
@ -330,17 +365,74 @@ impl Optimizer<'_> {
|
||||
}
|
||||
BlockStmtOrExpr::Expr(body) => {
|
||||
self.changed = true;
|
||||
log::trace!("inline: Inlining a call to an arrow function");
|
||||
|
||||
{
|
||||
let vars = f
|
||||
.params
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|name| VarDeclarator {
|
||||
span: DUMMY_SP.apply_mark(self.marks.non_top_level),
|
||||
name,
|
||||
init: Default::default(),
|
||||
definite: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !vars.is_empty() {
|
||||
self.prepend_stmts.push(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Var,
|
||||
declare: Default::default(),
|
||||
decls: vars,
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
let mut exprs = vec![];
|
||||
exprs.push(Box::new(make_number(DUMMY_SP, 0.0)));
|
||||
for (idx, param) in f.params.iter().enumerate() {
|
||||
if let Some(arg) = call.args.get_mut(idx) {
|
||||
exprs.push(Box::new(Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP.apply_mark(self.marks.non_top_level),
|
||||
op: op!("="),
|
||||
left: PatOrExpr::Pat(Box::new(param.clone())),
|
||||
right: arg.expr.take(),
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
if call.args.len() > f.params.len() {
|
||||
for arg in &mut call.args[f.params.len()..] {
|
||||
exprs.push(arg.expr.take());
|
||||
}
|
||||
}
|
||||
exprs.push(body.take());
|
||||
|
||||
log::debug!("inline: Inlining a call to an arrow function");
|
||||
*e = Expr::Seq(SeqExpr {
|
||||
span: DUMMY_SP,
|
||||
exprs: vec![Box::new(make_number(DUMMY_SP, 0.0)), body.take()],
|
||||
exprs,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Fn(f) => {
|
||||
if self.ctx.in_top_level() && !self.ctx.in_call_arg && self.options.negate_iife {
|
||||
let body = f.function.body.as_ref().unwrap();
|
||||
let has_decl = body.stmts.iter().any(|stmt| match stmt {
|
||||
Stmt::Decl(..) => true,
|
||||
_ => false,
|
||||
});
|
||||
if has_decl {
|
||||
log::trace!("iife: [x] Found decl");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if f.function.is_generator {
|
||||
log::trace!("iife: [x] Cannot inline generator");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -349,17 +441,29 @@ impl Optimizer<'_> {
|
||||
Pat::Object(..) | Pat::Array(..) | Pat::Assign(..) | Pat::Rest(..) => true,
|
||||
_ => false,
|
||||
}) {
|
||||
log::trace!("iife: [x] Found complex pattern");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(i) = &f.ident {
|
||||
if idents_used_by(&f.function.body).contains(&i.to_id()) {
|
||||
log::trace!("iife: [x] Recursive?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if is_param_used_by_body(&f.function.params, &f.function.body) {
|
||||
return;
|
||||
for arg in &call.args {
|
||||
if arg.spread.is_some() {
|
||||
log::trace!("iife: [x] Found spread argument");
|
||||
return;
|
||||
}
|
||||
match &*arg.expr {
|
||||
Expr::Fn(..) | Expr::Arrow(..) => {
|
||||
log::trace!("iife: [x] Found callable argument");
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let body = f.function.body.as_mut().unwrap();
|
||||
@ -368,10 +472,22 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
let new = self.inline_fn_like(body);
|
||||
let param_ids = f
|
||||
.function
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| p.pat.clone().ident().unwrap().id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !self.can_inline_fn_like(¶m_ids, body) {
|
||||
log::trace!("iife: [x] Body is not inliable");
|
||||
return;
|
||||
}
|
||||
|
||||
let new = self.inline_fn_like(¶m_ids, body, &mut call.args);
|
||||
if let Some(new) = new {
|
||||
self.changed = true;
|
||||
log::trace!("inline: Inlining a function call");
|
||||
log::debug!("inline: Inlining a function call");
|
||||
|
||||
*e = new;
|
||||
}
|
||||
@ -382,11 +498,39 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_fn_like(&mut self, body: &mut BlockStmt) -> Option<Expr> {
|
||||
fn can_inline_fn_like(&self, param_ids: &[Ident], body: &BlockStmt) -> bool {
|
||||
if !body.stmts.iter().all(|stmt| match stmt {
|
||||
Stmt::Expr(e) if e.expr.is_await_expr() => false,
|
||||
Stmt::Decl(Decl::Var(VarDecl {
|
||||
kind: VarDeclKind::Var | VarDeclKind::Let,
|
||||
decls,
|
||||
..
|
||||
})) => {
|
||||
if decls.iter().any(|decl| match decl.name {
|
||||
Pat::Ident(..) => false,
|
||||
_ => true,
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.ctx.executed_multiple_time {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
Stmt::Expr(e) => match &*e.expr {
|
||||
Expr::Await(..) => false,
|
||||
|
||||
// TODO: Check if paramter is used and inline if call is not related to parameters.
|
||||
Expr::Call(e) => {
|
||||
let used = idents_used_by(e);
|
||||
param_ids.iter().all(|param| !used.contains(¶m.to_id()))
|
||||
}
|
||||
|
||||
_ => true,
|
||||
},
|
||||
|
||||
Stmt::Expr(..) => true,
|
||||
Stmt::Return(ReturnStmt { arg, .. }) => match arg.as_deref() {
|
||||
Some(Expr::Await(..)) => false,
|
||||
|
||||
@ -401,12 +545,92 @@ impl Optimizer<'_> {
|
||||
},
|
||||
_ => false,
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if idents_used_by(&*body)
|
||||
.iter()
|
||||
.any(|v| v.0 == js_word!("arguments"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn inline_fn_like(
|
||||
&mut self,
|
||||
params: &[Ident],
|
||||
body: &mut BlockStmt,
|
||||
args: &mut [ExprOrSpread],
|
||||
) -> Option<Expr> {
|
||||
if !self.can_inline_fn_like(¶ms, &*body) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("inline: Inling an iife");
|
||||
|
||||
let mut exprs = vec![];
|
||||
for stmt in body.stmts.take() {
|
||||
|
||||
{
|
||||
let vars = params
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(BindingIdent::from)
|
||||
.map(Pat::Ident)
|
||||
.map(|name| VarDeclarator {
|
||||
span: DUMMY_SP.apply_mark(self.marks.non_top_level),
|
||||
name,
|
||||
init: Default::default(),
|
||||
definite: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !vars.is_empty() {
|
||||
self.prepend_stmts.push(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Var,
|
||||
declare: Default::default(),
|
||||
decls: vars,
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, param) in params.iter().enumerate() {
|
||||
if let Some(arg) = args.get_mut(idx) {
|
||||
exprs.push(Box::new(Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP.apply_mark(self.marks.non_top_level),
|
||||
op: op!("="),
|
||||
left: PatOrExpr::Pat(Box::new(Pat::Ident(param.clone().into()))),
|
||||
right: arg.expr.take(),
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() > params.len() {
|
||||
for arg in &mut args[params.len()..] {
|
||||
exprs.push(arg.expr.take());
|
||||
}
|
||||
}
|
||||
|
||||
for mut stmt in body.stmts.take() {
|
||||
match stmt {
|
||||
Stmt::Decl(Decl::Var(ref mut var)) => {
|
||||
for decl in &mut var.decls {
|
||||
if decl.init.is_some() {
|
||||
exprs.push(Box::new(Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP.apply_mark(self.marks.non_top_level),
|
||||
op: op!("="),
|
||||
left: PatOrExpr::Pat(Box::new(decl.name.clone())),
|
||||
right: decl.init.take().unwrap(),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
self.prepend_stmts.push(stmt);
|
||||
}
|
||||
|
||||
Stmt::Expr(stmt) => {
|
||||
exprs.push(stmt.expr);
|
||||
}
|
||||
@ -513,29 +737,3 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can't remove a function call if a parameter is declared by function and
|
||||
// the body of the function uses it.
|
||||
fn is_param_used_by_body<P, B>(params: &P, body: &B) -> bool
|
||||
where
|
||||
P: for<'any> VisitWith<DestructuringFinder<'any, Id>>,
|
||||
B: VisitWith<IdentUsageCollector>,
|
||||
{
|
||||
let declared: Vec<Id> = find_ids(params);
|
||||
|
||||
let used = idents_used_by(body);
|
||||
|
||||
for id in declared {
|
||||
if used.contains(&id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (sym, _) in used {
|
||||
if sym == js_word!("arguments") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
use crate::compress::optimize::util::class_has_side_effect;
|
||||
use crate::compress::optimize::util::is_valid_for_lhs;
|
||||
use crate::debug::dump;
|
||||
use crate::util::has_mark;
|
||||
use crate::util::idents_used_by;
|
||||
|
||||
use super::Optimizer;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
@ -21,9 +25,18 @@ impl Optimizer<'_> {
|
||||
None => return,
|
||||
};
|
||||
|
||||
let should_preserve = (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
let should_preserve = !has_mark(var.span, self.marks.non_top_level)
|
||||
&& (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
&& self.ctx.in_top_level();
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"inline: store_var_for_inining({}, should_preserve = {:?})",
|
||||
dump(&var.name),
|
||||
should_preserve
|
||||
);
|
||||
}
|
||||
|
||||
if self
|
||||
.data
|
||||
.as_ref()
|
||||
@ -57,13 +70,34 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
if should_preserve && usage.var_kind != Some(VarDeclKind::Const) {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"inline: Preserving non-const variable `{}` because it's top-level",
|
||||
dump(&var.name)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if usage.cond_init || usage.used_above_decl {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("inline: [x] It's cond init or used before decl",);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if !usage.is_fn_local {
|
||||
match &**init {
|
||||
Expr::Lit(..) => {}
|
||||
_ => {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("inline: [x] It's not fn-local");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !usage.reassigned {
|
||||
match &**init {
|
||||
Expr::Fn(..) | Expr::Arrow(..) => {
|
||||
@ -95,14 +129,12 @@ impl Optimizer<'_> {
|
||||
&& (!usage.mutated || (usage.assign_count == 0 && !usage.reassigned))
|
||||
&& match &**init {
|
||||
Expr::Lit(lit) => match lit {
|
||||
Lit::Str(_)
|
||||
| Lit::Bool(_)
|
||||
| Lit::Null(_)
|
||||
| Lit::Num(_)
|
||||
| Lit::BigInt(_) => true,
|
||||
Lit::Str(_) => true,
|
||||
Lit::Bool(_) | Lit::Null(_) | Lit::Num(_) | Lit::BigInt(_) => true,
|
||||
Lit::Regex(_) => self.options.unsafe_regexp,
|
||||
_ => false,
|
||||
},
|
||||
Expr::This(..) => usage.is_fn_local,
|
||||
Expr::Arrow(arr) => is_arrow_simple_enough(arr),
|
||||
_ => false,
|
||||
}
|
||||
@ -114,7 +146,7 @@ impl Optimizer<'_> {
|
||||
_ => true,
|
||||
}
|
||||
{
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"inline: Decided to inline '{}{:?}' because it's simple",
|
||||
i.id.sym,
|
||||
i.id.span.ctxt
|
||||
@ -122,7 +154,7 @@ impl Optimizer<'_> {
|
||||
|
||||
self.lits.insert(i.to_id(), init.take());
|
||||
} else {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"inline: Decided to copy '{}{:?}' because it's simple",
|
||||
i.id.sym,
|
||||
i.id.span.ctxt
|
||||
@ -133,6 +165,13 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.inline_as_assignment {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("inline: [x] inline_as_assignment is true");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Single use => inlined
|
||||
if is_inline_enabled
|
||||
&& !should_preserve
|
||||
@ -158,7 +197,11 @@ impl Optimizer<'_> {
|
||||
_ => {}
|
||||
}
|
||||
match &**init {
|
||||
Expr::Lit(Lit::Regex(..)) => return,
|
||||
Expr::Lit(Lit::Regex(..)) => {
|
||||
if !usage.is_fn_local || usage.used_in_loop {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Expr::This(..) => {
|
||||
// Don't inline this if it passes function boundaries.
|
||||
@ -170,17 +213,20 @@ impl Optimizer<'_> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if init.may_have_side_effects() {
|
||||
if !self
|
||||
.vars_accessible_without_side_effect
|
||||
.contains(&i.to_id())
|
||||
{
|
||||
// TODO: Inline partially
|
||||
return;
|
||||
if usage.used_in_loop {
|
||||
match &**init {
|
||||
Expr::Lit(..) | Expr::Ident(..) | Expr::Fn(..) => {}
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
if init.may_have_side_effects() {
|
||||
return;
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"inline: Decided to inline '{}{:?}' because it's used only once",
|
||||
i.id.sym,
|
||||
i.id.span.ctxt
|
||||
@ -210,6 +256,10 @@ impl Optimizer<'_> {
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Stmt::Try(TryStmt { block, .. }) => {
|
||||
return self.is_fn_body_simple_enough_to_inline(block)
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -234,7 +284,7 @@ impl Optimizer<'_> {
|
||||
.and_then(|data| data.vars.get(&i.to_id()))
|
||||
{
|
||||
if !usage.reassigned {
|
||||
log::trace!("typeofs: Storing typeof `{}{:?}`", i.sym, i.span.ctxt);
|
||||
log::debug!("typeofs: Storing typeof `{}{:?}`", i.sym, i.span.ctxt);
|
||||
match &*decl {
|
||||
Decl::Fn(..) => {
|
||||
self.typeofs.insert(i.to_id(), js_word!("function"));
|
||||
@ -286,6 +336,10 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.inline_as_assignment {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(usage) = self
|
||||
.data
|
||||
.as_ref()
|
||||
@ -295,7 +349,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if usage.reassigned {
|
||||
if usage.reassigned || usage.inline_prevented {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -305,14 +359,14 @@ impl Optimizer<'_> {
|
||||
match &f.function.body {
|
||||
Some(body) => {
|
||||
if self.is_fn_body_simple_enough_to_inline(body) {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"inline: Decided to inline function '{}{:?}' as it's very \
|
||||
simple",
|
||||
f.ident.sym,
|
||||
f.ident.span.ctxt
|
||||
);
|
||||
|
||||
self.lits.insert(
|
||||
self.vars_for_inlining.insert(
|
||||
i.to_id(),
|
||||
match decl {
|
||||
Decl::Fn(f) => Box::new(Expr::Fn(FnExpr {
|
||||
@ -351,14 +405,14 @@ impl Optimizer<'_> {
|
||||
self.changed = true;
|
||||
match &decl {
|
||||
Decl::Class(c) => {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"inline: Decided to inline class '{}{:?}' as it's used only once",
|
||||
c.ident.sym,
|
||||
c.ident.span.ctxt
|
||||
);
|
||||
}
|
||||
Decl::Fn(f) => {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"inline: Decided to inline function '{}{:?}' as it's used only once",
|
||||
f.ident.sym,
|
||||
f.ident.span.ctxt
|
||||
@ -366,6 +420,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.vars_for_inlining.insert(
|
||||
i.to_id(),
|
||||
match decl.take() {
|
||||
@ -399,33 +454,92 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
//
|
||||
if let Some(value) = self.lits.get(&i.to_id()).cloned() {
|
||||
if let Some(value) = self
|
||||
.lits
|
||||
.get(&i.to_id())
|
||||
.and_then(|v| {
|
||||
// Prevent infinite recursion.
|
||||
let ids = idents_used_by(&**v);
|
||||
if ids.contains(&i.to_id()) {
|
||||
None
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
{
|
||||
match &*value {
|
||||
Expr::Lit(Lit::Num(..)) => {
|
||||
if self.ctx.in_lhs_of_assign {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Expr::Member(..) => {
|
||||
if self.ctx.executed_multiple_time {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("inline: Replacing a variable with cheap expression");
|
||||
log::debug!("inline: Replacing a variable with cheap expression");
|
||||
|
||||
*e = *value;
|
||||
} else if let Some(value) = self.vars_for_inlining.get(&i.to_id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
match &**value {
|
||||
Expr::Member(..) => {
|
||||
if self.ctx.executed_multiple_time {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if self.ctx.inline_as_assignment {
|
||||
if let Some(value) = self.vars_for_inlining.remove(&i.to_id()) {
|
||||
self.changed = true;
|
||||
log::debug!(
|
||||
"inline: Inlining '{}{:?}' using assignment",
|
||||
i.sym,
|
||||
i.span.ctxt
|
||||
);
|
||||
|
||||
*e = Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP,
|
||||
op: op!("="),
|
||||
left: PatOrExpr::Pat(Box::new(Pat::Ident(i.clone().into()))),
|
||||
right: value,
|
||||
});
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("inline: [Change] {}", dump(&*e))
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = self.vars_for_inlining.get(&i.to_id()) {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"inline: Replacing '{}{:?}' with an expression",
|
||||
i.sym,
|
||||
i.span.ctxt
|
||||
);
|
||||
|
||||
*e = *value.clone();
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("inline: [Change] {}", dump(&*e))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -46,7 +46,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!("join_vars: Joining variables");
|
||||
log::debug!("join_vars: Joining variables");
|
||||
self.changed = true;
|
||||
|
||||
let mut cur: Option<VarDecl> = None;
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::compress::optimize::unused::UnreachableHandler;
|
||||
use crate::compress::optimize::Optimizer;
|
||||
use swc_common::Spanned;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
@ -19,7 +20,7 @@ impl Optimizer<'_> {
|
||||
match s {
|
||||
Stmt::While(stmt) => {
|
||||
self.changed = true;
|
||||
log::trace!("loops: Converting a while loop to a for loop");
|
||||
log::debug!("loops: Converting a while loop to a for loop");
|
||||
*s = Stmt::For(ForStmt {
|
||||
span: stmt.span,
|
||||
init: None,
|
||||
@ -32,7 +33,7 @@ impl Optimizer<'_> {
|
||||
let val = stmt.test.as_pure_bool();
|
||||
if let Known(true) = val {
|
||||
self.changed = true;
|
||||
log::trace!("loops: Converting an always-true do-while loop to a for loop");
|
||||
log::debug!("loops: Converting an always-true do-while loop to a for loop");
|
||||
|
||||
*s = Stmt::For(ForStmt {
|
||||
span: stmt.span,
|
||||
@ -79,7 +80,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("loops: Removing a for loop with instant break");
|
||||
log::debug!("loops: Removing a for loop with instant break");
|
||||
self.prepend_stmts
|
||||
.extend(f.init.take().map(|init| match init {
|
||||
VarDeclOrExpr::VarDecl(var) => Stmt::Decl(Decl::Var(var)),
|
||||
@ -97,6 +98,70 @@ impl Optimizer<'_> {
|
||||
*s = Stmt::Empty(EmptyStmt { span: DUMMY_SP })
|
||||
}
|
||||
|
||||
/// # Input
|
||||
///
|
||||
/// ```js
|
||||
/// for(; size--;)
|
||||
/// if (!(result = eq(a[size], b[size], aStack, bStack)))
|
||||
/// break;
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Output
|
||||
///
|
||||
/// ```js
|
||||
/// for (; size-- && (result = eq(a[size], b[size], aStack, bStack)););
|
||||
/// ```
|
||||
pub(super) fn merge_for_if_break(&mut self, s: &mut ForStmt) {
|
||||
match &mut *s.body {
|
||||
Stmt::If(IfStmt {
|
||||
test,
|
||||
cons,
|
||||
alt: None,
|
||||
..
|
||||
}) => {
|
||||
match &**cons {
|
||||
Stmt::Break(BreakStmt { label: None, .. }) => {
|
||||
// We only care about instant breaks.
|
||||
//
|
||||
// Note: As the minifier of swc is very fast, we don't
|
||||
// care about block statements with a single break as a
|
||||
// body.
|
||||
//
|
||||
// If it's optimizable, other pass for if statements
|
||||
// will remove block and with the next pass we can apply
|
||||
// this pass.
|
||||
self.changed = true;
|
||||
log::debug!("loops: Compressing for-if-break into a for statement");
|
||||
|
||||
// We negate because this `test` is used as a condition for `break`.
|
||||
self.negate(test);
|
||||
|
||||
match s.test.take() {
|
||||
Some(left) => {
|
||||
s.test = Some(Box::new(Expr::Bin(BinExpr {
|
||||
span: s.test.span(),
|
||||
op: op!("&&"),
|
||||
left,
|
||||
right: test.take(),
|
||||
})));
|
||||
}
|
||||
None => {
|
||||
s.test = Some(test.take());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove body
|
||||
s.body.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// - `while(false) { var a; foo() }` => `var a;`
|
||||
pub(super) fn optiimze_loops_if_cond_is_false(&mut self, stmt: &mut Stmt) {
|
||||
@ -112,7 +177,7 @@ impl Optimizer<'_> {
|
||||
let changed = UnreachableHandler::preserve_vars(stmt);
|
||||
self.changed |= changed;
|
||||
if changed {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"loops: Removing unreachable while statement without side effects"
|
||||
);
|
||||
}
|
||||
@ -120,7 +185,7 @@ impl Optimizer<'_> {
|
||||
let changed = UnreachableHandler::preserve_vars(&mut w.body);
|
||||
self.changed |= changed;
|
||||
if changed {
|
||||
log::trace!("loops: Removing unreachable body of a while statement");
|
||||
log::debug!("loops: Removing unreachable body of a while statement");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,7 +197,7 @@ impl Optimizer<'_> {
|
||||
let changed = UnreachableHandler::preserve_vars(&mut f.body);
|
||||
self.changed |= changed;
|
||||
if changed {
|
||||
log::trace!("loops: Removing unreachable body of a for statement");
|
||||
log::debug!("loops: Removing unreachable body of a for statement");
|
||||
}
|
||||
self.changed |= f.init.is_some() | f.update.is_some();
|
||||
|
||||
@ -154,7 +219,7 @@ impl Optimizer<'_> {
|
||||
} else if let Known(true) = val {
|
||||
if purity.is_pure() {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"loops: Remving `test` part of a for stmt as it's always true"
|
||||
);
|
||||
f.test = None;
|
||||
@ -190,7 +255,7 @@ impl Optimizer<'_> {
|
||||
} else {
|
||||
s.init = None;
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"loops: Removed side-effect-free expressions in `init` of a for stmt"
|
||||
);
|
||||
}
|
||||
|
38
ecmascript/minifier/src/compress/optimize/misc.rs
Normal file
38
ecmascript/minifier/src/compress/optimize/misc.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use super::Optimizer;
|
||||
use swc_ecma_ast::*;
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
impl Optimizer<'_> {
|
||||
pub(super) fn optimize_prop_name(&mut self, name: &mut PropName) {
|
||||
match name {
|
||||
PropName::Str(s) => {
|
||||
if s.value.is_reserved() || s.value.is_reserved_in_es3() {
|
||||
return;
|
||||
}
|
||||
|
||||
if s.value.starts_with(|c: char| c.is_xid_start())
|
||||
&& s.value.chars().all(|c: char| c.is_xid_continue())
|
||||
&& !s.value.contains("𝒶")
|
||||
{
|
||||
self.changed = true;
|
||||
log::debug!("misc: Optimizing string property name");
|
||||
*name = PropName::Ident(Ident {
|
||||
span: s.span,
|
||||
sym: s.value.clone(),
|
||||
optional: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn remove_useless_return(&mut self, stmts: &mut Vec<Stmt>) {
|
||||
if let Some(Stmt::Return(ReturnStmt { arg: None, .. })) = stmts.last() {
|
||||
self.changed = true;
|
||||
log::debug!("misc: Removing useless return");
|
||||
stmts.pop();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ impl Optimizer<'_> {
|
||||
let value = if value.is_empty() { 0f64 } else { 1f64 };
|
||||
|
||||
self.changed = true;
|
||||
log::trace!("numbers: Converting a string literal to {:?}", value);
|
||||
log::debug!("numbers: Converting a string literal to {:?}", value);
|
||||
*e = Expr::Lit(Lit::Num(Number { span: *span, value }));
|
||||
return;
|
||||
}
|
||||
@ -37,7 +37,7 @@ impl Optimizer<'_> {
|
||||
..
|
||||
}) => {
|
||||
self.changed = true;
|
||||
log::trace!("numbers: Lifting `-`");
|
||||
log::debug!("numbers: Lifting `-`");
|
||||
|
||||
*e = Expr::Unary(UnaryExpr {
|
||||
span: arg.span,
|
||||
@ -55,7 +55,7 @@ impl Optimizer<'_> {
|
||||
Expr::Lit(Lit::Num(Number { span, value, .. })) => {
|
||||
if value.is_sign_negative() {
|
||||
self.changed = true;
|
||||
log::trace!("numbers: Lifting `-` in a literal");
|
||||
log::debug!("numbers: Lifting `-` in a literal");
|
||||
|
||||
*e = Expr::Unary(UnaryExpr {
|
||||
span: arg.span,
|
||||
|
@ -1,4 +1,8 @@
|
||||
use super::Optimizer;
|
||||
use crate::compress::optimize::bools::is_ok_to_negate_for_cond;
|
||||
use crate::compress::optimize::bools::is_ok_to_negate_rhs;
|
||||
use crate::compress::optimize::is_pure_undefined;
|
||||
use crate::debug::dump;
|
||||
use crate::util::make_bool;
|
||||
use crate::util::ValueExt;
|
||||
use std::mem::swap;
|
||||
@ -74,7 +78,7 @@ impl Optimizer<'_> {
|
||||
match (lt, rt) {
|
||||
(Type::Undefined, Type::Null) | (Type::Null, Type::Undefined) => {
|
||||
if op == op!("===") {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"Reducing `!== null || !== undefined` check to `!= null`"
|
||||
);
|
||||
return Some(BinExpr {
|
||||
@ -84,7 +88,7 @@ impl Optimizer<'_> {
|
||||
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
|
||||
});
|
||||
} else {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"Reducing `=== null || === undefined` check to `== null`"
|
||||
);
|
||||
return Some(BinExpr {
|
||||
@ -167,7 +171,7 @@ impl Optimizer<'_> {
|
||||
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);
|
||||
log::debug!("Reducing comparison of same variable ({})", e.op);
|
||||
|
||||
e.op = if e.op == op!("===") {
|
||||
op!("==")
|
||||
@ -187,7 +191,7 @@ impl Optimizer<'_> {
|
||||
if lt == rt {
|
||||
e.op = op!("==");
|
||||
self.changed = true;
|
||||
log::trace!("Reduced `===` to `==` because types of operands are identical")
|
||||
log::debug!("Reduced `===` to `==` because types of operands are identical")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,7 +213,7 @@ impl Optimizer<'_> {
|
||||
|
||||
let value = if n.op == op!("==") { l == r } else { l != r };
|
||||
|
||||
log::trace!("Optimizing: literal comparison => bool");
|
||||
log::debug!("Optimizing: literal comparison => bool");
|
||||
self.changed = true;
|
||||
return Some(Expr::Lit(Lit::Bool(Bool {
|
||||
span: n.span,
|
||||
@ -249,7 +253,7 @@ impl Optimizer<'_> {
|
||||
| Expr::Bin(BinExpr { op: op!(">"), .. }) => {
|
||||
if let Known(Type::Bool) = arg.get_type() {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: `!!expr` => `expr`");
|
||||
log::debug!("Optimizing: `!!expr` => `expr`");
|
||||
*e = *arg.take();
|
||||
}
|
||||
|
||||
@ -278,9 +282,9 @@ impl Optimizer<'_> {
|
||||
///
|
||||
/// TODO: Handle special cases like !1 or !0
|
||||
pub(super) fn negate(&mut self, e: &mut Expr) {
|
||||
self.changed = true;
|
||||
let arg = Box::new(e.take());
|
||||
let start_str = dump(&*e);
|
||||
|
||||
self.changed = true;
|
||||
match e {
|
||||
Expr::Bin(bin @ BinExpr { op: op!("=="), .. })
|
||||
| Expr::Bin(bin @ BinExpr { op: op!("!="), .. })
|
||||
@ -304,15 +308,68 @@ impl Optimizer<'_> {
|
||||
}
|
||||
};
|
||||
self.changed = true;
|
||||
log::trace!("negate: binary");
|
||||
log::debug!("negate: binary");
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Bin(BinExpr {
|
||||
left,
|
||||
right,
|
||||
op: op @ op!("&&"),
|
||||
..
|
||||
}) if is_ok_to_negate_rhs(&right) => {
|
||||
log::debug!("negate: a && b => !a || !b");
|
||||
|
||||
self.negate(&mut **left);
|
||||
self.negate(&mut **right);
|
||||
*op = op!("||");
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Bin(BinExpr {
|
||||
left,
|
||||
right,
|
||||
op: op @ op!("||"),
|
||||
..
|
||||
}) if is_ok_to_negate_rhs(&right) => {
|
||||
log::debug!("negate: a || b => !a && !b");
|
||||
|
||||
self.negate(&mut **left);
|
||||
self.negate(&mut **right);
|
||||
*op = op!("&&");
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Cond(CondExpr { cons, alt, .. })
|
||||
if is_ok_to_negate_for_cond(&cons) && is_ok_to_negate_for_cond(&alt) =>
|
||||
{
|
||||
log::debug!("negate: cond");
|
||||
|
||||
self.negate(&mut **cons);
|
||||
self.negate(&mut **alt);
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Seq(SeqExpr { exprs, .. }) => {
|
||||
if let Some(last) = exprs.last_mut() {
|
||||
log::debug!("negate: seq");
|
||||
|
||||
self.negate(&mut **last);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut arg = Box::new(e.take());
|
||||
|
||||
match &mut *arg {
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) => match &mut **arg {
|
||||
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => {
|
||||
log::trace!("negate: !!bool => !bool");
|
||||
log::debug!("negate: !!bool => !bool");
|
||||
*e = *arg.take();
|
||||
return;
|
||||
}
|
||||
@ -321,22 +378,33 @@ impl Optimizer<'_> {
|
||||
op: op!("instanceof"),
|
||||
..
|
||||
}) => {
|
||||
log::trace!("negate: !bool => bool");
|
||||
log::debug!("negate: !bool => bool");
|
||||
*e = *arg.take();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
if self.ctx.in_bool_ctx {
|
||||
log::debug!("negate: !expr => expr (in bool context)");
|
||||
*e = *arg.take();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
log::trace!("negate: e => !e");
|
||||
log::debug!("negate: e => !e");
|
||||
|
||||
*e = Expr::Unary(UnaryExpr {
|
||||
span: DUMMY_SP,
|
||||
op: op!("!"),
|
||||
arg,
|
||||
});
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("[Change] Negated `{}` as `{}`", start_str, dump(&*e));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn handle_negated_seq(&mut self, n: &mut Expr) {
|
||||
@ -352,7 +420,7 @@ impl Optimizer<'_> {
|
||||
if exprs.is_empty() {
|
||||
return;
|
||||
}
|
||||
log::trace!("optimizing negated sequences");
|
||||
log::debug!("optimizing negated sequences");
|
||||
|
||||
{
|
||||
let last = exprs.last_mut().unwrap();
|
||||
@ -427,24 +495,60 @@ impl Optimizer<'_> {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
log::trace!("Compressing: `e = 3 & e` => `e &= 3`");
|
||||
log::debug!("Compressing: `e = 3 & e` => `e &= 3`");
|
||||
|
||||
self.changed = true;
|
||||
e.op = op;
|
||||
e.right = left.take();
|
||||
}
|
||||
|
||||
/// Rules:
|
||||
/// - `l > i` => `i < l`
|
||||
fn can_swap_bin_operands(&mut self, l: &Expr, r: &Expr, is_for_rel: bool) -> bool {
|
||||
match (l, r) {
|
||||
(Expr::Ident(l), Expr::Ident(r)) => {
|
||||
self.options.comparisons && (is_for_rel || l.sym > r.sym)
|
||||
(Expr::Member(_), _) if is_for_rel => false,
|
||||
|
||||
(Expr::Update(..) | Expr::Assign(..), Expr::Lit(..)) if is_for_rel => false,
|
||||
|
||||
(
|
||||
Expr::Member(..)
|
||||
| Expr::Call(..)
|
||||
| Expr::Assign(..)
|
||||
| Expr::Update(..)
|
||||
| Expr::Bin(BinExpr {
|
||||
op: op!("&&") | op!("||"),
|
||||
..
|
||||
}),
|
||||
Expr::Lit(..),
|
||||
) => true,
|
||||
|
||||
(
|
||||
Expr::Member(..) | Expr::Call(..) | Expr::Assign(..),
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}),
|
||||
) if match &**arg {
|
||||
Expr::Lit(..) => true,
|
||||
_ => false,
|
||||
} =>
|
||||
{
|
||||
true
|
||||
}
|
||||
|
||||
(Expr::Member(..) | Expr::Call(..) | Expr::Assign(..), r) if is_pure_undefined(r) => {
|
||||
true
|
||||
}
|
||||
|
||||
(Expr::Ident(..), Expr::Lit(..)) if is_for_rel => false,
|
||||
|
||||
(Expr::Ident(..), Expr::Ident(..)) => false,
|
||||
|
||||
(Expr::Ident(..), Expr::Lit(..))
|
||||
| (
|
||||
Expr::Ident(..),
|
||||
Expr::Ident(..) | Expr::Member(..),
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("void"), ..
|
||||
op: op!("void") | op!("!"),
|
||||
..
|
||||
}),
|
||||
)
|
||||
| (
|
||||
@ -479,7 +583,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
if self.can_swap_bin_operands(&left, &right, false) {
|
||||
log::trace!("Swapping operands of binary exprssion");
|
||||
log::debug!("Swapping operands of binary exprssion");
|
||||
swap(left, right);
|
||||
return true;
|
||||
}
|
||||
@ -494,7 +598,7 @@ impl Optimizer<'_> {
|
||||
| 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);
|
||||
log::debug!("comparisons: Swapping operands of {}", e.op);
|
||||
|
||||
e.op = if e.op == op!("<=") {
|
||||
op!(">=")
|
||||
@ -568,13 +672,13 @@ impl Optimizer<'_> {
|
||||
|
||||
if rb {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: e && true => !!e");
|
||||
log::debug!("Optimizing: e && true => !!e");
|
||||
|
||||
self.negate_twice(&mut bin.left);
|
||||
*e = *bin.left.take();
|
||||
} else {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: e && false => e");
|
||||
log::debug!("Optimizing: e && false => e");
|
||||
|
||||
*e = *bin.left.take();
|
||||
}
|
||||
@ -588,7 +692,7 @@ impl Optimizer<'_> {
|
||||
|
||||
if !rb {
|
||||
self.changed = true;
|
||||
log::trace!("Optimizing: e || false => !!e");
|
||||
log::debug!("Optimizing: e || false => !!e");
|
||||
|
||||
self.negate_twice(&mut bin.left);
|
||||
*e = *bin.left.take();
|
||||
@ -648,7 +752,7 @@ impl Optimizer<'_> {
|
||||
|
||||
match l {
|
||||
Expr::Lit(Lit::Null(..)) => {
|
||||
log::trace!("Removing null from lhs of ??");
|
||||
log::debug!("Removing null from lhs of ??");
|
||||
self.changed = true;
|
||||
*e = r.take();
|
||||
return;
|
||||
@ -658,7 +762,7 @@ impl Optimizer<'_> {
|
||||
| Expr::Lit(Lit::BigInt(..))
|
||||
| Expr::Lit(Lit::Bool(..))
|
||||
| Expr::Lit(Lit::Regex(..)) => {
|
||||
log::trace!("Removing rhs of ?? as lhs cannot be null nor undefined");
|
||||
log::debug!("Removing rhs of ?? as lhs cannot be null nor undefined");
|
||||
self.changed = true;
|
||||
*e = l.take();
|
||||
return;
|
||||
@ -682,7 +786,7 @@ impl Optimizer<'_> {
|
||||
}) => match &**arg {
|
||||
Expr::Ident(arg) => {
|
||||
if let Some(value) = self.typeofs.get(&arg.to_id()).cloned() {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"Converting typeof of variable to literal as we know the value"
|
||||
);
|
||||
self.changed = true;
|
||||
@ -697,7 +801,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
Expr::Arrow(..) | Expr::Fn(..) => {
|
||||
log::trace!("Converting typeof to 'function' as we know the value");
|
||||
log::debug!("Converting typeof to 'function' as we know the value");
|
||||
self.changed = true;
|
||||
*e = Expr::Lit(Lit::Str(Str {
|
||||
span: *span,
|
||||
@ -709,7 +813,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
Expr::Array(..) | Expr::Object(..) => {
|
||||
log::trace!("Converting typeof to 'object' as we know the value");
|
||||
log::debug!("Converting typeof to 'object' as we know the value");
|
||||
self.changed = true;
|
||||
*e = Expr::Lit(Lit::Str(Str {
|
||||
span: *span,
|
||||
|
@ -60,6 +60,10 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.ctx.is_callee {
|
||||
return;
|
||||
}
|
||||
|
||||
if obj.props.iter().any(|prop| match prop {
|
||||
PropOrSpread::Spread(_) => false,
|
||||
PropOrSpread::Prop(p) => match &**p {
|
||||
@ -87,7 +91,7 @@ impl Optimizer<'_> {
|
||||
Prop::Shorthand(_) => {}
|
||||
Prop::KeyValue(p) => {
|
||||
if prop_name_eq(&p.key, &key.sym) {
|
||||
log::trace!("properties: Inlining a key-value property `{}`", key.sym);
|
||||
log::debug!("properties: Inlining a key-value property `{}`", key.sym);
|
||||
self.changed = true;
|
||||
*e = *p.value.clone();
|
||||
return;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,7 @@ impl Optimizer<'_> {
|
||||
match &*args[0].expr {
|
||||
Expr::Lit(Lit::Str(..)) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"strings: Unsafely reduced `RegExp` call in a string context"
|
||||
);
|
||||
|
||||
@ -87,7 +87,7 @@ impl Optimizer<'_> {
|
||||
Expr::Lit(Lit::Str(..)) => {
|
||||
*n = *e.expr.take();
|
||||
self.changed = true;
|
||||
log::trace!("string: Removed a paren in a string context");
|
||||
log::debug!("string: Removed a paren in a string context");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -101,7 +101,7 @@ impl Optimizer<'_> {
|
||||
let value = n.as_string();
|
||||
if let Known(value) = value {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"strings: Converted an expression into a string literal (in string context)"
|
||||
);
|
||||
*n = Expr::Lit(Lit::Str(Str {
|
||||
@ -116,7 +116,7 @@ impl Optimizer<'_> {
|
||||
match n {
|
||||
Expr::Lit(Lit::Num(v)) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"strings: Converted a numeric literal ({}) into a string literal (in string \
|
||||
context)",
|
||||
v.value
|
||||
@ -136,7 +136,7 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"strings: Converted a regex (/{}/{}) into a string literal (in string context)",
|
||||
v.exp,
|
||||
v.flags
|
||||
@ -163,7 +163,7 @@ impl Optimizer<'_> {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"strings: Converting a reference ({}{:?}) into `undefined` (in string \
|
||||
context)",
|
||||
i.sym,
|
||||
@ -274,7 +274,7 @@ impl Optimizer<'_> {
|
||||
if let Some(l_last) = l.quasis.last_mut() {
|
||||
self.changed = true;
|
||||
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"template: Concatted a string (`{}`) on rhs of `+` to a template literal",
|
||||
rs.value
|
||||
);
|
||||
@ -294,7 +294,7 @@ impl Optimizer<'_> {
|
||||
if let Some(r_first) = r.quasis.first_mut() {
|
||||
self.changed = true;
|
||||
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"template: Prepended a string (`{}`) on lhs of `+` to a template literal",
|
||||
ls.value
|
||||
);
|
||||
@ -332,7 +332,7 @@ impl Optimizer<'_> {
|
||||
|
||||
debug_assert!(l.quasis.len() == l.exprs.len() + 1, "{:?} is invalid", l);
|
||||
self.changed = true;
|
||||
log::trace!("strings: Merged to template literals");
|
||||
log::debug!("strings: Merged to template literals");
|
||||
}
|
||||
|
||||
_ => {}
|
||||
@ -365,7 +365,7 @@ impl Optimizer<'_> {
|
||||
let left_span = left.span;
|
||||
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"string: Concatting `{} + {}` to `{}`",
|
||||
second_str,
|
||||
third_str,
|
||||
@ -413,7 +413,7 @@ impl Optimizer<'_> {
|
||||
..
|
||||
})) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"string: Dropping empty string literal (in lhs) because it \
|
||||
does not changes type"
|
||||
);
|
||||
@ -430,7 +430,7 @@ impl Optimizer<'_> {
|
||||
..
|
||||
})) => {
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"string: Dropping empty string literal (in rhs) because it \
|
||||
does not changes type"
|
||||
);
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::mem::take;
|
||||
|
||||
use super::Optimizer;
|
||||
use crate::util::ExprOptExt;
|
||||
use swc_common::EqIgnoreSpan;
|
||||
@ -5,7 +7,9 @@ use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::prepend;
|
||||
use swc_ecma_utils::ExprExt;
|
||||
use swc_ecma_utils::StmtExt;
|
||||
use swc_ecma_utils::Type;
|
||||
use swc_ecma_utils::Value::Known;
|
||||
use swc_ecma_visit::noop_visit_type;
|
||||
@ -45,6 +49,7 @@ impl Optimizer<'_> {
|
||||
});
|
||||
|
||||
if let Some(case_idx) = matching_case {
|
||||
let mut var_ids = vec![];
|
||||
let mut stmts = vec![];
|
||||
|
||||
let should_preserve_switch = stmt.cases.iter().skip(case_idx).any(|case| {
|
||||
@ -60,9 +65,9 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
log::trace!("switches: Removing unreachable cases from a constant switch");
|
||||
log::debug!("switches: Removing unreachable cases from a constant switch");
|
||||
} else {
|
||||
log::trace!("switches: Removing a constant switch");
|
||||
log::debug!("switches: Removing a constant switch");
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
@ -81,6 +86,21 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
for case in &stmt.cases[..case_idx] {
|
||||
for cons in &case.cons {
|
||||
var_ids.extend(
|
||||
cons.extract_var_ids()
|
||||
.into_iter()
|
||||
.map(|name| VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: Pat::Ident(name.into()),
|
||||
init: None,
|
||||
definite: Default::default(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for case in stmt.cases.iter_mut().skip(case_idx) {
|
||||
let mut found_break = false;
|
||||
case.cons.retain(|stmt| match stmt {
|
||||
@ -110,6 +130,18 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if !var_ids.is_empty() {
|
||||
prepend(
|
||||
&mut stmts,
|
||||
Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Var,
|
||||
declare: Default::default(),
|
||||
decls: take(&mut var_ids),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
let inner = if should_preserve_switch {
|
||||
let mut cases = stmt.cases.take();
|
||||
let case = SwitchCase {
|
||||
@ -193,7 +225,7 @@ impl Optimizer<'_> {
|
||||
if !preserve_cases {
|
||||
if let Some(last_non_empty) = last_non_empty {
|
||||
if last_non_empty + 1 != cases.len() {
|
||||
log::trace!("switches: Removing empty cases at the end");
|
||||
log::debug!("switches: Removing empty cases at the end");
|
||||
self.changed = true;
|
||||
cases.drain(last_non_empty + 1..);
|
||||
}
|
||||
@ -203,7 +235,7 @@ impl Optimizer<'_> {
|
||||
if let Some(last) = cases.last_mut() {
|
||||
match last.cons.last() {
|
||||
Some(Stmt::Break(BreakStmt { label: None, .. })) => {
|
||||
log::trace!("switches: Removing `break` at the end");
|
||||
log::debug!("switches: Removing `break` at the end");
|
||||
self.changed = true;
|
||||
last.cons.pop();
|
||||
}
|
||||
@ -215,12 +247,23 @@ impl Optimizer<'_> {
|
||||
/// If a case ends with break but content is same with the consequtive case
|
||||
/// except the break statement, we merge them.
|
||||
fn merge_cases_with_same_cons(&mut self, cases: &mut Vec<SwitchCase>) {
|
||||
let stop_pos = cases.iter().position(|case| match case.test.as_deref() {
|
||||
Some(Expr::Update(..)) => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let mut found = None;
|
||||
'l: for (li, l) in cases.iter().enumerate().rev() {
|
||||
if l.cons.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(stop_pos) = stop_pos {
|
||||
if li > stop_pos {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(l_last) = l.cons.last() {
|
||||
match l_last {
|
||||
Stmt::Break(BreakStmt { label: None, .. }) => {}
|
||||
@ -253,7 +296,7 @@ impl Optimizer<'_> {
|
||||
|
||||
if let Some(idx) = found {
|
||||
self.changed = true;
|
||||
log::trace!("switches: Merging cases with same cons");
|
||||
log::debug!("switches: Merging cases with same cons");
|
||||
cases[idx].cons.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,77 @@
|
||||
use crate::compress::optimize::util::class_has_side_effect;
|
||||
use crate::option::PureGetterOption;
|
||||
|
||||
use super::Optimizer;
|
||||
use crate::compress::optimize::util::class_has_side_effect;
|
||||
use crate::debug::dump;
|
||||
use crate::option::PureGetterOption;
|
||||
use crate::util::has_mark;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::Span;
|
||||
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_utils::StmtLike;
|
||||
use swc_ecma_visit::noop_visit_mut_type;
|
||||
use swc_ecma_visit::VisitMut;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
|
||||
/// Methods related to the option `unused`.
|
||||
impl Optimizer<'_> {
|
||||
pub(super) fn drop_useless_blocks<T>(&mut self, stmts: &mut Vec<T>)
|
||||
where
|
||||
T: StmtLike,
|
||||
{
|
||||
fn is_inliable(b: &BlockStmt) -> bool {
|
||||
b.stmts.iter().all(|s| match s {
|
||||
Stmt::Decl(Decl::Fn(FnDecl {
|
||||
ident:
|
||||
Ident {
|
||||
sym: js_word!("undefined"),
|
||||
..
|
||||
},
|
||||
..
|
||||
})) => false,
|
||||
|
||||
Stmt::Decl(
|
||||
Decl::Var(VarDecl {
|
||||
kind: VarDeclKind::Var,
|
||||
..
|
||||
})
|
||||
| Decl::Fn(..),
|
||||
) => true,
|
||||
Stmt::Decl(..) => false,
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
|
||||
if stmts.iter().all(|stmt| match stmt.as_stmt() {
|
||||
Some(Stmt::Block(b)) if is_inliable(b) => false,
|
||||
_ => true,
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::debug!("Dropping useless block");
|
||||
|
||||
let mut new = vec![];
|
||||
for stmt in stmts.take() {
|
||||
match stmt.try_into_stmt() {
|
||||
Ok(v) => match v {
|
||||
Stmt::Block(v) if is_inliable(&v) => {
|
||||
new.extend(v.stmts.into_iter().map(T::from_stmt));
|
||||
}
|
||||
_ => new.push(T::from_stmt(v)),
|
||||
},
|
||||
Err(v) => {
|
||||
new.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*stmts = new;
|
||||
}
|
||||
|
||||
pub(super) fn drop_unused_stmt_at_end_of_fn(&mut self, s: &mut Stmt) {
|
||||
match s {
|
||||
Stmt::Return(r) => match r.arg.as_deref_mut() {
|
||||
@ -22,7 +80,7 @@ impl Optimizer<'_> {
|
||||
op: op!("void"),
|
||||
arg,
|
||||
})) => {
|
||||
log::trace!("unused: Removing `return void` in end of a function");
|
||||
log::debug!("unused: Removing `return void` in end of a function");
|
||||
self.changed = true;
|
||||
*s = Stmt::Expr(ExprStmt {
|
||||
span: *span,
|
||||
@ -48,7 +106,7 @@ impl Optimizer<'_> {
|
||||
..
|
||||
}) => {}
|
||||
_ => {
|
||||
self.drop_unused_vars(&mut var.name, Some(init));
|
||||
self.drop_unused_vars(var.span, &mut var.name, Some(init));
|
||||
|
||||
if var.name.is_invalid() {
|
||||
let side_effects = self.ignore_return_value(init);
|
||||
@ -62,7 +120,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.drop_unused_vars(&mut var.name, var.init.as_deref_mut());
|
||||
self.drop_unused_vars(var.span, &mut var.name, var.init.as_deref_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,30 +145,53 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
self.take_pat_if_unused(pat, None)
|
||||
self.take_pat_if_unused(DUMMY_SP, pat, None)
|
||||
}
|
||||
|
||||
pub(super) fn drop_unused_vars(&mut self, name: &mut Pat, init: Option<&mut Expr>) {
|
||||
if !self.options.unused || self.ctx.in_var_decl_of_for_in_or_of_loop || self.ctx.is_exported
|
||||
{
|
||||
return;
|
||||
pub(super) fn drop_unused_vars(
|
||||
&mut self,
|
||||
var_declarator_span: Span,
|
||||
name: &mut Pat,
|
||||
init: Option<&mut Expr>,
|
||||
) {
|
||||
let has_mark = has_mark(var_declarator_span, self.marks.non_top_level);
|
||||
|
||||
if !has_mark {
|
||||
if !self.options.unused
|
||||
|| self.ctx.in_var_decl_of_for_in_or_of_loop
|
||||
|| self.ctx.is_exported
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("unused: drop_unused_vars({})", dump(&*name));
|
||||
}
|
||||
|
||||
// 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;
|
||||
if !has_mark {
|
||||
match self.ctx.var_kind {
|
||||
Some(VarDeclKind::Var) => {
|
||||
if (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
&& self.ctx.in_top_level()
|
||||
{
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"unused: Preserving `var` `{}` because it's top-level",
|
||||
dump(&*name)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VarDeclKind::Let) | Some(VarDeclKind::Const) => {
|
||||
if !self.options.top_level() && self.ctx.is_top_level_for_block_level_vars() {
|
||||
return;
|
||||
Some(VarDeclKind::Let) | Some(VarDeclKind::Const) => {
|
||||
if !self.options.top_level() && self.ctx.is_top_level_for_block_level_vars() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
if let Some(scope) = self
|
||||
@ -127,12 +208,12 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
self.take_pat_if_unused(name, init);
|
||||
self.take_pat_if_unused(var_declarator_span, 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);
|
||||
self.take_pat_if_unused(DUMMY_SP, &mut param.pat, None);
|
||||
|
||||
if !param.pat.is_invalid() {
|
||||
return;
|
||||
@ -140,7 +221,13 @@ impl Optimizer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn take_pat_if_unused(&mut self, name: &mut Pat, mut init: Option<&mut Expr>) {
|
||||
/// `parent_span` should be [Span] of [VarDeclarator] or [AssignExpr]
|
||||
pub(super) fn take_pat_if_unused(
|
||||
&mut self,
|
||||
parent_span: Span,
|
||||
name: &mut Pat,
|
||||
mut init: Option<&mut Expr>,
|
||||
) {
|
||||
let had_value = init.is_some();
|
||||
let can_drop_children = had_value;
|
||||
|
||||
@ -153,7 +240,9 @@ impl Optimizer<'_> {
|
||||
|
||||
match name {
|
||||
Pat::Ident(i) => {
|
||||
if self.options.top_retain.contains(&i.id.sym) {
|
||||
if !has_mark(parent_span, self.marks.non_top_level)
|
||||
&& self.options.top_retain.contains(&i.id.sym)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@ -165,7 +254,7 @@ impl Optimizer<'_> {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"unused: Dropping a variable '{}{:?}' because it is not used",
|
||||
i.id.sym,
|
||||
i.id.span.ctxt
|
||||
@ -173,6 +262,10 @@ impl Optimizer<'_> {
|
||||
// This will remove variable.
|
||||
name.take();
|
||||
return;
|
||||
} else {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!("unused: Cannot drop ({}) becaue it's used", dump(&*i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +281,7 @@ impl Optimizer<'_> {
|
||||
.as_mut()
|
||||
.and_then(|expr| self.access_numeric_property(expr, idx));
|
||||
|
||||
self.take_pat_if_unused(p, elem);
|
||||
self.take_pat_if_unused(parent_span, p, elem);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
@ -224,7 +317,7 @@ impl Optimizer<'_> {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.take_pat_if_unused(&mut p.value, prop);
|
||||
self.take_pat_if_unused(parent_span, &mut p.value, prop);
|
||||
}
|
||||
ObjectPatProp::Assign(_) => {}
|
||||
ObjectPatProp::Rest(_) => {}
|
||||
@ -303,7 +396,7 @@ impl Optimizer<'_> {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"unused: Dropping a decl '{}{:?}' because it is not used",
|
||||
ident.sym,
|
||||
ident.span.ctxt
|
||||
@ -327,7 +420,14 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
pub(super) fn drop_unused_assignments(&mut self, e: &mut Expr) {
|
||||
if !self.options.unused {
|
||||
let assign = match e {
|
||||
Expr::Assign(e) => e,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let has_mark = has_mark(assign.span, self.marks.non_top_level);
|
||||
|
||||
if !has_mark && !self.options.unused {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -344,19 +444,36 @@ impl Optimizer<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"unused: drop_unused_assignments: Target: `{}`",
|
||||
dump(&assign.left)
|
||||
)
|
||||
}
|
||||
|
||||
if !has_mark
|
||||
&& (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||
&& self.ctx.in_top_level()
|
||||
{
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"unused: Preserving assignment to `{}` because it's top-level",
|
||||
dump(&assign.left)
|
||||
)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let assign = match e {
|
||||
Expr::Assign(e) => e,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match &mut assign.left {
|
||||
PatOrExpr::Expr(_) => return,
|
||||
PatOrExpr::Expr(_) => {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"unused: Preserving assignment to `{}` because it's an expression",
|
||||
dump(&assign.left)
|
||||
)
|
||||
}
|
||||
return;
|
||||
}
|
||||
PatOrExpr::Pat(left) => match &**left {
|
||||
Pat::Ident(i) => {
|
||||
if self.options.top_retain.contains(&i.id.sym) {
|
||||
@ -369,7 +486,7 @@ impl Optimizer<'_> {
|
||||
.and_then(|data| data.vars.get(&i.to_id()))
|
||||
{
|
||||
if var.is_fn_local && var.usage_count == 0 {
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"unused: Dropping assignment to var '{}{:?}', which is never used",
|
||||
i.id.sym,
|
||||
i.id.span.ctxt
|
||||
@ -377,6 +494,14 @@ impl Optimizer<'_> {
|
||||
self.changed = true;
|
||||
*e = *assign.right.take();
|
||||
return;
|
||||
} else {
|
||||
if cfg!(feature = "debug") {
|
||||
log::trace!(
|
||||
"unused: Preserving assignment to `{}` because of usage: {:?}",
|
||||
dump(&assign.left),
|
||||
var
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,7 +530,7 @@ impl Optimizer<'_> {
|
||||
|
||||
if can_remove_ident {
|
||||
self.changed = true;
|
||||
log::trace!("Removing ident of an class / function expression");
|
||||
log::debug!("Removing ident of an class / function expression");
|
||||
*name = None;
|
||||
}
|
||||
}
|
||||
@ -429,7 +554,7 @@ impl Optimizer<'_> {
|
||||
}
|
||||
|
||||
self.changed = true;
|
||||
log::trace!(
|
||||
log::debug!(
|
||||
"unused: Removing the name of a function expression because it's not used by \
|
||||
it'"
|
||||
);
|
||||
|
@ -12,6 +12,12 @@ use swc_ecma_utils::prop_name_eq;
|
||||
use swc_ecma_utils::ExprExt;
|
||||
|
||||
impl<'b> Optimizer<'b> {
|
||||
pub(super) fn line_col(&self, span: Span) -> String {
|
||||
let loc = self.cm.lookup_char_pos(span.lo);
|
||||
|
||||
format!("{}:{}", loc.line, loc.col_display)
|
||||
}
|
||||
|
||||
pub(super) fn access_property<'e>(
|
||||
&mut self,
|
||||
expr: &'e mut Expr,
|
||||
@ -224,6 +230,40 @@ pub(crate) fn class_has_side_effect(c: &Class) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn get_lhs_ident(e: &PatOrExpr) -> Option<&Ident> {
|
||||
match e {
|
||||
PatOrExpr::Expr(v) => match &**v {
|
||||
Expr::Ident(i) => Some(i),
|
||||
_ => None,
|
||||
},
|
||||
PatOrExpr::Pat(v) => match &**v {
|
||||
Pat::Ident(i) => Some(&i.id),
|
||||
Pat::Expr(v) => match &**v {
|
||||
Expr::Ident(i) => Some(i),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_lhs_ident_mut(e: &mut PatOrExpr) -> Option<&mut Ident> {
|
||||
match e {
|
||||
PatOrExpr::Expr(v) => match &mut **v {
|
||||
Expr::Ident(i) => Some(i),
|
||||
_ => None,
|
||||
},
|
||||
PatOrExpr::Pat(v) => match &mut **v {
|
||||
Pat::Ident(i) => Some(&mut i.id),
|
||||
Pat::Expr(v) => match &mut **v {
|
||||
Expr::Ident(i) => Some(i),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {
|
||||
match e {
|
||||
Expr::Lit(..) => return false,
|
||||
|
@ -41,6 +41,10 @@ pub(crate) fn dump<N>(node: &N) -> String
|
||||
where
|
||||
N: swc_ecma_codegen::Node + Clone + VisitMutWith<DropSpan> + VisitMutWith<Debugger>,
|
||||
{
|
||||
if !cfg!(feature = "debug") {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut node = node.clone();
|
||||
node.visit_mut_with(&mut Debugger);
|
||||
node = drop_span(node);
|
||||
@ -109,5 +113,5 @@ pub(crate) fn invoke(module: &Module) {
|
||||
);
|
||||
}
|
||||
|
||||
log::debug!("[SWC_RUN]\n{}", String::from_utf8_lossy(&output.stdout))
|
||||
log::info!("[SWC_RUN]\n{}", String::from_utf8_lossy(&output.stdout))
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
use crate::compress::compressor;
|
||||
use crate::hygiene::unique_marker;
|
||||
use crate::marks::Marks;
|
||||
use crate::option::ExtraOptions;
|
||||
use crate::option::MinifyOptions;
|
||||
use crate::pass::compute_char_freq::compute_char_freq;
|
||||
@ -23,9 +24,13 @@ 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::single::single_pass_optimizer;
|
||||
use crate::pass::precompress::precompress_optimizer;
|
||||
use analyzer::analyze;
|
||||
use pass::postcompress::postcompress_optimizer;
|
||||
use std::time::Instant;
|
||||
use swc_common::comments::Comments;
|
||||
use swc_common::sync::Lrc;
|
||||
use swc_common::SourceMap;
|
||||
use swc_ecma_ast::Module;
|
||||
use swc_ecma_visit::FoldWith;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
@ -35,6 +40,7 @@ mod analyzer;
|
||||
mod compress;
|
||||
mod debug;
|
||||
mod hygiene;
|
||||
mod marks;
|
||||
pub mod option;
|
||||
mod pass;
|
||||
pub mod timing;
|
||||
@ -43,11 +49,15 @@ mod util;
|
||||
#[inline]
|
||||
pub fn optimize(
|
||||
mut m: Module,
|
||||
cm: Lrc<SourceMap>,
|
||||
comments: Option<&dyn Comments>,
|
||||
mut timings: Option<&mut Timings>,
|
||||
options: &MinifyOptions,
|
||||
extra: &ExtraOptions,
|
||||
) -> Module {
|
||||
let marks = Marks::new();
|
||||
|
||||
let start = Instant::now();
|
||||
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
|
||||
// Apply global defs.
|
||||
//
|
||||
@ -60,10 +70,13 @@ pub fn optimize(
|
||||
m.visit_mut_with(&mut global_defs::globals_defs(defs, extra.top_level_mark));
|
||||
}
|
||||
}
|
||||
log::info!("global_defs took {:?}", Instant::now() - start);
|
||||
|
||||
m.visit_mut_with(&mut single_pass_optimizer(
|
||||
options.compress.clone().unwrap_or_default(),
|
||||
));
|
||||
if let Some(options) = &options.compress {
|
||||
let start = Instant::now();
|
||||
m.visit_mut_with(&mut precompress_optimizer(options.clone()));
|
||||
log::info!("precompress took {:?}", Instant::now() - start);
|
||||
}
|
||||
|
||||
m.visit_mut_with(&mut unique_marker());
|
||||
|
||||
@ -95,8 +108,14 @@ pub fn optimize(
|
||||
t.section("compress");
|
||||
}
|
||||
if let Some(options) = &options.compress {
|
||||
m = m.fold_with(&mut compressor(&options, comments));
|
||||
let start = Instant::now();
|
||||
m = m.fold_with(&mut compressor(cm.clone(), marks, &options, comments));
|
||||
log::info!("compressor took {:?}", Instant::now() - start);
|
||||
// Again, we don't need to validate ast
|
||||
|
||||
let start = Instant::now();
|
||||
m.visit_mut_with(&mut postcompress_optimizer(options));
|
||||
log::info!("postcompressor took {:?}", Instant::now() - start);
|
||||
}
|
||||
|
||||
if let Some(ref mut _t) = timings {
|
||||
|
18
ecmascript/minifier/src/marks.rs
Normal file
18
ecmascript/minifier/src/marks.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use swc_common::Mark;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Marks {
|
||||
/// [Mark] applied to non-top level varaibles which is injected while
|
||||
/// inlining.
|
||||
pub(crate) non_top_level: Mark,
|
||||
}
|
||||
|
||||
impl Marks {
|
||||
pub fn new() -> Marks {
|
||||
fn m() -> Mark {
|
||||
Mark::fresh(Mark::root())
|
||||
}
|
||||
|
||||
Marks { non_top_level: m() }
|
||||
}
|
||||
}
|
@ -55,6 +55,9 @@ pub struct MangleOptions {
|
||||
#[serde(default, alias = "keep_fnames")]
|
||||
pub keep_fn_names: bool,
|
||||
|
||||
#[serde(default, alias = "keep_private_props")]
|
||||
pub keep_private_props: bool,
|
||||
|
||||
#[serde(default, alias = "ie8")]
|
||||
pub ie8: bool,
|
||||
|
||||
@ -171,6 +174,7 @@ pub struct CompressOptions {
|
||||
#[serde(alias = "hoist_vars")]
|
||||
pub hoist_vars: bool,
|
||||
|
||||
/// No effect.
|
||||
#[serde(default)]
|
||||
#[serde(alias = "ie8")]
|
||||
pub ie8: bool,
|
||||
|
@ -40,6 +40,21 @@ impl HygieneAnalyzer<'_> {
|
||||
|
||||
let ret = op(self);
|
||||
|
||||
let mut ids = vec![];
|
||||
{
|
||||
let scope = self.scope();
|
||||
for (sym, ctxts) in &scope.declared_symbols {
|
||||
if ctxts.len() == 1 {
|
||||
let id = (sym.clone(), *ctxts.iter().next().unwrap());
|
||||
ids.push(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in ids {
|
||||
self.hygiene.preserved.remove(&id);
|
||||
self.hygiene.modified.insert(id);
|
||||
}
|
||||
|
||||
self.cur_scope = old;
|
||||
|
||||
ret
|
||||
|
@ -69,7 +69,7 @@ impl VisitMut for Optimizer {
|
||||
}
|
||||
|
||||
fn visit_mut_module(&mut self, n: &mut Module) {
|
||||
log::debug!("hygiene: Analyzing span hygiene");
|
||||
log::info!("hygiene: Analyzing span hygiene");
|
||||
let start = Instant::now();
|
||||
|
||||
let mut analyzer = HygieneAnalyzer {
|
||||
@ -82,12 +82,12 @@ impl VisitMut for Optimizer {
|
||||
self.hygiene = analyzer.hygiene;
|
||||
|
||||
let end = Instant::now();
|
||||
log::debug!("hygiene: Span hygiene analysis took {:?}", end - start);
|
||||
log::info!("hygiene: Span hygiene analysis took {:?}", end - start);
|
||||
let start = end;
|
||||
|
||||
log::debug!("hygiene: Optimizing span hygiene");
|
||||
log::info!("hygiene: Optimizing span hygiene");
|
||||
n.visit_mut_children_with(self);
|
||||
let end = Instant::now();
|
||||
log::debug!("hygiene: Span hygiene optimiation took {:?}", end - start);
|
||||
log::info!("hygiene: Span hygiene optimiation took {:?}", end - start);
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,12 @@ pub fn name_mangler(options: MangleOptions, _char_freq_info: CharFreqInfo) -> im
|
||||
struct Mangler {
|
||||
options: MangleOptions,
|
||||
n: usize,
|
||||
private_n: usize,
|
||||
|
||||
preserved: FxHashSet<Id>,
|
||||
preserved_symbols: FxHashSet<JsWord>,
|
||||
renamed: FxHashMap<Id, JsWord>,
|
||||
renamed_private: FxHashMap<Id, JsWord>,
|
||||
data: Option<ProgramData>,
|
||||
}
|
||||
|
||||
@ -65,6 +68,23 @@ impl Mangler {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn rename_private(&mut self, private_name: &mut PrivateName) {
|
||||
let id = private_name.id.to_id();
|
||||
|
||||
let new_sym = if let Some(cached) = self.renamed_private.get(&id) {
|
||||
cached.clone()
|
||||
} else {
|
||||
let sym: JsWord = base54(self.private_n).into();
|
||||
self.private_n += 1;
|
||||
|
||||
self.renamed_private.insert(id.clone(), sym.clone());
|
||||
|
||||
sym
|
||||
};
|
||||
|
||||
private_name.id.sym = new_sym;
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitMut for Mangler {
|
||||
@ -95,6 +115,12 @@ impl VisitMut for Mangler {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_private_name(&mut self, private_name: &mut PrivateName) {
|
||||
if !self.options.keep_private_props {
|
||||
self.rename_private(private_name);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
|
||||
self.rename(&mut n.ident);
|
||||
n.function.visit_mut_with(self);
|
||||
|
@ -6,8 +6,8 @@ use once_cell::sync::Lazy;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use swc_atoms::JsWord;
|
||||
use swc_ecma_ast::{
|
||||
CallExpr, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, Module, PrivateName, Prop,
|
||||
PropName, Str, StrKind,
|
||||
CallExpr, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, Module, Prop, PropName, Str,
|
||||
StrKind,
|
||||
};
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::{VisitMut, VisitMutWith};
|
||||
@ -37,11 +37,9 @@ struct ManglePropertiesState {
|
||||
|
||||
// Cache of already mangled names
|
||||
cache: HashMap<JsWord, JsWord>,
|
||||
private_cache: HashMap<JsWord, JsWord>,
|
||||
|
||||
// Numbers to pass to base54()
|
||||
n: usize,
|
||||
private_n: usize,
|
||||
}
|
||||
|
||||
impl ManglePropertiesState {
|
||||
@ -103,23 +101,6 @@ impl ManglePropertiesState {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_private_name(&mut self, name: &JsWord) -> JsWord {
|
||||
// Always mangleable
|
||||
if let Some(cached) = self.private_cache.get(&name) {
|
||||
cached.clone()
|
||||
} else {
|
||||
let private_n = self.private_n;
|
||||
self.private_n += 1;
|
||||
|
||||
let mangled_name: JsWord = base54(private_n).into();
|
||||
|
||||
self.private_cache
|
||||
.insert(name.clone(), mangled_name.clone());
|
||||
|
||||
mangled_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mangle_properties<'a>(m: &mut Module, options: ManglePropertiesOptions) {
|
||||
@ -309,8 +290,4 @@ impl VisitMut for Mangler<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_private_name(&mut self, private_name: &mut PrivateName) {
|
||||
private_name.id.sym = self.state.gen_private_name(&private_name.id.sym);
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ pub mod global_defs;
|
||||
pub mod hygiene;
|
||||
pub mod mangle_names;
|
||||
pub mod mangle_props;
|
||||
pub mod single;
|
||||
pub mod postcompress;
|
||||
pub mod precompress;
|
||||
|
76
ecmascript/minifier/src/pass/postcompress.rs
Normal file
76
ecmascript/minifier/src/pass/postcompress.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use crate::option::CompressOptions;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
|
||||
|
||||
pub fn postcompress_optimizer<'a>(options: &'a CompressOptions) -> impl 'a + VisitMut {
|
||||
PostcompressOptimizer { options }
|
||||
}
|
||||
|
||||
struct PostcompressOptimizer<'a> {
|
||||
options: &'a CompressOptions,
|
||||
}
|
||||
|
||||
impl PostcompressOptimizer<'_> {
|
||||
fn optimize_in_bool_ctx(&mut self, e: &mut Expr) {
|
||||
if !self.options.bools {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: `||` is not handled because of precedence.
|
||||
match e {
|
||||
Expr::Bin(BinExpr {
|
||||
op: op @ op!("&&"),
|
||||
right,
|
||||
left,
|
||||
..
|
||||
}) => {
|
||||
match &**left {
|
||||
Expr::Bin(BinExpr { op: op!("&&"), .. }) => return,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match &mut **right {
|
||||
Expr::Unary(UnaryExpr {
|
||||
op: op!("!"), arg, ..
|
||||
}) if arg.is_ident() => {
|
||||
let new_op = if *op == op!("&&") {
|
||||
op!("||")
|
||||
} else {
|
||||
op!("&&")
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"bools: `(a {} !b)` => `(a {} b)` (in bool context)",
|
||||
*op,
|
||||
new_op
|
||||
);
|
||||
*op = new_op;
|
||||
*right = arg.take();
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitMut for PostcompressOptimizer<'_> {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_cond_expr(&mut self, e: &mut CondExpr) {
|
||||
e.visit_mut_children_with(self);
|
||||
|
||||
self.optimize_in_bool_ctx(&mut *e.test);
|
||||
}
|
||||
|
||||
fn visit_mut_if_stmt(&mut self, s: &mut IfStmt) {
|
||||
s.visit_mut_children_with(self);
|
||||
|
||||
self.optimize_in_bool_ctx(&mut *s.test);
|
||||
}
|
||||
}
|
@ -9,10 +9,11 @@ use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::{ident::IdentLike, Id};
|
||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
|
||||
|
||||
/// Optimizer invoked before invoking compressor.
|
||||
///
|
||||
/// - Remove parens.
|
||||
pub fn single_pass_optimizer(options: CompressOptions) -> impl VisitMut {
|
||||
SinglePassOptimizer {
|
||||
pub fn precompress_optimizer(options: CompressOptions) -> impl VisitMut {
|
||||
PrecompressOptimizer {
|
||||
options,
|
||||
data: Default::default(),
|
||||
fn_decl_count: Default::default(),
|
||||
@ -21,7 +22,7 @@ pub fn single_pass_optimizer(options: CompressOptions) -> impl VisitMut {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct SinglePassOptimizer {
|
||||
struct PrecompressOptimizer {
|
||||
options: CompressOptions,
|
||||
data: ProgramData,
|
||||
fn_decl_count: FxHashMap<Id, usize>,
|
||||
@ -33,7 +34,7 @@ struct Ctx {
|
||||
in_var_pat: bool,
|
||||
}
|
||||
|
||||
impl VisitMut for SinglePassOptimizer {
|
||||
impl VisitMut for PrecompressOptimizer {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_decl(&mut self, n: &mut Decl) {
|
@ -9,6 +9,7 @@ use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_utils::Id;
|
||||
use swc_ecma_utils::ModuleItemLike;
|
||||
use swc_ecma_utils::StmtLike;
|
||||
use swc_ecma_utils::Value;
|
||||
use swc_ecma_visit::noop_visit_type;
|
||||
@ -26,6 +27,55 @@ pub(crate) fn make_number(span: Span, value: f64) -> Expr {
|
||||
Expr::Lit(Lit::Num(Number { span, value }))
|
||||
}
|
||||
|
||||
pub trait MoudleItemExt: StmtLike + ModuleItemLike {
|
||||
fn as_module_decl(&self) -> Result<&ModuleDecl, &Stmt>;
|
||||
|
||||
fn from_module_item(item: ModuleItem) -> Self;
|
||||
|
||||
fn into_module_item(self) -> ModuleItem {
|
||||
match self.into_module_decl() {
|
||||
Ok(v) => ModuleItem::ModuleDecl(v),
|
||||
Err(v) => ModuleItem::Stmt(v),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_module_decl(self) -> Result<ModuleDecl, Stmt>;
|
||||
}
|
||||
|
||||
impl MoudleItemExt for Stmt {
|
||||
fn as_module_decl(&self) -> Result<&ModuleDecl, &Stmt> {
|
||||
Err(self)
|
||||
}
|
||||
|
||||
fn from_module_item(item: ModuleItem) -> Self {
|
||||
item.expect_stmt()
|
||||
}
|
||||
|
||||
fn into_module_decl(self) -> Result<ModuleDecl, Stmt> {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl MoudleItemExt for ModuleItem {
|
||||
fn as_module_decl(&self) -> Result<&ModuleDecl, &Stmt> {
|
||||
match self {
|
||||
ModuleItem::ModuleDecl(v) => Ok(v),
|
||||
ModuleItem::Stmt(v) => Err(v),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_module_item(item: ModuleItem) -> Self {
|
||||
item
|
||||
}
|
||||
|
||||
fn into_module_decl(self) -> Result<ModuleDecl, Stmt> {
|
||||
match self {
|
||||
ModuleItem::ModuleDecl(v) => Ok(v),
|
||||
ModuleItem::Stmt(v) => Err(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// - `!0` for true
|
||||
/// - `!1` for false
|
||||
@ -45,6 +95,18 @@ pub(crate) trait ExprOptExt: Sized {
|
||||
fn as_expr(&self) -> &Expr;
|
||||
fn as_mut(&mut self) -> &mut Expr;
|
||||
|
||||
fn first_expr_mut(&mut self) -> &mut Expr {
|
||||
let expr = self.as_mut();
|
||||
match expr {
|
||||
Expr::Seq(seq) => seq
|
||||
.exprs
|
||||
.first_mut()
|
||||
.expect("Sequence expressions should have at least one element")
|
||||
.first_expr_mut(),
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns itself for normal expressions and returns last exprssions
|
||||
/// for sequence expressions.
|
||||
fn value_mut(&mut self) -> &mut Expr {
|
||||
@ -279,11 +341,36 @@ where
|
||||
#[derive(Default)]
|
||||
pub(crate) struct IdentUsageCollector {
|
||||
ids: FxHashSet<Id>,
|
||||
ignore_nested: bool,
|
||||
}
|
||||
|
||||
impl Visit for IdentUsageCollector {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_block_stmt_or_expr(&mut self, n: &BlockStmtOrExpr, _: &dyn Node) {
|
||||
if self.ignore_nested {
|
||||
return;
|
||||
}
|
||||
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_constructor(&mut self, n: &Constructor, _: &dyn Node) {
|
||||
if self.ignore_nested {
|
||||
return;
|
||||
}
|
||||
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_function(&mut self, n: &Function, _: &dyn Node) {
|
||||
if self.ignore_nested {
|
||||
return;
|
||||
}
|
||||
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_ident(&mut self, n: &Ident, _: &dyn Node) {
|
||||
self.ids.insert(n.to_id());
|
||||
}
|
||||
@ -295,13 +382,37 @@ impl Visit for IdentUsageCollector {
|
||||
n.prop.visit_with(n, self);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_prop_name(&mut self, n: &PropName, _: &dyn Node) {
|
||||
match n {
|
||||
PropName::Computed(..) => {
|
||||
n.visit_children_with(self);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn idents_used_by<N>(n: &N) -> FxHashSet<Id>
|
||||
where
|
||||
N: VisitWith<IdentUsageCollector>,
|
||||
{
|
||||
let mut v = IdentUsageCollector::default();
|
||||
let mut v = IdentUsageCollector {
|
||||
ignore_nested: false,
|
||||
..Default::default()
|
||||
};
|
||||
n.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||
v.ids
|
||||
}
|
||||
|
||||
pub(crate) fn idents_used_by_ignoring_nested<N>(n: &N) -> FxHashSet<Id>
|
||||
where
|
||||
N: VisitWith<IdentUsageCollector>,
|
||||
{
|
||||
let mut v = IdentUsageCollector {
|
||||
ignore_nested: true,
|
||||
..Default::default()
|
||||
};
|
||||
n.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||
v.ids
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
extern crate swc_node_base;
|
||||
|
||||
use ansi_term::Color;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
@ -11,6 +13,7 @@ use std::panic::catch_unwind;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
use swc_common::comments::SingleThreadedComments;
|
||||
use swc_common::errors::Handler;
|
||||
use swc_common::sync::Lrc;
|
||||
@ -132,6 +135,8 @@ fn run(
|
||||
|
||||
let top_level_mark = Mark::fresh(Mark::root());
|
||||
|
||||
let minification_start = Instant::now();
|
||||
|
||||
let lexer = Lexer::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
@ -155,8 +160,10 @@ fn run(
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let optimization_start = Instant::now();
|
||||
let output = optimize(
|
||||
program,
|
||||
cm.clone(),
|
||||
Some(&comments),
|
||||
None,
|
||||
&MinifyOptions {
|
||||
@ -174,9 +181,22 @@ fn run(
|
||||
..Default::default()
|
||||
},
|
||||
&ExtraOptions { top_level_mark },
|
||||
)
|
||||
.fold_with(&mut hygiene())
|
||||
.fold_with(&mut fixer(None));
|
||||
);
|
||||
let end = Instant::now();
|
||||
log::info!(
|
||||
"optimize({}) took {:?}",
|
||||
input.display(),
|
||||
end - optimization_start
|
||||
);
|
||||
|
||||
let output = output.fold_with(&mut hygiene()).fold_with(&mut fixer(None));
|
||||
|
||||
let end = Instant::now();
|
||||
log::info!(
|
||||
"process({}) took {:?}",
|
||||
input.display(),
|
||||
end - minification_start
|
||||
);
|
||||
|
||||
Some(output)
|
||||
}
|
||||
@ -199,8 +219,52 @@ fn stdout_of(code: &str) -> Result<String, Error> {
|
||||
Ok(String::from_utf8_lossy(&actual_output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn find_config(dir: &Path) -> String {
|
||||
let mut cur = Some(dir);
|
||||
while let Some(dir) = cur {
|
||||
let config = dir.join("config.json");
|
||||
if config.exists() {
|
||||
let config = read_to_string(&config).expect("failed to read config.json");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
cur = dir.parent();
|
||||
}
|
||||
|
||||
panic!("failed to find config file for {}", dir.display())
|
||||
}
|
||||
|
||||
#[testing::fixture("tests/compress/fixture/**/input.js")]
|
||||
fn base_fixture(input: PathBuf) {
|
||||
let dir = input.parent().unwrap();
|
||||
let config = find_config(&dir);
|
||||
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()], false);
|
||||
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||
|
||||
println!("{}", input.display());
|
||||
|
||||
NormalizedOutput::from(output)
|
||||
.compare_to_file(dir.join("output.js"))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[testing::fixture("tests/projects/files/*.js")]
|
||||
fn projects(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");
|
||||
@ -213,14 +277,19 @@ fn base_fixture(input: PathBuf) {
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let output = print(cm.clone(), &[output_module.clone()]);
|
||||
let output = print(cm.clone(), &[output_module.clone()], false);
|
||||
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||
|
||||
println!("{}", input.display());
|
||||
|
||||
NormalizedOutput::from(output)
|
||||
.compare_to_file(dir.join("output.js"))
|
||||
.compare_to_file(
|
||||
dir.parent()
|
||||
.unwrap()
|
||||
.join("output")
|
||||
.join(input.file_name().unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
@ -241,7 +310,7 @@ fn base_exec(input: PathBuf) {
|
||||
|
||||
let output = run(cm.clone(), &handler, &input, &config, None);
|
||||
let output = output.expect("Parsing in base test should not fail");
|
||||
let output = print(cm.clone(), &[output]);
|
||||
let output = print(cm.clone(), &[output], false);
|
||||
|
||||
eprintln!(
|
||||
"---- {} -----\n{}",
|
||||
@ -297,7 +366,7 @@ fn fixture(input: PathBuf) {
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let output = print(cm.clone(), &[output_module.clone()]);
|
||||
let output = print(cm.clone(), &[output_module.clone()], false);
|
||||
|
||||
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||
|
||||
@ -327,7 +396,7 @@ fn fixture(input: PathBuf) {
|
||||
ModuleItem::Stmt(Stmt::Empty(..)) => false,
|
||||
_ => true,
|
||||
});
|
||||
print(cm.clone(), &[expected])
|
||||
print(cm.clone(), &[expected], false)
|
||||
};
|
||||
|
||||
if output == expected {
|
||||
@ -368,7 +437,7 @@ fn fixture(input: PathBuf) {
|
||||
});
|
||||
}
|
||||
|
||||
let output_str = print(cm.clone(), &[drop_span(output_module.clone())]);
|
||||
let output_str = print(cm.clone(), &[drop_span(output_module.clone())], false);
|
||||
|
||||
assert_eq!(DebugUsingDisplay(&output_str), DebugUsingDisplay(&expected));
|
||||
|
||||
@ -377,12 +446,12 @@ fn fixture(input: PathBuf) {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn print<N: swc_ecma_codegen::Node>(cm: Lrc<SourceMap>, nodes: &[N]) -> String {
|
||||
fn print<N: swc_ecma_codegen::Node>(cm: Lrc<SourceMap>, nodes: &[N], minify: bool) -> String {
|
||||
let mut buf = vec![];
|
||||
|
||||
{
|
||||
let mut emitter = Emitter {
|
||||
cfg: Default::default(),
|
||||
cfg: swc_ecma_codegen::Config { minify },
|
||||
cm: cm.clone(),
|
||||
comments: None,
|
||||
wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)),
|
||||
|
10
ecmascript/minifier/tests/compress/fixture/ projects/angular/1/input.js
vendored
Normal file
10
ecmascript/minifier/tests/compress/fixture/ projects/angular/1/input.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
function isUndefined(
|
||||
value
|
||||
) {
|
||||
return "undefined" == typeof value;
|
||||
}
|
||||
function isDefined(
|
||||
value
|
||||
) {
|
||||
return "undefined" != typeof value;
|
||||
}
|
6
ecmascript/minifier/tests/compress/fixture/ projects/angular/1/output.js
vendored
Normal file
6
ecmascript/minifier/tests/compress/fixture/ projects/angular/1/output.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
function isUndefined(value) {
|
||||
return void 0 === value;
|
||||
}
|
||||
function isDefined(value) {
|
||||
return void 0 !== value;
|
||||
}
|
12
ecmascript/minifier/tests/compress/fixture/ projects/angular/2/input.js
vendored
Normal file
12
ecmascript/minifier/tests/compress/fixture/ projects/angular/2/input.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
var h = destination.$$hashKey;
|
||||
forEach(
|
||||
destination,
|
||||
function (
|
||||
value, key
|
||||
) {
|
||||
delete destination[key];
|
||||
}
|
||||
);
|
||||
for (var key in source) destination[key] = copy(
|
||||
source[key]
|
||||
);
|
4
ecmascript/minifier/tests/compress/fixture/ projects/angular/2/output.js
vendored
Normal file
4
ecmascript/minifier/tests/compress/fixture/ projects/angular/2/output.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
var h = destination.$$hashKey;
|
||||
for(var key in forEach(destination, function(value, key) {
|
||||
delete destination[key];
|
||||
}), source)destination[key] = copy(source[key]);
|
5
ecmascript/minifier/tests/compress/fixture/ projects/angular/3/input.js
vendored
Normal file
5
ecmascript/minifier/tests/compress/fixture/ projects/angular/3/input.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
if (foo) {
|
||||
if (bar) {
|
||||
var baz = console.log('Foo bar');
|
||||
}
|
||||
}
|
1
ecmascript/minifier/tests/compress/fixture/ projects/angular/3/output.js
vendored
Normal file
1
ecmascript/minifier/tests/compress/fixture/ projects/angular/3/output.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
if (foo && bar) var baz = console.log("Foo bar");
|
15
ecmascript/minifier/tests/compress/fixture/ projects/angular/4/input.js
vendored
Normal file
15
ecmascript/minifier/tests/compress/fixture/ projects/angular/4/input.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
export const obj = {
|
||||
eq: function (
|
||||
jqLite,
|
||||
index
|
||||
) {
|
||||
return index >= 0
|
||||
? jqLite(
|
||||
this[index]
|
||||
)
|
||||
: jqLite(
|
||||
this[this.length + index]
|
||||
);
|
||||
},
|
||||
}
|
5
ecmascript/minifier/tests/compress/fixture/ projects/angular/4/output.js
vendored
Normal file
5
ecmascript/minifier/tests/compress/fixture/ projects/angular/4/output.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export const obj = {
|
||||
eq: function(jqLite, index) {
|
||||
return jqLite(index >= 0 ? this[index] : this[this.length + index]);
|
||||
}
|
||||
};
|
24
ecmascript/minifier/tests/compress/fixture/ projects/angular/5/input.js
vendored
Normal file
24
ecmascript/minifier/tests/compress/fixture/ projects/angular/5/input.js
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
function forEach(obj, iterator, context) {
|
||||
var key;
|
||||
if (obj) {
|
||||
if (isFunction(obj)) {
|
||||
for (key in obj) {
|
||||
if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
|
||||
iterator.call(context, obj[key], key);
|
||||
}
|
||||
}
|
||||
} else if (obj.forEach && obj.forEach !== forEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (isArrayLike(obj)) {
|
||||
for (key = 0; key < obj.length; key++)
|
||||
iterator.call(context, obj[key], key);
|
||||
} else {
|
||||
for (key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
iterator.call(context, obj[key], key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
8
ecmascript/minifier/tests/compress/fixture/ projects/angular/5/output.js
vendored
Normal file
8
ecmascript/minifier/tests/compress/fixture/ projects/angular/5/output.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
function forEach(obj, iterator, context) {
|
||||
var key;
|
||||
if (obj) if (isFunction(obj)) for(key in obj)"prototype" != key && "length" != key && "name" != key && obj.hasOwnProperty(key) && iterator.call(context, obj[key], key);
|
||||
else if (obj.forEach && obj.forEach !== forEach) obj.forEach(iterator, context);
|
||||
else if (isArrayLike(obj)) for(key = 0; key < obj.length; key++)iterator.call(context, obj[key], key);
|
||||
else for(key in obj)obj.hasOwnProperty(key) && iterator.call(context, obj[key], key);
|
||||
return obj;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
var _ = root._;
|
||||
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
|
@ -0,0 +1,2 @@
|
||||
var _ = root._;
|
||||
_ || "undefined" == typeof require || (_ = require("underscore"));
|
@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
const obj = {
|
||||
_ensureElement: function () {
|
||||
if (!this.el) {
|
||||
var attrs = _.extend({}, _.result(this, 'attributes'));
|
||||
if (this.id) attrs.id = _.result(this, 'id');
|
||||
if (this.className) attrs['class'] = _.result(this, 'className');
|
||||
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
|
||||
this.setElement($el, false);
|
||||
} else {
|
||||
this.setElement(_.result(this, 'el'), false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
const obj = {
|
||||
_ensureElement: function() {
|
||||
if (this.el) this.setElement(_.result(this, "el"), !1);
|
||||
else {
|
||||
var attrs = _.extend({
|
||||
}, _.result(this, "attributes"));
|
||||
this.id && (attrs.id = _.result(this, "id")), this.className && (attrs.class = _.result(this, "className"));
|
||||
var $el = Backbone.$("<" + _.result(this, "tagName") + ">").attr(attrs);
|
||||
this.setElement($el, !1);
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
const obj = {
|
||||
navigate: function (fragment, options) {
|
||||
if (!History.started) return false;
|
||||
if (!options || options === true) options = { trigger: !!options };
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
const obj = {
|
||||
navigate: function(fragment, options) {
|
||||
if (!History.started) return !1;
|
||||
options && !0 !== options || (options = {
|
||||
trigger: !!options
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
var names, i, l;
|
||||
|
||||
names = name ? [name] : _.keys(this._events);
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
name = names[i];
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
var names, i, l;
|
||||
for(i = 0, l = (names = name ? [name] : _.keys(this._events)).length; i < l; i++)name = names[i];
|
@ -0,0 +1,10 @@
|
||||
export const obj = {
|
||||
_validate: function (attrs, options) {
|
||||
if (!options.validate || !this.validate) return true;
|
||||
attrs = _.extend({}, this.attributes, attrs);
|
||||
var error = this.validationError = this.validate(attrs, options) || null;
|
||||
if (!error) return true;
|
||||
this.trigger('invalid', this, error, _.extend(options, { validationError: error }));
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export const obj = {
|
||||
_validate: function(attrs, options) {
|
||||
if (!options.validate || !this.validate) return !0;
|
||||
attrs = _.extend({
|
||||
}, this.attributes, attrs);
|
||||
var error = this.validationError = this.validate(attrs, options) || null;
|
||||
return !error || (this.trigger("invalid", this, error, _.extend(options, {
|
||||
validationError: error
|
||||
})), !1);
|
||||
}
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
export const obj = {
|
||||
set: function (key, val, options) {
|
||||
var attr, attrs, unset, changes, silent, changing, prev, current;
|
||||
if (key == null) return this;
|
||||
|
||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
||||
if (typeof key === 'object') {
|
||||
attrs = key;
|
||||
options = val;
|
||||
} else {
|
||||
(attrs = {})[key] = val;
|
||||
}
|
||||
|
||||
options || (options = {});
|
||||
|
||||
// Run validation.
|
||||
if (!this._validate(attrs, options)) return false;
|
||||
|
||||
// Extract attributes and options.
|
||||
unset = options.unset;
|
||||
silent = options.silent;
|
||||
changes = [];
|
||||
changing = this._changing;
|
||||
this._changing = true;
|
||||
|
||||
if (!changing) {
|
||||
this._previousAttributes = _.clone(this.attributes);
|
||||
this.changed = {};
|
||||
}
|
||||
current = this.attributes, prev = this._previousAttributes;
|
||||
|
||||
// Check for changes of `id`.
|
||||
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
|
||||
|
||||
// For each `set` attribute, update or delete the current value.
|
||||
for (attr in attrs) {
|
||||
val = attrs[attr];
|
||||
if (!_.isEqual(current[attr], val)) changes.push(attr);
|
||||
if (!_.isEqual(prev[attr], val)) {
|
||||
this.changed[attr] = val;
|
||||
} else {
|
||||
delete this.changed[attr];
|
||||
}
|
||||
unset ? delete current[attr] : current[attr] = val;
|
||||
}
|
||||
|
||||
// Trigger all relevant attribute changes.
|
||||
if (!silent) {
|
||||
if (changes.length) this._pending = true;
|
||||
for (var i = 0, l = changes.length; i < l; i++) {
|
||||
this.trigger('change:' + changes[i], this, current[changes[i]], options);
|
||||
}
|
||||
}
|
||||
|
||||
// You might be wondering why there's a `while` loop here. Changes can
|
||||
// be recursively nested within `"change"` events.
|
||||
if (changing) return this;
|
||||
if (!silent) {
|
||||
while (this._pending) {
|
||||
this._pending = false;
|
||||
this.trigger('change', this, options);
|
||||
}
|
||||
}
|
||||
this._pending = false;
|
||||
this._changing = false;
|
||||
return this;
|
||||
},
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
export const obj = {
|
||||
set: function(key, val, options) {
|
||||
var attr, attrs, unset, changes, silent, changing, prev, current;
|
||||
if (null == key) return this;
|
||||
if ("object" == typeof key ? (attrs = key, options = val) : (attrs = {
|
||||
})[key] = val, options || (options = {
|
||||
}), !this._validate(attrs, options)) return !1;
|
||||
for(attr in unset = options.unset, silent = options.silent, changes = [], changing = this._changing, this._changing = !0, changing || (this._previousAttributes = _.clone(this.attributes), this.changed = {
|
||||
}), current = this.attributes, prev = this._previousAttributes, this.idAttribute in attrs && (this.id = attrs[this.idAttribute]), attrs)val = attrs[attr], _.isEqual(current[attr], val) || changes.push(attr), _.isEqual(prev[attr], val) ? delete this.changed[attr] : this.changed[attr] = val, unset ? delete current[attr] : current[attr] = val;
|
||||
if (!silent) {
|
||||
changes.length && (this._pending = !0);
|
||||
for(var i = 0, l = changes.length; i < l; i++)this.trigger("change:" + changes[i], this, current[changes[i]], options);
|
||||
}
|
||||
if (changing) return this;
|
||||
if (!silent) for(; this._pending;)this._pending = !1, this.trigger("change", this, options);
|
||||
return this._pending = !1, this._changing = !1, this;
|
||||
}
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
export const obj = {
|
||||
changedAttributes: function (diff) {
|
||||
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
|
||||
var val, changed = false;
|
||||
var old = this._changing ? this._previousAttributes : this.attributes;
|
||||
for (var attr in diff) {
|
||||
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
|
||||
(changed || (changed = {}))[attr] = val;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
export const obj = {
|
||||
changedAttributes: function(diff) {
|
||||
if (!diff) return !!this.hasChanged() && _.clone(this.changed);
|
||||
var val, changed = !1, old = this._changing ? this._previousAttributes : this.attributes;
|
||||
for(var attr in diff)_.isEqual(old[attr], val = diff[attr]) || ((changed || (changed = {
|
||||
}))[attr] = val);
|
||||
return changed;
|
||||
}
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
export const obj = {
|
||||
remove: function (models, options) {
|
||||
var singular = !_.isArray(models);
|
||||
models = singular ? [models] : _.clone(models);
|
||||
options || (options = {});
|
||||
var i, l, index, model;
|
||||
for (i = 0, l = models.length; i < l; i++) {
|
||||
model = models[i] = this.get(models[i]);
|
||||
if (!model) continue;
|
||||
delete this._byId[model.id];
|
||||
delete this._byId[model.cid];
|
||||
index = this.indexOf(model);
|
||||
this.models.splice(index, 1);
|
||||
this.length--;
|
||||
if (!options.silent) {
|
||||
options.index = index;
|
||||
model.trigger('remove', model, this, options);
|
||||
}
|
||||
this._removeReference(model);
|
||||
}
|
||||
return singular ? models[0] : models;
|
||||
},
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
export const obj = {
|
||||
remove: function(models, options) {
|
||||
var i, l, index, model, singular = !_.isArray(models);
|
||||
for(options || (options = {
|
||||
}), i = 0, l = (models = singular ? [models] : _.clone(models)).length; i < l; i++)(model = models[i] = this.get(models[i])) && (delete this._byId[model.id], delete this._byId[model.cid], index = this.indexOf(model), this.models.splice(index, 1), this.length--, options.silent || (options.index = index, model.trigger("remove", model, this, options)), this._removeReference(model));
|
||||
return singular ? models[0] : models;
|
||||
}
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
export const obj = {
|
||||
create: function (model, options) {
|
||||
options = options ? _.clone(options) : {};
|
||||
if (!(model = this._prepareModel(model, options))) return false;
|
||||
if (!options.wait) this.add(model, options);
|
||||
var collection = this;
|
||||
var success = options.success;
|
||||
options.success = function (model, resp, options) {
|
||||
if (options.wait) collection.add(model, options);
|
||||
if (success) success(model, resp, options);
|
||||
};
|
||||
model.save(null, options);
|
||||
return model;
|
||||
},
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export const obj = {
|
||||
create: function(model, options) {
|
||||
if (options = options ? _.clone(options) : {
|
||||
}, !(model = this._prepareModel(model, options))) return !1;
|
||||
options.wait || this.add(model, options);
|
||||
var collection = this, success = options.success;
|
||||
return options.success = function(model, resp, options) {
|
||||
options.wait && collection.add(model, options), success && success(model, resp, options);
|
||||
}, model.save(null, options), model;
|
||||
}
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
export const obj = {
|
||||
_routeToRegExp: function (route) {
|
||||
route = route.replace(escapeRegExp, '\\$&')
|
||||
.replace(optionalParam, '(?:$1)?')
|
||||
.replace(namedParam, function (match, optional) {
|
||||
return optional ? match : '([^\/]+)';
|
||||
})
|
||||
.replace(splatParam, '(.*?)');
|
||||
return new RegExp('^' + route + '$');
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export const obj = {
|
||||
_routeToRegExp: function(route) {
|
||||
return route = route.replace(escapeRegExp, "\\$&").replace(optionalParam, "(?:$1)?").replace(namedParam, function(match, optional) {
|
||||
return optional ? match : "([^/]+)";
|
||||
}).replace(splatParam, "(.*?)"), new RegExp("^" + route + "$");
|
||||
}
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
export const obj = {
|
||||
navigate: function (fragment, options) {
|
||||
if (!History.started) return false;
|
||||
if (!options || options === true) options = { trigger: !!options };
|
||||
|
||||
var url = this.root + (fragment = this.getFragment(fragment || ''));
|
||||
|
||||
// Strip the fragment of the query and hash for matching.
|
||||
fragment = fragment.replace(pathStripper, '');
|
||||
|
||||
if (this.fragment === fragment) return;
|
||||
this.fragment = fragment;
|
||||
|
||||
// Don't include a trailing slash on the root.
|
||||
if (fragment === '' && url !== '/') url = url.slice(0, -1);
|
||||
|
||||
// If pushState is available, we use it to set the fragment as a real URL.
|
||||
if (this._hasPushState) {
|
||||
this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
|
||||
|
||||
// If hash changes haven't been explicitly disabled, update the hash
|
||||
// fragment to store history.
|
||||
} else if (this._wantsHashChange) {
|
||||
this._updateHash(this.location, fragment, options.replace);
|
||||
if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
|
||||
// Opening and closing the iframe tricks IE7 and earlier to push a
|
||||
// history entry on hash-tag change. When replace is true, we don't
|
||||
// want this.
|
||||
if (!options.replace) this.iframe.document.open().close();
|
||||
this._updateHash(this.iframe.location, fragment, options.replace);
|
||||
}
|
||||
|
||||
// If you've told us that you explicitly don't want fallback hashchange-
|
||||
// based history, then `navigate` becomes a page refresh.
|
||||
} else {
|
||||
return this.location.assign(url);
|
||||
}
|
||||
if (options.trigger) return this.loadUrl(fragment);
|
||||
},
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
export const obj = {
|
||||
navigate: function(fragment, options) {
|
||||
if (!History.started) return !1;
|
||||
options && !0 !== options || (options = {
|
||||
trigger: !!options
|
||||
});
|
||||
var url = this.root + (fragment = this.getFragment(fragment || ""));
|
||||
if (fragment = fragment.replace(pathStripper, ""), this.fragment !== fragment) {
|
||||
if (this.fragment = fragment, "" === fragment && "/" !== url && (url = url.slice(0, -1)), this._hasPushState) this.history[options.replace ? "replaceState" : "pushState"]({
|
||||
}, document.title, url);
|
||||
else {
|
||||
if (!this._wantsHashChange) return this.location.assign(url);
|
||||
this._updateHash(this.location, fragment, options.replace), this.iframe && fragment !== this.getFragment(this.getHash(this.iframe)) && (options.replace || this.iframe.document.open().close(), this._updateHash(this.iframe.location, fragment, options.replace));
|
||||
}
|
||||
if (options.trigger) return this.loadUrl(fragment);
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
function foo() {
|
||||
if (!eventsApi(this, "on", name, [callback, context]) || !callback)
|
||||
return this;
|
||||
return (
|
||||
this._events || (this._events = {}),
|
||||
(this._events[name] || (this._events[name] = [])).push({
|
||||
callback: callback,
|
||||
context: context,
|
||||
ctx: context || this,
|
||||
}),
|
||||
this
|
||||
);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
function foo() {
|
||||
return eventsApi(this, "on", name, [callback,
|
||||
context]) && callback && (this._events || (this._events = {
|
||||
}), (this._events[name] || (this._events[name] = [])).push({
|
||||
callback: callback,
|
||||
context: context,
|
||||
ctx: context || this
|
||||
})), this;
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
export const E = {
|
||||
set: function (models, options) {
|
||||
options = _.defaults({}, options, setOptions);
|
||||
if (options.parse) models = this.parse(models, options);
|
||||
var singular = !_.isArray(models);
|
||||
models = singular ? (models ? [models] : []) : _.clone(models);
|
||||
var i, l, id, model, attrs, existing, sort;
|
||||
var at = options.at;
|
||||
var targetModel = this.model;
|
||||
var sortable = this.comparator && (at == null) && options.sort !== false;
|
||||
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
|
||||
var toAdd = [], toRemove = [], modelMap = {};
|
||||
var add = options.add, merge = options.merge, remove = options.remove;
|
||||
var order = !sortable && add && remove ? [] : false;
|
||||
|
||||
// Turn bare objects into model references, and prevent invalid models
|
||||
// from being added.
|
||||
for (i = 0, l = models.length; i < l; i++) {
|
||||
attrs = models[i];
|
||||
if (attrs instanceof Model) {
|
||||
id = model = attrs;
|
||||
} else {
|
||||
id = attrs[targetModel.prototype.idAttribute];
|
||||
}
|
||||
|
||||
// If a duplicate is found, prevent it from being added and
|
||||
// optionally merge it into the existing model.
|
||||
if (existing = this.get(id)) {
|
||||
if (remove) modelMap[existing.cid] = true;
|
||||
if (merge) {
|
||||
attrs = attrs === model ? model.attributes : attrs;
|
||||
if (options.parse) attrs = existing.parse(attrs, options);
|
||||
existing.set(attrs, options);
|
||||
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
|
||||
}
|
||||
models[i] = existing;
|
||||
|
||||
// If this is a new, valid model, push it to the `toAdd` list.
|
||||
} else if (add) {
|
||||
model = models[i] = this._prepareModel(attrs, options);
|
||||
if (!model) continue;
|
||||
toAdd.push(model);
|
||||
|
||||
// Listen to added models' events, and index models for lookup by
|
||||
// `id` and by `cid`.
|
||||
model.on('all', this._onModelEvent, this);
|
||||
this._byId[model.cid] = model;
|
||||
if (model.id != null) this._byId[model.id] = model;
|
||||
}
|
||||
if (order) order.push(existing || model);
|
||||
}
|
||||
|
||||
// Remove nonexistent models if appropriate.
|
||||
if (remove) {
|
||||
for (i = 0, l = this.length; i < l; ++i) {
|
||||
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
|
||||
}
|
||||
if (toRemove.length) this.remove(toRemove, options);
|
||||
}
|
||||
|
||||
// See if sorting is needed, update `length` and splice in new models.
|
||||
if (toAdd.length || (order && order.length)) {
|
||||
if (sortable) sort = true;
|
||||
this.length += toAdd.length;
|
||||
if (at != null) {
|
||||
for (i = 0, l = toAdd.length; i < l; i++) {
|
||||
this.models.splice(at + i, 0, toAdd[i]);
|
||||
}
|
||||
} else {
|
||||
if (order) this.models.length = 0;
|
||||
var orderedModels = order || toAdd;
|
||||
for (i = 0, l = orderedModels.length; i < l; i++) {
|
||||
this.models.push(orderedModels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Silently sort the collection if appropriate.
|
||||
if (sort) this.sort({ silent: true });
|
||||
|
||||
// Unless silenced, it's time to fire all appropriate add/sort events.
|
||||
if (!options.silent) {
|
||||
for (i = 0, l = toAdd.length; i < l; i++) {
|
||||
(model = toAdd[i]).trigger('add', model, this, options);
|
||||
}
|
||||
if (sort || (order && order.length)) this.trigger('sort', this, options);
|
||||
}
|
||||
|
||||
// Return the added (or merged) model (or models).
|
||||
return singular ? models[0] : models;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
export const E = {
|
||||
set: function(models, options) {
|
||||
(options = _.defaults({
|
||||
}, options, setOptions)).parse && (models = this.parse(models, options));
|
||||
var singular = !_.isArray(models);
|
||||
models = singular ? models ? [models] : [] : _.clone(models);
|
||||
var i, l, id, model, attrs, existing, sort, at = options.at, targetModel = this.model, sortable = this.comparator && null == at && !1 !== options.sort, sortAttr = _.isString(this.comparator) ? this.comparator : null, toAdd = [], toRemove = [], modelMap = {
|
||||
}, add = options.add, merge = options.merge, remove = options.remove, order = !sortable && !!add && !!remove && [];
|
||||
for(i = 0, l = models.length; i < l; i++){
|
||||
if (id = (attrs = models[i]) instanceof Model ? model = attrs : attrs[targetModel.prototype.idAttribute], existing = this.get(id)) remove && (modelMap[existing.cid] = !0), merge && (attrs = attrs === model ? model.attributes : attrs, options.parse && (attrs = existing.parse(attrs, options)), existing.set(attrs, options), sortable && !sort && existing.hasChanged(sortAttr) && (sort = !0)), models[i] = existing;
|
||||
else if (add) {
|
||||
if (!(model = models[i] = this._prepareModel(attrs, options))) continue;
|
||||
toAdd.push(model), model.on("all", this._onModelEvent, this), this._byId[model.cid] = model, null != model.id && (this._byId[model.id] = model);
|
||||
}
|
||||
order && order.push(existing || model);
|
||||
}
|
||||
if (remove) {
|
||||
for(i = 0, l = this.length; i < l; ++i)modelMap[(model = this.models[i]).cid] || toRemove.push(model);
|
||||
toRemove.length && this.remove(toRemove, options);
|
||||
}
|
||||
if (toAdd.length || order && order.length) if (sortable && (sort = !0), this.length += toAdd.length, null != at) for(i = 0, l = toAdd.length; i < l; i++)this.models.splice(at + i, 0, toAdd[i]);
|
||||
else {
|
||||
order && (this.models.length = 0);
|
||||
var orderedModels = order || toAdd;
|
||||
for(i = 0, l = orderedModels.length; i < l; i++)this.models.push(orderedModels[i]);
|
||||
}
|
||||
if (sort && this.sort({
|
||||
silent: !0
|
||||
}), !options.silent) {
|
||||
for(i = 0, l = toAdd.length; i < l; i++)(model = toAdd[i]).trigger("add", model, this, options);
|
||||
(sort || order && order.length) && this.trigger("sort", this, options);
|
||||
}
|
||||
return singular ? models[0] : models;
|
||||
}
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
|
||||
if (!name && !callback && !context) {
|
||||
console.log('foo')
|
||||
}
|
@ -0,0 +1 @@
|
||||
name || callback || context || console.log("foo");
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user