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
|
# Source map format
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "14"
|
node-version: "16"
|
||||||
|
|
||||||
# We explicitly do this to cache properly.
|
# We explicitly do this to cache properly.
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
@ -170,7 +170,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 16
|
||||||
|
|
||||||
- uses: denoland/setup-deno@v1
|
- uses: denoland/setup-deno@v1
|
||||||
with:
|
with:
|
||||||
|
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2430,7 +2430,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecma_codegen"
|
name = "swc_ecma_codegen"
|
||||||
version = "0.64.0"
|
version = "0.64.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
@ -2500,11 +2500,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecma_minifier"
|
name = "swc_ecma_minifier"
|
||||||
version = "0.14.0"
|
version = "0.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.12.1",
|
"ansi_term 0.12.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pretty_assertions 0.6.1",
|
"pretty_assertions 0.6.1",
|
||||||
@ -2522,7 +2523,9 @@ dependencies = [
|
|||||||
"swc_ecma_transforms_base",
|
"swc_ecma_transforms_base",
|
||||||
"swc_ecma_utils",
|
"swc_ecma_utils",
|
||||||
"swc_ecma_visit",
|
"swc_ecma_visit",
|
||||||
|
"swc_node_base",
|
||||||
"testing",
|
"testing",
|
||||||
|
"unicode-xid",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2605,7 +2608,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecma_transforms_base"
|
name = "swc_ecma_transforms_base"
|
||||||
version = "0.24.0"
|
version = "0.24.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -2788,7 +2791,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecma_transforms_typescript"
|
name = "swc_ecma_transforms_typescript"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"serde",
|
"serde",
|
||||||
@ -2835,7 +2838,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecmascript"
|
name = "swc_ecmascript"
|
||||||
version = "0.48.0"
|
version = "0.49.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"swc_ecma_ast",
|
"swc_ecma_ast",
|
||||||
"swc_ecma_codegen",
|
"swc_ecma_codegen",
|
||||||
@ -2940,7 +2943,7 @@ dependencies = [
|
|||||||
"env_logger 0.7.1",
|
"env_logger 0.7.1",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pretty_assertions 0.6.1",
|
"pretty_assertions 0.7.2",
|
||||||
"regex",
|
"regex",
|
||||||
"swc_common",
|
"swc_common",
|
||||||
"testing_macros",
|
"testing_macros",
|
||||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||||||
license = "Apache-2.0/MIT"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc_ecmascript"
|
name = "swc_ecmascript"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
repository = "https://github.com/swc-project/swc.git"
|
||||||
version = "0.48.0"
|
version = "0.49.0"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
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_ast = {version = "0.49.0", path = "./ast"}
|
||||||
swc_ecma_codegen = {version = "0.64.0", path = "./codegen", optional = true}
|
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_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_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_transforms = {version = "0.61.0", path = "./transforms", optional = true}
|
||||||
swc_ecma_utils = {version = "0.41.0", path = "./utils", 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"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc_ecma_codegen"
|
name = "swc_ecma_codegen"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
repository = "https://github.com/swc-project/swc.git"
|
||||||
version = "0.64.1"
|
version = "0.64.2"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1"
|
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);
|
let _ = write!(buf, "\\x{:x}", c as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
'\u{2028}' => {
|
||||||
|
buf.push_str("\\u2028");
|
||||||
|
}
|
||||||
|
'\u{2029}' => {
|
||||||
|
buf.push_str("\\u2029");
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
|
@ -560,7 +560,7 @@ fn test_escape_without_source() {
|
|||||||
es2020("abcde", "abcde");
|
es2020("abcde", "abcde");
|
||||||
es2020(
|
es2020(
|
||||||
"\x00\r\n\u{85}\u{2028}\u{2029};",
|
"\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");
|
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"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc_ecma_minifier"
|
name = "swc_ecma_minifier"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
repository = "https://github.com/swc-project/swc.git"
|
||||||
version = "0.14.0"
|
version = "0.15.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = []
|
debug = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
|
indexmap = "1.7.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
pretty_assertions = {version = "0.6.1", optional = true}
|
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_transforms_base = {version = "0.24.0", path = "../transforms/base"}
|
||||||
swc_ecma_utils = {version = "0.41.0", path = "../utils"}
|
swc_ecma_utils = {version = "0.41.0", path = "../utils"}
|
||||||
swc_ecma_visit = {version = "0.35.0", path = "../visit"}
|
swc_ecma_visit = {version = "0.35.0", path = "../visit"}
|
||||||
|
unicode-xid = "0.2.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
pretty_assertions = "0.6.1"
|
pretty_assertions = "0.6.1"
|
||||||
|
swc_node_base = {version = "0.2.0", path = "../../node/base"}
|
||||||
testing = {version = "0.12.0", path = "../../testing"}
|
testing = {version = "0.12.0", path = "../../testing"}
|
||||||
walkdir = "2.3.1"
|
walkdir = "2.3.1"
|
||||||
|
@ -8,6 +8,26 @@ Currently name mangler is very simple. To focus on creating a MVP, I (kdy1) will
|
|||||||
|
|
||||||
## Testing
|
## 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
|
Tests ported from terser has
|
||||||
|
|
||||||
- input.js
|
- 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.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)
|
- 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`
|
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
|
export SWC_RUN=0
|
||||||
|
|
||||||
cargo test --test compress --all-features \
|
cargo test --test compress --all-features fixture_tests__terser__compress__ \
|
||||||
| grep 'terser__compress' \
|
| grep 'fixture_tests__terser__compress__' \
|
||||||
| grep 'js .\.\. ok$' \
|
| 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! ... ok!!' \
|
||||||
| sed -e 's!__!/!g' \
|
| sed -e 's!__!/!g' \
|
||||||
| sed -e 's!_js!.js!' \
|
| 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.
|
# To prevent regression, we run base test before real tests.
|
||||||
touch tests/compress.rs
|
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
|
export SWC_RUN=0
|
||||||
|
|
||||||
cargo test --test compress --all-features ${1-''} \
|
cargo test --test compress --all-features ${1-'fixture_tests__terser__compress__'} \
|
||||||
| grep 'terser__compress' \
|
| grep 'fixture_tests__terser__compress__' \
|
||||||
| grep 'js .\.\. FAILED$' \
|
| 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! ... FAILED!!' \
|
||||||
| sed -e 's!__!/!g' \
|
| sed -e 's!__!/!g' \
|
||||||
| sed -e 's!_js!.js!' \
|
| 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
|
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.
|
# To prevent regression, we run base test before real tests.
|
||||||
touch tests/compress.rs
|
touch tests/compress.rs
|
||||||
UPDATE=1 ./scripts/base.sh base_fixture
|
UPDATE=1 ./scripts/base.sh base_fixture
|
||||||
@ -21,7 +24,7 @@ UPDATE=1 ./scripts/base.sh base_fixture
|
|||||||
if [ -z "$@" ]; then
|
if [ -z "$@" ]; then
|
||||||
./scripts/sort.sh
|
./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
|
fi
|
||||||
|
|
||||||
SKIP_GOLDEN=1 cargo test --test compress --all-features $@
|
SKIP_GOLDEN=1 cargo test --test compress --all-features $@
|
||||||
|
@ -12,4 +12,4 @@ set -eu
|
|||||||
echo '' > tests/ignored.txt
|
echo '' > tests/ignored.txt
|
||||||
|
|
||||||
scripts/add-golden.sh
|
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)]
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub(super) struct Ctx {
|
pub(super) struct Ctx {
|
||||||
pub var_decl_kind_of_pat: Option<VarDeclKind>,
|
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: bool,
|
||||||
pub in_pat_of_var_decl_with_init: bool,
|
pub in_pat_of_var_decl_with_init: bool,
|
||||||
pub in_pat_of_param: bool,
|
pub in_pat_of_param: bool,
|
||||||
@ -38,6 +41,8 @@ pub(super) struct Ctx {
|
|||||||
pub in_update_arg: bool,
|
pub in_update_arg: bool,
|
||||||
pub in_assign_lhs: bool,
|
pub in_assign_lhs: bool,
|
||||||
pub in_cond: bool,
|
pub in_cond: bool,
|
||||||
|
|
||||||
|
pub inline_prevented: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct WithCtx<'a> {
|
pub(super) struct WithCtx<'a> {
|
||||||
|
@ -4,6 +4,7 @@ use crate::util::idents_used_by;
|
|||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use fxhash::FxHashSet;
|
use fxhash::FxHashSet;
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::time::Instant;
|
||||||
use swc_atoms::JsWord;
|
use swc_atoms::JsWord;
|
||||||
use swc_common::SyntaxContext;
|
use swc_common::SyntaxContext;
|
||||||
use swc_common::DUMMY_SP;
|
use swc_common::DUMMY_SP;
|
||||||
@ -24,6 +25,8 @@ pub(crate) fn analyze<N>(n: &N) -> ProgramData
|
|||||||
where
|
where
|
||||||
N: VisitWith<UsageAnalyzer>,
|
N: VisitWith<UsageAnalyzer>,
|
||||||
{
|
{
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let mut v = UsageAnalyzer {
|
let mut v = UsageAnalyzer {
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
scope: Default::default(),
|
scope: Default::default(),
|
||||||
@ -33,11 +36,17 @@ where
|
|||||||
let top_scope = v.scope;
|
let top_scope = v.scope;
|
||||||
v.data.top.merge(top_scope, false);
|
v.data.top.merge(top_scope, false);
|
||||||
|
|
||||||
|
let end_time = Instant::now();
|
||||||
|
|
||||||
|
log::debug!("Scope analysis took {:?}", end_time - start_time);
|
||||||
|
|
||||||
v.data
|
v.data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct VarUsageInfo {
|
pub(crate) struct VarUsageInfo {
|
||||||
|
pub inline_prevented: bool,
|
||||||
|
|
||||||
/// The number of reference to this identifier.
|
/// The number of reference to this identifier.
|
||||||
pub ref_count: usize,
|
pub ref_count: usize,
|
||||||
|
|
||||||
@ -51,6 +60,8 @@ pub(crate) struct VarUsageInfo {
|
|||||||
/// `true` if the enclosing function defines this variable as a parameter.
|
/// `true` if the enclosing function defines this variable as a parameter.
|
||||||
pub declared_as_fn_param: bool,
|
pub declared_as_fn_param: bool,
|
||||||
|
|
||||||
|
pub declared_as_fn_expr: bool,
|
||||||
|
|
||||||
pub assign_count: usize,
|
pub assign_count: usize,
|
||||||
pub mutation_by_call_count: usize,
|
pub mutation_by_call_count: usize,
|
||||||
pub usage_count: usize,
|
pub usage_count: usize,
|
||||||
@ -71,6 +82,8 @@ pub(crate) struct VarUsageInfo {
|
|||||||
/// functions.
|
/// functions.
|
||||||
pub is_fn_local: bool,
|
pub is_fn_local: bool,
|
||||||
|
|
||||||
|
used_by_nested_fn: bool,
|
||||||
|
|
||||||
pub used_in_loop: bool,
|
pub used_in_loop: bool,
|
||||||
|
|
||||||
pub var_kind: Option<VarDeclKind>,
|
pub var_kind: Option<VarDeclKind>,
|
||||||
@ -83,6 +96,8 @@ pub(crate) struct VarUsageInfo {
|
|||||||
/// Indicates a variable or function is overrided without using it.
|
/// Indicates a variable or function is overrided without using it.
|
||||||
pub overriden_without_used: bool,
|
pub overriden_without_used: bool,
|
||||||
|
|
||||||
|
pub no_side_effect_for_member_access: bool,
|
||||||
|
|
||||||
/// In `c = b`, `b` inffects `c`.
|
/// In `c = b`, `b` inffects `c`.
|
||||||
infects: Vec<Id>,
|
infects: Vec<Id>,
|
||||||
}
|
}
|
||||||
@ -143,9 +158,12 @@ impl ProgramData {
|
|||||||
to.merge(scope, false);
|
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) {
|
match self.vars.entry(id) {
|
||||||
Entry::Occupied(mut e) => {
|
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().ref_count += var_info.ref_count;
|
||||||
e.get_mut().cond_init |= var_info.cond_init;
|
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 |= var_info.declared;
|
||||||
e.get_mut().declared_count += var_info.declared_count;
|
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_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
|
// If a var is registered at a parent scope, it means that it's delcared before
|
||||||
// usages.
|
// usages.
|
||||||
@ -172,14 +191,30 @@ impl ProgramData {
|
|||||||
|
|
||||||
e.get_mut().infects.extend(var_info.infects);
|
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 {
|
match kind {
|
||||||
ScopeKind::Fn => {
|
ScopeKind::Fn => {
|
||||||
e.get_mut().is_fn_local = false;
|
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) => {
|
Entry::Vacant(e) => {
|
||||||
|
match kind {
|
||||||
|
ScopeKind::Fn => {
|
||||||
|
var_info.used_by_nested_fn = true;
|
||||||
|
}
|
||||||
|
ScopeKind::Block => {}
|
||||||
|
}
|
||||||
e.insert(var_info);
|
e.insert(var_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,17 +260,25 @@ impl UsageAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report(&mut self, i: Id, is_modify: bool, dejavu: &mut FxHashSet<Id>) {
|
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();
|
let is_first = dejavu.is_empty();
|
||||||
|
|
||||||
if !dejavu.insert(i.clone()) {
|
if !dejavu.insert(i.clone()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let e = self.data.vars.entry(i).or_insert_with(|| VarUsageInfo {
|
let e = self.data.vars.entry(i.clone()).or_insert_with(|| {
|
||||||
used_above_decl: true,
|
// log::trace!("insert({}{:?})", i.0, i.1);
|
||||||
..Default::default()
|
|
||||||
|
VarUsageInfo {
|
||||||
|
used_above_decl: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
e.inline_prevented |= self.ctx.inline_prevented;
|
||||||
|
|
||||||
e.ref_count += 1;
|
e.ref_count += 1;
|
||||||
e.reassigned |= is_first && is_modify && self.ctx.is_exact_reassignment;
|
e.reassigned |= is_first && is_modify && self.ctx.is_exact_reassignment;
|
||||||
// Passing object as a argument is possibly modification.
|
// Passing object as a argument is possibly modification.
|
||||||
@ -258,7 +301,7 @@ impl UsageAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_usage(&mut self, i: &Ident, is_assign: bool) {
|
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(
|
fn declare_decl(
|
||||||
@ -266,7 +309,12 @@ impl UsageAnalyzer {
|
|||||||
i: &Ident,
|
i: &Ident,
|
||||||
has_init: bool,
|
has_init: bool,
|
||||||
kind: Option<VarDeclKind>,
|
kind: Option<VarDeclKind>,
|
||||||
|
is_fn_decl: bool,
|
||||||
) -> &mut VarUsageInfo {
|
) -> &mut VarUsageInfo {
|
||||||
|
// log::trace!("declare_decl({}{:?})", i.sym, i.span.ctxt);
|
||||||
|
|
||||||
|
let ctx = self.ctx;
|
||||||
|
|
||||||
let v = self
|
let v = self
|
||||||
.data
|
.data
|
||||||
.vars
|
.vars
|
||||||
@ -282,6 +330,9 @@ impl UsageAnalyzer {
|
|||||||
is_fn_local: true,
|
is_fn_local: true,
|
||||||
var_kind: kind,
|
var_kind: kind,
|
||||||
var_initialized: has_init,
|
var_initialized: has_init,
|
||||||
|
no_side_effect_for_member_access: ctx
|
||||||
|
.in_var_decl_with_no_side_effect_for_member_access,
|
||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
self.scope
|
self.scope
|
||||||
@ -297,6 +348,8 @@ impl UsageAnalyzer {
|
|||||||
}
|
}
|
||||||
v.declared_as_catch_param |= self.ctx.in_catch_param;
|
v.declared_as_catch_param |= self.ctx.in_catch_param;
|
||||||
|
|
||||||
|
v.declared_as_fn_expr |= is_fn_decl;
|
||||||
|
|
||||||
v
|
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) {
|
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);
|
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) {
|
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);
|
n.visit_children_with(self);
|
||||||
}
|
}
|
||||||
@ -432,12 +511,14 @@ impl Visit for UsageAnalyzer {
|
|||||||
};
|
};
|
||||||
n.left.visit_with(n, &mut *child.with_ctx(ctx));
|
n.left.visit_with(n, &mut *child.with_ctx(ctx));
|
||||||
|
|
||||||
|
n.right.visit_with(n, child);
|
||||||
|
|
||||||
let ctx = Ctx {
|
let ctx = Ctx {
|
||||||
in_loop: true,
|
in_loop: true,
|
||||||
in_cond: true,
|
in_cond: true,
|
||||||
..child.ctx
|
..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,
|
&i.id,
|
||||||
self.ctx.in_pat_of_var_decl_with_init,
|
self.ctx.in_pat_of_var_decl_with_init,
|
||||||
self.ctx.var_decl_kind_of_pat,
|
self.ctx.var_decl_kind_of_pat,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if in_pat_of_param {
|
if in_pat_of_param {
|
||||||
@ -735,6 +817,10 @@ impl Visit for UsageAnalyzer {
|
|||||||
let ctx = Ctx {
|
let ctx = Ctx {
|
||||||
in_pat_of_var_decl: true,
|
in_pat_of_var_decl: true,
|
||||||
in_pat_of_var_decl_with_init: e.init.is_some(),
|
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
|
..self.ctx
|
||||||
};
|
};
|
||||||
e.name.visit_with(e, &mut *self.with_ctx(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::ProgramData;
|
||||||
use crate::analyzer::UsageAnalyzer;
|
use crate::analyzer::UsageAnalyzer;
|
||||||
use crate::util::is_hoisted_var_decl_without_init;
|
use crate::util::is_hoisted_var_decl_without_init;
|
||||||
@ -24,21 +23,21 @@ pub(super) struct DeclHoisterConfig {
|
|||||||
pub top_level: bool,
|
pub top_level: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn decl_hoister(config: DeclHoisterConfig) -> Hoister {
|
pub(super) fn decl_hoister(config: DeclHoisterConfig, data: &ProgramData) -> Hoister {
|
||||||
Hoister {
|
Hoister {
|
||||||
config,
|
config,
|
||||||
changed: false,
|
changed: false,
|
||||||
data: None,
|
data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct Hoister {
|
pub(super) struct Hoister<'a> {
|
||||||
config: DeclHoisterConfig,
|
config: DeclHoisterConfig,
|
||||||
changed: bool,
|
changed: bool,
|
||||||
data: Option<ProgramData>,
|
data: &'a ProgramData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repeated for Hoister {
|
impl Repeated for Hoister<'_> {
|
||||||
fn changed(&self) -> bool {
|
fn changed(&self) -> bool {
|
||||||
self.changed
|
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>)
|
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
|
||||||
where
|
where
|
||||||
T: StmtLike + IsModuleItem,
|
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);
|
stmts.visit_mut_children_with(self);
|
||||||
|
|
||||||
let should_hoist = !is_sorted_by_key(stmts.iter(), |stmt| match stmt.as_stmt() {
|
let should_hoist = !is_sorted_by_key(stmts.iter(), |stmt| match stmt.as_stmt() {
|
||||||
Some(stmt) => match stmt {
|
Some(stmt) => match stmt {
|
||||||
Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1,
|
Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1,
|
||||||
Stmt::Decl(Decl::Var(var)) if self.config.hoist_vars => {
|
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| {
|
if ids.iter().any(|id| {
|
||||||
data.vars
|
self.data
|
||||||
.get(id)
|
.vars
|
||||||
.map(|v| !v.used_above_decl)
|
.get(id)
|
||||||
.unwrap_or(false)
|
.map(|v| !v.used_above_decl)
|
||||||
}) {
|
.unwrap_or(false)
|
||||||
2
|
}) {
|
||||||
} else {
|
|
||||||
3
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
2
|
2
|
||||||
|
} else {
|
||||||
|
3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => 3,
|
_ => 3,
|
||||||
@ -133,8 +122,8 @@ impl Hoister {
|
|||||||
if decl.init.is_none()
|
if decl.init.is_none()
|
||||||
&& self
|
&& self
|
||||||
.data
|
.data
|
||||||
.as_ref()
|
.vars
|
||||||
.and_then(|v| v.vars.get(&id.to_id()))
|
.get(&id.to_id())
|
||||||
.map(|v| v.declared_as_fn_param)
|
.map(|v| v.declared_as_fn_param)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
@ -207,8 +196,8 @@ impl Hoister {
|
|||||||
if decl.init.is_none()
|
if decl.init.is_none()
|
||||||
&& self
|
&& self
|
||||||
.data
|
.data
|
||||||
.as_ref()
|
.vars
|
||||||
.and_then(|v| v.vars.get(&name.to_id()))
|
.get(&name.to_id())
|
||||||
.map(|v| v.declared_as_fn_param)
|
.map(|v| v.declared_as_fn_param)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
@ -258,7 +247,7 @@ impl Hoister {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitMut for Hoister {
|
impl VisitMut for Hoister<'_> {
|
||||||
noop_visit_mut_type!();
|
noop_visit_mut_type!();
|
||||||
|
|
||||||
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
|
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
use self::drop_console::drop_console;
|
use self::drop_console::drop_console;
|
||||||
use self::hoist_decls::DeclHoisterConfig;
|
use self::hoist_decls::DeclHoisterConfig;
|
||||||
use self::optimize::optimizer;
|
use self::optimize::optimizer;
|
||||||
|
use crate::analyzer::analyze;
|
||||||
|
use crate::analyzer::ProgramData;
|
||||||
use crate::compress::hoist_decls::decl_hoister;
|
use crate::compress::hoist_decls::decl_hoister;
|
||||||
use crate::debug::dump;
|
use crate::debug::dump;
|
||||||
use crate::debug::invoke;
|
use crate::debug::invoke;
|
||||||
|
use crate::marks::Marks;
|
||||||
use crate::option::CompressOptions;
|
use crate::option::CompressOptions;
|
||||||
use crate::util::Optional;
|
use crate::util::Optional;
|
||||||
#[cfg(feature = "pretty_assertions")]
|
#[cfg(feature = "pretty_assertions")]
|
||||||
@ -13,12 +16,15 @@ use std::fmt;
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use swc_common::chain;
|
use std::time::Instant;
|
||||||
use swc_common::comments::Comments;
|
use swc_common::comments::Comments;
|
||||||
use swc_common::pass::CompilerPass;
|
use swc_common::pass::CompilerPass;
|
||||||
use swc_common::pass::Repeat;
|
use swc_common::pass::Repeat;
|
||||||
use swc_common::pass::Repeated;
|
use swc_common::pass::Repeated;
|
||||||
|
use swc_common::sync::Lrc;
|
||||||
|
use swc_common::{chain, SourceMap};
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
|
use swc_ecma_transforms::fixer;
|
||||||
use swc_ecma_transforms::optimization::simplify::dead_branch_remover;
|
use swc_ecma_transforms::optimization::simplify::dead_branch_remover;
|
||||||
use swc_ecma_transforms::optimization::simplify::expr_simplifier;
|
use swc_ecma_transforms::optimization::simplify::expr_simplifier;
|
||||||
use swc_ecma_transforms::pass::JsPass;
|
use swc_ecma_transforms::pass::JsPass;
|
||||||
@ -34,7 +40,9 @@ mod drop_console;
|
|||||||
mod hoist_decls;
|
mod hoist_decls;
|
||||||
mod optimize;
|
mod optimize;
|
||||||
|
|
||||||
pub fn compressor<'a>(
|
pub(crate) fn compressor<'a>(
|
||||||
|
cm: Lrc<SourceMap>,
|
||||||
|
marks: Marks,
|
||||||
options: &'a CompressOptions,
|
options: &'a CompressOptions,
|
||||||
comments: Option<&'a dyn Comments>,
|
comments: Option<&'a dyn Comments>,
|
||||||
) -> impl 'a + JsPass {
|
) -> impl 'a + JsPass {
|
||||||
@ -43,10 +51,13 @@ pub fn compressor<'a>(
|
|||||||
visitor: drop_console(),
|
visitor: drop_console(),
|
||||||
};
|
};
|
||||||
let compressor = Compressor {
|
let compressor = Compressor {
|
||||||
comments,
|
cm,
|
||||||
|
marks,
|
||||||
options,
|
options,
|
||||||
pass: 0,
|
comments,
|
||||||
changed: false,
|
changed: false,
|
||||||
|
pass: 0,
|
||||||
|
data: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
chain!(
|
chain!(
|
||||||
@ -57,10 +68,13 @@ pub fn compressor<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Compressor<'a> {
|
struct Compressor<'a> {
|
||||||
|
cm: Lrc<SourceMap>,
|
||||||
|
marks: Marks,
|
||||||
options: &'a CompressOptions,
|
options: &'a CompressOptions,
|
||||||
comments: Option<&'a dyn Comments>,
|
comments: Option<&'a dyn Comments>,
|
||||||
changed: bool,
|
changed: bool,
|
||||||
pass: usize,
|
pass: usize,
|
||||||
|
data: Option<ProgramData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompilerPass for Compressor<'_> {
|
impl CompilerPass for Compressor<'_> {
|
||||||
@ -77,6 +91,7 @@ impl Repeated for Compressor<'_> {
|
|||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.changed = false;
|
self.changed = false;
|
||||||
self.pass += 1;
|
self.pass += 1;
|
||||||
|
self.data = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +99,7 @@ impl Compressor<'_> {
|
|||||||
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
|
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
|
||||||
where
|
where
|
||||||
T: StmtLike,
|
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.
|
// Skip if `use asm` exists.
|
||||||
if stmts.iter().any(|stmt| match stmt.as_stmt() {
|
if stmts.iter().any(|stmt| match stmt.as_stmt() {
|
||||||
@ -104,17 +119,6 @@ impl Compressor<'_> {
|
|||||||
return;
|
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);
|
stmts.visit_mut_children_with(self);
|
||||||
|
|
||||||
// TODO: drop unused
|
// TODO: drop unused
|
||||||
@ -125,38 +129,56 @@ impl VisitMut for Compressor<'_> {
|
|||||||
noop_visit_mut_type!();
|
noop_visit_mut_type!();
|
||||||
|
|
||||||
fn visit_mut_module(&mut self, n: &mut Module) {
|
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 {
|
if self.options.passes != 0 && self.options.passes + 1 <= self.pass {
|
||||||
let done = dump(&*n);
|
let done = dump(&*n);
|
||||||
log::trace!("===== Done =====\n{}", done);
|
log::debug!("===== Done =====\n{}", done);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary
|
// Temporary
|
||||||
if self.pass > 10 {
|
if self.pass > 30 {
|
||||||
panic!("Infinite loop detected")
|
panic!("Infinite loop detected (current pass = {})", self.pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = if cfg!(feature = "debug") {
|
let start = if cfg!(feature = "debug") {
|
||||||
let start = dump(&*n);
|
let start = dump(&n.clone().fold_with(&mut fixer(None)));
|
||||||
log::trace!("===== Start =====\n{}", start);
|
log::debug!("===== Start =====\n{}", start);
|
||||||
start
|
start
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
log::info!(
|
||||||
|
"compress: Running expression simplifier (pass = {})",
|
||||||
|
self.pass
|
||||||
|
);
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let mut visitor = expr_simplifier();
|
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();
|
self.changed |= visitor.changed();
|
||||||
if visitor.changed() {
|
if visitor.changed() {
|
||||||
log::trace!("compressor: Simplified expressions");
|
log::debug!("compressor: Simplified expressions");
|
||||||
if cfg!(feature = "debug") {
|
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() {
|
if cfg!(feature = "debug") && !visitor.changed() {
|
||||||
let simplified = dump(&*n);
|
let simplified = dump(&n.clone().fold_with(&mut fixer(None)));
|
||||||
if start != simplified {
|
if start != simplified {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DebugUsingDisplay(&start),
|
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
|
// TODO: reset_opt_flags
|
||||||
//
|
//
|
||||||
// This is swc version of `node.optimize(this);`.
|
// 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);
|
n.visit_mut_with(&mut visitor);
|
||||||
self.changed |= visitor.changed();
|
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 {
|
if self.options.conditionals || self.options.dead_code {
|
||||||
@ -184,14 +225,18 @@ impl VisitMut for Compressor<'_> {
|
|||||||
"".into()
|
"".into()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
let mut v = dead_branch_remover();
|
let mut v = dead_branch_remover();
|
||||||
n.map_with_mut(|n| n.fold_with(&mut v));
|
n.map_with_mut(|n| n.fold_with(&mut v));
|
||||||
|
|
||||||
|
let end_time = Instant::now();
|
||||||
|
|
||||||
if cfg!(feature = "debug") {
|
if cfg!(feature = "debug") {
|
||||||
let simplified = dump(&*n);
|
let simplified = dump(&*n);
|
||||||
|
|
||||||
if start != simplified {
|
if start != simplified {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"===== Removed dead branches =====\n{}\n==== ===== ===== ===== ======\n{}",
|
"===== Removed dead branches =====\n{}\n==== ===== ===== ===== ======\n{}",
|
||||||
start,
|
start,
|
||||||
simplified
|
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();
|
self.changed |= v.changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,14 +258,20 @@ impl VisitMut for Compressor<'_> {
|
|||||||
invoke(&*n);
|
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>) {
|
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);
|
self.handle_stmt_likes(stmts);
|
||||||
|
|
||||||
stmts.retain(|stmt| match stmt {
|
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>) {
|
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
|
||||||
self.handle_stmt_likes(stmts);
|
self.handle_stmt_likes(stmts);
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use super::Optimizer;
|
use super::Optimizer;
|
||||||
use crate::analyzer::analyze;
|
|
||||||
use crate::compress::optimize::is_left_access_to_arguments;
|
use crate::compress::optimize::is_left_access_to_arguments;
|
||||||
use std::iter::repeat_with;
|
use std::iter::repeat_with;
|
||||||
use swc_atoms::js_word;
|
use swc_atoms::js_word;
|
||||||
use swc_common::DUMMY_SP;
|
use swc_common::DUMMY_SP;
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
|
use swc_ecma_utils::find_ids;
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
use swc_ecma_utils::private_ident;
|
use swc_ecma_utils::private_ident;
|
||||||
|
use swc_ecma_utils::Id;
|
||||||
use swc_ecma_visit::noop_visit_mut_type;
|
use swc_ecma_visit::noop_visit_mut_type;
|
||||||
use swc_ecma_visit::VisitMut;
|
use swc_ecma_visit::VisitMut;
|
||||||
use swc_ecma_visit::VisitMutWith;
|
use swc_ecma_visit::VisitMutWith;
|
||||||
@ -36,7 +37,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("arguments: Optimizing computed access to arguments");
|
log::debug!("arguments: Optimizing computed access to arguments");
|
||||||
member.computed = false;
|
member.computed = false;
|
||||||
member.prop = Box::new(Expr::Ident(Ident {
|
member.prop = Box::new(Expr::Ident(Ident {
|
||||||
span: prop.span,
|
span: prop.span,
|
||||||
@ -75,9 +76,9 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// If a function has a variable named `arguments`, we abort.
|
// If a function has a variable named `arguments`, we abort.
|
||||||
let data = analyze(&f.body);
|
let data: Vec<Id> = find_ids(&f.body);
|
||||||
for (id, var) in &data.vars {
|
for id in &data {
|
||||||
if id.0 == js_word!("arguments") && var.declared {
|
if id.0 == js_word!("arguments") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,7 +114,7 @@ impl ArgReplacer<'_> {
|
|||||||
let new_args = idx + 1 - self.params.len();
|
let new_args = idx + 1 - self.params.len();
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("arguments: Injecting {} parameters", new_args);
|
log::debug!("arguments: Injecting {} parameters", new_args);
|
||||||
let mut start = self.params.len();
|
let mut start = self.params.len();
|
||||||
self.params.extend(
|
self.params.extend(
|
||||||
repeat_with(|| {
|
repeat_with(|| {
|
||||||
@ -191,7 +192,7 @@ impl VisitMut for ArgReplacer<'_> {
|
|||||||
if let Some(param) = self.params.get(idx) {
|
if let Some(param) = self.params.get(idx) {
|
||||||
match ¶m.pat {
|
match ¶m.pat {
|
||||||
Pat::Ident(i) => {
|
Pat::Ident(i) => {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"arguments: Replacing access to arguments to \
|
"arguments: Replacing access to arguments to \
|
||||||
normal reference",
|
normal reference",
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,7 @@ impl Optimizer<'_> {
|
|||||||
if s.stmts.len() == 1 {
|
if s.stmts.len() == 1 {
|
||||||
if let Stmt::Return(s) = &mut s.stmts[0] {
|
if let Stmt::Return(s) = &mut s.stmts[0] {
|
||||||
if let Some(arg) = &mut s.arg {
|
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());
|
*b = BlockStmtOrExpr::Expr(arg.take());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,20 @@
|
|||||||
use super::Optimizer;
|
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 crate::util::make_bool;
|
||||||
use swc_atoms::js_word;
|
use swc_atoms::js_word;
|
||||||
use swc_common::Spanned;
|
use swc_common::Spanned;
|
||||||
use swc_common::DUMMY_SP;
|
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
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::Type;
|
||||||
use swc_ecma_utils::Value;
|
use swc_ecma_utils::Value;
|
||||||
use swc_ecma_utils::Value::Known;
|
use swc_ecma_utils::Value::Known;
|
||||||
use swc_ecma_utils::Value::Unknown;
|
use swc_ecma_utils::Value::Unknown;
|
||||||
|
use swc_ecma_utils::{undefined, ExprExt};
|
||||||
|
|
||||||
/// Methods related to the options `bools` and `bool_as_ints`.
|
/// Methods related to the options `bools` and `bool_as_ints`.
|
||||||
impl Optimizer<'_> {
|
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()`
|
/// - `!condition() || !-3.5` => `!condition()`
|
||||||
///
|
///
|
||||||
@ -104,9 +53,9 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
if can_remove {
|
if can_remove {
|
||||||
if *op == op!("&&") {
|
if *op == op!("&&") {
|
||||||
log::trace!("booleans: Compressing `!foo && true` as `!foo`");
|
log::debug!("booleans: Compressing `!foo && true` as `!foo`");
|
||||||
} else {
|
} else {
|
||||||
log::trace!("booleans: Compressing `!foo || false` as `!foo`");
|
log::debug!("booleans: Compressing `!foo || false` as `!foo`");
|
||||||
}
|
}
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
*e = *left.take();
|
*e = *left.take();
|
||||||
@ -128,10 +77,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
match e {
|
match e {
|
||||||
Expr::Unary(UnaryExpr {
|
Expr::Unary(UnaryExpr {
|
||||||
span,
|
op: op!("!"), arg, ..
|
||||||
op: op!("!"),
|
|
||||||
arg,
|
|
||||||
..
|
|
||||||
}) => match &mut **arg {
|
}) => match &mut **arg {
|
||||||
Expr::Bin(BinExpr {
|
Expr::Bin(BinExpr {
|
||||||
op: op!("&&"),
|
op: op!("&&"),
|
||||||
@ -139,30 +85,123 @@ impl Optimizer<'_> {
|
|||||||
right,
|
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;
|
self.changed = true;
|
||||||
*e = Expr::Bin(BinExpr {
|
self.negate(arg);
|
||||||
span: *span,
|
*e = *arg.take();
|
||||||
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(),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
return;
|
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) {
|
pub(super) fn compress_useless_deletes(&mut self, e: &mut Expr) {
|
||||||
if !self.options.bools {
|
if !self.options.bools {
|
||||||
return;
|
return;
|
||||||
@ -226,7 +265,7 @@ impl Optimizer<'_> {
|
|||||||
} else {
|
} else {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
let span = delete.arg.span();
|
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 {
|
*e = Expr::Seq(SeqExpr {
|
||||||
span,
|
span,
|
||||||
exprs: vec![delete.arg.take(), Box::new(make_bool(span, true))],
|
exprs: vec![delete.arg.take(), Box::new(make_bool(span, true))],
|
||||||
@ -241,26 +280,84 @@ impl Optimizer<'_> {
|
|||||||
if convert_to_true {
|
if convert_to_true {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
let span = delete.arg.span();
|
let span = delete.arg.span();
|
||||||
log::trace!("booleans: Compressing `delete` => true");
|
log::debug!("booleans: Compressing `delete` => true");
|
||||||
*e = make_bool(span, true);
|
*e = make_bool(span, true);
|
||||||
return;
|
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`.
|
/// This method converts `!1` to `0`.
|
||||||
pub(super) fn optimize_expr_in_bool_ctx(&mut self, n: &mut Expr) {
|
pub(super) fn optimize_expr_in_bool_ctx(&mut self, n: &mut Expr) {
|
||||||
if !self.options.bools {
|
if !self.options.bools {
|
||||||
return;
|
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 {
|
match n {
|
||||||
Expr::Unary(UnaryExpr {
|
Expr::Unary(UnaryExpr {
|
||||||
span,
|
span,
|
||||||
op: op!("!"),
|
op: op!("!"),
|
||||||
arg,
|
arg,
|
||||||
}) => match &**arg {
|
}) => match &mut **arg {
|
||||||
Expr::Lit(Lit::Num(Number { value, .. })) => {
|
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;
|
self.changed = true;
|
||||||
*n = Expr::Lit(Lit::Num(Number {
|
*n = Expr::Lit(Lit::Num(Number {
|
||||||
@ -268,6 +365,15 @@ impl Optimizer<'_> {
|
|||||||
value: if *value == 0.0 { 1.0 } else { 0.0 },
|
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"),
|
op: op!("typeof"),
|
||||||
arg,
|
arg,
|
||||||
}) => {
|
}) => {
|
||||||
log::trace!("Optimizing: typeof => true (in bool context)");
|
log::debug!("Optimizing: typeof => true (in bool context)");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
|
|
||||||
match &**arg {
|
match &**arg {
|
||||||
@ -301,7 +407,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr::Lit(Lit::Str(s)) => {
|
Expr::Lit(Lit::Str(s)) => {
|
||||||
log::trace!("Converting string as boolean expressions");
|
log::debug!("Converting string as boolean expressions");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
*n = Expr::Lit(Lit::Num(Number {
|
*n = Expr::Lit(Lit::Num(Number {
|
||||||
span: s.span,
|
span: s.span,
|
||||||
@ -314,7 +420,7 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if self.options.bools {
|
if self.options.bools {
|
||||||
log::trace!("booleans: Converting number as boolean expressions");
|
log::debug!("booleans: Converting number as boolean expressions");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
*n = Expr::Lit(Lit::Num(Number {
|
*n = Expr::Lit(Lit::Num(Number {
|
||||||
span: num.span,
|
span: num.span,
|
||||||
@ -331,7 +437,7 @@ impl Optimizer<'_> {
|
|||||||
}) => {
|
}) => {
|
||||||
// Optimize if (a ?? false); as if (a);
|
// Optimize if (a ?? false); as if (a);
|
||||||
if let Value::Known(false) = right.as_pure_bool() {
|
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)"
|
"Dropping right operand of `??` as it's always false (in bool context)"
|
||||||
);
|
);
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
@ -348,7 +454,7 @@ impl Optimizer<'_> {
|
|||||||
// `a || false` => `a` (as it will be casted to boolean anyway)
|
// `a || false` => `a` (as it will be casted to boolean anyway)
|
||||||
|
|
||||||
if let Known(false) = right.as_pure_bool() {
|
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;
|
self.changed = true;
|
||||||
*n = *left.take();
|
*n = *left.take();
|
||||||
return;
|
return;
|
||||||
@ -359,8 +465,9 @@ impl Optimizer<'_> {
|
|||||||
let span = n.span();
|
let span = n.span();
|
||||||
let v = n.as_pure_bool();
|
let v = n.as_pure_bool();
|
||||||
if let Known(v) = v {
|
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);
|
*n = make_bool(span, v);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -381,7 +488,7 @@ impl Optimizer<'_> {
|
|||||||
None => match &mut *stmt.cons {
|
None => match &mut *stmt.cons {
|
||||||
Stmt::Expr(cons) => {
|
Stmt::Expr(cons) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("conditionals: `if (foo) bar;` => `foo && bar`");
|
log::debug!("conditionals: `if (foo) bar;` => `foo && bar`");
|
||||||
*s = Stmt::Expr(ExprStmt {
|
*s = Stmt::Expr(ExprStmt {
|
||||||
span: stmt.span,
|
span: stmt.span,
|
||||||
expr: Box::new(Expr::Bin(BinExpr {
|
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_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
|
use swc_ecma_utils::prepend;
|
||||||
use swc_ecma_utils::Id;
|
use swc_ecma_utils::Id;
|
||||||
use swc_ecma_utils::StmtLike;
|
use swc_ecma_utils::StmtLike;
|
||||||
use swc_ecma_visit::noop_visit_mut_type;
|
use swc_ecma_visit::noop_visit_mut_type;
|
||||||
@ -57,7 +58,7 @@ impl Optimizer<'_> {
|
|||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +85,20 @@ impl Optimizer<'_> {
|
|||||||
{
|
{
|
||||||
return;
|
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 {
|
let value = match &*assign.right {
|
||||||
@ -94,6 +109,12 @@ impl Optimizer<'_> {
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"collpase_vars: Decided to inline {}{:?}",
|
||||||
|
left.id.sym,
|
||||||
|
left.id.span.ctxt
|
||||||
|
);
|
||||||
|
|
||||||
self.lits.insert(left.to_id(), value);
|
self.lits.insert(left.to_id(), value);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -101,139 +122,284 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collapse single-use non-constant variables, side effects permitting.
|
/// 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
|
where
|
||||||
T: StmtLike,
|
T: StmtLike,
|
||||||
|
Vec<T>:
|
||||||
|
VisitWith<VarWithOutInitCounter> + VisitMutWith<VarMover> + VisitMutWith<VarPrepender>,
|
||||||
{
|
{
|
||||||
if !self.options.collapse_vars {
|
if !self.options.collapse_vars {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let indexes = stmts
|
{
|
||||||
.windows(2)
|
// let mut found_other = false;
|
||||||
.enumerate()
|
// let mut need_work = false;
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only inline for some subset of statements.
|
// for stmt in &*stmts {
|
||||||
match r {
|
// match stmt.as_stmt() {
|
||||||
Stmt::Expr(..) | Stmt::Return(..) => {}
|
// Some(Stmt::Decl(Decl::Var(
|
||||||
_ => return None,
|
// 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() {
|
// found_other = true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
// Check for nested variable declartions.
|
||||||
let mut chekcer = InlinabiltyChecker { can_inline: true };
|
let mut v = VarWithOutInitCounter::default();
|
||||||
last.init
|
stmts.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||||
.visit_with(&Invalid { span: DUMMY_SP }, &mut chekcer);
|
if !v.need_work {
|
||||||
if !chekcer.can_inline {
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
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 vars = {
|
||||||
let mut values = FxHashMap::default();
|
let mut v = VarMover {
|
||||||
for (idx, stmt) in stmts.take().into_iter().enumerate() {
|
vars: Default::default(),
|
||||||
match stmt.try_into_stmt() {
|
var_decl_kind: Default::default(),
|
||||||
Ok(mut stmt) => match stmt {
|
};
|
||||||
Stmt::Decl(Decl::Var(mut v)) if indexes.contains(&idx) => {
|
stmts.visit_mut_with(&mut v);
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
new.push(T::from_stmt(stmt));
|
v.vars
|
||||||
}
|
};
|
||||||
},
|
|
||||||
Err(item) => {
|
// Prepend vars
|
||||||
new.push(item);
|
|
||||||
|
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.
|
pub(super) struct VarPrepender {
|
||||||
struct InlinabiltyChecker {
|
vars: Vec<VarDeclarator>,
|
||||||
can_inline: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visit for InlinabiltyChecker {
|
impl VisitMut for VarPrepender {
|
||||||
noop_visit_type!();
|
noop_visit_mut_type!();
|
||||||
|
|
||||||
fn visit_update_expr(&mut self, _: &UpdateExpr, _: &dyn Node) {
|
fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
|
||||||
self.can_inline = false;
|
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> {
|
struct Inliner<'a> {
|
||||||
@ -249,6 +415,8 @@ impl VisitMut for Inliner<'_> {
|
|||||||
match e {
|
match e {
|
||||||
Expr::Ident(i) => {
|
Expr::Ident(i) => {
|
||||||
if let Some(value) = self.values.remove(&i.to_id()) {
|
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");
|
*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 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::make_bool;
|
||||||
use crate::util::SpanExt;
|
use crate::util::SpanExt;
|
||||||
|
use std::mem::swap;
|
||||||
use swc_common::EqIgnoreSpan;
|
use swc_common::EqIgnoreSpan;
|
||||||
use swc_common::Spanned;
|
use swc_common::Spanned;
|
||||||
use swc_common::DUMMY_SP;
|
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
|
/// Methods related to the option `conditionals`. All methods are noop if
|
||||||
/// `conditionals` is false.
|
/// `conditionals` is false.
|
||||||
impl Optimizer<'_> {
|
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.
|
/// Removes useless operands of an logical expressions.
|
||||||
pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
|
pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
|
||||||
if !self.options.conditionals {
|
if !self.options.conditionals {
|
||||||
@ -47,7 +150,7 @@ impl Optimizer<'_> {
|
|||||||
// `!!b || true` => true
|
// `!!b || true` => true
|
||||||
if let Known(true) = rb {
|
if let Known(true) = rb {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("conditionals: `!!foo || true` => `true`");
|
log::debug!("conditionals: `!!foo || true` => `true`");
|
||||||
*e = make_bool(bin.span, true);
|
*e = make_bool(bin.span, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -70,7 +173,7 @@ impl Optimizer<'_> {
|
|||||||
(Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
|
(Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
|
||||||
if (*cons.right).eq_ignore_span(&*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;
|
self.changed = true;
|
||||||
|
|
||||||
*e = Expr::Bin(BinExpr {
|
*e = Expr::Bin(BinExpr {
|
||||||
@ -91,13 +194,10 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// - `foo ? 1 : false` => `!!foo && 1`
|
/// - `foo ? bar : false` => `!!foo && bar`
|
||||||
/// - `!foo ? true : 0` => `!foo || 0`
|
/// - `!foo ? true : bar` => `!foo || bar`
|
||||||
|
/// - `foo ? false : bar` => `!foo && bar`
|
||||||
pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
|
pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
|
||||||
if !self.options.conditionals {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cond = match e {
|
let cond = match e {
|
||||||
Expr::Cond(cond) => cond,
|
Expr::Cond(cond) => cond,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -107,7 +207,7 @@ impl Optimizer<'_> {
|
|||||||
if let Known(Type::Bool) = lt {
|
if let Known(Type::Bool) = lt {
|
||||||
let lb = cond.cons.as_pure_bool();
|
let lb = cond.cons.as_pure_bool();
|
||||||
if let Known(true) = lb {
|
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.
|
// Negate twice to convert `test` to boolean.
|
||||||
self.negate_twice(&mut cond.test);
|
self.negate_twice(&mut cond.test);
|
||||||
@ -123,21 +223,19 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify this rule.
|
// TODO: Verify this rule.
|
||||||
if false {
|
if let Known(false) = lb {
|
||||||
if let Known(false) = lb {
|
log::debug!("conditionals: `foo ? false : bar` => `!foo && bar`");
|
||||||
log::trace!("conditionals: `foo ? false : bar` => `!foo && bar`");
|
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
self.negate(&mut cond.test);
|
self.negate(&mut cond.test);
|
||||||
|
|
||||||
*e = Expr::Bin(BinExpr {
|
*e = Expr::Bin(BinExpr {
|
||||||
span: cond.span,
|
span: cond.span,
|
||||||
op: op!("&&"),
|
op: op!("&&"),
|
||||||
left: cond.test.take(),
|
left: cond.test.take(),
|
||||||
right: cond.alt.take(),
|
right: cond.alt.take(),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +243,7 @@ impl Optimizer<'_> {
|
|||||||
if let Known(Type::Bool) = rt {
|
if let Known(Type::Bool) = rt {
|
||||||
let rb = cond.alt.as_pure_bool();
|
let rb = cond.alt.as_pure_bool();
|
||||||
if let Known(false) = rb {
|
if let Known(false) = rb {
|
||||||
log::trace!("conditionals: `foo ? 1 : false` => `!!foo && 1`");
|
log::debug!("conditionals: `foo ? bar : false` => `!!foo && bar`");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
|
|
||||||
// Negate twice to convert `test` to boolean.
|
// Negate twice to convert `test` to boolean.
|
||||||
@ -159,6 +257,22 @@ impl Optimizer<'_> {
|
|||||||
});
|
});
|
||||||
return;
|
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;
|
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 cur: Option<IfStmt> = None;
|
||||||
let mut new = Vec::with_capacity(stmts.len());
|
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) {
|
pub(super) fn compress_if_stmt_as_cond(&mut self, s: &mut Stmt) {
|
||||||
if !self.options.conditionals {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stmt = match s {
|
let stmt = match s {
|
||||||
Stmt::If(v) => v,
|
Stmt::If(v) => v,
|
||||||
_ => return,
|
_ => 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
|
// If alt does not exist, an if statement is better than a conditional
|
||||||
// expression.
|
// expression.
|
||||||
let alt = match &mut stmt.alt {
|
let alt = match &mut stmt.alt {
|
||||||
@ -303,7 +430,7 @@ impl Optimizer<'_> {
|
|||||||
Expr::Unary(UnaryExpr {
|
Expr::Unary(UnaryExpr {
|
||||||
op: op!("!"), arg, ..
|
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 {
|
let mut expr = Box::new(Expr::Bin(BinExpr {
|
||||||
span: DUMMY_SP,
|
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 {
|
let mut expr = Box::new(Expr::Bin(BinExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
@ -356,29 +483,27 @@ impl Optimizer<'_> {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (a) b(); else c(); => a ? b() : c()
|
if self.options.conditionals || self.options.bools {
|
||||||
log::trace!(
|
// if (a) b(); else c(); => a ? b() : c()
|
||||||
"Compressing if statement as conditional expression (even though cons and alt is not \
|
log::debug!(
|
||||||
compressable)"
|
"Compressing if statement as conditional expression (even though cons and alt is \
|
||||||
);
|
not compressable)"
|
||||||
self.changed = true;
|
);
|
||||||
*s = Stmt::Expr(ExprStmt {
|
self.changed = true;
|
||||||
span: stmt.span.with_mark(self.done),
|
*s = Stmt::Expr(ExprStmt {
|
||||||
expr: Box::new(Expr::Cond(CondExpr {
|
span: stmt.span.with_mark(self.done),
|
||||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
expr: Box::new(Expr::Cond(CondExpr {
|
||||||
test: stmt.test.take(),
|
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||||
cons: Box::new(cons.take()),
|
test: stmt.test.take(),
|
||||||
alt: Box::new(alt.take()),
|
cons: Box::new(cons.take()),
|
||||||
})),
|
alt: Box::new(alt.take()),
|
||||||
})
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compress a conditional expression if cons and alt is simillar
|
/// Compress a conditional expression if cons and alt is simillar
|
||||||
pub(super) fn compress_cond_expr_if_simillar(&mut self, e: &mut Expr) {
|
pub(super) fn compress_cond_expr_if_simillar(&mut self, e: &mut Expr) {
|
||||||
if !self.options.conditionals {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cond = match e {
|
let cond = match e {
|
||||||
Expr::Cond(expr) => expr,
|
Expr::Cond(expr) => expr,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -398,7 +523,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
// x ? x : y => x || y
|
// x ? x : y => x || y
|
||||||
if cond.test.is_ident() && cond.test.eq_ignore_span(&cond.cons) {
|
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;
|
self.changed = true;
|
||||||
*e = Expr::Bin(BinExpr {
|
*e = Expr::Bin(BinExpr {
|
||||||
span: cond.span,
|
span: cond.span,
|
||||||
@ -417,43 +542,24 @@ impl Optimizer<'_> {
|
|||||||
alt: &mut Expr,
|
alt: &mut Expr,
|
||||||
is_for_if_stmt: bool,
|
is_for_if_stmt: bool,
|
||||||
) -> Option<Expr> {
|
) -> 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 {
|
return Some(Expr::Seq(SeqExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
exprs: vec![test.take(), Box::new(cons.take())],
|
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) {
|
match (cons, alt) {
|
||||||
(Expr::Call(cons), Expr::Call(alt)) => {
|
(Expr::Call(cons), Expr::Call(alt)) => {
|
||||||
let cons_callee = cons.callee.as_expr().and_then(|e| e.as_ident())?;
|
let cons_callee = cons.callee.as_expr().and_then(|e| e.as_ident())?;
|
||||||
//
|
//
|
||||||
|
|
||||||
if !cons.callee.eq_ignore_span(&alt.callee) {
|
if !cons.callee.eq_ignore_span(&alt.callee) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -462,7 +568,7 @@ impl Optimizer<'_> {
|
|||||||
.data
|
.data
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|data| data.vars.get(&cons_callee.to_id()))
|
.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);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if side_effect_free
|
if side_effect_free
|
||||||
@ -476,8 +582,9 @@ impl Optimizer<'_> {
|
|||||||
.zip(alt.args.iter())
|
.zip(alt.args.iter())
|
||||||
.filter(|(cons, alt)| !cons.eq_ignore_span(alt))
|
.filter(|(cons, alt)| !cons.eq_ignore_span(alt))
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
if diff_count == 1 {
|
if diff_count == 1 {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"conditionals: Merging cons and alt as only one argument differs"
|
"conditionals: Merging cons and alt as only one argument differs"
|
||||||
);
|
);
|
||||||
self.changed = true;
|
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 \
|
"Compreessing if into cond as there's no side effect and the number of \
|
||||||
arguments is 1"
|
arguments is 1"
|
||||||
);
|
);
|
||||||
@ -556,7 +663,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !side_effect_free && is_for_if_stmt {
|
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 {
|
return Some(Expr::Cond(CondExpr {
|
||||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||||
test: test.take(),
|
test: test.take(),
|
||||||
@ -601,7 +708,7 @@ impl Optimizer<'_> {
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"Compreessing if statement into a condiotnal expression of `new` as \
|
"Compreessing if statement into a condiotnal expression of `new` as \
|
||||||
there's no side effect and the number of arguments is 1"
|
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)
|
&& cons.left.eq_ignore_span(&alt.left)
|
||||||
&& is_simple_lhs(&cons.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 {
|
Some(Expr::Assign(AssignExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
op: cons.op,
|
op: cons.op,
|
||||||
@ -639,7 +746,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
// a ? b ? c() : d() : d() => a && b ? c() : d()
|
// a ? b ? c() : d() : d() => a && b ? c() : d()
|
||||||
(Expr::Cond(cons), alt) if (*cons.alt).eq_ignore_span(&*alt) => {
|
(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 {
|
return Some(Expr::Cond(CondExpr {
|
||||||
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
span: DUMMY_SP.with_ctxt(self.done_ctxt),
|
||||||
test: Box::new(Expr::Bin(BinExpr {
|
test: Box::new(Expr::Bin(BinExpr {
|
||||||
@ -656,16 +763,19 @@ impl Optimizer<'_> {
|
|||||||
// z ? "fuji" : (condition(), "fuji");
|
// z ? "fuji" : (condition(), "fuji");
|
||||||
// =>
|
// =>
|
||||||
// (z || condition(), "fuji");
|
// (z || condition(), "fuji");
|
||||||
(cons, Expr::Seq(alt))
|
(cons, Expr::Seq(alt)) if (**alt.exprs.last().unwrap()).eq_ignore_span(&*cons) => {
|
||||||
if alt.exprs.len() == 2 && (**alt.exprs.last().unwrap()).eq_ignore_span(&*cons) =>
|
self.changed = true;
|
||||||
{
|
log::debug!("conditionals: Reducing seq expr in alt");
|
||||||
log::trace!("conditionals: Reducing seq expr in alt");
|
|
||||||
//
|
//
|
||||||
|
alt.exprs.pop();
|
||||||
let first = Box::new(Expr::Bin(BinExpr {
|
let first = Box::new(Expr::Bin(BinExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
left: test.take(),
|
left: test.take(),
|
||||||
op: op!("||"),
|
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 {
|
return Some(Expr::Seq(SeqExpr {
|
||||||
span: DUMMY_SP,
|
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,
|
_ => 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> {
|
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 {
|
PatOrExpr::Pat(l) => match &**l {
|
||||||
Pat::Ident(_) => return true,
|
Pat::Ident(_) => return true,
|
||||||
|
Pat::Expr(e) => match &**e {
|
||||||
|
Expr::Ident(..) => return true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
_ => 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)
|
.map(|var| var.is_fn_local)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"dead_code: Dropping an assigment to a varaible declared in \
|
"dead_code: Dropping an assigment to a varaible declared in \
|
||||||
function because function is being terminated"
|
function because function is being terminated"
|
||||||
);
|
);
|
||||||
|
@ -65,7 +65,7 @@ impl Optimizer<'_> {
|
|||||||
sym: js_word!("undefined"),
|
sym: js_word!("undefined"),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
log::trace!("evaluate: `undefined` -> `void 0`");
|
log::debug!("evaluate: `undefined` -> `void 0`");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
*e = *undefined(*span);
|
*e = *undefined(*span);
|
||||||
return;
|
return;
|
||||||
@ -76,7 +76,7 @@ impl Optimizer<'_> {
|
|||||||
sym: js_word!("Infinity"),
|
sym: js_word!("Infinity"),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
log::trace!("evaluate: `Infinity` -> `1 / 0`");
|
log::debug!("evaluate: `Infinity` -> `1 / 0`");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
*e = Expr::Bin(BinExpr {
|
*e = Expr::Bin(BinExpr {
|
||||||
span: span.with_ctxt(self.done_ctxt),
|
span: span.with_ctxt(self.done_ctxt),
|
||||||
@ -149,7 +149,7 @@ impl Optimizer<'_> {
|
|||||||
1 => match &*args[0].expr {
|
1 => match &*args[0].expr {
|
||||||
Expr::Lit(Lit::Str(exp)) => {
|
Expr::Lit(Lit::Str(exp)) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Converting RegExpr call into a regexp literal `/{}/`",
|
"evaluate: Converting RegExpr call into a regexp literal `/{}/`",
|
||||||
exp.value
|
exp.value
|
||||||
);
|
);
|
||||||
@ -165,7 +165,7 @@ impl Optimizer<'_> {
|
|||||||
_ => match (&*args[0].expr, &*args[1].expr) {
|
_ => match (&*args[0].expr, &*args[1].expr) {
|
||||||
(Expr::Lit(Lit::Str(exp)), Expr::Lit(Lit::Str(flags))) => {
|
(Expr::Lit(Lit::Str(exp)), Expr::Lit(Lit::Str(flags))) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Converting RegExpr call into a regexp literal `/{}/{}`",
|
"evaluate: Converting RegExpr call into a regexp literal `/{}/{}`",
|
||||||
exp.value,
|
exp.value,
|
||||||
flags.value
|
flags.value
|
||||||
@ -209,7 +209,7 @@ impl Optimizer<'_> {
|
|||||||
match char::from_u32(v) {
|
match char::from_u32(v) {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evanluate: Evaluated `String.charCodeAt({})` as `{}`",
|
"evanluate: Evaluated `String.charCodeAt({})` as `{}`",
|
||||||
char_code,
|
char_code,
|
||||||
v
|
v
|
||||||
@ -362,7 +362,7 @@ impl Optimizer<'_> {
|
|||||||
match c {
|
match c {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Evaluated `charCodeAt` of a string literal as `{}`",
|
"evaluate: Evaluated `charCodeAt` of a string literal as `{}`",
|
||||||
v
|
v
|
||||||
);
|
);
|
||||||
@ -373,7 +373,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Evaluated `charCodeAt` of a string literal as `NaN`",
|
"evaluate: Evaluated `charCodeAt` of a string literal as `NaN`",
|
||||||
);
|
);
|
||||||
*e = Expr::Ident(Ident::new(
|
*e = Expr::Ident(Ident::new(
|
||||||
@ -389,7 +389,7 @@ impl Optimizer<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.changed = true;
|
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 {
|
*e = Expr::Lit(Lit::Str(Str {
|
||||||
value: new_val.into(),
|
value: new_val.into(),
|
||||||
..s
|
..s
|
||||||
@ -412,7 +412,7 @@ impl Optimizer<'_> {
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Reducing a call to `Number` into an unary operation"
|
"evaluate: Reducing a call to `Number` into an unary operation"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -442,7 +442,7 @@ impl Optimizer<'_> {
|
|||||||
Expr::Call(..) => {
|
Expr::Call(..) => {
|
||||||
if let Some(value) = self.eval_as_number(&e) {
|
if let Some(value) = self.eval_as_number(&e) {
|
||||||
self.changed = true;
|
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 {
|
*e = Expr::Lit(Lit::Num(Number {
|
||||||
span: e.span(),
|
span: e.span(),
|
||||||
@ -462,7 +462,7 @@ impl Optimizer<'_> {
|
|||||||
if let Known(l) = l {
|
if let Known(l) = l {
|
||||||
if let Known(r) = r {
|
if let Known(r) = r {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("evaluate: Evaulated `{:?} ** {:?}`", l, r);
|
log::debug!("evaluate: Evaulated `{:?} ** {:?}`", l, r);
|
||||||
|
|
||||||
let value = l.powf(r);
|
let value = l.powf(r);
|
||||||
*e = Expr::Lit(Lit::Num(Number {
|
*e = Expr::Lit(Lit::Num(Number {
|
||||||
@ -507,7 +507,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("evaluate: `0 / 0` => `NaN`");
|
log::debug!("evaluate: `0 / 0` => `NaN`");
|
||||||
|
|
||||||
// Sign does not matter for NaN
|
// Sign does not matter for NaN
|
||||||
*e = Expr::Ident(Ident::new(
|
*e = Expr::Ident(Ident::new(
|
||||||
@ -518,7 +518,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
(FpCategory::Normal, FpCategory::Zero) => {
|
(FpCategory::Normal, FpCategory::Zero) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("evaluate: `constant / 0` => `Infinity`");
|
log::debug!("evaluate: `constant / 0` => `Infinity`");
|
||||||
|
|
||||||
// Sign does not matter for NaN
|
// Sign does not matter for NaN
|
||||||
*e = if ln.is_sign_positive() == rn.is_sign_positive() {
|
*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);
|
let value = num_to_fixed(num.value, precision + 1);
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Evaluating `{}.toFixed({})` as `{}`",
|
"evaluate: Evaluating `{}.toFixed({})` as `{}`",
|
||||||
num,
|
num,
|
||||||
precision,
|
precision,
|
||||||
@ -816,7 +816,7 @@ impl Optimizer<'_> {
|
|||||||
match call.args.len() {
|
match call.args.len() {
|
||||||
0 => {
|
0 => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("evaluate: Dropping array.slice call");
|
log::debug!("evaluate: Dropping array.slice call");
|
||||||
*e = *obj.take();
|
*e = *obj.take();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -825,7 +825,7 @@ impl Optimizer<'_> {
|
|||||||
let start = start.floor() as usize;
|
let start = start.floor() as usize;
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("evaluate: Reducing array.slice({}) call", start);
|
log::debug!("evaluate: Reducing array.slice({}) call", start);
|
||||||
|
|
||||||
if start >= arr.elems.len() {
|
if start >= arr.elems.len() {
|
||||||
*e = Expr::Array(ArrayLit {
|
*e = Expr::Array(ArrayLit {
|
||||||
@ -850,7 +850,7 @@ impl Optimizer<'_> {
|
|||||||
if let Known(end) = end {
|
if let Known(end) = end {
|
||||||
let end = end.floor() as usize;
|
let end = end.floor() as usize;
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Reducing array.slice({}, {}) call",
|
"evaluate: Reducing array.slice({}, {}) call",
|
||||||
start,
|
start,
|
||||||
end
|
end
|
||||||
@ -936,7 +936,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaludate: Reduced `funtion.valueOf()` into a function expression"
|
"evaludate: Reduced `funtion.valueOf()` into a function expression"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -977,7 +977,7 @@ impl Optimizer<'_> {
|
|||||||
// As we used as_pure_bool, we can drop it.
|
// As we used as_pure_bool, we can drop it.
|
||||||
if v && e.op == op!("&&") {
|
if v && e.op == op!("&&") {
|
||||||
self.changed = true;
|
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();
|
left.right.take();
|
||||||
return;
|
return;
|
||||||
@ -985,7 +985,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
if !v && e.op == op!("||") {
|
if !v && e.op == op!("||") {
|
||||||
self.changed = true;
|
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();
|
left.right.take();
|
||||||
return;
|
return;
|
||||||
@ -1011,7 +1011,7 @@ impl Optimizer<'_> {
|
|||||||
//
|
//
|
||||||
if is_pure_undefined_or_null(&obj) {
|
if is_pure_undefined_or_null(&obj) {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Reduced an optioanl chaining operation because object is \
|
"evaluate: Reduced an optioanl chaining operation because object is \
|
||||||
always null or undefined"
|
always null or undefined"
|
||||||
);
|
);
|
||||||
@ -1028,7 +1028,7 @@ impl Optimizer<'_> {
|
|||||||
}) => {
|
}) => {
|
||||||
if is_pure_undefined_or_null(&callee) {
|
if is_pure_undefined_or_null(&callee) {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"evaluate: Reduced a call expression with optioanl chaining operation \
|
"evaluate: Reduced a call expression with optioanl chaining operation \
|
||||||
because object is always null or undefined"
|
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 {
|
match &p.key {
|
||||||
PropName::Str(s) => {
|
PropName::Str(s) => {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"hoist_props: Storing a varaible to inline \
|
"hoist_props: Storing a varaible to inline \
|
||||||
properties"
|
properties"
|
||||||
);
|
);
|
||||||
@ -96,7 +96,7 @@ impl Optimizer<'_> {
|
|||||||
.insert((name.to_id(), s.value.clone()), value);
|
.insert((name.to_id(), s.value.clone()), value);
|
||||||
}
|
}
|
||||||
PropName::Ident(i) => {
|
PropName::Ident(i) => {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"hoist_props: Storing a varaible to inline \
|
"hoist_props: Storing a varaible to inline \
|
||||||
properties"
|
properties"
|
||||||
);
|
);
|
||||||
@ -118,9 +118,12 @@ impl Optimizer<'_> {
|
|||||||
.data
|
.data
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|data| {
|
.and_then(|data| {
|
||||||
data.vars
|
data.vars.get(&name.to_id()).map(|v| {
|
||||||
.get(&name.to_id())
|
v.ref_count == 1
|
||||||
.map(|v| v.ref_count == 1 && v.has_property_access)
|
&& v.has_property_access
|
||||||
|
&& v.is_fn_local
|
||||||
|
&& !v.used_in_loop
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
@ -132,6 +135,15 @@ impl Optimizer<'_> {
|
|||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match &*init {
|
||||||
|
Expr::This(..) => {
|
||||||
|
n.init = Some(init);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
match self.vars_for_prop_hoisting.insert(name.to_id(), init) {
|
match self.vars_for_prop_hoisting.insert(name.to_id(), init) {
|
||||||
Some(prev) => {
|
Some(prev) => {
|
||||||
panic!(
|
panic!(
|
||||||
@ -140,7 +152,13 @@ impl Optimizer<'_> {
|
|||||||
prev
|
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()) {
|
if let Some(value) = self.vars_for_prop_hoisting.remove(&obj.to_id()) {
|
||||||
member.obj = ExprOrSuper::Expr(value);
|
member.obj = ExprOrSuper::Expr(value);
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("hoist_props: Inlined a property");
|
log::debug!("hoist_props: Inlined a property");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +192,7 @@ impl Optimizer<'_> {
|
|||||||
if let Some(value) =
|
if let Some(value) =
|
||||||
self.simple_props.get(&(obj.to_id(), prop.sym.clone()))
|
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;
|
self.changed = true;
|
||||||
*e = *value.clone()
|
*e = *value.clone()
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
use super::Optimizer;
|
use super::Optimizer;
|
||||||
use crate::compress::optimize::is_pure_undefined;
|
use crate::compress::optimize::is_pure_undefined;
|
||||||
|
use crate::debug::dump;
|
||||||
use crate::util::ExprOptExt;
|
use crate::util::ExprOptExt;
|
||||||
|
use swc_common::Spanned;
|
||||||
use swc_common::DUMMY_SP;
|
use swc_common::DUMMY_SP;
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
|
use swc_ecma_utils::prepend;
|
||||||
use swc_ecma_utils::undefined;
|
use swc_ecma_utils::undefined;
|
||||||
use swc_ecma_utils::StmtLike;
|
use swc_ecma_utils::StmtLike;
|
||||||
use swc_ecma_visit::noop_visit_type;
|
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
|
/// Methods related to the option `if_return`. All methods are noop if
|
||||||
/// `if_return` is false.
|
/// `if_return` is false.
|
||||||
impl Optimizer<'_> {
|
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.
|
/// Merge simple return statements in if statements.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -34,33 +247,94 @@ impl Optimizer<'_> {
|
|||||||
/// return a ? foo() : bar();
|
/// return a ? foo() : bar();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub(super) fn merge_if_returns<T>(&mut self, stmts: &mut Vec<T>)
|
pub(super) fn merge_if_returns(&mut self, stmts: &mut Vec<Stmt>) {
|
||||||
where
|
if !self.options.if_return {
|
||||||
T: StmtLike,
|
return;
|
||||||
Vec<T>: VisitWith<ReturnFinder>,
|
}
|
||||||
{
|
|
||||||
if !self.options.if_return || stmts.is_empty() {
|
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;
|
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
|
// There's no return statment so merging requires injecting unnecessary `void 0`
|
||||||
.last()
|
if return_count == 0 {
|
||||||
.map(|stmt| match stmt.as_stmt() {
|
log::trace!("if_return: [x] Aborting because we failed to find return");
|
||||||
Some(Stmt::If(..)) => true,
|
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,
|
_ => false,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.count();
|
||||||
if return_count <= 1 && ends_with_if {
|
|
||||||
return;
|
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
|
let start = stmts
|
||||||
.iter()
|
.iter()
|
||||||
|
.skip(skip)
|
||||||
.position(|stmt| match stmt.as_stmt() {
|
.position(|stmt| match stmt.as_stmt() {
|
||||||
Some(v) => self.can_merge_stmt_as_if_return(v),
|
Some(v) => self.can_merge_stmt_as_if_return(v),
|
||||||
None => false,
|
None => false,
|
||||||
@ -70,16 +344,16 @@ impl Optimizer<'_> {
|
|||||||
let ends_with_mergable = stmts
|
let ends_with_mergable = stmts
|
||||||
.last()
|
.last()
|
||||||
.map(|stmt| match stmt.as_stmt() {
|
.map(|stmt| match stmt.as_stmt() {
|
||||||
Some(Stmt::Return(..)) | Some(Stmt::Expr(..)) => true,
|
Some(s) => self.can_merge_stmt_as_if_return(s),
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if stmts.len() - start == 1 && ends_with_mergable {
|
if stmts.len() == start + skip + 1 || !ends_with_mergable {
|
||||||
return;
|
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),
|
Some(s) => self.can_merge_stmt_as_if_return(s),
|
||||||
_ => false,
|
_ => 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;
|
self.changed = true;
|
||||||
|
|
||||||
let mut cur: Option<Box<Expr>> = None;
|
let mut cur: Option<Box<Expr>> = None;
|
||||||
let mut new = Vec::with_capacity(stmts.len());
|
let mut new = Vec::with_capacity(stmts.len());
|
||||||
|
|
||||||
for stmt in stmts.take() {
|
for (idx, stmt) in stmts.take().into_iter().enumerate() {
|
||||||
let stmt = match stmt.try_into_stmt() {
|
if let Some(not_mergable) = idx_of_not_mergable {
|
||||||
Ok(stmt) => {
|
if idx < not_mergable {
|
||||||
if !self.can_merge_stmt_as_if_return(&stmt) {
|
new.push(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);
|
|
||||||
continue;
|
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 {
|
let is_nonconditional_return = match stmt {
|
||||||
Stmt::Return(..) => true,
|
Stmt::Return(..) => true,
|
||||||
@ -203,17 +482,17 @@ impl Optimizer<'_> {
|
|||||||
let expr = self.ignore_return_value(&mut cur);
|
let expr = self.ignore_return_value(&mut cur);
|
||||||
|
|
||||||
if let Some(cur) = expr {
|
if let Some(cur) = expr {
|
||||||
new.push(T::from_stmt(Stmt::Expr(ExprStmt {
|
new.push(Stmt::Expr(ExprStmt {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
expr: Box::new(cur),
|
expr: Box::new(cur),
|
||||||
})))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
new.push(T::from_stmt(Stmt::Return(ReturnStmt {
|
new.push(Stmt::Return(ReturnStmt {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
arg: Some(cur),
|
arg: Some(cur),
|
||||||
})));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,6 +506,11 @@ impl Optimizer<'_> {
|
|||||||
fn merge_if_returns_to(&mut self, stmt: Stmt, mut exprs: Vec<Box<Expr>>) -> Expr {
|
fn merge_if_returns_to(&mut self, stmt: Stmt, mut exprs: Vec<Box<Expr>>) -> Expr {
|
||||||
//
|
//
|
||||||
match stmt {
|
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 {
|
Stmt::If(IfStmt {
|
||||||
span,
|
span,
|
||||||
test,
|
test,
|
||||||
@ -276,9 +560,10 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn can_merge_stmt_as_if_return(&self, s: &Stmt) -> bool {
|
fn can_merge_stmt_as_if_return(&self, s: &Stmt) -> bool {
|
||||||
match s {
|
let res = match s {
|
||||||
Stmt::Expr(..) | Stmt::Return(..) => true,
|
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)
|
self.can_merge_stmt_as_if_return(&stmt.cons)
|
||||||
&& stmt
|
&& stmt
|
||||||
.alt
|
.alt
|
||||||
@ -287,7 +572,12 @@ impl Optimizer<'_> {
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => 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_function(&mut self, _: &Function, _: &dyn Node) {}
|
||||||
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &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::compress::optimize::Ctx;
|
||||||
use crate::util::idents_used_by;
|
use crate::util::idents_used_by;
|
||||||
use crate::util::make_number;
|
use crate::util::make_number;
|
||||||
use crate::util::IdentUsageCollector;
|
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
@ -13,20 +12,17 @@ use swc_common::Spanned;
|
|||||||
use swc_common::DUMMY_SP;
|
use swc_common::DUMMY_SP;
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
use swc_ecma_utils::find_ids;
|
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
use swc_ecma_utils::undefined;
|
use swc_ecma_utils::undefined;
|
||||||
use swc_ecma_utils::DestructuringFinder;
|
use swc_ecma_utils::ExprFactory;
|
||||||
use swc_ecma_utils::ExprExt;
|
|
||||||
use swc_ecma_utils::Id;
|
use swc_ecma_utils::Id;
|
||||||
use swc_ecma_visit::VisitMutWith;
|
use swc_ecma_visit::VisitMutWith;
|
||||||
use swc_ecma_visit::VisitWith;
|
|
||||||
|
|
||||||
/// Methods related to the option `negate_iife`.
|
/// Methods related to the option `negate_iife`.
|
||||||
impl Optimizer<'_> {
|
impl Optimizer<'_> {
|
||||||
/// Negates iife, while ignore return value.
|
/// Negates iife, while ignore return value.
|
||||||
pub(super) fn negate_iife_ignoring_ret(&mut self, e: &mut Expr) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +38,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
match callee {
|
match callee {
|
||||||
Expr::Fn(..) => {
|
Expr::Fn(..) => {
|
||||||
log::trace!("negate_iife: Negating iife");
|
log::debug!("negate_iife: Negating iife");
|
||||||
*e = Expr::Unary(UnaryExpr {
|
*e = Expr::Unary(UnaryExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
op: op!("!"),
|
op: op!("!"),
|
||||||
@ -54,38 +50,72 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if it did any work.
|
||||||
|
///
|
||||||
///
|
///
|
||||||
/// - `iife ? foo : bar` => `!iife ? bar : foo`
|
/// - `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 {
|
let cond = match e {
|
||||||
Expr::Cond(v) => v,
|
Expr::Cond(v) => v,
|
||||||
_ => return,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let test_call = match &mut *cond.test {
|
let test_call = match &mut *cond.test {
|
||||||
Expr::Call(e) => e,
|
Expr::Call(e) => e,
|
||||||
_ => return,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let callee = match &mut test_call.callee {
|
let callee = match &mut test_call.callee {
|
||||||
ExprOrSuper::Super(_) => return,
|
ExprOrSuper::Super(_) => return false,
|
||||||
ExprOrSuper::Expr(e) => &mut **e,
|
ExprOrSuper::Expr(e) => &mut **e,
|
||||||
};
|
};
|
||||||
|
|
||||||
match callee {
|
match callee {
|
||||||
Expr::Fn(..) => {
|
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 {
|
cond.test = Box::new(Expr::Unary(UnaryExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
op: op!("!"),
|
op: op!("!"),
|
||||||
arg: cond.test.take(),
|
arg: cond.test.take(),
|
||||||
}));
|
}));
|
||||||
swap(&mut cond.cons, &mut cond.alt);
|
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.
|
/// Methods related to iife.
|
||||||
@ -168,21 +198,33 @@ impl Optimizer<'_> {
|
|||||||
if usage.reassigned {
|
if usage.reassigned {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if usage.ref_count != 1 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(arg) = arg {
|
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);
|
let should_be_inlined = self.can_be_inlined_for_iife(arg);
|
||||||
if should_be_inlined {
|
if should_be_inlined {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"iife: Trying to inline {}{:?}",
|
"iife: Trying to inline argument ({}{:?})",
|
||||||
param.id.sym,
|
param.id.sym,
|
||||||
param.id.span.ctxt
|
param.id.span.ctxt
|
||||||
);
|
);
|
||||||
vars.insert(param.to_id(), arg.clone());
|
vars.insert(param.to_id(), arg.clone());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"iife: Trying to inline {}{:?} (undefined)",
|
"iife: Trying to inline argument ({}{:?}) (undefined)",
|
||||||
param.id.sym,
|
param.id.sym,
|
||||||
param.id.span.ctxt
|
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
|
where
|
||||||
N: VisitMutWith<Self>,
|
N: VisitMutWith<Self>,
|
||||||
{
|
{
|
||||||
|
log::debug!("inline: inline_vars_in_node");
|
||||||
let ctx = Ctx {
|
let ctx = Ctx {
|
||||||
inline_prevented: false,
|
inline_prevented: false,
|
||||||
..self.ctx
|
..self.ctx
|
||||||
@ -249,10 +292,6 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ctx.inline_prevented {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let call = match e {
|
let call = match e {
|
||||||
Expr::Call(v) => v,
|
Expr::Call(v) => v,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -267,31 +306,44 @@ impl Optimizer<'_> {
|
|||||||
ExprOrSuper::Expr(e) => &mut **e,
|
ExprOrSuper::Expr(e) => &mut **e,
|
||||||
};
|
};
|
||||||
|
|
||||||
if call.args.iter().any(|arg| match &*arg.expr {
|
if self.ctx.inline_prevented {
|
||||||
Expr::Member(MemberExpr {
|
log::trace!("iife: [x] Inline is prevented");
|
||||||
computed: false, ..
|
|
||||||
}) => false,
|
|
||||||
_ => arg.expr.may_have_side_effects(),
|
|
||||||
}) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match callee {
|
match callee {
|
||||||
Expr::Arrow(f) => {
|
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()) {
|
if f.params.iter().any(|param| !param.is_ident()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_param_used_by_body(&f.params, &f.body) {
|
let param_ids = f
|
||||||
return;
|
.params
|
||||||
}
|
.iter()
|
||||||
|
.map(|p| p.clone().ident().unwrap().id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
match &mut f.body {
|
match &mut f.body {
|
||||||
BlockStmtOrExpr::BlockStmt(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 {
|
if let Some(new) = new {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("inline: Inlining a function call (arrow)");
|
log::debug!("inline: Inlining a function call (arrow)");
|
||||||
|
|
||||||
*e = new;
|
*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 {
|
match &mut f.body {
|
||||||
BlockStmtOrExpr::BlockStmt(_) => {
|
BlockStmtOrExpr::BlockStmt(_) => {
|
||||||
@ -330,17 +365,74 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
BlockStmtOrExpr::Expr(body) => {
|
BlockStmtOrExpr::Expr(body) => {
|
||||||
self.changed = true;
|
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 {
|
*e = Expr::Seq(SeqExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
exprs: vec![Box::new(make_number(DUMMY_SP, 0.0)), body.take()],
|
exprs,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Fn(f) => {
|
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 {
|
if f.function.is_generator {
|
||||||
|
log::trace!("iife: [x] Cannot inline generator");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,17 +441,29 @@ impl Optimizer<'_> {
|
|||||||
Pat::Object(..) | Pat::Array(..) | Pat::Assign(..) | Pat::Rest(..) => true,
|
Pat::Object(..) | Pat::Array(..) | Pat::Assign(..) | Pat::Rest(..) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
|
log::trace!("iife: [x] Found complex pattern");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(i) = &f.ident {
|
if let Some(i) = &f.ident {
|
||||||
if idents_used_by(&f.function.body).contains(&i.to_id()) {
|
if idents_used_by(&f.function.body).contains(&i.to_id()) {
|
||||||
|
log::trace!("iife: [x] Recursive?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_param_used_by_body(&f.function.params, &f.function.body) {
|
for arg in &call.args {
|
||||||
return;
|
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();
|
let body = f.function.body.as_mut().unwrap();
|
||||||
@ -368,10 +472,22 @@ impl Optimizer<'_> {
|
|||||||
return;
|
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 {
|
if let Some(new) = new {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("inline: Inlining a function call");
|
log::debug!("inline: Inlining a function call");
|
||||||
|
|
||||||
*e = new;
|
*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 {
|
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() {
|
Stmt::Return(ReturnStmt { arg, .. }) => match arg.as_deref() {
|
||||||
Some(Expr::Await(..)) => false,
|
Some(Expr::Await(..)) => false,
|
||||||
|
|
||||||
@ -401,12 +545,92 @@ impl Optimizer<'_> {
|
|||||||
},
|
},
|
||||||
_ => false,
|
_ => 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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.changed = true;
|
||||||
|
log::debug!("inline: Inling an iife");
|
||||||
|
|
||||||
let mut exprs = vec![];
|
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 {
|
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) => {
|
Stmt::Expr(stmt) => {
|
||||||
exprs.push(stmt.expr);
|
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::class_has_side_effect;
|
||||||
use crate::compress::optimize::util::is_valid_for_lhs;
|
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 super::Optimizer;
|
||||||
use swc_atoms::js_word;
|
use swc_atoms::js_word;
|
||||||
use swc_common::Spanned;
|
use swc_common::Spanned;
|
||||||
|
use swc_common::DUMMY_SP;
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
@ -21,9 +25,18 @@ impl Optimizer<'_> {
|
|||||||
None => return,
|
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();
|
&& 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
|
if self
|
||||||
.data
|
.data
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -57,13 +70,34 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if should_preserve && usage.var_kind != Some(VarDeclKind::Const) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if usage.cond_init || usage.used_above_decl {
|
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;
|
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 {
|
if !usage.reassigned {
|
||||||
match &**init {
|
match &**init {
|
||||||
Expr::Fn(..) | Expr::Arrow(..) => {
|
Expr::Fn(..) | Expr::Arrow(..) => {
|
||||||
@ -95,14 +129,12 @@ impl Optimizer<'_> {
|
|||||||
&& (!usage.mutated || (usage.assign_count == 0 && !usage.reassigned))
|
&& (!usage.mutated || (usage.assign_count == 0 && !usage.reassigned))
|
||||||
&& match &**init {
|
&& match &**init {
|
||||||
Expr::Lit(lit) => match lit {
|
Expr::Lit(lit) => match lit {
|
||||||
Lit::Str(_)
|
Lit::Str(_) => true,
|
||||||
| Lit::Bool(_)
|
Lit::Bool(_) | Lit::Null(_) | Lit::Num(_) | Lit::BigInt(_) => true,
|
||||||
| Lit::Null(_)
|
|
||||||
| Lit::Num(_)
|
|
||||||
| Lit::BigInt(_) => true,
|
|
||||||
Lit::Regex(_) => self.options.unsafe_regexp,
|
Lit::Regex(_) => self.options.unsafe_regexp,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
Expr::This(..) => usage.is_fn_local,
|
||||||
Expr::Arrow(arr) => is_arrow_simple_enough(arr),
|
Expr::Arrow(arr) => is_arrow_simple_enough(arr),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -114,7 +146,7 @@ impl Optimizer<'_> {
|
|||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"inline: Decided to inline '{}{:?}' because it's simple",
|
"inline: Decided to inline '{}{:?}' because it's simple",
|
||||||
i.id.sym,
|
i.id.sym,
|
||||||
i.id.span.ctxt
|
i.id.span.ctxt
|
||||||
@ -122,7 +154,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
self.lits.insert(i.to_id(), init.take());
|
self.lits.insert(i.to_id(), init.take());
|
||||||
} else {
|
} else {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"inline: Decided to copy '{}{:?}' because it's simple",
|
"inline: Decided to copy '{}{:?}' because it's simple",
|
||||||
i.id.sym,
|
i.id.sym,
|
||||||
i.id.span.ctxt
|
i.id.span.ctxt
|
||||||
@ -133,6 +165,13 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.ctx.inline_as_assignment {
|
||||||
|
if cfg!(feature = "debug") {
|
||||||
|
log::trace!("inline: [x] inline_as_assignment is true");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Single use => inlined
|
// Single use => inlined
|
||||||
if is_inline_enabled
|
if is_inline_enabled
|
||||||
&& !should_preserve
|
&& !should_preserve
|
||||||
@ -158,7 +197,11 @@ impl Optimizer<'_> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
match &**init {
|
match &**init {
|
||||||
Expr::Lit(Lit::Regex(..)) => return,
|
Expr::Lit(Lit::Regex(..)) => {
|
||||||
|
if !usage.is_fn_local || usage.used_in_loop {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Expr::This(..) => {
|
Expr::This(..) => {
|
||||||
// Don't inline this if it passes function boundaries.
|
// Don't inline this if it passes function boundaries.
|
||||||
@ -170,17 +213,20 @@ impl Optimizer<'_> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if init.may_have_side_effects() {
|
if usage.used_in_loop {
|
||||||
if !self
|
match &**init {
|
||||||
.vars_accessible_without_side_effect
|
Expr::Lit(..) | Expr::Ident(..) | Expr::Fn(..) => {}
|
||||||
.contains(&i.to_id())
|
_ => {
|
||||||
{
|
return;
|
||||||
// TODO: Inline partially
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!(
|
if init.may_have_side_effects() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
"inline: Decided to inline '{}{:?}' because it's used only once",
|
"inline: Decided to inline '{}{:?}' because it's used only once",
|
||||||
i.id.sym,
|
i.id.sym,
|
||||||
i.id.span.ctxt
|
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()))
|
.and_then(|data| data.vars.get(&i.to_id()))
|
||||||
{
|
{
|
||||||
if !usage.reassigned {
|
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 {
|
match &*decl {
|
||||||
Decl::Fn(..) => {
|
Decl::Fn(..) => {
|
||||||
self.typeofs.insert(i.to_id(), js_word!("function"));
|
self.typeofs.insert(i.to_id(), js_word!("function"));
|
||||||
@ -286,6 +336,10 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.ctx.inline_as_assignment {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(usage) = self
|
if let Some(usage) = self
|
||||||
.data
|
.data
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -295,7 +349,7 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if usage.reassigned {
|
if usage.reassigned || usage.inline_prevented {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,14 +359,14 @@ impl Optimizer<'_> {
|
|||||||
match &f.function.body {
|
match &f.function.body {
|
||||||
Some(body) => {
|
Some(body) => {
|
||||||
if self.is_fn_body_simple_enough_to_inline(body) {
|
if self.is_fn_body_simple_enough_to_inline(body) {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"inline: Decided to inline function '{}{:?}' as it's very \
|
"inline: Decided to inline function '{}{:?}' as it's very \
|
||||||
simple",
|
simple",
|
||||||
f.ident.sym,
|
f.ident.sym,
|
||||||
f.ident.span.ctxt
|
f.ident.span.ctxt
|
||||||
);
|
);
|
||||||
|
|
||||||
self.lits.insert(
|
self.vars_for_inlining.insert(
|
||||||
i.to_id(),
|
i.to_id(),
|
||||||
match decl {
|
match decl {
|
||||||
Decl::Fn(f) => Box::new(Expr::Fn(FnExpr {
|
Decl::Fn(f) => Box::new(Expr::Fn(FnExpr {
|
||||||
@ -351,14 +405,14 @@ impl Optimizer<'_> {
|
|||||||
self.changed = true;
|
self.changed = true;
|
||||||
match &decl {
|
match &decl {
|
||||||
Decl::Class(c) => {
|
Decl::Class(c) => {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"inline: Decided to inline class '{}{:?}' as it's used only once",
|
"inline: Decided to inline class '{}{:?}' as it's used only once",
|
||||||
c.ident.sym,
|
c.ident.sym,
|
||||||
c.ident.span.ctxt
|
c.ident.span.ctxt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Decl::Fn(f) => {
|
Decl::Fn(f) => {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"inline: Decided to inline function '{}{:?}' as it's used only once",
|
"inline: Decided to inline function '{}{:?}' as it's used only once",
|
||||||
f.ident.sym,
|
f.ident.sym,
|
||||||
f.ident.span.ctxt
|
f.ident.span.ctxt
|
||||||
@ -366,6 +420,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.vars_for_inlining.insert(
|
self.vars_for_inlining.insert(
|
||||||
i.to_id(),
|
i.to_id(),
|
||||||
match decl.take() {
|
match decl.take() {
|
||||||
@ -399,33 +454,92 @@ impl Optimizer<'_> {
|
|||||||
return;
|
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 {
|
match &*value {
|
||||||
Expr::Lit(Lit::Num(..)) => {
|
Expr::Lit(Lit::Num(..)) => {
|
||||||
if self.ctx.in_lhs_of_assign {
|
if self.ctx.in_lhs_of_assign {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Member(..) => {
|
||||||
|
if self.ctx.executed_multiple_time {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("inline: Replacing a variable with cheap expression");
|
log::debug!("inline: Replacing a variable with cheap expression");
|
||||||
|
|
||||||
*e = *value;
|
*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) {
|
if self.ctx.is_exact_lhs_of_assign && !is_valid_for_lhs(&value) {
|
||||||
return;
|
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;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"inline: Replacing '{}{:?}' with an expression",
|
"inline: Replacing '{}{:?}' with an expression",
|
||||||
i.sym,
|
i.sym,
|
||||||
i.span.ctxt
|
i.span.ctxt
|
||||||
);
|
);
|
||||||
|
|
||||||
*e = *value.clone();
|
*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;
|
self.changed = true;
|
||||||
|
|
||||||
let mut cur: Option<VarDecl> = None;
|
let mut cur: Option<VarDecl> = None;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::compress::optimize::unused::UnreachableHandler;
|
use crate::compress::optimize::unused::UnreachableHandler;
|
||||||
use crate::compress::optimize::Optimizer;
|
use crate::compress::optimize::Optimizer;
|
||||||
|
use swc_common::Spanned;
|
||||||
use swc_common::DUMMY_SP;
|
use swc_common::DUMMY_SP;
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
@ -19,7 +20,7 @@ impl Optimizer<'_> {
|
|||||||
match s {
|
match s {
|
||||||
Stmt::While(stmt) => {
|
Stmt::While(stmt) => {
|
||||||
self.changed = true;
|
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 {
|
*s = Stmt::For(ForStmt {
|
||||||
span: stmt.span,
|
span: stmt.span,
|
||||||
init: None,
|
init: None,
|
||||||
@ -32,7 +33,7 @@ impl Optimizer<'_> {
|
|||||||
let val = stmt.test.as_pure_bool();
|
let val = stmt.test.as_pure_bool();
|
||||||
if let Known(true) = val {
|
if let Known(true) = val {
|
||||||
self.changed = true;
|
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 {
|
*s = Stmt::For(ForStmt {
|
||||||
span: stmt.span,
|
span: stmt.span,
|
||||||
@ -79,7 +80,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
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
|
self.prepend_stmts
|
||||||
.extend(f.init.take().map(|init| match init {
|
.extend(f.init.take().map(|init| match init {
|
||||||
VarDeclOrExpr::VarDecl(var) => Stmt::Decl(Decl::Var(var)),
|
VarDeclOrExpr::VarDecl(var) => Stmt::Decl(Decl::Var(var)),
|
||||||
@ -97,6 +98,70 @@ impl Optimizer<'_> {
|
|||||||
*s = Stmt::Empty(EmptyStmt { span: DUMMY_SP })
|
*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;`
|
/// - `while(false) { var a; foo() }` => `var a;`
|
||||||
pub(super) fn optiimze_loops_if_cond_is_false(&mut self, stmt: &mut Stmt) {
|
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);
|
let changed = UnreachableHandler::preserve_vars(stmt);
|
||||||
self.changed |= changed;
|
self.changed |= changed;
|
||||||
if changed {
|
if changed {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"loops: Removing unreachable while statement without side effects"
|
"loops: Removing unreachable while statement without side effects"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -120,7 +185,7 @@ impl Optimizer<'_> {
|
|||||||
let changed = UnreachableHandler::preserve_vars(&mut w.body);
|
let changed = UnreachableHandler::preserve_vars(&mut w.body);
|
||||||
self.changed |= changed;
|
self.changed |= changed;
|
||||||
if 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);
|
let changed = UnreachableHandler::preserve_vars(&mut f.body);
|
||||||
self.changed |= changed;
|
self.changed |= changed;
|
||||||
if 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();
|
self.changed |= f.init.is_some() | f.update.is_some();
|
||||||
|
|
||||||
@ -154,7 +219,7 @@ impl Optimizer<'_> {
|
|||||||
} else if let Known(true) = val {
|
} else if let Known(true) = val {
|
||||||
if purity.is_pure() {
|
if purity.is_pure() {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"loops: Remving `test` part of a for stmt as it's always true"
|
"loops: Remving `test` part of a for stmt as it's always true"
|
||||||
);
|
);
|
||||||
f.test = None;
|
f.test = None;
|
||||||
@ -190,7 +255,7 @@ impl Optimizer<'_> {
|
|||||||
} else {
|
} else {
|
||||||
s.init = None;
|
s.init = None;
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"loops: Removed side-effect-free expressions in `init` of a for stmt"
|
"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 };
|
let value = if value.is_empty() { 0f64 } else { 1f64 };
|
||||||
|
|
||||||
self.changed = true;
|
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 }));
|
*e = Expr::Lit(Lit::Num(Number { span: *span, value }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ impl Optimizer<'_> {
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("numbers: Lifting `-`");
|
log::debug!("numbers: Lifting `-`");
|
||||||
|
|
||||||
*e = Expr::Unary(UnaryExpr {
|
*e = Expr::Unary(UnaryExpr {
|
||||||
span: arg.span,
|
span: arg.span,
|
||||||
@ -55,7 +55,7 @@ impl Optimizer<'_> {
|
|||||||
Expr::Lit(Lit::Num(Number { span, value, .. })) => {
|
Expr::Lit(Lit::Num(Number { span, value, .. })) => {
|
||||||
if value.is_sign_negative() {
|
if value.is_sign_negative() {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("numbers: Lifting `-` in a literal");
|
log::debug!("numbers: Lifting `-` in a literal");
|
||||||
|
|
||||||
*e = Expr::Unary(UnaryExpr {
|
*e = Expr::Unary(UnaryExpr {
|
||||||
span: arg.span,
|
span: arg.span,
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use super::Optimizer;
|
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::make_bool;
|
||||||
use crate::util::ValueExt;
|
use crate::util::ValueExt;
|
||||||
use std::mem::swap;
|
use std::mem::swap;
|
||||||
@ -74,7 +78,7 @@ impl Optimizer<'_> {
|
|||||||
match (lt, rt) {
|
match (lt, rt) {
|
||||||
(Type::Undefined, Type::Null) | (Type::Null, Type::Undefined) => {
|
(Type::Undefined, Type::Null) | (Type::Null, Type::Undefined) => {
|
||||||
if op == op!("===") {
|
if op == op!("===") {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"Reducing `!== null || !== undefined` check to `!= null`"
|
"Reducing `!== null || !== undefined` check to `!= null`"
|
||||||
);
|
);
|
||||||
return Some(BinExpr {
|
return Some(BinExpr {
|
||||||
@ -84,7 +88,7 @@ impl Optimizer<'_> {
|
|||||||
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
|
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"Reducing `=== null || === undefined` check to `== null`"
|
"Reducing `=== null || === undefined` check to `== null`"
|
||||||
);
|
);
|
||||||
return Some(BinExpr {
|
return Some(BinExpr {
|
||||||
@ -167,7 +171,7 @@ impl Optimizer<'_> {
|
|||||||
if e.op == op!("===") || e.op == op!("!==") {
|
if e.op == op!("===") || e.op == op!("!==") {
|
||||||
if (e.left.is_ident() || e.left.is_member()) && e.left.eq_ignore_span(&e.right) {
|
if (e.left.is_ident() || e.left.is_member()) && e.left.eq_ignore_span(&e.right) {
|
||||||
self.changed = true;
|
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!("===") {
|
e.op = if e.op == op!("===") {
|
||||||
op!("==")
|
op!("==")
|
||||||
@ -187,7 +191,7 @@ impl Optimizer<'_> {
|
|||||||
if lt == rt {
|
if lt == rt {
|
||||||
e.op = op!("==");
|
e.op = op!("==");
|
||||||
self.changed = true;
|
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 };
|
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;
|
self.changed = true;
|
||||||
return Some(Expr::Lit(Lit::Bool(Bool {
|
return Some(Expr::Lit(Lit::Bool(Bool {
|
||||||
span: n.span,
|
span: n.span,
|
||||||
@ -249,7 +253,7 @@ impl Optimizer<'_> {
|
|||||||
| Expr::Bin(BinExpr { op: op!(">"), .. }) => {
|
| Expr::Bin(BinExpr { op: op!(">"), .. }) => {
|
||||||
if let Known(Type::Bool) = arg.get_type() {
|
if let Known(Type::Bool) = arg.get_type() {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("Optimizing: `!!expr` => `expr`");
|
log::debug!("Optimizing: `!!expr` => `expr`");
|
||||||
*e = *arg.take();
|
*e = *arg.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,9 +282,9 @@ impl Optimizer<'_> {
|
|||||||
///
|
///
|
||||||
/// TODO: Handle special cases like !1 or !0
|
/// TODO: Handle special cases like !1 or !0
|
||||||
pub(super) fn negate(&mut self, e: &mut Expr) {
|
pub(super) fn negate(&mut self, e: &mut Expr) {
|
||||||
self.changed = true;
|
let start_str = dump(&*e);
|
||||||
let arg = Box::new(e.take());
|
|
||||||
|
|
||||||
|
self.changed = true;
|
||||||
match e {
|
match e {
|
||||||
Expr::Bin(bin @ BinExpr { op: op!("=="), .. })
|
Expr::Bin(bin @ BinExpr { op: op!("=="), .. })
|
||||||
| Expr::Bin(bin @ BinExpr { op: op!("!="), .. })
|
| Expr::Bin(bin @ BinExpr { op: op!("!="), .. })
|
||||||
@ -304,15 +308,68 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("negate: binary");
|
log::debug!("negate: binary");
|
||||||
return;
|
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 {
|
Expr::Unary(UnaryExpr {
|
||||||
op: op!("!"), arg, ..
|
op: op!("!"), arg, ..
|
||||||
}) => match &mut **arg {
|
}) => match &mut **arg {
|
||||||
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => {
|
Expr::Unary(UnaryExpr { op: op!("!"), .. }) => {
|
||||||
log::trace!("negate: !!bool => !bool");
|
log::debug!("negate: !!bool => !bool");
|
||||||
*e = *arg.take();
|
*e = *arg.take();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -321,22 +378,33 @@ impl Optimizer<'_> {
|
|||||||
op: op!("instanceof"),
|
op: op!("instanceof"),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
log::trace!("negate: !bool => bool");
|
log::debug!("negate: !bool => bool");
|
||||||
*e = *arg.take();
|
*e = *arg.take();
|
||||||
return;
|
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 {
|
*e = Expr::Unary(UnaryExpr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
op: op!("!"),
|
op: op!("!"),
|
||||||
arg,
|
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) {
|
pub(super) fn handle_negated_seq(&mut self, n: &mut Expr) {
|
||||||
@ -352,7 +420,7 @@ impl Optimizer<'_> {
|
|||||||
if exprs.is_empty() {
|
if exprs.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log::trace!("optimizing negated sequences");
|
log::debug!("optimizing negated sequences");
|
||||||
|
|
||||||
{
|
{
|
||||||
let last = exprs.last_mut().unwrap();
|
let last = exprs.last_mut().unwrap();
|
||||||
@ -427,24 +495,60 @@ impl Optimizer<'_> {
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::trace!("Compressing: `e = 3 & e` => `e &= 3`");
|
log::debug!("Compressing: `e = 3 & e` => `e &= 3`");
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
e.op = op;
|
e.op = op;
|
||||||
e.right = left.take();
|
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 {
|
fn can_swap_bin_operands(&mut self, l: &Expr, r: &Expr, is_for_rel: bool) -> bool {
|
||||||
match (l, r) {
|
match (l, r) {
|
||||||
(Expr::Ident(l), Expr::Ident(r)) => {
|
(Expr::Member(_), _) if is_for_rel => false,
|
||||||
self.options.comparisons && (is_for_rel || l.sym > r.sym)
|
|
||||||
|
(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::Lit(..))
|
||||||
| (
|
| (
|
||||||
Expr::Ident(..),
|
Expr::Ident(..) | Expr::Member(..),
|
||||||
Expr::Unary(UnaryExpr {
|
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) {
|
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);
|
swap(left, right);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -494,7 +598,7 @@ impl Optimizer<'_> {
|
|||||||
| Expr::Bin(e @ BinExpr { op: op!("<"), .. }) => {
|
| Expr::Bin(e @ BinExpr { op: op!("<"), .. }) => {
|
||||||
if self.options.comparisons && self.can_swap_bin_operands(&e.left, &e.right, true) {
|
if self.options.comparisons && self.can_swap_bin_operands(&e.left, &e.right, true) {
|
||||||
self.changed = 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!("<=") {
|
e.op = if e.op == op!("<=") {
|
||||||
op!(">=")
|
op!(">=")
|
||||||
@ -568,13 +672,13 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
if rb {
|
if rb {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("Optimizing: e && true => !!e");
|
log::debug!("Optimizing: e && true => !!e");
|
||||||
|
|
||||||
self.negate_twice(&mut bin.left);
|
self.negate_twice(&mut bin.left);
|
||||||
*e = *bin.left.take();
|
*e = *bin.left.take();
|
||||||
} else {
|
} else {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("Optimizing: e && false => e");
|
log::debug!("Optimizing: e && false => e");
|
||||||
|
|
||||||
*e = *bin.left.take();
|
*e = *bin.left.take();
|
||||||
}
|
}
|
||||||
@ -588,7 +692,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
if !rb {
|
if !rb {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("Optimizing: e || false => !!e");
|
log::debug!("Optimizing: e || false => !!e");
|
||||||
|
|
||||||
self.negate_twice(&mut bin.left);
|
self.negate_twice(&mut bin.left);
|
||||||
*e = *bin.left.take();
|
*e = *bin.left.take();
|
||||||
@ -648,7 +752,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
match l {
|
match l {
|
||||||
Expr::Lit(Lit::Null(..)) => {
|
Expr::Lit(Lit::Null(..)) => {
|
||||||
log::trace!("Removing null from lhs of ??");
|
log::debug!("Removing null from lhs of ??");
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
*e = r.take();
|
*e = r.take();
|
||||||
return;
|
return;
|
||||||
@ -658,7 +762,7 @@ impl Optimizer<'_> {
|
|||||||
| Expr::Lit(Lit::BigInt(..))
|
| Expr::Lit(Lit::BigInt(..))
|
||||||
| Expr::Lit(Lit::Bool(..))
|
| Expr::Lit(Lit::Bool(..))
|
||||||
| Expr::Lit(Lit::Regex(..)) => {
|
| 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;
|
self.changed = true;
|
||||||
*e = l.take();
|
*e = l.take();
|
||||||
return;
|
return;
|
||||||
@ -682,7 +786,7 @@ impl Optimizer<'_> {
|
|||||||
}) => match &**arg {
|
}) => match &**arg {
|
||||||
Expr::Ident(arg) => {
|
Expr::Ident(arg) => {
|
||||||
if let Some(value) = self.typeofs.get(&arg.to_id()).cloned() {
|
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"
|
"Converting typeof of variable to literal as we know the value"
|
||||||
);
|
);
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
@ -697,7 +801,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr::Arrow(..) | Expr::Fn(..) => {
|
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;
|
self.changed = true;
|
||||||
*e = Expr::Lit(Lit::Str(Str {
|
*e = Expr::Lit(Lit::Str(Str {
|
||||||
span: *span,
|
span: *span,
|
||||||
@ -709,7 +813,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr::Array(..) | Expr::Object(..) => {
|
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;
|
self.changed = true;
|
||||||
*e = Expr::Lit(Lit::Str(Str {
|
*e = Expr::Lit(Lit::Str(Str {
|
||||||
span: *span,
|
span: *span,
|
||||||
|
@ -60,6 +60,10 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.ctx.is_callee {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if obj.props.iter().any(|prop| match prop {
|
if obj.props.iter().any(|prop| match prop {
|
||||||
PropOrSpread::Spread(_) => false,
|
PropOrSpread::Spread(_) => false,
|
||||||
PropOrSpread::Prop(p) => match &**p {
|
PropOrSpread::Prop(p) => match &**p {
|
||||||
@ -87,7 +91,7 @@ impl Optimizer<'_> {
|
|||||||
Prop::Shorthand(_) => {}
|
Prop::Shorthand(_) => {}
|
||||||
Prop::KeyValue(p) => {
|
Prop::KeyValue(p) => {
|
||||||
if prop_name_eq(&p.key, &key.sym) {
|
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;
|
self.changed = true;
|
||||||
*e = *p.value.clone();
|
*e = *p.value.clone();
|
||||||
return;
|
return;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,7 @@ impl Optimizer<'_> {
|
|||||||
match &*args[0].expr {
|
match &*args[0].expr {
|
||||||
Expr::Lit(Lit::Str(..)) => {
|
Expr::Lit(Lit::Str(..)) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"strings: Unsafely reduced `RegExp` call in a string context"
|
"strings: Unsafely reduced `RegExp` call in a string context"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ impl Optimizer<'_> {
|
|||||||
Expr::Lit(Lit::Str(..)) => {
|
Expr::Lit(Lit::Str(..)) => {
|
||||||
*n = *e.expr.take();
|
*n = *e.expr.take();
|
||||||
self.changed = true;
|
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();
|
let value = n.as_string();
|
||||||
if let Known(value) = value {
|
if let Known(value) = value {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"strings: Converted an expression into a string literal (in string context)"
|
"strings: Converted an expression into a string literal (in string context)"
|
||||||
);
|
);
|
||||||
*n = Expr::Lit(Lit::Str(Str {
|
*n = Expr::Lit(Lit::Str(Str {
|
||||||
@ -116,7 +116,7 @@ impl Optimizer<'_> {
|
|||||||
match n {
|
match n {
|
||||||
Expr::Lit(Lit::Num(v)) => {
|
Expr::Lit(Lit::Num(v)) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"strings: Converted a numeric literal ({}) into a string literal (in string \
|
"strings: Converted a numeric literal ({}) into a string literal (in string \
|
||||||
context)",
|
context)",
|
||||||
v.value
|
v.value
|
||||||
@ -136,7 +136,7 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"strings: Converted a regex (/{}/{}) into a string literal (in string context)",
|
"strings: Converted a regex (/{}/{}) into a string literal (in string context)",
|
||||||
v.exp,
|
v.exp,
|
||||||
v.flags
|
v.flags
|
||||||
@ -163,7 +163,7 @@ impl Optimizer<'_> {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"strings: Converting a reference ({}{:?}) into `undefined` (in string \
|
"strings: Converting a reference ({}{:?}) into `undefined` (in string \
|
||||||
context)",
|
context)",
|
||||||
i.sym,
|
i.sym,
|
||||||
@ -274,7 +274,7 @@ impl Optimizer<'_> {
|
|||||||
if let Some(l_last) = l.quasis.last_mut() {
|
if let Some(l_last) = l.quasis.last_mut() {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
|
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"template: Concatted a string (`{}`) on rhs of `+` to a template literal",
|
"template: Concatted a string (`{}`) on rhs of `+` to a template literal",
|
||||||
rs.value
|
rs.value
|
||||||
);
|
);
|
||||||
@ -294,7 +294,7 @@ impl Optimizer<'_> {
|
|||||||
if let Some(r_first) = r.quasis.first_mut() {
|
if let Some(r_first) = r.quasis.first_mut() {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
|
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"template: Prepended a string (`{}`) on lhs of `+` to a template literal",
|
"template: Prepended a string (`{}`) on lhs of `+` to a template literal",
|
||||||
ls.value
|
ls.value
|
||||||
);
|
);
|
||||||
@ -332,7 +332,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
debug_assert!(l.quasis.len() == l.exprs.len() + 1, "{:?} is invalid", l);
|
debug_assert!(l.quasis.len() == l.exprs.len() + 1, "{:?} is invalid", l);
|
||||||
self.changed = true;
|
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;
|
let left_span = left.span;
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"string: Concatting `{} + {}` to `{}`",
|
"string: Concatting `{} + {}` to `{}`",
|
||||||
second_str,
|
second_str,
|
||||||
third_str,
|
third_str,
|
||||||
@ -413,7 +413,7 @@ impl Optimizer<'_> {
|
|||||||
..
|
..
|
||||||
})) => {
|
})) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"string: Dropping empty string literal (in lhs) because it \
|
"string: Dropping empty string literal (in lhs) because it \
|
||||||
does not changes type"
|
does not changes type"
|
||||||
);
|
);
|
||||||
@ -430,7 +430,7 @@ impl Optimizer<'_> {
|
|||||||
..
|
..
|
||||||
})) => {
|
})) => {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"string: Dropping empty string literal (in rhs) because it \
|
"string: Dropping empty string literal (in rhs) because it \
|
||||||
does not changes type"
|
does not changes type"
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::mem::take;
|
||||||
|
|
||||||
use super::Optimizer;
|
use super::Optimizer;
|
||||||
use crate::util::ExprOptExt;
|
use crate::util::ExprOptExt;
|
||||||
use swc_common::EqIgnoreSpan;
|
use swc_common::EqIgnoreSpan;
|
||||||
@ -5,7 +7,9 @@ use swc_common::DUMMY_SP;
|
|||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
|
use swc_ecma_utils::prepend;
|
||||||
use swc_ecma_utils::ExprExt;
|
use swc_ecma_utils::ExprExt;
|
||||||
|
use swc_ecma_utils::StmtExt;
|
||||||
use swc_ecma_utils::Type;
|
use swc_ecma_utils::Type;
|
||||||
use swc_ecma_utils::Value::Known;
|
use swc_ecma_utils::Value::Known;
|
||||||
use swc_ecma_visit::noop_visit_type;
|
use swc_ecma_visit::noop_visit_type;
|
||||||
@ -45,6 +49,7 @@ impl Optimizer<'_> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(case_idx) = matching_case {
|
if let Some(case_idx) = matching_case {
|
||||||
|
let mut var_ids = vec![];
|
||||||
let mut stmts = vec![];
|
let mut stmts = vec![];
|
||||||
|
|
||||||
let should_preserve_switch = stmt.cases.iter().skip(case_idx).any(|case| {
|
let should_preserve_switch = stmt.cases.iter().skip(case_idx).any(|case| {
|
||||||
@ -60,9 +65,9 @@ impl Optimizer<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("switches: Removing unreachable cases from a constant switch");
|
log::debug!("switches: Removing unreachable cases from a constant switch");
|
||||||
} else {
|
} else {
|
||||||
log::trace!("switches: Removing a constant switch");
|
log::debug!("switches: Removing a constant switch");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
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) {
|
for case in stmt.cases.iter_mut().skip(case_idx) {
|
||||||
let mut found_break = false;
|
let mut found_break = false;
|
||||||
case.cons.retain(|stmt| match stmt {
|
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 inner = if should_preserve_switch {
|
||||||
let mut cases = stmt.cases.take();
|
let mut cases = stmt.cases.take();
|
||||||
let case = SwitchCase {
|
let case = SwitchCase {
|
||||||
@ -193,7 +225,7 @@ impl Optimizer<'_> {
|
|||||||
if !preserve_cases {
|
if !preserve_cases {
|
||||||
if let Some(last_non_empty) = last_non_empty {
|
if let Some(last_non_empty) = last_non_empty {
|
||||||
if last_non_empty + 1 != cases.len() {
|
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;
|
self.changed = true;
|
||||||
cases.drain(last_non_empty + 1..);
|
cases.drain(last_non_empty + 1..);
|
||||||
}
|
}
|
||||||
@ -203,7 +235,7 @@ impl Optimizer<'_> {
|
|||||||
if let Some(last) = cases.last_mut() {
|
if let Some(last) = cases.last_mut() {
|
||||||
match last.cons.last() {
|
match last.cons.last() {
|
||||||
Some(Stmt::Break(BreakStmt { label: None, .. })) => {
|
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;
|
self.changed = true;
|
||||||
last.cons.pop();
|
last.cons.pop();
|
||||||
}
|
}
|
||||||
@ -215,12 +247,23 @@ impl Optimizer<'_> {
|
|||||||
/// If a case ends with break but content is same with the consequtive case
|
/// If a case ends with break but content is same with the consequtive case
|
||||||
/// except the break statement, we merge them.
|
/// except the break statement, we merge them.
|
||||||
fn merge_cases_with_same_cons(&mut self, cases: &mut Vec<SwitchCase>) {
|
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;
|
let mut found = None;
|
||||||
'l: for (li, l) in cases.iter().enumerate().rev() {
|
'l: for (li, l) in cases.iter().enumerate().rev() {
|
||||||
if l.cons.is_empty() {
|
if l.cons.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(stop_pos) = stop_pos {
|
||||||
|
if li > stop_pos {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(l_last) = l.cons.last() {
|
if let Some(l_last) = l.cons.last() {
|
||||||
match l_last {
|
match l_last {
|
||||||
Stmt::Break(BreakStmt { label: None, .. }) => {}
|
Stmt::Break(BreakStmt { label: None, .. }) => {}
|
||||||
@ -253,7 +296,7 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
if let Some(idx) = found {
|
if let Some(idx) = found {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("switches: Merging cases with same cons");
|
log::debug!("switches: Merging cases with same cons");
|
||||||
cases[idx].cons.clear();
|
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 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_atoms::js_word;
|
||||||
|
use swc_common::Span;
|
||||||
use swc_common::DUMMY_SP;
|
use swc_common::DUMMY_SP;
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_base::ext::MapWithMut;
|
use swc_ecma_transforms_base::ext::MapWithMut;
|
||||||
use swc_ecma_utils::contains_ident_ref;
|
use swc_ecma_utils::contains_ident_ref;
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
|
use swc_ecma_utils::StmtLike;
|
||||||
use swc_ecma_visit::noop_visit_mut_type;
|
use swc_ecma_visit::noop_visit_mut_type;
|
||||||
use swc_ecma_visit::VisitMut;
|
use swc_ecma_visit::VisitMut;
|
||||||
use swc_ecma_visit::VisitMutWith;
|
use swc_ecma_visit::VisitMutWith;
|
||||||
|
|
||||||
/// Methods related to the option `unused`.
|
/// Methods related to the option `unused`.
|
||||||
impl Optimizer<'_> {
|
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) {
|
pub(super) fn drop_unused_stmt_at_end_of_fn(&mut self, s: &mut Stmt) {
|
||||||
match s {
|
match s {
|
||||||
Stmt::Return(r) => match r.arg.as_deref_mut() {
|
Stmt::Return(r) => match r.arg.as_deref_mut() {
|
||||||
@ -22,7 +80,7 @@ impl Optimizer<'_> {
|
|||||||
op: op!("void"),
|
op: op!("void"),
|
||||||
arg,
|
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;
|
self.changed = true;
|
||||||
*s = Stmt::Expr(ExprStmt {
|
*s = Stmt::Expr(ExprStmt {
|
||||||
span: *span,
|
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() {
|
if var.name.is_invalid() {
|
||||||
let side_effects = self.ignore_return_value(init);
|
let side_effects = self.ignore_return_value(init);
|
||||||
@ -62,7 +120,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
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;
|
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>) {
|
pub(super) fn drop_unused_vars(
|
||||||
if !self.options.unused || self.ctx.in_var_decl_of_for_in_or_of_loop || self.ctx.is_exported
|
&mut self,
|
||||||
{
|
var_declarator_span: Span,
|
||||||
return;
|
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
|
// Top-level
|
||||||
match self.ctx.var_kind {
|
if !has_mark {
|
||||||
Some(VarDeclKind::Var) => {
|
match self.ctx.var_kind {
|
||||||
if (!self.options.top_level() && self.options.top_retain.is_empty())
|
Some(VarDeclKind::Var) => {
|
||||||
&& self.ctx.in_top_level()
|
if (!self.options.top_level() && self.options.top_retain.is_empty())
|
||||||
{
|
&& self.ctx.in_top_level()
|
||||||
return;
|
{
|
||||||
|
if cfg!(feature = "debug") {
|
||||||
|
log::trace!(
|
||||||
|
"unused: Preserving `var` `{}` because it's top-level",
|
||||||
|
dump(&*name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Some(VarDeclKind::Let) | Some(VarDeclKind::Const) => {
|
||||||
Some(VarDeclKind::Let) | Some(VarDeclKind::Const) => {
|
if !self.options.top_level() && self.ctx.is_top_level_for_block_level_vars() {
|
||||||
if !self.options.top_level() && self.ctx.is_top_level_for_block_level_vars() {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scope) = self
|
if let Some(scope) = self
|
||||||
@ -127,12 +208,12 @@ impl Optimizer<'_> {
|
|||||||
return;
|
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>) {
|
pub(super) fn drop_unused_params(&mut self, params: &mut Vec<Param>) {
|
||||||
for param in params.iter_mut().rev() {
|
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() {
|
if !param.pat.is_invalid() {
|
||||||
return;
|
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 had_value = init.is_some();
|
||||||
let can_drop_children = had_value;
|
let can_drop_children = had_value;
|
||||||
|
|
||||||
@ -153,7 +240,9 @@ impl Optimizer<'_> {
|
|||||||
|
|
||||||
match name {
|
match name {
|
||||||
Pat::Ident(i) => {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +254,7 @@ impl Optimizer<'_> {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"unused: Dropping a variable '{}{:?}' because it is not used",
|
"unused: Dropping a variable '{}{:?}' because it is not used",
|
||||||
i.id.sym,
|
i.id.sym,
|
||||||
i.id.span.ctxt
|
i.id.span.ctxt
|
||||||
@ -173,6 +262,10 @@ impl Optimizer<'_> {
|
|||||||
// This will remove variable.
|
// This will remove variable.
|
||||||
name.take();
|
name.take();
|
||||||
return;
|
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()
|
.as_mut()
|
||||||
.and_then(|expr| self.access_numeric_property(expr, idx));
|
.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 => {}
|
None => {}
|
||||||
}
|
}
|
||||||
@ -224,7 +317,7 @@ impl Optimizer<'_> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.take_pat_if_unused(&mut p.value, prop);
|
self.take_pat_if_unused(parent_span, &mut p.value, prop);
|
||||||
}
|
}
|
||||||
ObjectPatProp::Assign(_) => {}
|
ObjectPatProp::Assign(_) => {}
|
||||||
ObjectPatProp::Rest(_) => {}
|
ObjectPatProp::Rest(_) => {}
|
||||||
@ -303,7 +396,7 @@ impl Optimizer<'_> {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"unused: Dropping a decl '{}{:?}' because it is not used",
|
"unused: Dropping a decl '{}{:?}' because it is not used",
|
||||||
ident.sym,
|
ident.sym,
|
||||||
ident.span.ctxt
|
ident.span.ctxt
|
||||||
@ -327,7 +420,14 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn drop_unused_assignments(&mut self, e: &mut Expr) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,19 +444,36 @@ impl Optimizer<'_> {
|
|||||||
return;
|
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()
|
&& self.ctx.in_top_level()
|
||||||
{
|
{
|
||||||
|
if cfg!(feature = "debug") {
|
||||||
|
log::trace!(
|
||||||
|
"unused: Preserving assignment to `{}` because it's top-level",
|
||||||
|
dump(&assign.left)
|
||||||
|
)
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let assign = match e {
|
|
||||||
Expr::Assign(e) => e,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
match &mut assign.left {
|
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 {
|
PatOrExpr::Pat(left) => match &**left {
|
||||||
Pat::Ident(i) => {
|
Pat::Ident(i) => {
|
||||||
if self.options.top_retain.contains(&i.id.sym) {
|
if self.options.top_retain.contains(&i.id.sym) {
|
||||||
@ -369,7 +486,7 @@ impl Optimizer<'_> {
|
|||||||
.and_then(|data| data.vars.get(&i.to_id()))
|
.and_then(|data| data.vars.get(&i.to_id()))
|
||||||
{
|
{
|
||||||
if var.is_fn_local && var.usage_count == 0 {
|
if var.is_fn_local && var.usage_count == 0 {
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"unused: Dropping assignment to var '{}{:?}', which is never used",
|
"unused: Dropping assignment to var '{}{:?}', which is never used",
|
||||||
i.id.sym,
|
i.id.sym,
|
||||||
i.id.span.ctxt
|
i.id.span.ctxt
|
||||||
@ -377,6 +494,14 @@ impl Optimizer<'_> {
|
|||||||
self.changed = true;
|
self.changed = true;
|
||||||
*e = *assign.right.take();
|
*e = *assign.right.take();
|
||||||
return;
|
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 {
|
if can_remove_ident {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!("Removing ident of an class / function expression");
|
log::debug!("Removing ident of an class / function expression");
|
||||||
*name = None;
|
*name = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,7 +554,7 @@ impl Optimizer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
log::trace!(
|
log::debug!(
|
||||||
"unused: Removing the name of a function expression because it's not used by \
|
"unused: Removing the name of a function expression because it's not used by \
|
||||||
it'"
|
it'"
|
||||||
);
|
);
|
||||||
|
@ -12,6 +12,12 @@ use swc_ecma_utils::prop_name_eq;
|
|||||||
use swc_ecma_utils::ExprExt;
|
use swc_ecma_utils::ExprExt;
|
||||||
|
|
||||||
impl<'b> Optimizer<'b> {
|
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>(
|
pub(super) fn access_property<'e>(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: &'e mut Expr,
|
expr: &'e mut Expr,
|
||||||
@ -224,6 +230,40 @@ pub(crate) fn class_has_side_effect(c: &Class) -> bool {
|
|||||||
false
|
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 {
|
pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {
|
||||||
match e {
|
match e {
|
||||||
Expr::Lit(..) => return false,
|
Expr::Lit(..) => return false,
|
||||||
|
@ -41,6 +41,10 @@ pub(crate) fn dump<N>(node: &N) -> String
|
|||||||
where
|
where
|
||||||
N: swc_ecma_codegen::Node + Clone + VisitMutWith<DropSpan> + VisitMutWith<Debugger>,
|
N: swc_ecma_codegen::Node + Clone + VisitMutWith<DropSpan> + VisitMutWith<Debugger>,
|
||||||
{
|
{
|
||||||
|
if !cfg!(feature = "debug") {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
let mut node = node.clone();
|
let mut node = node.clone();
|
||||||
node.visit_mut_with(&mut Debugger);
|
node.visit_mut_with(&mut Debugger);
|
||||||
node = drop_span(node);
|
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::compress::compressor;
|
||||||
use crate::hygiene::unique_marker;
|
use crate::hygiene::unique_marker;
|
||||||
|
use crate::marks::Marks;
|
||||||
use crate::option::ExtraOptions;
|
use crate::option::ExtraOptions;
|
||||||
use crate::option::MinifyOptions;
|
use crate::option::MinifyOptions;
|
||||||
use crate::pass::compute_char_freq::compute_char_freq;
|
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;
|
pub use crate::pass::hygiene::optimize_hygiene;
|
||||||
use crate::pass::mangle_names::name_mangler;
|
use crate::pass::mangle_names::name_mangler;
|
||||||
use crate::pass::mangle_props::mangle_properties;
|
use crate::pass::mangle_props::mangle_properties;
|
||||||
use crate::pass::single::single_pass_optimizer;
|
use crate::pass::precompress::precompress_optimizer;
|
||||||
use analyzer::analyze;
|
use analyzer::analyze;
|
||||||
|
use pass::postcompress::postcompress_optimizer;
|
||||||
|
use std::time::Instant;
|
||||||
use swc_common::comments::Comments;
|
use swc_common::comments::Comments;
|
||||||
|
use swc_common::sync::Lrc;
|
||||||
|
use swc_common::SourceMap;
|
||||||
use swc_ecma_ast::Module;
|
use swc_ecma_ast::Module;
|
||||||
use swc_ecma_visit::FoldWith;
|
use swc_ecma_visit::FoldWith;
|
||||||
use swc_ecma_visit::VisitMutWith;
|
use swc_ecma_visit::VisitMutWith;
|
||||||
@ -35,6 +40,7 @@ mod analyzer;
|
|||||||
mod compress;
|
mod compress;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod hygiene;
|
mod hygiene;
|
||||||
|
mod marks;
|
||||||
pub mod option;
|
pub mod option;
|
||||||
mod pass;
|
mod pass;
|
||||||
pub mod timing;
|
pub mod timing;
|
||||||
@ -43,11 +49,15 @@ mod util;
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn optimize(
|
pub fn optimize(
|
||||||
mut m: Module,
|
mut m: Module,
|
||||||
|
cm: Lrc<SourceMap>,
|
||||||
comments: Option<&dyn Comments>,
|
comments: Option<&dyn Comments>,
|
||||||
mut timings: Option<&mut Timings>,
|
mut timings: Option<&mut Timings>,
|
||||||
options: &MinifyOptions,
|
options: &MinifyOptions,
|
||||||
extra: &ExtraOptions,
|
extra: &ExtraOptions,
|
||||||
) -> Module {
|
) -> Module {
|
||||||
|
let marks = Marks::new();
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
|
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
|
||||||
// Apply 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));
|
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(
|
if let Some(options) = &options.compress {
|
||||||
options.compress.clone().unwrap_or_default(),
|
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());
|
m.visit_mut_with(&mut unique_marker());
|
||||||
|
|
||||||
@ -95,8 +108,14 @@ pub fn optimize(
|
|||||||
t.section("compress");
|
t.section("compress");
|
||||||
}
|
}
|
||||||
if let Some(options) = &options.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
|
// 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 {
|
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")]
|
#[serde(default, alias = "keep_fnames")]
|
||||||
pub keep_fn_names: bool,
|
pub keep_fn_names: bool,
|
||||||
|
|
||||||
|
#[serde(default, alias = "keep_private_props")]
|
||||||
|
pub keep_private_props: bool,
|
||||||
|
|
||||||
#[serde(default, alias = "ie8")]
|
#[serde(default, alias = "ie8")]
|
||||||
pub ie8: bool,
|
pub ie8: bool,
|
||||||
|
|
||||||
@ -171,6 +174,7 @@ pub struct CompressOptions {
|
|||||||
#[serde(alias = "hoist_vars")]
|
#[serde(alias = "hoist_vars")]
|
||||||
pub hoist_vars: bool,
|
pub hoist_vars: bool,
|
||||||
|
|
||||||
|
/// No effect.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(alias = "ie8")]
|
#[serde(alias = "ie8")]
|
||||||
pub ie8: bool,
|
pub ie8: bool,
|
||||||
|
@ -40,6 +40,21 @@ impl HygieneAnalyzer<'_> {
|
|||||||
|
|
||||||
let ret = op(self);
|
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;
|
self.cur_scope = old;
|
||||||
|
|
||||||
ret
|
ret
|
||||||
|
@ -69,7 +69,7 @@ impl VisitMut for Optimizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_mut_module(&mut self, n: &mut Module) {
|
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 start = Instant::now();
|
||||||
|
|
||||||
let mut analyzer = HygieneAnalyzer {
|
let mut analyzer = HygieneAnalyzer {
|
||||||
@ -82,12 +82,12 @@ impl VisitMut for Optimizer {
|
|||||||
self.hygiene = analyzer.hygiene;
|
self.hygiene = analyzer.hygiene;
|
||||||
|
|
||||||
let end = Instant::now();
|
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;
|
let start = end;
|
||||||
|
|
||||||
log::debug!("hygiene: Optimizing span hygiene");
|
log::info!("hygiene: Optimizing span hygiene");
|
||||||
n.visit_mut_children_with(self);
|
n.visit_mut_children_with(self);
|
||||||
let end = Instant::now();
|
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 {
|
struct Mangler {
|
||||||
options: MangleOptions,
|
options: MangleOptions,
|
||||||
n: usize,
|
n: usize,
|
||||||
|
private_n: usize,
|
||||||
|
|
||||||
preserved: FxHashSet<Id>,
|
preserved: FxHashSet<Id>,
|
||||||
preserved_symbols: FxHashSet<JsWord>,
|
preserved_symbols: FxHashSet<JsWord>,
|
||||||
renamed: FxHashMap<Id, JsWord>,
|
renamed: FxHashMap<Id, JsWord>,
|
||||||
|
renamed_private: FxHashMap<Id, JsWord>,
|
||||||
data: Option<ProgramData>,
|
data: Option<ProgramData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +68,23 @@ impl Mangler {
|
|||||||
break;
|
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 {
|
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) {
|
fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
|
||||||
self.rename(&mut n.ident);
|
self.rename(&mut n.ident);
|
||||||
n.function.visit_mut_with(self);
|
n.function.visit_mut_with(self);
|
||||||
|
@ -6,8 +6,8 @@ use once_cell::sync::Lazy;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use swc_atoms::JsWord;
|
use swc_atoms::JsWord;
|
||||||
use swc_ecma_ast::{
|
use swc_ecma_ast::{
|
||||||
CallExpr, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, Module, PrivateName, Prop,
|
CallExpr, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, Module, Prop, PropName, Str,
|
||||||
PropName, Str, StrKind,
|
StrKind,
|
||||||
};
|
};
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
use swc_ecma_visit::{VisitMut, VisitMutWith};
|
use swc_ecma_visit::{VisitMut, VisitMutWith};
|
||||||
@ -37,11 +37,9 @@ struct ManglePropertiesState {
|
|||||||
|
|
||||||
// Cache of already mangled names
|
// Cache of already mangled names
|
||||||
cache: HashMap<JsWord, JsWord>,
|
cache: HashMap<JsWord, JsWord>,
|
||||||
private_cache: HashMap<JsWord, JsWord>,
|
|
||||||
|
|
||||||
// Numbers to pass to base54()
|
// Numbers to pass to base54()
|
||||||
n: usize,
|
n: usize,
|
||||||
private_n: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManglePropertiesState {
|
impl ManglePropertiesState {
|
||||||
@ -103,23 +101,6 @@ impl ManglePropertiesState {
|
|||||||
None
|
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) {
|
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 hygiene;
|
||||||
pub mod mangle_names;
|
pub mod mangle_names;
|
||||||
pub mod mangle_props;
|
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_utils::{ident::IdentLike, Id};
|
||||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
|
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
|
||||||
|
|
||||||
|
/// Optimizer invoked before invoking compressor.
|
||||||
///
|
///
|
||||||
/// - Remove parens.
|
/// - Remove parens.
|
||||||
pub fn single_pass_optimizer(options: CompressOptions) -> impl VisitMut {
|
pub fn precompress_optimizer(options: CompressOptions) -> impl VisitMut {
|
||||||
SinglePassOptimizer {
|
PrecompressOptimizer {
|
||||||
options,
|
options,
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
fn_decl_count: Default::default(),
|
fn_decl_count: Default::default(),
|
||||||
@ -21,7 +22,7 @@ pub fn single_pass_optimizer(options: CompressOptions) -> impl VisitMut {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct SinglePassOptimizer {
|
struct PrecompressOptimizer {
|
||||||
options: CompressOptions,
|
options: CompressOptions,
|
||||||
data: ProgramData,
|
data: ProgramData,
|
||||||
fn_decl_count: FxHashMap<Id, usize>,
|
fn_decl_count: FxHashMap<Id, usize>,
|
||||||
@ -33,7 +34,7 @@ struct Ctx {
|
|||||||
in_var_pat: bool,
|
in_var_pat: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitMut for SinglePassOptimizer {
|
impl VisitMut for PrecompressOptimizer {
|
||||||
noop_visit_mut_type!();
|
noop_visit_mut_type!();
|
||||||
|
|
||||||
fn visit_mut_decl(&mut self, n: &mut Decl) {
|
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_transforms_base::ext::MapWithMut;
|
||||||
use swc_ecma_utils::ident::IdentLike;
|
use swc_ecma_utils::ident::IdentLike;
|
||||||
use swc_ecma_utils::Id;
|
use swc_ecma_utils::Id;
|
||||||
|
use swc_ecma_utils::ModuleItemLike;
|
||||||
use swc_ecma_utils::StmtLike;
|
use swc_ecma_utils::StmtLike;
|
||||||
use swc_ecma_utils::Value;
|
use swc_ecma_utils::Value;
|
||||||
use swc_ecma_visit::noop_visit_type;
|
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 }))
|
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
|
/// - `!0` for true
|
||||||
/// - `!1` for false
|
/// - `!1` for false
|
||||||
@ -45,6 +95,18 @@ pub(crate) trait ExprOptExt: Sized {
|
|||||||
fn as_expr(&self) -> &Expr;
|
fn as_expr(&self) -> &Expr;
|
||||||
fn as_mut(&mut self) -> &mut 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
|
/// This returns itself for normal expressions and returns last exprssions
|
||||||
/// for sequence expressions.
|
/// for sequence expressions.
|
||||||
fn value_mut(&mut self) -> &mut Expr {
|
fn value_mut(&mut self) -> &mut Expr {
|
||||||
@ -279,11 +341,36 @@ where
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct IdentUsageCollector {
|
pub(crate) struct IdentUsageCollector {
|
||||||
ids: FxHashSet<Id>,
|
ids: FxHashSet<Id>,
|
||||||
|
ignore_nested: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visit for IdentUsageCollector {
|
impl Visit for IdentUsageCollector {
|
||||||
noop_visit_type!();
|
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) {
|
fn visit_ident(&mut self, n: &Ident, _: &dyn Node) {
|
||||||
self.ids.insert(n.to_id());
|
self.ids.insert(n.to_id());
|
||||||
}
|
}
|
||||||
@ -295,13 +382,37 @@ impl Visit for IdentUsageCollector {
|
|||||||
n.prop.visit_with(n, self);
|
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>
|
pub(crate) fn idents_used_by<N>(n: &N) -> FxHashSet<Id>
|
||||||
where
|
where
|
||||||
N: VisitWith<IdentUsageCollector>,
|
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);
|
n.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||||
v.ids
|
v.ids
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
extern crate swc_node_base;
|
||||||
|
|
||||||
use ansi_term::Color;
|
use ansi_term::Color;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@ -11,6 +13,7 @@ use std::panic::catch_unwind;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::time::Instant;
|
||||||
use swc_common::comments::SingleThreadedComments;
|
use swc_common::comments::SingleThreadedComments;
|
||||||
use swc_common::errors::Handler;
|
use swc_common::errors::Handler;
|
||||||
use swc_common::sync::Lrc;
|
use swc_common::sync::Lrc;
|
||||||
@ -132,6 +135,8 @@ fn run(
|
|||||||
|
|
||||||
let top_level_mark = Mark::fresh(Mark::root());
|
let top_level_mark = Mark::fresh(Mark::root());
|
||||||
|
|
||||||
|
let minification_start = Instant::now();
|
||||||
|
|
||||||
let lexer = Lexer::new(
|
let lexer = Lexer::new(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@ -155,8 +160,10 @@ fn run(
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let optimization_start = Instant::now();
|
||||||
let output = optimize(
|
let output = optimize(
|
||||||
program,
|
program,
|
||||||
|
cm.clone(),
|
||||||
Some(&comments),
|
Some(&comments),
|
||||||
None,
|
None,
|
||||||
&MinifyOptions {
|
&MinifyOptions {
|
||||||
@ -174,9 +181,22 @@ fn run(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&ExtraOptions { top_level_mark },
|
&ExtraOptions { top_level_mark },
|
||||||
)
|
);
|
||||||
.fold_with(&mut hygiene())
|
let end = Instant::now();
|
||||||
.fold_with(&mut fixer(None));
|
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)
|
Some(output)
|
||||||
}
|
}
|
||||||
@ -199,8 +219,52 @@ fn stdout_of(code: &str) -> Result<String, Error> {
|
|||||||
Ok(String::from_utf8_lossy(&actual_output.stdout).to_string())
|
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")]
|
#[testing::fixture("tests/compress/fixture/**/input.js")]
|
||||||
fn base_fixture(input: PathBuf) {
|
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 dir = input.parent().unwrap();
|
||||||
let config = dir.join("config.json");
|
let config = dir.join("config.json");
|
||||||
let config = read_to_string(&config).expect("failed to read 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(()),
|
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);
|
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||||
|
|
||||||
println!("{}", input.display());
|
println!("{}", input.display());
|
||||||
|
|
||||||
NormalizedOutput::from(output)
|
NormalizedOutput::from(output)
|
||||||
.compare_to_file(dir.join("output.js"))
|
.compare_to_file(
|
||||||
|
dir.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("output")
|
||||||
|
.join(input.file_name().unwrap()),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -241,7 +310,7 @@ fn base_exec(input: PathBuf) {
|
|||||||
|
|
||||||
let output = run(cm.clone(), &handler, &input, &config, None);
|
let output = run(cm.clone(), &handler, &input, &config, None);
|
||||||
let output = output.expect("Parsing in base test should not fail");
|
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!(
|
eprintln!(
|
||||||
"---- {} -----\n{}",
|
"---- {} -----\n{}",
|
||||||
@ -297,7 +366,7 @@ fn fixture(input: PathBuf) {
|
|||||||
None => return Ok(()),
|
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);
|
eprintln!("---- {} -----\n{}", Color::Green.paint("Ouput"), output);
|
||||||
|
|
||||||
@ -327,7 +396,7 @@ fn fixture(input: PathBuf) {
|
|||||||
ModuleItem::Stmt(Stmt::Empty(..)) => false,
|
ModuleItem::Stmt(Stmt::Empty(..)) => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
});
|
});
|
||||||
print(cm.clone(), &[expected])
|
print(cm.clone(), &[expected], false)
|
||||||
};
|
};
|
||||||
|
|
||||||
if output == expected {
|
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));
|
assert_eq!(DebugUsingDisplay(&output_str), DebugUsingDisplay(&expected));
|
||||||
|
|
||||||
@ -377,12 +446,12 @@ fn fixture(input: PathBuf) {
|
|||||||
.unwrap()
|
.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 buf = vec![];
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut emitter = Emitter {
|
let mut emitter = Emitter {
|
||||||
cfg: Default::default(),
|
cfg: swc_ecma_codegen::Config { minify },
|
||||||
cm: cm.clone(),
|
cm: cm.clone(),
|
||||||
comments: None,
|
comments: None,
|
||||||
wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, 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