-
-
Notifications
You must be signed in to change notification settings - Fork 501
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(minifier): add boilerplate for
StatementFusion
and `ExploitAs…
…signs` (#5914)
- Loading branch information
Showing
5 changed files
with
519 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
use oxc_traverse::Traverse; | ||
|
||
use crate::CompressorPass; | ||
|
||
/// Tries to chain assignments together. | ||
/// | ||
/// <https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/ExploitAssigns.java> | ||
pub struct ExploitAssigns; | ||
|
||
impl<'a> CompressorPass<'a> for ExploitAssigns {} | ||
|
||
impl<'a> Traverse<'a> for ExploitAssigns {} | ||
|
||
impl ExploitAssigns { | ||
pub fn new() -> Self { | ||
Self {} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use oxc_allocator::Allocator; | ||
|
||
use crate::tester; | ||
|
||
fn test(source_text: &str, expected: &str) { | ||
let allocator = Allocator::default(); | ||
let mut pass = super::ExploitAssigns::new(); | ||
tester::test(&allocator, source_text, expected, &mut pass); | ||
} | ||
|
||
fn test_same(source_text: &str) { | ||
test(source_text, source_text); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_expr_exploitation_types() { | ||
test("a = true; b = true", "b = a = true"); | ||
test("a = !0; b = !0", "b = a = !0"); | ||
test("a = !1; b = !1", "b = a = !1"); | ||
test("a = void 0; b = void 0", "b = a = void 0"); | ||
test("a = -Infinity; b = -Infinity", "b = a = -Infinity"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_expr_exploitation_types2() { | ||
test("a = !0; b = !0", "b = a = !0"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn nullish_coalesce() { | ||
test("a = null; a ?? b;", "(a = null)??b"); | ||
test("a = true; if (a ?? a) { foo(); }", "if ((a = true) ?? a) { foo() }"); | ||
test("a = !0; if (a ?? a) { foo(); }", "if ((a = !0) ?? a) { foo() }"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_expr_exploitation() { | ||
test("a = null; b = null; var c = b", "var c = b = a = null"); | ||
test("a = null; b = null", "b = a = null"); | ||
test("a = undefined; b = undefined", "b = a = undefined"); | ||
test("a = 0; b = 0", "b=a=0"); | ||
test("a = 'foo'; b = 'foo'", "b = a = \"foo\""); | ||
test("a = c; b = c", "b=a=c"); | ||
|
||
test_same("a = 0; b = 1"); | ||
test_same("a = \"foo\"; b = \"foox\""); | ||
|
||
test("a = null; a && b;", "(a = null)&&b"); | ||
test("a = null; a || b;", "(a = null)||b"); | ||
|
||
test("a = null; a ? b : c;", "(a = null) ? b : c"); | ||
|
||
test("a = null; this.foo = null;", "this.foo = a = null"); | ||
test("function f(){ a = null; return null; }", "function f(){return a = null}"); | ||
|
||
test("a = true; if (a) { foo(); }", "if (a = true) { foo() }"); | ||
test("a = true; if (a && a) { foo(); }", "if ((a = true) && a) { foo() }"); | ||
test("a = false; if (a) { foo(); }", "if (a = false) { foo() }"); | ||
|
||
test("a = !0; if (a) { foo(); }", "if (a = !0) { foo() }"); | ||
test("a = !0; if (a && a) { foo(); }", "if ((a = !0) && a) { foo() }"); | ||
test("a = !1; if (a) { foo(); }", "if (a = !1) { foo() }"); | ||
|
||
test_same("a = this.foo; a();"); | ||
test("a = b; b = a;", "b = a = b"); | ||
test_same("a = b; a.c = a"); | ||
test("this.foo = null; this.bar = null;", "this.bar = this.foo = null"); | ||
test( | ||
"this.foo = null; this.bar = null; this.baz = this.bar", | ||
"this.baz = this.bar = this.foo = null", | ||
); | ||
test( | ||
"this.foo = null; this.bar = null; this.baz = this?.bar", | ||
"this.bar = this.foo = null; this.baz = this?.bar;", | ||
); | ||
test("this.foo = null; a = null;", "a = this.foo = null"); | ||
test("this.foo = null; a = this.foo;", "a = this.foo = null"); | ||
test_same("this.foo = null; a = this?.foo;"); | ||
test("a.b.c=null; a=null;", "a = a.b.c = null"); | ||
test_same("a = null; a.b.c = null"); | ||
test("(a=b).c = null; this.b = null;", "this.b = (a=b).c = null"); | ||
test_same("if(x) a = null; else b = a"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_let_const_assignment() { | ||
test("a = null; b = null; let c = b", "let c = b = a = null"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_block_scope() { | ||
test("{ a = null; b = null; c = b }", "{ c = b = a = null }"); | ||
|
||
// TODO (simranarora) What should we have as the intended behavior with block scoping? | ||
test( | ||
"a = null; b = null; { c = b; }", | ||
// "{ c = b = a = null; } | ||
"b = a = null; { c = b; }", | ||
); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_exploit_in_arrow_function() { | ||
test("() => { a = null; return null; }", "() => { return a = null }"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_nested_expr_exploitation() { | ||
test( | ||
"this.foo = null; this.bar = null; this.baz = null;", | ||
"this.baz = this.bar = this.foo = null", | ||
); | ||
|
||
test( | ||
"a = 3; this.foo = a; this.bar = a; this.baz = 3;", | ||
"this.baz = this.bar = this.foo = a = 3", | ||
); | ||
test( | ||
"a = 3; this.foo = a; this.bar = this.foo; this.baz = a;", | ||
"this.baz = this.bar = this.foo = a = 3", | ||
); | ||
// recursively optimize assigns until optional chaining on RHS | ||
test( | ||
"a = 3; this.foo = a; this.bar = this?.foo; this.baz = a;", | ||
"this.foo = a = 3; this.bar = this?.foo; this.baz = a;", | ||
); | ||
test( | ||
"a = 3; this.foo = a; this.bar = 3; this.baz = this.foo;", | ||
"this.baz = this.bar = this.foo = a = 3", | ||
); | ||
// recursively optimize assigns until optional chaining on RHS | ||
test( | ||
"a = 3; this.foo = a; this.bar = 3; this.baz = this?.foo;", | ||
"this.bar = this.foo = a = 3; this.baz = this?.foo;", | ||
); | ||
// test( | ||
// "a = 3; this.foo = a; a = 3; this.bar = 3; " + "a = 3; this.baz = this.foo;", | ||
// "this.baz = a = this.bar = a = this.foo = a = 3", | ||
// ); | ||
// recursively optimize assigns until optional chaining on RHS | ||
// test( | ||
// lines("a = 3; this.foo = a; a = 3; this.bar = 3; a = 3; this.baz = this?.foo;"), | ||
// lines("a = this.bar = a = this.foo = a = 3; this.baz = this?.foo;"), | ||
// ); | ||
|
||
// test( | ||
// "a = 4; this.foo = a; a = 3; this.bar = 3; " + "a = 3; this.baz = this.foo;", | ||
// "this.foo = a = 4; a = this.bar = a = 3; this.baz = this.foo", | ||
// ); | ||
// recursively optimize assigns until optional chaining on RHS | ||
// test( | ||
// lines("a = 4; this.foo = a; a = 3; this.bar = 3; a = 3; this.baz = this?.foo;"), | ||
// lines("this.foo = a = 4;", "a = this.bar = a = 3;", "this.baz = this?.foo;"), | ||
// ); | ||
|
||
// test( | ||
// "a = 3; this.foo = a; a = 4; this.bar = 3; " + "a = 3; this.baz = this.foo;", | ||
// "this.foo = a = 3; a = 4; a = this.bar = 3; this.baz = this.foo", | ||
// ); | ||
// test( | ||
// lines("a = 3; this.foo = a; a = 4; this.bar = 3; ", "a = 3; this.baz = this?.foo;"), | ||
// lines("this.foo = a = 3;", "a = 4;", "a = this.bar = 3;", "this.baz = this?.foo;"), | ||
// ); | ||
// test( | ||
// "a = 3; this.foo = a; a = 3; this.bar = 3; " + "a = 4; this.baz = this.foo;", | ||
// "this.bar = a = this.foo = a = 3; a = 4; this.baz = this.foo", | ||
// ); | ||
// test( | ||
// lines("a = 3; this.foo = a; a = 3; this.bar = 3; a = 4; this.baz = this?.foo;"), | ||
// lines("this.bar = a = this.foo = a = 3;", "a = 4;", "this.baz = this?.foo;"), | ||
// ); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_bug1840071() { | ||
// Some external properties are implemented as setters. Let's | ||
// make sure that we don't collapse them inappropriately. | ||
test("a.b = a.x; if (a.x) {}", "if (a.b = a.x) {}"); | ||
test_same("a.b = a?.x; if (a?.x) {}"); | ||
test_same("a.b = a.x; if (a.b) {}"); | ||
test("a.b = a.c = a.x; if (a.x) {}", "if (a.b = a.c = a.x) {}"); | ||
test_same("a.b = a.c = a?.x; if (a?.x) {}"); | ||
|
||
test_same("a.b = a.c = a.x; if (a.c) {}"); | ||
test_same("a.b = a.c = a.x; if (a.b) {}"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_bug2072343() { | ||
test_same("a = a.x;a = a.x"); | ||
test_same("a = a.x;b = a.x"); | ||
test("b = a.x;a = a.x", "a = b = a.x"); | ||
test_same("b = a?.x;a = a?.x"); | ||
test_same("a.x = a;a = a.x"); | ||
test_same("a.b = a.b.x;a.b = a.b.x"); | ||
test_same("a.y = a.y.x;b = a.y;c = a.y.x"); | ||
test("a = a.x;b = a;c = a.x", "b = a = a.x;c = a.x"); | ||
test("b = a.x;a = b;c = a.x", "a = b = a.x;c = a.x"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_bad_collapse_into_call() { | ||
// Can't collapse this, because if we did, 'foo' would be called | ||
// in the wrong 'this' context. | ||
test_same("this.foo = function() {}; this.foo();"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_bad_collapse() { | ||
test_same("this.$e$ = []; this.$b$ = null;"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_issue1017() { | ||
test_same("x = x.parentNode.parentNode; x = x.parentNode.parentNode;"); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_destructuring_lhs_array_ideal_behaviours() { | ||
test_same("a => { ([a] = a); return a; }"); // `a` is being reassigned. | ||
test_same("a => { ([b] = a); return a; }"); // Evaluating `b` could side-effect `a`. | ||
test_same("a => { ([a = foo()] = a); return a; }"); // `foo` may be invoked. | ||
test_same("(a, b) => { (a = [a] = b); return b; }"); // Evaluating `a` could side-effect `b`. | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_destructuring_lhs_array_backoff_behaviours() { | ||
// TODO(b/123102446): We really like to collapse some of these chained assignments. | ||
|
||
test_same("(a, b) => { ([a] = a = b); return b; }"); // The middle `a` is redundant. | ||
test_same("(a, b) => { ([a] = a = b); return a; }"); // The middle `a` is redundant. | ||
test( | ||
"(a, b) => { (a = [a] = b); return a; }", // The final `a` is redundant. | ||
"(a, b) => { return (a = [a] = b); }", | ||
); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_destructuring_lhs_object_ideal_behaviours() { | ||
test_same("a => { ({a} = a); return a; }"); // `a` is being reassigned. | ||
test_same("a => { ({b} = a); return a; }"); // Evaluating `b` could side-effect `a`. | ||
test_same("a => { ({a = foo()} = a); return a; }"); // `foo` may be invoked. | ||
test_same("(a, b) => { (a = {a} = b); return b; }"); // Evaluating `a` could side-effect `b`. | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn test_destructuring_lhs_object_backoff_behaviours() { | ||
// TODO(b/123102446): We really like to collapse some of these chained assignments. | ||
|
||
test_same("(a, b) => { ({a} = a = b); return b; }"); // The middle `a` is redundant. | ||
test_same("(a, b) => { ({a} = a = b); return a; }"); // The middle `a` is redundant. | ||
test( | ||
"(a, b) => { (a = {a} = b); return a; }", // The final `a` is redundant. | ||
"(a, b) => { return (a = {a} = b); }", | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.