mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 21:21:31 +03:00
3ca954b9f9
**Related issue:** - Closes https://github.com/swc-project/swc/issues/7144. - Closes https://github.com/swc-project/swc/issues/7118.
741 lines
18 KiB
Rust
741 lines
18 KiB
Rust
//! Copied from PeepholeIntegrationTest from the google closure compiler.
|
|
|
|
#![deny(warnings)]
|
|
|
|
use swc_common::{chain, pass::Repeat, Mark};
|
|
use swc_ecma_parser::{Syntax, TsConfig};
|
|
use swc_ecma_transforms_base::{helpers::inject_helpers, resolver};
|
|
use swc_ecma_transforms_compat::{es2015, es2016, es2017, es2018, es2022::class_properties, es3};
|
|
use swc_ecma_transforms_module::{common_js::common_js, import_analysis::import_analyzer};
|
|
use swc_ecma_transforms_optimization::simplify::{
|
|
dce::{self, dce},
|
|
dead_branch_remover, expr_simplifier,
|
|
inlining::{self, inlining},
|
|
};
|
|
use swc_ecma_transforms_proposal::decorators;
|
|
use swc_ecma_transforms_testing::{test, test_transform};
|
|
use swc_ecma_transforms_typescript::strip;
|
|
|
|
fn test(src: &str, expected: &str) {
|
|
test_transform(
|
|
::swc_ecma_parser::Syntax::default(),
|
|
|_| {
|
|
let unresolved_mark = Mark::new();
|
|
let top_level_mark = Mark::new();
|
|
|
|
chain!(
|
|
resolver(unresolved_mark, top_level_mark, false),
|
|
Repeat::new(chain!(
|
|
expr_simplifier(unresolved_mark, Default::default()),
|
|
inlining::inlining(Default::default()),
|
|
dead_branch_remover(unresolved_mark),
|
|
dce::dce(Default::default(), unresolved_mark)
|
|
)),
|
|
)
|
|
},
|
|
src,
|
|
expected,
|
|
true,
|
|
)
|
|
}
|
|
|
|
fn test_same(src: &str) {
|
|
test(src, src)
|
|
}
|
|
|
|
macro_rules! to {
|
|
($name:ident, $src:expr, $expected:expr) => {
|
|
test!(
|
|
Default::default(),
|
|
|_| {
|
|
let unresolved_mark = Mark::new();
|
|
let top_level_mark = Mark::new();
|
|
|
|
chain!(
|
|
resolver(unresolved_mark, top_level_mark, false),
|
|
Repeat::new(chain!(
|
|
expr_simplifier(unresolved_mark, Default::default()),
|
|
inlining::inlining(Default::default()),
|
|
dead_branch_remover(unresolved_mark),
|
|
dce::dce(Default::default(), unresolved_mark)
|
|
)),
|
|
)
|
|
},
|
|
$name,
|
|
$src,
|
|
$expected
|
|
);
|
|
};
|
|
}
|
|
|
|
macro_rules! optimized_out {
|
|
($name:ident, $src:expr) => {
|
|
to!($name, $src, "");
|
|
};
|
|
}
|
|
|
|
to!(
|
|
single_pass,
|
|
"
|
|
const a = 1;
|
|
|
|
if (a) {
|
|
const b = 2;
|
|
}
|
|
",
|
|
"
|
|
const a = 1;
|
|
a
|
|
"
|
|
);
|
|
|
|
optimized_out!(issue_607, "let a");
|
|
|
|
to!(
|
|
multi_run,
|
|
"
|
|
let b = 2;
|
|
|
|
let a = 1;
|
|
if (b) { // Removed by first run of remove_dead_branch
|
|
a = 2; // It becomes `flat assignment` to a on second run of inlining
|
|
}
|
|
|
|
let c;
|
|
if (a) { // Removed by second run of remove_dead_branch
|
|
c = 3; // It becomes `flat assignment` to c on third run of inlining.
|
|
}
|
|
console.log(c); // Prevent optimizing out.
|
|
",
|
|
"console.log(3)"
|
|
);
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_fold_one_child_blocks_integration() {
|
|
test(
|
|
"function f(){switch(foo()){default:{break}}}",
|
|
"function f(){foo()}",
|
|
);
|
|
|
|
test(
|
|
"function f(){switch(x){default:{break}}} use(f);",
|
|
"function f(){} use(f);",
|
|
);
|
|
|
|
test(
|
|
"function f(){switch(x){default:x;case 1:return 2}} use(f);",
|
|
"function f(){switch(x){default:case 1:return 2}} use(f);",
|
|
);
|
|
|
|
// ensure that block folding does not break hook ifs
|
|
test(
|
|
"if(x){if(true){foo();foo()}else{bar();bar()}}",
|
|
"if(x){foo();foo()}",
|
|
);
|
|
|
|
test(
|
|
"if(x){if(false){foo();foo()}else{bar();bar()}}",
|
|
"if(x){bar();bar()}",
|
|
);
|
|
|
|
// Cases where the then clause has no side effects.
|
|
test("if(x()){}", "x()");
|
|
|
|
test("if(x()){} else {x()}", "x()||x()");
|
|
test("if(x){}", ""); // Even the condition has no side effect.
|
|
test(
|
|
"if(a()){A()} else if (b()) {} else {C()}",
|
|
"a()?A():b()||C()",
|
|
);
|
|
|
|
test(
|
|
"if(a()){} else if (b()) {} else {C()}",
|
|
"a() || (b() || C())",
|
|
);
|
|
test(
|
|
"if(a()){A()} else if (b()) {} else if (c()) {} else{D()}",
|
|
"a() ? A() : b() || (c() || D())",
|
|
);
|
|
test(
|
|
"if(a()){} else if (b()) {} else if (c()) {} else{D()}",
|
|
"a() || (b() || (c() || D()))",
|
|
);
|
|
test(
|
|
"if(a()){A()} else if (b()) {} else if (c()) {} else{}",
|
|
"a()?A():b()||c()",
|
|
);
|
|
|
|
// Verify that non-global scope works.
|
|
test("function foo(){if(x()){}}", "function foo(){x()}");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // swc optimizes out everything
|
|
fn test_fold_one_child_blocks_string_compare() {
|
|
test(
|
|
"if (x) {if (y) { var x; } } else{ var z; }",
|
|
"if (x) { if (y) var x } else var z",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_necessary_dangling_else() {
|
|
test(
|
|
"if (x) if (y){ y(); z() } else; else x()",
|
|
"if (x) { if(y) { y(); z() } } else x()",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_fold_returns_integration() {
|
|
// if-then-else duplicate statement removal handles this case:
|
|
test("function f(){if(x)return;else return}", "function f(){}");
|
|
}
|
|
|
|
#[test]
|
|
fn test_bug1059649() {
|
|
// ensure that folding blocks with a single var node doesn't explode
|
|
test(
|
|
"if(x){var y=3;}var z=5; use(y, z)",
|
|
"if(x)var y=3; use(y, 5)",
|
|
);
|
|
|
|
test(
|
|
"for(var i=0;i<10;i++){var y=3;}var z=5; use(y, z)",
|
|
"for(var i=0;i<10;i++)var y=3; use(y, 5)",
|
|
);
|
|
test(
|
|
"for(var i in x){var y=3;}var z=5; use(y, z)",
|
|
"for(var i in x)var y=3; use(y, 5)",
|
|
);
|
|
test(
|
|
"do{var y=3;}while(x);var z=5; use(y, z)",
|
|
"do var y=3;while(x); use(y, 5)",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_hook_if_integration() {
|
|
test(
|
|
"if (false){ x = 1; } else if (cond) { x = 2; } else { x = 3; }",
|
|
"x=cond?2:3",
|
|
);
|
|
|
|
test("x?void 0:y()", "x||y()");
|
|
test("!x?void 0:y()", "x&&y()");
|
|
test("x?y():void 0", "x&&y()");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // normalize
|
|
fn test_remove_duplicate_statements_integration() {
|
|
test(
|
|
concat!(
|
|
"function z() {if (a) { return true }",
|
|
"else if (b) { return true }",
|
|
"else { return true }}",
|
|
),
|
|
"function z() {return true;}",
|
|
);
|
|
|
|
test(
|
|
concat!(
|
|
"function z() {if (a()) { return true }",
|
|
"else if (b()) { return true }",
|
|
"else { return true }}",
|
|
),
|
|
"function z() {a()||b();return true;}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_fold_logical_op_integration() {
|
|
test("if(x && true) z()", "x&&z()");
|
|
test("if(x && false) z()", "");
|
|
test("if(x || 3) z()", "z()");
|
|
test("if(x || false) z()", "x&&z()");
|
|
test("if(x==y && false) z()", "");
|
|
test("if(y() || x || 3) z()", "y();z()");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO: This is a bug, but does anyone write code like this?
|
|
fn test_fold_bitwise_op_string_compare_integration() {
|
|
test("for (;-1 | 0;) {}", "for (;;);");
|
|
}
|
|
|
|
#[test]
|
|
fn test_var_lifting_integration() {
|
|
test("if(true);else var a;", "");
|
|
test("if(false) foo();else var a;", "");
|
|
test("if(true)var a;else;", "");
|
|
test("if(false)var a;else;", "");
|
|
test("if(false)var a,b;", "");
|
|
test("if(false){var a;var a;}", "");
|
|
test("if(false)var a=function(){var b};", "");
|
|
|
|
// TODO(kdy1): We can optimize this.
|
|
// test("if(a)if(false)var a;else var b;", "");
|
|
test("if(a)if(false)var a;else var b;", "if(a) var a;");
|
|
}
|
|
|
|
#[test]
|
|
fn test_bug1438784() {
|
|
test_same("for(var i=0;i<10;i++)if(x)x.y;");
|
|
}
|
|
|
|
#[test]
|
|
fn test_fold_useless_for_integration() {
|
|
test("for(;!true;) { foo() }", "");
|
|
test("for(;void 0;) { foo() }", "");
|
|
// test("for(;undefined;) { foo() }", "");
|
|
test("for(;1;) foo()", "for(;;) foo()");
|
|
test("for(;!void 0;) foo()", "for(;;) foo()");
|
|
|
|
// Make sure proper empty nodes are inserted.
|
|
// test("if(foo())for(;false;){foo()}else bar()", "foo()||bar()");
|
|
test(
|
|
"if(foo())for(;false;){foo()}else bar()",
|
|
"if (foo()); else bar();",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fold_useless_do_integration() {
|
|
test("do { foo() } while(!true);", "foo()");
|
|
test("do { foo() } while(void 0);", "foo()");
|
|
// test("do { foo() } while(undefined);", "foo()");
|
|
test("do { foo() } while(!void 0);", "for(;;)foo();");
|
|
|
|
// Make sure proper empty nodes are inserted.
|
|
// test(
|
|
// "if(foo())do {foo()} while(false) else bar()",
|
|
// "foo()?foo():bar()",
|
|
// );
|
|
|
|
test(
|
|
"if(foo())do {foo()} while(false) else bar()",
|
|
"if (foo()) foo(); else bar();",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_minimize_expr() {
|
|
test("!!true", "");
|
|
|
|
test("!!x()", "x()");
|
|
test("!(!x()&&!y())", "x()||y()");
|
|
test("x()||!!y()", "x()||y()");
|
|
|
|
/* This is similar to the !!true case */
|
|
test("!!x()&&y()", "x()&&y()");
|
|
}
|
|
|
|
#[test]
|
|
fn test_bug_issue3() {
|
|
test(
|
|
concat!(
|
|
"function foo() {",
|
|
" if(sections.length != 1) children[i] = 0;",
|
|
" else var selectedid = children[i]",
|
|
"}",
|
|
"foo()"
|
|
),
|
|
concat!(
|
|
"function foo() {",
|
|
" if(sections.length != 1) children[i] = 0;",
|
|
" else children[i]",
|
|
"}",
|
|
"foo()"
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bug_issue43() {
|
|
test_same("function foo() {\n if (a) bar(); else a.b = 1; \n} use(foo);");
|
|
}
|
|
|
|
#[test]
|
|
fn test_fold_negative_bug() {
|
|
test("for (;-3;){};", "for (;;);");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // normalize
|
|
fn test_no_normalize_labeled_expr() {
|
|
test_same("var x; foo:{x = 3;}");
|
|
test_same("var x; foo:x = 3;");
|
|
}
|
|
|
|
#[test]
|
|
fn test_short_circuit1() {
|
|
test("1 && a()", "a()");
|
|
}
|
|
|
|
#[test]
|
|
fn test_short_circuit2() {
|
|
test("1 && a() && 2", "a()");
|
|
}
|
|
|
|
#[test]
|
|
fn test_short_circuit3() {
|
|
test("a() && 1 && 2", "a()");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_short_circuit4() {
|
|
test("a() && (1 && b())", "a() && b()");
|
|
test("a() && 1 && b()", "a() && b()");
|
|
test("(a() && 1) && b()", "a() && b()");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_minimize_expr_condition() {
|
|
test("(x || true) && y()", "y()");
|
|
test("(x || false) && y()", "x&&y()");
|
|
test("(x && true) && y()", "x && y()");
|
|
test("(x && false) && y()", "");
|
|
test("a = x || false ? b : c", "a=x?b:c");
|
|
test("do {x()} while((x && false) && y())", "x()");
|
|
}
|
|
|
|
// A few miscellaneous cases where one of the peephole passes increases the
|
|
// size, but it does it in such a way that a later pass can decrease it.
|
|
// Test to make sure the overall change is a decrease, not an increase.
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_misc() {
|
|
test("use(x = [foo()] && x)", "use(x = (foo(),x))");
|
|
test("x = foo() && false || bar()", "x = (foo(), bar())");
|
|
test("if(foo() && false) z()", "foo()");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // swc strips out whole of this
|
|
fn test_comma_spliting_constant_condition() {
|
|
test("(b=0,b=1);if(b)x=b;", "b=0;b=1;x=b;");
|
|
test("(b=0,b=1);if(b)x=b;", "b=0;b=1;x=b;");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_avoid_comma_splitting() {
|
|
test("x(),y(),z()", "x();y();z()");
|
|
}
|
|
|
|
#[test]
|
|
fn test_object_literal() {
|
|
test("({})", "");
|
|
test("({a:1})", "");
|
|
test("({a:foo()})", "foo()");
|
|
test("({'a':foo()})", "foo()");
|
|
}
|
|
|
|
#[test]
|
|
fn test_array_literal() {
|
|
test("([])", "");
|
|
test("([1])", "");
|
|
test("([a])", "a");
|
|
test("([foo()])", "foo()");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_fold_ifs1() {
|
|
test(
|
|
"function f() {if (x) return 1; else if (y) return 1;}",
|
|
"function f() {if (x||y) return 1;}",
|
|
);
|
|
test(
|
|
"function f() {if (x) return 1; else {if (y) return 1; else foo();}}",
|
|
"function f() {if (x||y) return 1; foo();}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_fold_ifs2() {
|
|
test(
|
|
"function f() {if (x) { a(); } else if (y) { a() }}",
|
|
"function f() {x?a():y&&a();}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_fold_hook2() {
|
|
test(
|
|
"function f(a) {if (!a) return a; else return a;}",
|
|
"function f(a) {return a}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO
|
|
fn test_template_strings_known_methods() {
|
|
test("x = `abcdef`.indexOf('b')", "x = 1");
|
|
test("x = [`a`, `b`, `c`].join(``)", "x='abc'");
|
|
test("x = `abcdef`.substr(0,2)", "x = 'ab'");
|
|
test("x = `abcdef`.substring(0,2)", "x = 'ab'");
|
|
test("x = `abcdef`.slice(0,2)", "x = 'ab'");
|
|
test("x = `abcdef`.charAt(0)", "x = 'a'");
|
|
test("x = `abcdef`.charCodeAt(0)", "x = 97");
|
|
test("x = `abc`.toUpperCase()", "x = 'ABC'");
|
|
test("x = `ABC`.toLowerCase()", "x = 'abc'");
|
|
//test("x = `\t\n\uFEFF\t asd foo bar \r\n`.trim()", "x = 'asd foo bar'");
|
|
test("x = parseInt(`123`)", "x = 123");
|
|
test("x = parseFloat(`1.23`)", "x = 1.23");
|
|
}
|
|
|
|
test!(
|
|
Syntax::Typescript(TsConfig {
|
|
decorators: true,
|
|
..Default::default()
|
|
}),
|
|
|t| {
|
|
let unresolved_mark = Mark::new();
|
|
let top_level_mark = Mark::new();
|
|
|
|
chain!(
|
|
resolver(unresolved_mark, top_level_mark, false),
|
|
strip(top_level_mark),
|
|
class_properties(
|
|
Some(t.comments.clone()),
|
|
class_properties::Config {
|
|
set_public_fields: true,
|
|
..Default::default()
|
|
}
|
|
),
|
|
dce(Default::default(), unresolved_mark),
|
|
inlining(Default::default())
|
|
)
|
|
},
|
|
issue_1156_1,
|
|
"
|
|
interface D {
|
|
resolve: any;
|
|
reject: any;
|
|
}
|
|
|
|
function d(): D {
|
|
let methods;
|
|
const promise = new Promise((resolve, reject) => {
|
|
methods = { resolve, reject };
|
|
});
|
|
return Object.assign(promise, methods);
|
|
}
|
|
|
|
class A {
|
|
private s: D = d();
|
|
|
|
a() {
|
|
this.s.resolve();
|
|
}
|
|
|
|
b() {
|
|
this.s = d();
|
|
}
|
|
}
|
|
|
|
new A();
|
|
",
|
|
"
|
|
function d() {
|
|
let methods;
|
|
const promise = new Promise((resolve, reject)=>{
|
|
methods = {
|
|
resolve,
|
|
reject
|
|
};
|
|
});
|
|
return Object.assign(promise, methods);
|
|
}
|
|
class A {
|
|
a() {
|
|
this.s.resolve();
|
|
}
|
|
b() {
|
|
this.s = d();
|
|
}
|
|
constructor(){
|
|
this.s = d();
|
|
}
|
|
}
|
|
new A();
|
|
"
|
|
);
|
|
|
|
test!(
|
|
Syntax::Es(Default::default()),
|
|
|t| {
|
|
let unresolved_mark = Mark::new();
|
|
let top_level_mark = Mark::new();
|
|
|
|
chain!(
|
|
decorators(Default::default()),
|
|
resolver(unresolved_mark, top_level_mark, false),
|
|
strip(top_level_mark),
|
|
class_properties(Some(t.comments.clone()), Default::default()),
|
|
Repeat::new(chain!(
|
|
expr_simplifier(unresolved_mark, Default::default()),
|
|
inlining::inlining(Default::default()),
|
|
dead_branch_remover(unresolved_mark),
|
|
dce::dce(Default::default(), unresolved_mark)
|
|
)),
|
|
es2018(Default::default()),
|
|
es2017(
|
|
Default::default(),
|
|
Some(t.comments.clone()),
|
|
unresolved_mark
|
|
),
|
|
es2016(),
|
|
es2015(
|
|
unresolved_mark,
|
|
Some(t.comments.clone()),
|
|
Default::default()
|
|
),
|
|
es3(true),
|
|
import_analyzer(false.into(), false),
|
|
inject_helpers(unresolved_mark),
|
|
common_js(
|
|
Mark::fresh(Mark::root()),
|
|
Default::default(),
|
|
Default::default(),
|
|
Some(t.comments.clone())
|
|
),
|
|
)
|
|
},
|
|
issue_389_3,
|
|
"
|
|
import Foo from 'foo';
|
|
Foo.bar = true;
|
|
",
|
|
"
|
|
\"use strict\";
|
|
Object.defineProperty(exports, \"__esModule\", {
|
|
value: true
|
|
});
|
|
var _foo = _interop_require_default(require(\"foo\"));
|
|
function _interop_require_default(obj) {
|
|
return obj && obj.__esModule ? obj : {
|
|
default: obj
|
|
};
|
|
}
|
|
_foo.default.bar = true;
|
|
"
|
|
);
|
|
|
|
test!(
|
|
Syntax::default(),
|
|
|_| {
|
|
let top_level_mark = Mark::fresh(Mark::root());
|
|
|
|
expr_simplifier(top_level_mark, Default::default())
|
|
},
|
|
issue_1619_1,
|
|
r#"
|
|
"use strict";
|
|
|
|
console.log("\x00" + "\x31");
|
|
|
|
"#,
|
|
r#"
|
|
"use strict";
|
|
console.log("\x001");
|
|
"#,
|
|
ok_if_code_eq
|
|
);
|
|
|
|
test!(
|
|
Syntax::default(),
|
|
|_| {
|
|
let top_level_mark = Mark::fresh(Mark::root());
|
|
|
|
dead_branch_remover(top_level_mark)
|
|
},
|
|
issue_2466_1,
|
|
"
|
|
const X = {
|
|
run() {
|
|
console.log(this === globalThis);
|
|
},
|
|
};
|
|
|
|
X.run();
|
|
(0, X.run)();
|
|
",
|
|
"
|
|
const X = {
|
|
run() {
|
|
console.log(this === globalThis);
|
|
},
|
|
};
|
|
|
|
X.run();
|
|
(0, X.run)();
|
|
"
|
|
);
|
|
|
|
test!(
|
|
Syntax::default(),
|
|
|_| {
|
|
let top_level_mark = Mark::fresh(Mark::root());
|
|
|
|
dead_branch_remover(top_level_mark)
|
|
},
|
|
issue_4272,
|
|
"
|
|
function oe() {
|
|
var e, t;
|
|
return t !== i && (e, t = t), e = t;
|
|
}
|
|
",
|
|
"
|
|
function oe() {
|
|
var e, t;
|
|
return t !== i && (e, t), e = t;
|
|
}
|
|
"
|
|
);
|
|
|
|
test!(
|
|
Syntax::default(),
|
|
|_| {
|
|
let unresolved_mark = Mark::new();
|
|
let top_level_mark = Mark::new();
|
|
|
|
chain!(
|
|
resolver(unresolved_mark, top_level_mark, false),
|
|
Repeat::new(chain!(
|
|
inlining(Default::default()),
|
|
dead_branch_remover(unresolved_mark)
|
|
))
|
|
)
|
|
},
|
|
issue_4173,
|
|
"
|
|
function emit(type) {
|
|
const e = events[type];
|
|
if (Array.isArray(e)) {
|
|
for (let i = 0; i < e.length; i += 1) {
|
|
e[i].apply(this);
|
|
}
|
|
}
|
|
}
|
|
",
|
|
"
|
|
function emit(type) {
|
|
const e = events[type];
|
|
if (Array.isArray(e)) for(let i = 0; i < e.length; i += 1)e[i].apply(this);
|
|
}
|
|
"
|
|
);
|