diff --git a/crates/oxc_minifier/Cargo.toml b/crates/oxc_minifier/Cargo.toml index 50940061dcad2..98ec8efeeb4c7 100644 --- a/crates/oxc_minifier/Cargo.toml +++ b/crates/oxc_minifier/Cargo.toml @@ -17,7 +17,7 @@ description.workspace = true workspace = true [lib] -test = false +test = true doctest = false [dependencies] diff --git a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs index c76f7c5118173..92dab5674dcb8 100644 --- a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs @@ -86,3 +86,34 @@ impl<'a> CollapseVariableDeclarations { *stmts = new_stmts; } } + +/// +#[cfg(test)] +mod test { + use oxc_allocator::Allocator; + + use crate::{tester, CompressOptions}; + + fn test(source_text: &str, expected: &str) { + let allocator = Allocator::default(); + let mut pass = super::CollapseVariableDeclarations::new(CompressOptions::default()); + tester::test(&allocator, source_text, expected, &mut pass); + } + + fn test_same(source_text: &str) { + test(source_text, source_text); + } + + #[test] + fn cjs() { + // Do not join `require` calls for cjs-module-lexer. + test_same( + " + Object.defineProperty(exports, '__esModule', { value: true }); + var compilerDom = require('@vue/compiler-dom'); + var runtimeDom = require('@vue/runtime-dom'); + var shared = require('@vue/shared'); + ", + ); + } +} diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 911c60f85d7df..e7d4c265a5fb6 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -28,7 +28,7 @@ impl<'a> NodeUtil for TraverseCtx<'a> { } } -pub trait CompressorPass<'a> { +pub trait CompressorPass<'a>: Traverse<'a> { fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) where Self: Traverse<'a>, diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 89a587ea5fd1c..505ffe374379f 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -696,3 +696,686 @@ impl<'a> PeepholeFoldConstants { } } } + +/// +#[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::PeepholeFoldConstants::new(); + tester::test(&allocator, source_text, expected, &mut pass); + } + + fn test_same(source_text: &str) { + test(source_text, source_text); + } + + #[test] + fn undefined_comparison1() { + test("undefined == undefined", "true"); + test("undefined == null", "true"); + test("undefined == void 0", "true"); + + test("undefined == 0", "false"); + test("undefined == 1", "false"); + test("undefined == 'hi'", "false"); + test("undefined == true", "false"); + test("undefined == false", "false"); + + test("undefined === undefined", "true"); + test("undefined === null", "false"); + test("undefined === void 0", "true"); + + test_same("undefined == this"); + test_same("undefined == x"); + + test("undefined != undefined", "false"); + test("undefined != null", "false"); + test("undefined != void 0", "false"); + + test("undefined != 0", "true"); + test("undefined != 1", "true"); + test("undefined != 'hi'", "true"); + test("undefined != true", "true"); + test("undefined != false", "true"); + + test("undefined !== undefined", "false"); + test("undefined !== void 0", "false"); + test("undefined !== null", "true"); + + test_same("undefined != this"); + test_same("undefined != x"); + + test("undefined < undefined", "false"); + test("undefined > undefined", "false"); + test("undefined >= undefined", "false"); + test("undefined <= undefined", "false"); + + test("0 < undefined", "false"); + test("true > undefined", "false"); + test("'hi' >= undefined", "false"); + test("null <= undefined", "false"); + + test("undefined < 0", "false"); + test("undefined > true", "false"); + test("undefined >= 'hi'", "false"); + test("undefined <= null", "false"); + + test("null == undefined", "true"); + test("0 == undefined", "false"); + test("1 == undefined", "false"); + test("'hi' == undefined", "false"); + test("true == undefined", "false"); + test("false == undefined", "false"); + test("null === undefined", "false"); + test("void 0 === undefined", "true"); + + test("undefined == NaN", "false"); + test("NaN == undefined", "false"); + test("undefined == Infinity", "false"); + test("Infinity == undefined", "false"); + test("undefined == -Infinity", "false"); + test("-Infinity == undefined", "false"); + test("({}) == undefined", "false"); + test("undefined == ({})", "false"); + test("([]) == undefined", "false"); + test("undefined == ([])", "false"); + test("(/a/g) == undefined", "false"); + test("undefined == (/a/g)", "false"); + test("(function(){}) == undefined", "false"); + test("undefined == (function(){})", "false"); + + test("undefined != NaN", "true"); + test("NaN != undefined", "true"); + test("undefined != Infinity", "true"); + test("Infinity != undefined", "true"); + test("undefined != -Infinity", "true"); + test("-Infinity != undefined", "true"); + test("({}) != undefined", "true"); + test("undefined != ({})", "true"); + test("([]) != undefined", "true"); + test("undefined != ([])", "true"); + test("(/a/g) != undefined", "true"); + test("undefined != (/a/g)", "true"); + test("(function(){}) != undefined", "true"); + test("undefined != (function(){})", "true"); + + test_same("this == undefined"); + test_same("x == undefined"); + } + + #[test] + fn test_undefined_comparison2() { + test("\"123\" !== void 0", "true"); + test("\"123\" === void 0", "false"); + + test("void 0 !== \"123\"", "true"); + test("void 0 === \"123\"", "false"); + } + + #[test] + fn test_undefined_comparison3() { + test("\"123\" !== undefined", "true"); + test("\"123\" === undefined", "false"); + + test("undefined !== \"123\"", "true"); + test("undefined === \"123\"", "false"); + } + + #[test] + fn test_null_comparison1() { + test("null == undefined", "true"); + test("null == null", "true"); + test("null == void 0", "true"); + + test("null == 0", "false"); + test("null == 1", "false"); + // test("null == 0n", "false"); + // test("null == 1n", "false"); + test("null == 'hi'", "false"); + test("null == true", "false"); + test("null == false", "false"); + + test("null === undefined", "false"); + test("null === null", "true"); + test("null === void 0", "false"); + test_same("null===x"); + + test_same("null==this"); + test_same("null==x"); + + test("null != undefined", "false"); + test("null != null", "false"); + test("null != void 0", "false"); + + test("null != 0", "true"); + test("null != 1", "true"); + // test("null != 0n", "true"); + // test("null != 1n", "true"); + test("null != 'hi'", "true"); + test("null != true", "true"); + test("null != false", "true"); + + test("null !== undefined", "true"); + test("null !== void 0", "true"); + test("null !== null", "false"); + + test_same("null!=this"); + test_same("null!=x"); + + test("null < null", "false"); + test("null > null", "false"); + test("null >= null", "true"); + test("null <= null", "true"); + + test("0 < null", "false"); + test("0 > null", "false"); + test("0 >= null", "true"); + // test("0n < null", "false"); + // test("0n > null", "false"); + // test("0n >= null", "true"); + test("true > null", "true"); + test("'hi' < null", "false"); + test("'hi' >= null", "false"); + test("null <= null", "true"); + + test("null < 0", "false"); + // test("null < 0n", "false"); + test("null > true", "false"); + test("null < 'hi'", "false"); + test("null >= 'hi'", "false"); + test("null <= null", "true"); + + test("null == null", "true"); + test("0 == null", "false"); + test("1 == null", "false"); + test("'hi' == null", "false"); + test("true == null", "false"); + test("false == null", "false"); + test("null === null", "true"); + test("void 0 === null", "false"); + + test("null == NaN", "false"); + test("NaN == null", "false"); + test("null == Infinity", "false"); + test("Infinity == null", "false"); + test("null == -Infinity", "false"); + test("-Infinity == null", "false"); + test("({}) == null", "false"); + test("null == ({})", "false"); + test("([]) == null", "false"); + test("null == ([])", "false"); + test("(/a/g) == null", "false"); + test("null == (/a/g)", "false"); + test("(function(){}) == null", "false"); + test("null == (function(){})", "false"); + + test("null != NaN", "true"); + test("NaN != null", "true"); + test("null != Infinity", "true"); + test("Infinity != null", "true"); + test("null != -Infinity", "true"); + test("-Infinity != null", "true"); + test("({}) != null", "true"); + test("null != ({})", "true"); + test("([]) != null", "true"); + test("null != ([])", "true"); + test("(/a/g) != null", "true"); + test("null != (/a/g)", "true"); + test("(function(){}) != null", "true"); + test("null != (function(){})", "true"); + + test_same("({a:f()})==null"); + test_same("null=={a:f()}"); + test_same("[f()]==null"); + test_same("null==[f()]"); + + test_same("this==null"); + test_same("x==null"); + } + + #[test] + fn test_boolean_boolean_comparison() { + test_same("!x==!y"); + test_same("!x 'b'", "false"); + test("'a' >= 'b'", "false"); + test("+'a' < +'b'", "false"); + test_same("typeof a < 'a'"); + test_same("'a' >= typeof a"); + test("typeof a < typeof a", "false"); + test("typeof a >= typeof a", "true"); + test("typeof 3 > typeof 4", "false"); + test("typeof function() {} < typeof function() {}", "false"); + test("'a' == 'a'", "true"); + test("'b' != 'a'", "true"); + test_same("'undefined' == typeof a"); + test_same("typeof a != 'number'"); + test_same("'undefined' == typeof a"); + test_same("'undefined' == typeof a"); + test("typeof a == typeof a", "true"); + test("'a' === 'a'", "true"); + test("'b' !== 'a'", "true"); + test("typeof a === typeof a", "true"); + test("typeof a !== typeof a", "false"); + test_same("'' + x <= '' + y"); + test_same("'' + x != '' + y"); + test_same("'' + x === '' + y"); + + test_same("'' + x <= '' + x"); // potentially foldable + test_same("'' + x != '' + x"); // potentially foldable + test_same("'' + x === '' + x"); // potentially foldable + } + + #[test] + fn test_number_string_comparison() { + test("1 < '2'", "true"); + test("2 > '1'", "true"); + test("123 > '34'", "true"); + test("NaN >= 'NaN'", "false"); + test("1 == '2'", "false"); + test("1 != '1'", "false"); + test("NaN == 'NaN'", "false"); + test("1 === '1'", "false"); + test("1 !== '1'", "true"); + test_same("+x>''+y"); + test_same("+x==''+y"); + test("+x !== '' + y", "true"); + } + + #[test] + fn test_string_number_comparison() { + test("'1' < 2", "true"); + test("'2' > 1", "true"); + test("'123' > 34", "true"); + test("'NaN' < NaN", "false"); + test("'1' == 2", "false"); + test("'1' != 1", "false"); + test("'NaN' == NaN", "false"); + test("'1' === 1", "false"); + test("'1' !== 1", "true"); + test_same("''+x<+y"); + test_same("''+x==+y"); + test("'' + x === +y", "false"); + } + + #[test] + #[ignore] + fn test_bigint_number_comparison() { + test("1n < 2", "true"); + test("1n > 2", "false"); + test("1n == 1", "true"); + test("1n == 2", "false"); + + // comparing with decimals is allowed + test("1n < 1.1", "true"); + test("1n < 1.9", "true"); + test("1n < 0.9", "false"); + test("-1n < -1.1", "false"); + test("-1n < -1.9", "false"); + test("-1n < -0.9", "true"); + test("1n > 1.1", "false"); + test("1n > 0.9", "true"); + test("-1n > -1.1", "true"); + test("-1n > -0.9", "false"); + + // Don't fold unsafely large numbers because there might be floating-point error + let max_safe_int = 9_007_199_254_740_991_i64; + let neg_max_safe_int = -9_007_199_254_740_991_i64; + let max_safe_float = 9_007_199_254_740_991_f64; + let neg_max_safe_float = -9_007_199_254_740_991_f64; + test(&format!("0n > {max_safe_int}"), "false"); + test(&format!("0n < {max_safe_int}"), "true"); + test(&format!("0n > {neg_max_safe_int}"), "true"); + test(&format!("0n < {neg_max_safe_int}"), "false"); + test(&format!("0n > {max_safe_float}"), "false"); + test(&format!("0n < {max_safe_float}"), "true"); + test(&format!("0n > {neg_max_safe_float}"), "true"); + test(&format!("0n < {neg_max_safe_float}"), "false"); + + // comparing with Infinity is allowed + test("1n < Infinity", "true"); + test("1n > Infinity", "false"); + test("1n < -Infinity", "false"); + test("1n > -Infinity", "true"); + + // null is interpreted as 0 when comparing with bigint + test("1n < null", "false"); + test("1n > null", "true"); + } + + #[test] + #[ignore] + fn test_bigint_string_comparison() { + test("1n < '2'", "true"); + test("2n > '1'", "true"); + test("123n > '34'", "true"); + test("1n == '1'", "true"); + test("1n == '2'", "false"); + test("1n != '1'", "false"); + test("1n === '1'", "false"); + test("1n !== '1'", "true"); + } + + #[test] + #[ignore] + fn test_string_bigint_comparison() { + test("'1' < 2n", "true"); + test("'2' > 1n", "true"); + test("'123' > 34n", "true"); + test("'1' == 1n", "true"); + test("'1' == 2n", "false"); + test("'1' != 1n", "false"); + test("'1' === 1n", "false"); + test("'1' !== 1n", "true"); + } + + #[test] + fn test_nan_comparison() { + test("NaN < 1", "false"); + test("NaN <= 1", "false"); + test("NaN > 1", "false"); + test("NaN >= 1", "false"); + // test("NaN < 1n", "false"); + // test("NaN <= 1n", "false"); + // test("NaN > 1n", "false"); + // test("NaN >= 1n", "false"); + + test("NaN < NaN", "false"); + test("NaN >= NaN", "false"); + test("NaN == NaN", "false"); + test("NaN === NaN", "false"); + + test("NaN < null", "false"); + test("null >= NaN", "false"); + test("NaN == null", "false"); + test("null != NaN", "true"); + test("null === NaN", "false"); + + test("NaN < undefined", "false"); + test("undefined >= NaN", "false"); + test("NaN == undefined", "false"); + test("undefined != NaN", "true"); + test("undefined === NaN", "false"); + + test_same("NaN=NaN"); + test_same("NaN==x"); + test_same("x!=NaN"); + test("NaN === x", "false"); + test("x !== NaN", "true"); + test_same("NaN==foo()"); + } + + #[test] + fn js_typeof() { + test("x = typeof 1", "x = \"number\""); + test("x = typeof 'foo'", "x = \"string\""); + test("x = typeof true", "x = \"boolean\""); + test("x = typeof false", "x = \"boolean\""); + test("x = typeof null", "x = \"object\""); + test("x = typeof undefined", "x = \"undefined\""); + test("x = typeof void 0", "x = \"undefined\""); + test("x = typeof []", "x = \"object\""); + test("x = typeof [1]", "x = \"object\""); + test("x = typeof [1,[]]", "x = \"object\""); + test("x = typeof {}", "x = \"object\""); + test("x = typeof function() {}", "x = 'function'"); + + test_same("x = typeof[1,[foo()]]"); + test_same("x = typeof{bathwater:baby()}"); + } + + #[test] + fn unary_ops() { + // TODO: need to port + // These cases are handled by PeepholeRemoveDeadCode in closure-compiler. + // test_same("!foo()"); + // test_same("~foo()"); + // test_same("-foo()"); + + // These cases are handled here. + test("a=!true", "a=false"); + test("a=!10", "a=false"); + test("a=!false", "a=true"); + test_same("a=!foo()"); + // test("a=-0", "a=-0.0"); + // test("a=-(0)", "a=-0.0"); + test_same("a=-Infinity"); + test("a=-NaN", "a=NaN"); + test_same("a=-foo()"); + // test("a=~~0", "a=0"); + // test("a=~~10", "a=10"); + // test("a=~-7", "a=6"); + + // test("a=+true", "a=1"); + test("a=+10", "a=10"); + // test("a=+false", "a=0"); + test_same("a=+foo()"); + test_same("a=+f"); + // test("a=+(f?true:false)", "a=+(f?1:0)"); + test("a=+0", "a=0"); + // test("a=+Infinity", "a=Infinity"); + // test("a=+NaN", "a=NaN"); + // test("a=+-7", "a=-7"); + // test("a=+.5", "a=.5"); + + // test("a=~0xffffffff", "a=0"); + // test("a=~~0xffffffff", "a=-1"); + // test_same("a=~.5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); + } + + #[test] + #[ignore] + fn unary_with_big_int() { + test("-(1n)", "-1n"); + test("- -1n", "1n"); + test("!1n", "false"); + test("~0n", "-1n"); + } + + #[test] + #[ignore] + fn test_unary_ops_string_compare() { + test_same("a = -1"); + test("a = ~0", "a = -1"); + test("a = ~1", "a = -2"); + test("a = ~101", "a = -102"); + } + + #[test] + fn test_fold_logical_op() { + test("x = true && x", "x = x"); + test("x = [foo()] && x", "x = ([foo()],x)"); + + test("x = false && x", "x = false"); + test("x = true || x", "x = true"); + test("x = false || x", "x = x"); + test("x = 0 && x", "x = 0"); + test("x = 3 || x", "x = 3"); + test("x = 0n && x", "x = 0n"); + test("x = 3n || x", "x = 3n"); + test("x = false || 0", "x = 0"); + + // unfoldable, because the right-side may be the result + test("a = x && true", "a=x && true"); + test("a = x && false", "a=x && false"); + test("a = x || 3", "a=x || 3"); + test("a = x || false", "a=x || false"); + test("a = b ? c : x || false", "a=b ? c:x || false"); + test("a = b ? x || false : c", "a=b ? x || false:c"); + test("a = b ? c : x && true", "a=b ? c:x && true"); + test("a = b ? x && true : c", "a=b ? x && true:c"); + + // folded, but not here. + test_same("a = x || false ? b : c"); + test_same("a = x && true ? b : c"); + + test("x = foo() || true || bar()", "x = foo() || true"); + test("x = foo() || true && bar()", "x = foo() || bar()"); + test("x = foo() || false && bar()", "x = foo() || false"); + test("x = foo() && false && bar()", "x = foo() && false"); + test("x = foo() && false || bar()", "x = (foo() && false,bar())"); + test("x = foo() || false || bar()", "x = foo() || bar()"); + test("x = foo() && true && bar()", "x = foo() && bar()"); + test("x = foo() || true || bar()", "x = foo() || true"); + test("x = foo() && false && bar()", "x = foo() && false"); + test("x = foo() && 0 && bar()", "x = foo() && 0"); + test("x = foo() && 1 && bar()", "x = foo() && bar()"); + test("x = foo() || 0 || bar()", "x = foo() || bar()"); + test("x = foo() || 1 || bar()", "x = foo() || 1"); + test("x = foo() && 0n && bar()", "x = foo() && 0n"); + test("x = foo() && 1n && bar()", "x = foo() && bar()"); + test("x = foo() || 0n || bar()", "x = foo() || bar()"); + test("x = foo() || 1n || bar()", "x = foo() || 1n"); + test_same("x = foo() || bar() || baz()"); + test_same("x = foo() && bar() && baz()"); + + test("0 || b()", "b()"); + test("1 && b()", "b()"); + test("a() && (1 && b())", "a() && b()"); + test("(a() && 1) && b()", "a() && b()"); + + test("(x || '') || y;", "x || y"); + test("false || (x || '');", "x || ''"); + test("(x && 1) && y;", "x && y"); + test("true && (x && 1);", "x && 1"); + + // Really not foldable, because it would change the type of the + // expression if foo() returns something truthy but not true. + // Cf. FoldConstants.tryFoldAndOr(). + // An example would be if foo() is 1 (truthy) and bar() is 0 (falsey): + // (1 && true) || 0 == true + // 1 || 0 == 1, but true =/= 1 + test_same("x = foo() && true || bar()"); + test_same("foo() && true || bar()"); + } + + #[test] + fn test_fold_logical_op2() { + test("x = function(){} && x", "x=x"); + test("x = true && function(){}", "x=function(){}"); + test("x = [(function(){alert(x)})()] && x", "x=([function(){alert(x)}()],x)"); + } + + #[test] + fn test_fold_nullish_coalesce() { + // fold if left is null/undefined + test("null ?? 1", "1"); + test("undefined ?? false", "false"); + test("(a(), null) ?? 1", "(a(), null, 1)"); + + test("x = [foo()] ?? x", "x = [foo()]"); + + // short circuit on all non nullish LHS + test("x = false ?? x", "x = false"); + test("x = true ?? x", "x = true"); + test("x = 0 ?? x", "x = 0"); + test("x = 3 ?? x", "x = 3"); + + // unfoldable, because the right-side may be the result + test_same("a = x ?? true"); + test_same("a = x ?? false"); + test_same("a = x ?? 3"); + test_same("a = b ? c : x ?? false"); + test_same("a = b ? x ?? false : c"); + + // folded, but not here. + test_same("a = x ?? false ? b : c"); + test_same("a = x ?? true ? b : c"); + + test_same("x = foo() ?? true ?? bar()"); + test("x = foo() ?? (true && bar())", "x = foo() ?? bar()"); + test_same("x = (foo() || false) ?? bar()"); + + test("a() ?? (1 ?? b())", "a() ?? 1"); + test("(a() ?? 1) ?? b()", "a() ?? 1 ?? b()"); + } + + #[test] + fn test_fold_void() { + test_same("void 0"); + test("void 1", "void 0"); + test("void x", "void 0"); + test_same("void x()"); + } + + #[test] + fn test_fold_bit_shift() { + test("x = 1 << 0", "x=1"); + test("x = -1 << 0", "x=-1"); + test("x = 1 << 1", "x=2"); + test("x = 3 << 1", "x=6"); + test("x = 1 << 8", "x=256"); + + test("x = 1 >> 0", "x=1"); + test("x = -1 >> 0", "x=-1"); + test("x = 1 >> 1", "x=0"); + test("x = 2 >> 1", "x=1"); + test("x = 5 >> 1", "x=2"); + test("x = 127 >> 3", "x=15"); + test("x = 3 >> 1", "x=1"); + test("x = 3 >> 2", "x=0"); + test("x = 10 >> 1", "x=5"); + test("x = 10 >> 2", "x=2"); + test("x = 10 >> 5", "x=0"); + + test("x = 10 >>> 1", "x=5"); + test("x = 10 >>> 2", "x=2"); + test("x = 10 >>> 5", "x=0"); + test("x = -1 >>> 1", "x=2147483647"); // 0x7fffffff + test("x = -1 >>> 0", "x=4294967295"); // 0xffffffff + test("x = -2 >>> 0", "x=4294967294"); // 0xfffffffe + test("x = 0x90000000 >>> 28", "x=9"); + + test("x = 0xffffffff << 0", "x=-1"); + test("x = 0xffffffff << 4", "x=-16"); + test("1 << 32", "1<<32"); + test("1 << -1", "1<<-1"); + test("1 >> 32", "1>>32"); + } +} diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index a0593721b0635..660667ac81a11 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -72,3 +72,1069 @@ impl<'a> PeepholeMinimizeConditions { None } } + +/// +#[cfg(test)] +mod test { + use oxc_allocator::Allocator; + + use crate::tester; + + fn test(source_text: &str, positive: &str) { + let allocator = Allocator::default(); + let mut pass = super::PeepholeMinimizeConditions::new(); + tester::test(&allocator, source_text, positive, &mut pass); + } + + fn test_same(source_text: &str) { + test(source_text, source_text); + } + + fn fold_same(js: &str) { + test_same(js); + } + + fn fold(js: &str, expected: &str) { + test(js, expected); + } + + /** Check that removing blocks with 1 child works */ + #[test] + #[ignore] + fn test_fold_one_child_blocks() { + // late = false; + fold("function f(){if(x)a();x=3}", "function f(){x&&a();x=3}"); + fold("function f(){if(x)a?.();x=3}", "function f(){x&&a?.();x=3}"); + + fold("function f(){if(x){a()}x=3}", "function f(){x&&a();x=3}"); + fold("function f(){if(x){a?.()}x=3}", "function f(){x&&a?.();x=3}"); + + fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); + fold("function f(){if(x){a()}}", "function f(){x&&a()}"); + fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); + + // Try it out with functions + fold("function f(){if(x){foo()}}", "function f(){x&&foo()}"); + fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); + + // Try it out with properties and methods + fold("function f(){if(x){a.b=1}}", "function f(){if(x)a.b=1}"); + fold("function f(){if(x){a.b*=1}}", "function f(){x&&(a.b*=1)}"); + fold("function f(){if(x){a.b+=1}}", "function f(){x&&(a.b+=1)}"); + fold("function f(){if(x){++a.b}}", "function f(){x&&++a.b}"); + fold("function f(){if(x){a.foo()}}", "function f(){x&&a.foo()}"); + fold("function f(){if(x){a?.foo()}}", "function f(){x&&a?.foo()}"); + + // Try it out with throw/catch/finally [which should not change] + fold_same("function f(){try{foo()}catch(e){bar(e)}finally{baz()}}"); + + // Try it out with switch statements + fold_same("function f(){switch(x){case 1:break}}"); + + // Do while loops stay in a block if that's where they started + fold_same("function f(){if(e1){do foo();while(e2)}else foo2()}"); + // Test an obscure case with do and while + fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); + + // Play with nested IFs + fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); + fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); + fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); + fold( + "function f(){if(x){if(y)foo();else bar()}else{baz()}}", + "function f(){x?y?foo():bar():baz()}", + ); + + fold("if(e1){while(e2){if(e3){foo()}}}else{bar()}", "if(e1)while(e2)e3&&foo();else bar()"); + + fold("if(e1){with(e2){if(e3){foo()}}}else{bar()}", "if(e1)with(e2)e3&&foo();else bar()"); + + fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); + fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); + + // NOTE - technically we can remove the blocks since both the parent + // and child have elses. But we don't since it causes ambiguities in + // some cases where not all descendent ifs having elses + fold( + "if(x){ if(y){var x;}else{var z;} }else{var w}", + "if(x)if(y)var x;else var z;else var w", + ); + fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); + + // Here's some of the ambiguous cases + fold( + "if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", + "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()", + ); + + fold("function f(){foo()}", "function f(){foo()}"); + fold("switch(x){case y: foo()}", "switch(x){case y:foo()}"); + fold( + "try{foo()}catch(ex){bar()}finally{baz()}", + "try{foo()}catch(ex){bar()}finally{baz()}", + ); + } + + /** Try to minimize returns */ + #[test] + #[ignore] + fn test_fold_returns() { + fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}"); + fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}"); + fold("function f(){if(x)return;return 2}", "function f(){return x?void 0:2}"); + fold("function f(){if(x)return 1+x;else return 2-x}", "function f(){return x?1+x:2-x}"); + fold("function f(){if(x)return 1+x;return 2-x}", "function f(){return x?1+x:2-x}"); + fold( + "function f(){if(x)return y += 1;else return y += 2}", + "function f(){return x?(y+=1):(y+=2)}", + ); + + fold("function f(){if(x)return;else return 2-x}", "function f(){if(x);else return 2-x}"); + fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}"); + fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;{}}"); + fold("function f(){if(x)return x;return}", "function f(){if(x)return x}"); + + fold_same("function f(){for(var x in y) { return x.y; } return k}"); + } + + #[test] + #[ignore] + fn test_combine_ifs1() { + fold( + "function f() {if (x) return 1; if (y) return 1}", + "function f() {if (x||y) return 1;}", + ); + fold( + "function f() {if (x) return 1; if (y) foo(); else return 1}", + "function f() {if ((!x)&&y) foo(); else return 1;}", + ); + } + + #[test] + #[ignore] + fn test_combine_ifs2() { + // combinable but not yet done + fold_same("function f() {if (x) throw 1; if (y) throw 1}"); + // Can't combine, side-effect + fold("function f(){ if (x) g(); if (y) g() }", "function f(){ x&&g(); y&&g() }"); + fold("function f(){ if (x) g?.(); if (y) g?.() }", "function f(){ x&&g?.(); y&&g?.() }"); + // Can't combine, side-effect + fold( + "function f(){ if (x) y = 0; if (y) y = 0; }", + "function f(){ x&&(y = 0); y&&(y = 0); }", + ); + } + + #[test] + #[ignore] + fn test_combine_ifs3() { + fold_same("function f() {if (x) return 1; if (y) {g();f()}}"); + } + + /** Try to minimize assignments */ + #[test] + #[ignore] + fn test_fold_assignments() { + fold("function f(){if(x)y=3;else y=4;}", "function f(){y=x?3:4}"); + fold("function f(){if(x)y=1+a;else y=2+a;}", "function f(){y=x?1+a:2+a}"); + + // and operation assignments + fold("function f(){if(x)y+=1;else y+=2;}", "function f(){y+=x?1:2}"); + fold("function f(){if(x)y-=1;else y-=2;}", "function f(){y-=x?1:2}"); + fold("function f(){if(x)y%=1;else y%=2;}", "function f(){y%=x?1:2}"); + fold("function f(){if(x)y|=1;else y|=2;}", "function f(){y|=x?1:2}"); + + // Don't fold if the 2 ops don't match. + fold_same("function f(){x ? y-=1 : y+=2}"); + + // Don't fold if the 2 LHS don't match. + fold_same("function f(){x ? y-=1 : z-=1}"); + + // Don't fold if there are potential effects. + fold_same("function f(){x ? y().a=3 : y().a=4}"); + } + + #[test] + #[ignore] + fn test_remove_duplicate_statements() { + // enableNormalize(); + // TODO(bradfordcsmith): Stop normalizing the expected output or document why it is necessary. + // enableNormalizeExpectedOutput(); + fold("if (a) { x = 1; x++ } else { x = 2; x++ }", "x=(a) ? 1 : 2; x++"); + // fold( + // "if (a) { x = 1; x++; y += 1; z = pi; }" + " else { x = 2; x++; y += 1; z = pi; }", + // "x=(a) ? 1 : 2; x++; y += 1; z = pi;", + // ); + // fold( + // "function z() {" + "if (a) { foo(); return !0 } else { goo(); return !0 }" + "}", + // "function z() {(a) ? foo() : goo(); return !0}", + // ); + // fold( + // "function z() {if (a) { foo(); x = true; return true " + // + "} else { goo(); x = true; return true }}", + // "function z() {(a) ? foo() : goo(); x = true; return true}", + // ); + + // fold( + // "function z() {" + // + " if (a) { bar(); foo(); return true }" + // + " else { bar(); goo(); return true }" + // + "}", + // "function z() {" + // + " if (a) { bar(); foo(); }" + // + " else { bar(); goo(); }" + // + " return true;" + // + "}", + // ); + } + + #[test] + #[ignore] + fn test_fold_returns_integration2() { + // late = true; + // disableNormalize(); + + // if-then-else duplicate statement removal handles this case: + test_same( + "function test(a) {if (a) {const a = Math.random();if(a) {return a;}} return a; }", + ); + } + + #[test] + #[ignore] + fn test_dont_remove_duplicate_statements_without_normalization() { + // In the following test case, we can't remove the duplicate "alert(x);" lines since each "x" + // refers to a different variable. + // We only try removing duplicate statements if the AST is normalized and names are unique. + test_same( + "if (Math.random() < 0.5) { const x = 3; alert(x); } else { const x = 5; alert(x); }", + ); + } + + #[test] + #[ignore] + fn test_not_cond() { + fold("function f(){if(!x)foo()}", "function f(){x||foo()}"); + fold("function f(){if(!x)b=1}", "function f(){x||(b=1)}"); + fold("if(!x)z=1;else if(y)z=2", "if(x){y&&(z=2);}else{z=1;}"); + fold("if(x)y&&(z=2);else z=1;", "x ? y&&(z=2) : z=1"); + fold("function f(){if(!(x=1))a.b=1}", "function f(){(x=1)||(a.b=1)}"); + } + + #[test] + #[ignore] + fn test_and_parentheses_count() { + fold("function f(){if(x||y)a.foo()}", "function f(){(x||y)&&a.foo()}"); + fold("function f(){if(x.a)x.a=0}", "function f(){x.a&&(x.a=0)}"); + fold("function f(){if(x?.a)x.a=0}", "function f(){x?.a&&(x.a=0)}"); + fold_same("function f(){if(x()||y()){x()||y()}}"); + } + + #[test] + #[ignore] + fn test_fold_logical_op_string_compare() { + // side-effects + // There is two way to parse two &&'s and both are correct. + fold("if (foo() && false) z()", "(foo(), 0) && z()"); + } + + #[test] + #[ignore] + fn test_fold_not() { + fold("while(!(x==y)){a=b;}", "while(x!=y){a=b;}"); + fold("while(!(x!=y)){a=b;}", "while(x==y){a=b;}"); + fold("while(!(x===y)){a=b;}", "while(x!==y){a=b;}"); + fold("while(!(x!==y)){a=b;}", "while(x===y){a=b;}"); + // Because !(x=NaN don't fold < and > cases. + fold_same("while(!(x>y)){a=b;}"); + fold_same("while(!(x>=y)){a=b;}"); + fold_same("while(!(x 20)) {foo();foo()}", "if(f() > 20){foo();foo()}"); + } + + #[test] + #[ignore] + fn test_fold_loop_break_late() { + // late = true; + fold("for(;;) if (a) break", "for(;!a;);"); + fold_same("for(;;) if (a) { f(); break }"); + fold("for(;;) if (a) break; else f()", "for(;!a;) { { f(); } }"); + fold("for(;a;) if (b) break", "for(;a && !b;);"); + fold("for(;a;) { if (b) break; if (c) break; }", "for(;(a && !b);) if (c) break;"); + fold("for(;(a && !b);) if (c) break;", "for(;(a && !b) && !c;);"); + fold("for(;;) { if (foo) { break; var x; } } x;", "var x; for(;!foo;) {} x;"); + + // 'while' is normalized to 'for' + // enableNormalize(); + fold("while(true) if (a) break", "for(;1&&!a;);"); + // disableNormalize(); + } + + #[test] + #[ignore] + fn test_fold_loop_break_early() { + // late = false; + fold_same("for(;;) if (a) break"); + fold_same("for(;;) if (a) { f(); break }"); + fold_same("for(;;) if (a) break; else f()"); + fold_same("for(;a;) if (b) break"); + fold_same("for(;a;) { if (b) break; if (c) break; }"); + + fold_same("while(1) if (a) break"); + // enableNormalize(); + fold_same("for (; 1; ) if (a) break"); + } + + #[test] + #[ignore] + fn test_fold_conditional_var_declaration() { + fold("if(x) var y=1;else y=2", "var y=x?1:2"); + fold("if(x) y=1;else var y=2", "var y=x?1:2"); + + fold_same("if(x) var y = 1; z = 2"); + fold_same("if(x||y) y = 1; var z = 2"); + + fold_same("if(x) { var y = 1; print(y)} else y = 2 "); + fold_same("if(x) var y = 1; else {y = 2; print(y)}"); + } + + #[test] + #[ignore] + fn test_fold_if_with_lower_operators_inside() { + fold("if (x + (y=5)) z && (w,z);", "x + (y=5) && (z && (w,z))"); + fold("if (!(x+(y=5))) z && (w,z);", "x + (y=5) || z && (w,z)"); + fold( + "if (x + (y=5)) if (z && (w,z)) for(;;) foo();", + "if (x + (y=5) && (z && (w,z))) for(;;) foo();", + ); + } + + #[test] + #[ignore] + fn test_substitute_return() { + // late = false; + // enableNormalize(); + // TODO(bradfordcsmith): Stop normalizing the expected output or document why it is necessary. + // enableNormalizeExpectedOutput(); + + fold("function f() { while(x) { return }}", "function f() { while(x) { break }}"); + + fold_same("function f() { while(x) { return 5 } }"); + + fold_same("function f() { a: { return 5 } }"); + + fold( + "function f() { while(x) { return 5} return 5}", + "function f() { while(x) { break } return 5}", + ); + + fold( + "function f() { while(x) { return x} return x}", + "function f() { while(x) { break } return x}", + ); + + fold( + "function f() { while(x) { if (y) { return }}}", + "function f() { while(x) { if (y) { break }}}", + ); + + fold( + "function f() { while(x) { if (y) { return }} return}", + "function f() { while(x) { if (y) { break }}}", + ); + + fold( + "function f() { while(x) { if (y) { return 5 }} return 5}", + "function f() { while(x) { if (y) { break }} return 5}", + ); + + // It doesn't matter if x is changed between them. We are still returning + // x at whatever x value current holds. The whole x = 1 is skipped. + fold( + "function f() { while(x) { if (y) { return x } x = 1} return x}", + "function f() { while(x) { if (y) { break } x = 1} return x}", + ); + + fold( + "function f() { while(x) { if (y) { return x } return x} return x}", + "function f() { while(x) { if (y) {} break }return x}", + ); + + // A break here only breaks out of the inner loop. + fold_same("function f() { while(x) { while (y) { return } } }"); + + fold_same("function f() { while(1) { return 7} return 5}"); + + // fold_same("function f() {" + " try { while(x) {return f()}} catch (e) { } return f()}"); + + // fold_same("function f() {" + " try { while(x) {return f()}} finally {alert(1)} return f()}"); + + // Both returns has the same handler + // fold( + // "function f() {" + " try { while(x) { return f() } return f() } catch (e) { } }", + // "function f() {" + " try { while(x) { break } return f() } catch (e) { } }", + // ); + + // We can't fold this because it'll change the order of when foo is called. + // fold_same( + // "function f() {" + // + " try { while(x) { return foo() } } finally { alert(1) } " + // + " return foo()}", + // ); + + // This is fine, we have no side effect in the return value. + // fold( + // "function f() {" + " try { while(x) { return 1 } } finally { alert(1) } return 1}", + // "function f() {" + " try { while(x) { break } } finally { alert(1) } return 1}", + // ); + + fold_same("function f() { try{ return a } finally { a = 2 } return a; }"); + + fold( + "function f() { switch(a){ case 1: return a; default: g();} return a;}", + "function f() { switch(a){ case 1: break; default: g();} return a; }", + ); + } + + #[test] + #[ignore] + fn test_substitute_break_for_throw() { + // late = false; + // enableNormalize(); + // TODO(bradfordcsmith): Stop normalizing the expected output or document why it is necessary. + // enableNormalizeExpectedOutput(); + + fold_same("function f() { while(x) { throw Error }}"); + + fold( + "function f() { while(x) { throw Error } throw Error }", + "function f() { while(x) { break } throw Error}", + ); + fold_same("function f() { while(x) { throw Error(1) } throw Error(2)}"); + fold_same("function f() { while(x) { throw Error(1) } return Error(2)}"); + + fold_same("function f() { while(x) { throw 5 } }"); + + fold_same("function f() { a: { throw 5 } }"); + + fold( + "function f() { while(x) { throw 5} throw 5}", + "function f() { while(x) { break } throw 5}", + ); + + fold( + "function f() { while(x) { throw x} throw x}", + "function f() { while(x) { break } throw x}", + ); + + fold_same("function f() { while(x) { if (y) { throw Error }}}"); + + fold( + "function f() { while(x) { if (y) { throw Error }} throw Error}", + "function f() { while(x) { if (y) { break }} throw Error}", + ); + + fold( + "function f() { while(x) { if (y) { throw 5 }} throw 5}", + "function f() { while(x) { if (y) { break }} throw 5}", + ); + + // It doesn't matter if x is changed between them. We are still throwing + // x at whatever x value current holds. The whole x = 1 is skipped. + fold( + "function f() { while(x) { if (y) { throw x } x = 1} throw x}", + "function f() { while(x) { if (y) { break } x = 1} throw x}", + ); + + fold( + "function f() { while(x) { if (y) { throw x } throw x} throw x}", + "function f() { while(x) { if (y) {} break }throw x}", + ); + + // A break here only breaks out of the inner loop. + fold_same("function f() { while(x) { while (y) { throw Error } } }"); + + fold_same("function f() { while(1) { throw 7} throw 5}"); + + // fold_same("function f() {" + " try { while(x) {throw f()}} catch (e) { } throw f()}"); + + // fold_same("function f() {" + " try { while(x) {throw f()}} finally {alert(1)} throw f()}"); + + // Both throws has the same handler + // fold( + // "function f() {" + " try { while(x) { throw f() } throw f() } catch (e) { } }", + // "function f() {" + " try { while(x) { break } throw f() } catch (e) { } }", + // ); + + // We can't fold this because it'll change the order of when foo is called. + // fold_same( + // "function f() {" + // + " try { while(x) { throw foo() } } finally { alert(1) } " + // + " throw foo()}", + // ); + + // This is fine, we have no side effect in the throw value. + // fold( + // "function f() {" + " try { while(x) { throw 1 } } finally { alert(1) } throw 1}", + // "function f() {" + " try { while(x) { break } } finally { alert(1) } throw 1}", + // ); + + fold_same("function f() { try{ throw a } finally { a = 2 } throw a; }"); + + fold( + "function f() { switch(a){ case 1: throw a; default: g();} throw a;}", + "function f() { switch(a){ case 1: break; default: g();} throw a; }", + ); + } + + #[test] + #[ignore] + fn test_remove_duplicate_return() { + // late = false; + // enableNormalize(); + + fold("function f() { return; }", "function f(){}"); + fold_same("function f() { return a; }"); + fold( + "function f() { if (x) { return a } return a; }", + "function f() { if (x) {} return a; }", + ); + fold_same("function f() { try { if (x) { return a } } catch(e) {} return a; }"); + fold_same("function f() { try { if (x) {} } catch(e) {} return 1; }"); + + // finally clauses may have side effects + fold_same("function f() { try { if (x) { return a } } finally { a++ } return a; }"); + // but they don't matter if the result doesn't have side effects and can't + // be affect by side-effects. + fold( + "function f() { try { if (x) { return 1 } } finally {} return 1; }", + "function f() { try { if (x) {} } finally {} return 1; }", + ); + + fold( + "function f() { switch(a){ case 1: return a; } return a; }", + "function f() { switch(a){ case 1: } return a; }", + ); + + // fold( + // "function f() { switch(a){ " + " case 1: return a; case 2: return a; } return a; }", + // "function f() { switch(a){ " + " case 1: break; case 2: } return a; }", + // ); + } + + #[test] + #[ignore] + fn test_remove_duplicate_throw() { + // late = false; + // enableNormalize(); + + fold_same("function f() { throw a; }"); + fold("function f() { if (x) { throw a } throw a; }", "function f() { if (x) {} throw a; }"); + fold_same("function f() { try { if (x) {throw a} } catch(e) {} throw a; }"); + fold_same("function f() { try { if (x) {throw 1} } catch(e) {f()} throw 1; }"); + fold_same("function f() { try { if (x) {throw 1} } catch(e) {f()} throw 1; }"); + fold_same("function f() { try { if (x) {throw 1} } catch(e) {throw 1}}"); + fold( + "function f() { try { if (x) {throw 1} } catch(e) {throw 1} throw 1; }", + "function f() { try { if (x) {throw 1} } catch(e) {} throw 1; }", + ); + + // finally clauses may have side effects + fold_same("function f() { try { if (x) { throw a } } finally { a++ } throw a; }"); + // but they don't matter if the result doesn't have side effects and can't + // be affect by side-effects. + fold( + "function f() { try { if (x) { throw 1 } } finally {} throw 1; }", + "function f() { try { if (x) {} } finally {} throw 1; }", + ); + + fold( + "function f() { switch(a){ case 1: throw a; } throw a; }", + "function f() { switch(a){ case 1: } throw a; }", + ); + + // fold( + // "function f() { switch(a){ " + "case 1: throw a; case 2: throw a; } throw a; }", + // "function f() { switch(a){ case 1: break; case 2: } throw a; }", + // ); + } + + #[test] + #[ignore] + fn test_nested_if_combine() { + fold("if(x)if(y){while(1){}}", "if(x&&y){while(1){}}"); + fold("if(x||z)if(y){while(1){}}", "if((x||z)&&y){while(1){}}"); + fold("if(x)if(y||z){while(1){}}", "if((x)&&(y||z)){while(1){}}"); + fold_same("if(x||z)if(y||z){while(1){}}"); + fold("if(x)if(y){if(z){while(1){}}}", "if(x&&(y&&z)){while(1){}}"); + } + + // See: http://blickly.github.io/closure-compiler-issues/#291 + #[test] + #[ignore] + fn test_issue291() { + fold("if (true) { f.onchange(); }", "if (1) f.onchange();"); + fold_same("if (f) { f.onchange(); }"); + fold_same("if (f) { f.bar(); } else { f.onchange(); }"); + fold("if (f) { f.bonchange(); }", "f && f.bonchange();"); + fold_same("if (f) { f['x'](); }"); + + // optional versions + fold("if (true) { f?.onchange(); }", "if (1) f?.onchange();"); + fold_same("if (f) { f?.onchange(); }"); + fold_same("if (f) { f?.bar(); } else { f?.onchange(); }"); + fold("if (f) { f?.bonchange(); }", "f && f?.bonchange();"); + fold_same("if (f) { f?.['x'](); }"); + } + + #[test] + #[ignore] + fn test_object_literal() { + test("({})", "1"); + test("({a:1})", "1"); + test_same("({a:foo()})"); + test_same("({'a':foo()})"); + } + + #[test] + #[ignore] + fn test_array_literal() { + test("([])", "1"); + test("([1])", "1"); + test("([a])", "1"); + test_same("([foo()])"); + } + + #[test] + #[ignore] + fn test_remove_else_cause() { + // test( + // "function f() {" + " if(x) return 1;" + " else if(x) return 2;" + " else if(x) return 3 }", + // "function f() {" + " if(x) return 1;" + "{ if(x) return 2;" + "{ if(x) return 3 } } }", + // ); + } + + #[test] + #[ignore] + fn test_remove_else_cause1() { + test( + "function f() { if (x) throw 1; else f() }", + "function f() { if (x) throw 1; { f() } }", + ); + } + + #[test] + #[ignore] + fn test_remove_else_cause2() { + test( + "function f() { if (x) return 1; else f() }", + "function f() { if (x) return 1; { f() } }", + ); + test("function f() { if (x) return; else f() }", "function f() { if (x) {} else { f() } }"); + // This case is handled by minimize exit points. + test_same("function f() { if (x) return; f() }"); + } + + #[test] + #[ignore] + fn test_remove_else_cause3() { + test_same("function f() { a:{if (x) break a; else f() } }"); + test_same("function f() { if (x) { a:{ break a } } else f() }"); + test_same("function f() { if (x) a:{ break a } else f() }"); + } + + #[test] + #[ignore] + fn test_remove_else_cause4() { + test_same("function f() { if (x) { if (y) { return 1; } } else f() }"); + } + + #[test] + #[ignore] + fn test_issue925() { + // test( + // "if (x[--y] === 1) {\n" + " x[y] = 0;\n" + "} else {\n" + " x[y] = 1;\n" + "}", + // "(x[--y] === 1) ? x[y] = 0 : x[y] = 1;", + // ); + + // test( + // "if (x[--y]) {\n" + " a = 0;\n" + "} else {\n" + " a = 1;\n" + "}", + // "a = (x[--y]) ? 0 : 1;", + // ); + + // test( + // lines( + // "if (x?.[--y]) {", // + // " a = 0;", + // "} else {", + // " a = 1;", + // "}", + // ), + // "a = (x?.[--y]) ? 0 : 1;", + // ); + + test("if (x++) { x += 2 } else { x += 3 }", "x++ ? x += 2 : x += 3"); + + test("if (x++) { x = x + 2 } else { x = x + 3 }", "x = x++ ? x + 2 : x + 3"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_disabled() { + // enableTypeCheck(); + test_same("var x = {}; if (x != null) throw 'a';"); + test_same("var x = {}; var y = x != null;"); + + test_same("var x = 1; if (x != 0) throw 'a';"); + test_same("var x = 1; var y = x != 0;"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_boolean_result0() { + // enableTypeCheck(); + test_same("var x = {}; var y = x != null;"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_boolean_result1() { + // enableTypeCheck(); + test_same("var x = {}; var y = x == null;"); + test_same("var x = {}; var y = x !== null;"); + test_same("var x = undefined; var y = x !== null;"); + test_same("var x = {}; var y = x === null;"); + test_same("var x = undefined; var y = x === null;"); + + test_same("var x = 1; var y = x != 0;"); + test_same("var x = 1; var y = x == 0;"); + test_same("var x = 1; var y = x !== 0;"); + test_same("var x = 1; var y = x === 0;"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_if() { + // enableTypeCheck(); + test("var x = {};\nif (x != null) throw 'a';\n", "var x={}; if (x!=null) throw 'a'"); + test_same("var x = {};\nif (x == null) throw 'a';\n"); + test_same("var x = {};\nif (x !== null) throw 'a';\n"); + test_same("var x = {};\nif (x === null) throw 'a';\n"); + test_same("var x = {};\nif (null != x) throw 'a';\n"); + test_same("var x = {};\nif (null == x) throw 'a';\n"); + test_same("var x = {};\nif (null !== x) throw 'a';\n"); + test_same("var x = {};\nif (null === x) throw 'a';\n"); + + test_same("var x = 1;\nif (x != 0) throw 'a';\n"); + test_same("var x = 1;\nif (x != 0) throw 'a';\n"); + test_same("var x = 1;\nif (x == 0) throw 'a';\n"); + test_same("var x = 1;\nif (x !== 0) throw 'a';\n"); + test_same("var x = 1;\nif (x === 0) throw 'a';\n"); + test_same("var x = 1;\nif (0 != x) throw 'a';\n"); + test_same("var x = 1;\nif (0 == x) throw 'a';\n"); + test_same("var x = 1;\nif (0 !== x) throw 'a';\n"); + test_same("var x = 1;\nif (0 === x) throw 'a';\n"); + test_same("var x = NaN;\nif (0 === x) throw 'a';\n"); + test_same("var x = NaN;\nif (x === 0) throw 'a';\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_expression() { + // enableTypeCheck(); + test_same("var x = {}; x != null && alert('b');"); + test_same("var x = 1; x != 0 && alert('b');"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_hook() { + // enableTypeCheck(); + // test_same(lines("var x = {};", "var y = x != null ? 1 : 2;")); + // test_same(lines("var x = 1;", "var y = x != 0 ? 1 : 2;")); + } + + #[test] + #[ignore] + fn test_coercion_substitution_not() { + // enableTypeCheck(); + test( + "var x = {};\nvar y = !(x != null) ? 1 : 2;\n", + "var x = {};\nvar y = (x == null) ? 1 : 2;\n", + ); + test("var x = 1;\nvar y = !(x != 0) ? 1 : 2;\n", "var x = 1;\nvar y = x == 0 ? 1 : 2;\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_while() { + // enableTypeCheck(); + test_same("var x = {}; while (x != null) throw 'a';"); + test_same("var x = 1; while (x != 0) throw 'a';"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_unknown_type() { + // enableTypeCheck(); + test_same("var x = /** @type {?} */ ({});\nif (x != null) throw 'a';\n"); + test_same("var x = /** @type {?} */ (1);\nif (x != 0) throw 'a';\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_all_type() { + // enableTypeCheck(); + test_same("var x = /** @type {*} */ ({});\nif (x != null) throw 'a';\n"); + test_same("var x = /** @type {*} */ (1);\nif (x != 0) throw 'a';\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_primitives_vs_null() { + // enableTypeCheck(); + test_same("var x = 0;\nif (x != null) throw 'a';\n"); + test_same("var x = '';\nif (x != null) throw 'a';\n"); + test_same("var x = false;\nif (x != null) throw 'a';\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_non_number_vs_zero() { + // enableTypeCheck(); + test_same("var x = {};\nif (x != 0) throw 'a';\n"); + test_same("var x = '';\nif (x != 0) throw 'a';\n"); + test_same("var x = false;\nif (x != 0) throw 'a';\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_boxed_number_vs_zero() { + // enableTypeCheck(); + test_same("var x = new Number(0);\nif (x != 0) throw 'a';\n"); + } + + #[test] + #[ignore] + fn test_coercion_substitution_boxed_primitives() { + // enableTypeCheck(); + test_same("var x = new Number(); if (x != null) throw 'a';"); + test_same("var x = new String(); if (x != null) throw 'a';"); + test_same("var x = new Boolean();\nif (x != null) throw 'a';"); + } + + #[test] + #[ignore] + fn test_minimize_if_with_new_target_condition() { + // Related to https://github.com/google/closure-compiler/issues/3097 + // test( + // lines( + // "function x() {", + // " if (new.target) {", + // " return 1;", + // " } else {", + // " return 2;", + // " }", + // "}", + // ), + // lines("function x() {", " return new.target ? 1 : 2;", "}"), + // ); + } +} diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index 7a84f389b6785..28fad6d6f1b89 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -115,3 +115,21 @@ impl<'a> PeepholeRemoveDeadCode { } } } + +// /// +// #[cfg(test)] +// mod test { +// use oxc_allocator::Allocator; + +// use crate::{tester, CompressOptions}; + +// fn test(source_text: &str, expected: &str) { +// let allocator = Allocator::default(); +// let mut pass = super::PeepholeRemoveDeadCode::new(); +// tester::test(&allocator, source_text, expected, &mut pass); +// } + +// fn test_same(source_text: &str) { +// test(source_text, source_text); +// } +// } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 56e3dd57da747..9302ce0331713 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -245,3 +245,45 @@ impl<'a> PeepholeSubstituteAlternateSyntax { } } } + +/// +#[cfg(test)] +mod test { + use oxc_allocator::Allocator; + + use crate::{tester, CompressOptions}; + + fn test(source_text: &str, expected: &str) { + let allocator = Allocator::default(); + let mut pass = super::PeepholeSubstituteAlternateSyntax::new(CompressOptions::default()); + tester::test(&allocator, source_text, expected, &mut pass); + } + + fn test_same(source_text: &str) { + test(source_text, source_text); + } + + #[test] + fn fold_return_result() { + test("function f(){return !1;}", "function f(){return !1}"); + test("function f(){return null;}", "function f(){return null}"); + test("function f(){return void 0;}", "function f(){return}"); + test("function f(){return void foo();}", "function f(){return void foo()}"); + test("function f(){return undefined;}", "function f(){return}"); + test("function f(){if(a()){return undefined;}}", "function f(){if(a())return}"); + } + + #[test] + fn undefined() { + test("var x = undefined", "var x"); + test_same("var undefined = 1;function f() {var undefined=2;var x;}"); + test("function f(undefined) {}", "function f(undefined){}"); + test("try {} catch(undefined) {}", "try{}catch(undefined){}"); + test("for (undefined in {}) {}", "for(undefined in {}){}"); + test("undefined++", "undefined++"); + test("undefined += undefined", "undefined+=void 0"); + + // shadowd + test_same("(function(undefined) { let x = typeof undefined; })()"); + } +} diff --git a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs index 8a38721f4df7b..c6dbaad3fb242 100644 --- a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs @@ -88,3 +88,32 @@ impl<'a> RemoveSyntax { ident.name == "console" } } + +#[cfg(test)] +mod test { + use oxc_allocator::Allocator; + + use crate::{tester, CompressOptions}; + + fn test(source_text: &str, expected: &str) { + let allocator = Allocator::default(); + let mut pass = super::RemoveSyntax::new(CompressOptions::all_true()); + tester::test(&allocator, source_text, expected, &mut pass); + } + + #[test] + fn parens() { + test("(((x)))", "x"); + test("(((a + b))) * c", "(a + b) * c"); + } + + #[test] + fn drop_console() { + test("console.log()", ""); + } + + #[test] + fn drop_debugger() { + test("debugger", ""); + } +} diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 12338e72443db..7245f65ee415a 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -34,9 +34,14 @@ impl<'a> Compressor<'a> { program: &mut Program<'a>, ) { let mut ctx = TraverseCtx::new(scopes, symbols, self.allocator); - // Run separate AST passes - // TODO: inline variables self.remove_syntax(program, &mut ctx); + + if self.options.dead_code_elimination { + self.dead_code_elimination(program, &mut ctx); + return; + } + + // TODO: inline variables self.fold_constants(program, &mut ctx); self.minimize_conditions(program, &mut ctx); self.remove_dead_code(program, &mut ctx); @@ -45,39 +50,33 @@ impl<'a> Compressor<'a> { self.collapse(program, &mut ctx); } + fn dead_code_elimination(self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.fold_constants(program, ctx); + self.minimize_conditions(program, ctx); + self.remove_dead_code(program, ctx); + } + fn remove_syntax(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.remove_syntax { - RemoveSyntax::new(self.options).build(program, ctx); - } + RemoveSyntax::new(self.options).build(program, ctx); } fn minimize_conditions(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.minimize_conditions { - PeepholeMinimizeConditions::new().build(program, ctx); - } + PeepholeMinimizeConditions::new().build(program, ctx); } fn fold_constants(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.fold_constants { - PeepholeFoldConstants::new().with_evaluate(self.options.evaluate).build(program, ctx); - } + PeepholeFoldConstants::new().with_evaluate(self.options.evaluate).build(program, ctx); } fn substitute_alternate_syntax(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.substitute_alternate_syntax { - PeepholeSubstituteAlternateSyntax::new(self.options).build(program, ctx); - } + PeepholeSubstituteAlternateSyntax::new(self.options).build(program, ctx); } fn remove_dead_code(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.remove_dead_code { - PeepholeRemoveDeadCode::new().build(program, ctx); - } + PeepholeRemoveDeadCode::new().build(program, ctx); } fn collapse(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.collapse { - CollapseVariableDeclarations::new(self.options).build(program, ctx); - } + CollapseVariableDeclarations::new(self.options).build(program, ctx); } } diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index 077f81b0877fd..2715235572897 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -11,6 +11,9 @@ mod plugins; mod tri; mod ty; +#[cfg(test)] +mod tester; + use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_mangler::Mangler; diff --git a/crates/oxc_minifier/src/options.rs b/crates/oxc_minifier/src/options.rs index 6a0476cf4ea51..2dff7c98d2328 100644 --- a/crates/oxc_minifier/src/options.rs +++ b/crates/oxc_minifier/src/options.rs @@ -1,11 +1,6 @@ #[derive(Debug, Clone, Copy)] pub struct CompressOptions { - pub remove_syntax: bool, - pub minimize_conditions: bool, - pub substitute_alternate_syntax: bool, - pub fold_constants: bool, - pub remove_dead_code: bool, - pub collapse: bool, + pub dead_code_elimination: bool, /// Various optimizations for boolean context, for example `!!a ? b : c` → `a ? b : c`. /// @@ -46,19 +41,14 @@ pub struct CompressOptions { #[allow(clippy::derivable_impls)] impl Default for CompressOptions { fn default() -> Self { - Self { drop_console: false, ..Self::all_true() } + Self { dead_code_elimination: false, drop_console: false, ..Self::all_true() } } } impl CompressOptions { pub fn all_true() -> Self { Self { - remove_syntax: true, - minimize_conditions: true, - substitute_alternate_syntax: true, - fold_constants: true, - remove_dead_code: true, - collapse: true, + dead_code_elimination: false, booleans: true, drop_debugger: true, drop_console: true, @@ -71,12 +61,7 @@ impl CompressOptions { pub fn all_false() -> Self { Self { - remove_syntax: false, - minimize_conditions: false, - substitute_alternate_syntax: false, - fold_constants: false, - remove_dead_code: false, - collapse: false, + dead_code_elimination: false, booleans: false, drop_debugger: false, drop_console: false, @@ -88,12 +73,6 @@ impl CompressOptions { } pub fn dead_code_elimination() -> Self { - Self { - remove_syntax: true, - minimize_conditions: true, - fold_constants: true, - remove_dead_code: true, - ..Self::all_false() - } + Self { dead_code_elimination: true, ..Self::all_false() } } } diff --git a/crates/oxc_minifier/src/tester.rs b/crates/oxc_minifier/src/tester.rs new file mode 100644 index 0000000000000..be10af518e08c --- /dev/null +++ b/crates/oxc_minifier/src/tester.rs @@ -0,0 +1,41 @@ +use oxc_allocator::Allocator; +use oxc_codegen::{CodeGenerator, CodegenOptions}; +use oxc_parser::Parser; +use oxc_semantic::SemanticBuilder; +use oxc_span::SourceType; +use oxc_traverse::TraverseCtx; + +use crate::{ast_passes::CompressorPass, ast_passes::RemoveSyntax, CompressOptions}; + +pub fn test<'a, P: CompressorPass<'a>>( + allocator: &'a Allocator, + source_text: &'a str, + expected: &'a str, + pass: &mut P, +) { + let result = run(allocator, source_text, Some(pass)); + let expected = run::

(allocator, expected, None); + assert_eq!(result, expected, "\nfor source\n{source_text}\nexpect\n{expected}\ngot\n{result}"); +} + +fn run<'a, P: CompressorPass<'a>>( + allocator: &'a Allocator, + source_text: &'a str, + pass: Option<&mut P>, +) -> String { + let source_type = SourceType::mjs(); + let mut program = Parser::new(allocator, source_text, source_type).parse().program; + + if let Some(pass) = pass { + let (symbols, scopes) = + SemanticBuilder::new("").build(&program).semantic.into_symbol_table_and_scope_tree(); + let mut ctx = TraverseCtx::new(scopes, symbols, allocator); + RemoveSyntax::new(CompressOptions::all_false()).build(&mut program, &mut ctx); + pass.build(&mut program, &mut ctx); + } + + CodeGenerator::new() + .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) + .build(&program) + .source_text +} diff --git a/crates/oxc_minifier/tests/ast_passes/collapse_variable_declarations.rs b/crates/oxc_minifier/tests/ast_passes/collapse_variable_declarations.rs deleted file mode 100644 index 18f6e85af5d2c..0000000000000 --- a/crates/oxc_minifier/tests/ast_passes/collapse_variable_declarations.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! - -use crate::CompressOptions; - -fn test(source_text: &str, expected: &str) { - let options = CompressOptions::all_true(); - crate::test(source_text, expected, options); -} - -fn test_same(source_text: &str) { - test(source_text, source_text); -} - -#[test] -fn cjs() { - // Do not join `require` calls for cjs-module-lexer. - test_same( - " - Object.defineProperty(exports, '__esModule', { value: true }); - var compilerDom = require('@vue/compiler-dom'); - var runtimeDom = require('@vue/runtime-dom'); - var shared = require('@vue/shared'); - ", - ); -} diff --git a/crates/oxc_minifier/tests/ast_passes/minimized_condition.rs b/crates/oxc_minifier/tests/ast_passes/minimized_condition.rs deleted file mode 100644 index e1745d41990da..0000000000000 --- a/crates/oxc_minifier/tests/ast_passes/minimized_condition.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! - -use oxc_minifier::CompressOptions; - -// TODO: handle negative cases -fn test(source_text: &str, positive: &str, _negative: &str) { - let options = CompressOptions { - remove_syntax: true, - minimize_conditions: true, - ..CompressOptions::all_false() - }; - crate::test(source_text, positive, options); -} - -#[test] -#[ignore] -fn try_minimize_cond_simple() { - test("x", "x", "x"); - test("!x", "!x", "!x"); - test("!!x", "x", "x"); - test("!(x && y)", "!x || !y", "!(x && y)"); -} - -#[test] -#[ignore] -fn minimize_demorgan_simple() { - test("!(x&&y)", "!x||!y", "!(x&&y)"); - test("!(x||y)", "!x&&!y", "!(x||y)"); - test("!x||!y", "!x||!y", "!(x&&y)"); - test("!x&&!y", "!x&&!y", "!(x||y)"); - test("!(x && y && z)", "!(x && y && z)", "!(x && y && z)"); - test("(!a||!b)&&c", "(!a||!b)&&c", "!(a&&b||!c)"); - test("(!a||!b)&&(c||d)", "!(a&&b||!c&&!d)", "!(a&&b||!c&&!d)"); -} - -#[test] -#[ignore] -fn minimize_bug8494751() { - test( - "x && (y===2 || !f()) && (y===3 || !h())", - // TODO(tbreisacher): The 'positive' option could be better: - // "x && !((y!==2 && f()) || (y!==3 && h()))", - "!(!x || (y!==2 && f()) || (y!==3 && h()))", - "!(!x || (y!==2 && f()) || (y!==3 && h()))", - ); - - test( - "x && (y===2 || !f?.()) && (y===3 || !h?.())", - "!(!x || (y!==2 && f?.()) || (y!==3 && h?.()))", - "!(!x || (y!==2 && f?.()) || (y!==3 && h?.()))", - ); -} - -#[test] -#[ignore] -fn minimize_complementable_operator() { - test("0===c && (2===a || 1===a)", "0===c && (2===a || 1===a)", "!(0!==c || 2!==a && 1!==a)"); -} - -#[test] -#[ignore] -fn minimize_hook() { - test("!(x ? y : z)", "(x ? !y : !z)", "!(x ? y : z)"); -} - -#[test] -#[ignore] -fn minimize_comma() { - test("!(inc(), test())", "inc(), !test()", "!(inc(), test())"); - test("!(inc?.(), test?.())", "inc?.(), !test?.()", "!(inc?.(), test?.())"); - test("!((x,y)&&z)", "(x,!y)||!z", "!((x,y)&&z)"); -} diff --git a/crates/oxc_minifier/tests/ast_passes/mod.rs b/crates/oxc_minifier/tests/ast_passes/mod.rs index 22840d9ac04ab..88d302d7e20b6 100644 --- a/crates/oxc_minifier/tests/ast_passes/mod.rs +++ b/crates/oxc_minifier/tests/ast_passes/mod.rs @@ -1,14 +1,6 @@ -mod collapse_variable_declarations; mod dead_code_elimination; -mod minimized_condition; -mod peephole_fold_constants; -mod peephole_minimize_conditions; -mod peephole_substitute_alternate_syntax; -mod remove_syntax; -// Oxc Integration Tests - -use crate::CompressOptions; +use oxc_minifier::CompressOptions; fn test(source_text: &str, expected: &str) { let options = CompressOptions::default(); @@ -19,10 +11,38 @@ fn test_same(source_text: &str) { test(source_text, source_text); } +// Oxc Integration Tests + #[test] fn cjs() { // Bail `cjs-module-lexer`. test_same("0 && (module.exports = { version });"); + + // Bail `cjs-module-lexer`. + // Export is undefined when `enumerable` is "!0". + // https://github.com/nodejs/cjs-module-lexer/issues/64 + test_same( + "Object.defineProperty(exports, 'ConnectableObservable', { + enumerable: true, + get: function() { + return ConnectableObservable_1.ConnectableObservable; + } + });", + ); + // @babel/types/lib/index.js + test_same( + r#"Object.keys(_index6).forEach(function(key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index6[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function() { + return _index6[key]; + } + }); + });"#, + ); } #[test] // https://github.com/oxc-project/oxc/issues/4341 diff --git a/crates/oxc_minifier/tests/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/tests/ast_passes/peephole_fold_constants.rs deleted file mode 100644 index f29120e407dd0..0000000000000 --- a/crates/oxc_minifier/tests/ast_passes/peephole_fold_constants.rs +++ /dev/null @@ -1,681 +0,0 @@ -//! - -use crate::CompressOptions; - -fn test(source_text: &str, expected: &str) { - let options = CompressOptions { - remove_syntax: true, - fold_constants: true, - ..CompressOptions::all_false() - }; - crate::test(source_text, expected, options); -} - -fn test_same(source_text: &str) { - test(source_text, source_text); -} - -#[test] -fn undefined_comparison1() { - test("undefined == undefined", "true"); - test("undefined == null", "true"); - test("undefined == void 0", "true"); - - test("undefined == 0", "false"); - test("undefined == 1", "false"); - test("undefined == 'hi'", "false"); - test("undefined == true", "false"); - test("undefined == false", "false"); - - test("undefined === undefined", "true"); - test("undefined === null", "false"); - test("undefined === void 0", "true"); - - test_same("undefined == this"); - test_same("undefined == x"); - - test("undefined != undefined", "false"); - test("undefined != null", "false"); - test("undefined != void 0", "false"); - - test("undefined != 0", "true"); - test("undefined != 1", "true"); - test("undefined != 'hi'", "true"); - test("undefined != true", "true"); - test("undefined != false", "true"); - - test("undefined !== undefined", "false"); - test("undefined !== void 0", "false"); - test("undefined !== null", "true"); - - test_same("undefined != this"); - test_same("undefined != x"); - - test("undefined < undefined", "false"); - test("undefined > undefined", "false"); - test("undefined >= undefined", "false"); - test("undefined <= undefined", "false"); - - test("0 < undefined", "false"); - test("true > undefined", "false"); - test("'hi' >= undefined", "false"); - test("null <= undefined", "false"); - - test("undefined < 0", "false"); - test("undefined > true", "false"); - test("undefined >= 'hi'", "false"); - test("undefined <= null", "false"); - - test("null == undefined", "true"); - test("0 == undefined", "false"); - test("1 == undefined", "false"); - test("'hi' == undefined", "false"); - test("true == undefined", "false"); - test("false == undefined", "false"); - test("null === undefined", "false"); - test("void 0 === undefined", "true"); - - test("undefined == NaN", "false"); - test("NaN == undefined", "false"); - test("undefined == Infinity", "false"); - test("Infinity == undefined", "false"); - test("undefined == -Infinity", "false"); - test("-Infinity == undefined", "false"); - test("({}) == undefined", "false"); - test("undefined == ({})", "false"); - test("([]) == undefined", "false"); - test("undefined == ([])", "false"); - test("(/a/g) == undefined", "false"); - test("undefined == (/a/g)", "false"); - test("(function(){}) == undefined", "false"); - test("undefined == (function(){})", "false"); - - test("undefined != NaN", "true"); - test("NaN != undefined", "true"); - test("undefined != Infinity", "true"); - test("Infinity != undefined", "true"); - test("undefined != -Infinity", "true"); - test("-Infinity != undefined", "true"); - test("({}) != undefined", "true"); - test("undefined != ({})", "true"); - test("([]) != undefined", "true"); - test("undefined != ([])", "true"); - test("(/a/g) != undefined", "true"); - test("undefined != (/a/g)", "true"); - test("(function(){}) != undefined", "true"); - test("undefined != (function(){})", "true"); - - test_same("this == undefined"); - test_same("x == undefined"); -} - -#[test] -fn test_undefined_comparison2() { - test("\"123\" !== void 0", "true"); - test("\"123\" === void 0", "false"); - - test("void 0 !== \"123\"", "true"); - test("void 0 === \"123\"", "false"); -} - -#[test] -fn test_undefined_comparison3() { - test("\"123\" !== undefined", "true"); - test("\"123\" === undefined", "false"); - - test("undefined !== \"123\"", "true"); - test("undefined === \"123\"", "false"); -} - -#[test] -fn test_null_comparison1() { - test("null == undefined", "true"); - test("null == null", "true"); - test("null == void 0", "true"); - - test("null == 0", "false"); - test("null == 1", "false"); - // test("null == 0n", "false"); - // test("null == 1n", "false"); - test("null == 'hi'", "false"); - test("null == true", "false"); - test("null == false", "false"); - - test("null === undefined", "false"); - test("null === null", "true"); - test("null === void 0", "false"); - test_same("null===x"); - - test_same("null==this"); - test_same("null==x"); - - test("null != undefined", "false"); - test("null != null", "false"); - test("null != void 0", "false"); - - test("null != 0", "true"); - test("null != 1", "true"); - // test("null != 0n", "true"); - // test("null != 1n", "true"); - test("null != 'hi'", "true"); - test("null != true", "true"); - test("null != false", "true"); - - test("null !== undefined", "true"); - test("null !== void 0", "true"); - test("null !== null", "false"); - - test_same("null!=this"); - test_same("null!=x"); - - test("null < null", "false"); - test("null > null", "false"); - test("null >= null", "true"); - test("null <= null", "true"); - - test("0 < null", "false"); - test("0 > null", "false"); - test("0 >= null", "true"); - // test("0n < null", "false"); - // test("0n > null", "false"); - // test("0n >= null", "true"); - test("true > null", "true"); - test("'hi' < null", "false"); - test("'hi' >= null", "false"); - test("null <= null", "true"); - - test("null < 0", "false"); - // test("null < 0n", "false"); - test("null > true", "false"); - test("null < 'hi'", "false"); - test("null >= 'hi'", "false"); - test("null <= null", "true"); - - test("null == null", "true"); - test("0 == null", "false"); - test("1 == null", "false"); - test("'hi' == null", "false"); - test("true == null", "false"); - test("false == null", "false"); - test("null === null", "true"); - test("void 0 === null", "false"); - - test("null == NaN", "false"); - test("NaN == null", "false"); - test("null == Infinity", "false"); - test("Infinity == null", "false"); - test("null == -Infinity", "false"); - test("-Infinity == null", "false"); - test("({}) == null", "false"); - test("null == ({})", "false"); - test("([]) == null", "false"); - test("null == ([])", "false"); - test("(/a/g) == null", "false"); - test("null == (/a/g)", "false"); - test("(function(){}) == null", "false"); - test("null == (function(){})", "false"); - - test("null != NaN", "true"); - test("NaN != null", "true"); - test("null != Infinity", "true"); - test("Infinity != null", "true"); - test("null != -Infinity", "true"); - test("-Infinity != null", "true"); - test("({}) != null", "true"); - test("null != ({})", "true"); - test("([]) != null", "true"); - test("null != ([])", "true"); - test("(/a/g) != null", "true"); - test("null != (/a/g)", "true"); - test("(function(){}) != null", "true"); - test("null != (function(){})", "true"); - - test_same("({a:f()})==null"); - test_same("null=={a:f()}"); - test_same("[f()]==null"); - test_same("null==[f()]"); - - test_same("this==null"); - test_same("x==null"); -} - -#[test] -fn test_boolean_boolean_comparison() { - test_same("!x==!y"); - test_same("!x 'b'", "false"); - test("'a' >= 'b'", "false"); - test("+'a' < +'b'", "false"); - test_same("typeof a < 'a'"); - test_same("'a' >= typeof a"); - test("typeof a < typeof a", "false"); - test("typeof a >= typeof a", "true"); - test("typeof 3 > typeof 4", "false"); - test("typeof function() {} < typeof function() {}", "false"); - test("'a' == 'a'", "true"); - test("'b' != 'a'", "true"); - test_same("'undefined' == typeof a"); - test_same("typeof a != 'number'"); - test_same("'undefined' == typeof a"); - test_same("'undefined' == typeof a"); - test("typeof a == typeof a", "true"); - test("'a' === 'a'", "true"); - test("'b' !== 'a'", "true"); - test("typeof a === typeof a", "true"); - test("typeof a !== typeof a", "false"); - test_same("'' + x <= '' + y"); - test_same("'' + x != '' + y"); - test_same("'' + x === '' + y"); - - test_same("'' + x <= '' + x"); // potentially foldable - test_same("'' + x != '' + x"); // potentially foldable - test_same("'' + x === '' + x"); // potentially foldable -} - -#[test] -fn test_number_string_comparison() { - test("1 < '2'", "true"); - test("2 > '1'", "true"); - test("123 > '34'", "true"); - test("NaN >= 'NaN'", "false"); - test("1 == '2'", "false"); - test("1 != '1'", "false"); - test("NaN == 'NaN'", "false"); - test("1 === '1'", "false"); - test("1 !== '1'", "true"); - test_same("+x>''+y"); - test_same("+x==''+y"); - test("+x !== '' + y", "true"); -} - -#[test] -fn test_string_number_comparison() { - test("'1' < 2", "true"); - test("'2' > 1", "true"); - test("'123' > 34", "true"); - test("'NaN' < NaN", "false"); - test("'1' == 2", "false"); - test("'1' != 1", "false"); - test("'NaN' == NaN", "false"); - test("'1' === 1", "false"); - test("'1' !== 1", "true"); - test_same("''+x<+y"); - test_same("''+x==+y"); - test("'' + x === +y", "false"); -} - -#[test] -#[ignore] -fn test_bigint_number_comparison() { - test("1n < 2", "true"); - test("1n > 2", "false"); - test("1n == 1", "true"); - test("1n == 2", "false"); - - // comparing with decimals is allowed - test("1n < 1.1", "true"); - test("1n < 1.9", "true"); - test("1n < 0.9", "false"); - test("-1n < -1.1", "false"); - test("-1n < -1.9", "false"); - test("-1n < -0.9", "true"); - test("1n > 1.1", "false"); - test("1n > 0.9", "true"); - test("-1n > -1.1", "true"); - test("-1n > -0.9", "false"); - - // Don't fold unsafely large numbers because there might be floating-point error - let max_safe_int = 9_007_199_254_740_991_i64; - let neg_max_safe_int = -9_007_199_254_740_991_i64; - let max_safe_float = 9_007_199_254_740_991_f64; - let neg_max_safe_float = -9_007_199_254_740_991_f64; - test(&format!("0n > {max_safe_int}"), "false"); - test(&format!("0n < {max_safe_int}"), "true"); - test(&format!("0n > {neg_max_safe_int}"), "true"); - test(&format!("0n < {neg_max_safe_int}"), "false"); - test(&format!("0n > {max_safe_float}"), "false"); - test(&format!("0n < {max_safe_float}"), "true"); - test(&format!("0n > {neg_max_safe_float}"), "true"); - test(&format!("0n < {neg_max_safe_float}"), "false"); - - // comparing with Infinity is allowed - test("1n < Infinity", "true"); - test("1n > Infinity", "false"); - test("1n < -Infinity", "false"); - test("1n > -Infinity", "true"); - - // null is interpreted as 0 when comparing with bigint - test("1n < null", "false"); - test("1n > null", "true"); -} - -#[test] -#[ignore] -fn test_bigint_string_comparison() { - test("1n < '2'", "true"); - test("2n > '1'", "true"); - test("123n > '34'", "true"); - test("1n == '1'", "true"); - test("1n == '2'", "false"); - test("1n != '1'", "false"); - test("1n === '1'", "false"); - test("1n !== '1'", "true"); -} - -#[test] -#[ignore] -fn test_string_bigint_comparison() { - test("'1' < 2n", "true"); - test("'2' > 1n", "true"); - test("'123' > 34n", "true"); - test("'1' == 1n", "true"); - test("'1' == 2n", "false"); - test("'1' != 1n", "false"); - test("'1' === 1n", "false"); - test("'1' !== 1n", "true"); -} - -#[test] -fn test_nan_comparison() { - test("NaN < 1", "false"); - test("NaN <= 1", "false"); - test("NaN > 1", "false"); - test("NaN >= 1", "false"); - // test("NaN < 1n", "false"); - // test("NaN <= 1n", "false"); - // test("NaN > 1n", "false"); - // test("NaN >= 1n", "false"); - - test("NaN < NaN", "false"); - test("NaN >= NaN", "false"); - test("NaN == NaN", "false"); - test("NaN === NaN", "false"); - - test("NaN < null", "false"); - test("null >= NaN", "false"); - test("NaN == null", "false"); - test("null != NaN", "true"); - test("null === NaN", "false"); - - test("NaN < undefined", "false"); - test("undefined >= NaN", "false"); - test("NaN == undefined", "false"); - test("undefined != NaN", "true"); - test("undefined === NaN", "false"); - - test_same("NaN=NaN"); - test_same("NaN==x"); - test_same("x!=NaN"); - test("NaN === x", "false"); - test("x !== NaN", "true"); - test_same("NaN==foo()"); -} - -#[test] -fn js_typeof() { - test("x = typeof 1", "x = \"number\""); - test("x = typeof 'foo'", "x = \"string\""); - test("x = typeof true", "x = \"boolean\""); - test("x = typeof false", "x = \"boolean\""); - test("x = typeof null", "x = \"object\""); - test("x = typeof undefined", "x = \"undefined\""); - test("x = typeof void 0", "x = \"undefined\""); - test("x = typeof []", "x = \"object\""); - test("x = typeof [1]", "x = \"object\""); - test("x = typeof [1,[]]", "x = \"object\""); - test("x = typeof {}", "x = \"object\""); - test("x = typeof function() {}", "x = 'function'"); - - test_same("x = typeof[1,[foo()]]"); - test_same("x = typeof{bathwater:baby()}"); -} - -#[test] -fn unary_ops() { - // TODO: need to port - // These cases are handled by PeepholeRemoveDeadCode in closure-compiler. - // test_same("!foo()"); - // test_same("~foo()"); - // test_same("-foo()"); - - // These cases are handled here. - test("a=!true", "a=false"); - test("a=!10", "a=false"); - test("a=!false", "a=true"); - test_same("a=!foo()"); - // test("a=-0", "a=-0.0"); - // test("a=-(0)", "a=-0.0"); - test_same("a=-Infinity"); - test("a=-NaN", "a=NaN"); - test_same("a=-foo()"); - // test("a=~~0", "a=0"); - // test("a=~~10", "a=10"); - // test("a=~-7", "a=6"); - - // test("a=+true", "a=1"); - test("a=+10", "a=10"); - // test("a=+false", "a=0"); - test_same("a=+foo()"); - test_same("a=+f"); - // test("a=+(f?true:false)", "a=+(f?1:0)"); - test("a=+0", "a=0"); - // test("a=+Infinity", "a=Infinity"); - // test("a=+NaN", "a=NaN"); - // test("a=+-7", "a=-7"); - // test("a=+.5", "a=.5"); - - // test("a=~0xffffffff", "a=0"); - // test("a=~~0xffffffff", "a=-1"); - // test_same("a=~.5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); -} - -#[test] -#[ignore] -fn unary_with_big_int() { - test("-(1n)", "-1n"); - test("- -1n", "1n"); - test("!1n", "false"); - test("~0n", "-1n"); -} - -#[test] -#[ignore] -fn test_unary_ops_string_compare() { - test_same("a = -1"); - test("a = ~0", "a = -1"); - test("a = ~1", "a = -2"); - test("a = ~101", "a = -102"); -} - -#[test] -fn test_fold_logical_op() { - test("x = true && x", "x = x"); - test("x = [foo()] && x", "x = ([foo()],x)"); - - test("x = false && x", "x = false"); - test("x = true || x", "x = true"); - test("x = false || x", "x = x"); - test("x = 0 && x", "x = 0"); - test("x = 3 || x", "x = 3"); - test("x = 0n && x", "x = 0n"); - test("x = 3n || x", "x = 3n"); - test("x = false || 0", "x = 0"); - - // unfoldable, because the right-side may be the result - test("a = x && true", "a=x && true"); - test("a = x && false", "a=x && false"); - test("a = x || 3", "a=x || 3"); - test("a = x || false", "a=x || false"); - test("a = b ? c : x || false", "a=b ? c:x || false"); - test("a = b ? x || false : c", "a=b ? x || false:c"); - test("a = b ? c : x && true", "a=b ? c:x && true"); - test("a = b ? x && true : c", "a=b ? x && true:c"); - - // folded, but not here. - test_same("a = x || false ? b : c"); - test_same("a = x && true ? b : c"); - - test("x = foo() || true || bar()", "x = foo() || true"); - test("x = foo() || true && bar()", "x = foo() || bar()"); - test("x = foo() || false && bar()", "x = foo() || false"); - test("x = foo() && false && bar()", "x = foo() && false"); - test("x = foo() && false || bar()", "x = (foo() && false,bar())"); - test("x = foo() || false || bar()", "x = foo() || bar()"); - test("x = foo() && true && bar()", "x = foo() && bar()"); - test("x = foo() || true || bar()", "x = foo() || true"); - test("x = foo() && false && bar()", "x = foo() && false"); - test("x = foo() && 0 && bar()", "x = foo() && 0"); - test("x = foo() && 1 && bar()", "x = foo() && bar()"); - test("x = foo() || 0 || bar()", "x = foo() || bar()"); - test("x = foo() || 1 || bar()", "x = foo() || 1"); - test("x = foo() && 0n && bar()", "x = foo() && 0n"); - test("x = foo() && 1n && bar()", "x = foo() && bar()"); - test("x = foo() || 0n || bar()", "x = foo() || bar()"); - test("x = foo() || 1n || bar()", "x = foo() || 1n"); - test_same("x = foo() || bar() || baz()"); - test_same("x = foo() && bar() && baz()"); - - test("0 || b()", "b()"); - test("1 && b()", "b()"); - test("a() && (1 && b())", "a() && b()"); - test("(a() && 1) && b()", "a() && b()"); - - test("(x || '') || y;", "x || y"); - test("false || (x || '');", "x || ''"); - test("(x && 1) && y;", "x && y"); - test("true && (x && 1);", "x && 1"); - - // Really not foldable, because it would change the type of the - // expression if foo() returns something truthy but not true. - // Cf. FoldConstants.tryFoldAndOr(). - // An example would be if foo() is 1 (truthy) and bar() is 0 (falsey): - // (1 && true) || 0 == true - // 1 || 0 == 1, but true =/= 1 - test_same("x = foo() && true || bar()"); - test_same("foo() && true || bar()"); -} - -#[test] -fn test_fold_logical_op2() { - test("x = function(){} && x", "x=x"); - test("x = true && function(){}", "x=function(){}"); - test("x = [(function(){alert(x)})()] && x", "x=([function(){alert(x)}()],x)"); -} - -#[test] -fn test_fold_nullish_coalesce() { - // fold if left is null/undefined - test("null ?? 1", "1"); - test("undefined ?? false", "false"); - test("(a(), null) ?? 1", "(a(), null, 1)"); - - test("x = [foo()] ?? x", "x = [foo()]"); - - // short circuit on all non nullish LHS - test("x = false ?? x", "x = false"); - test("x = true ?? x", "x = true"); - test("x = 0 ?? x", "x = 0"); - test("x = 3 ?? x", "x = 3"); - - // unfoldable, because the right-side may be the result - test_same("a = x ?? true"); - test_same("a = x ?? false"); - test_same("a = x ?? 3"); - test_same("a = b ? c : x ?? false"); - test_same("a = b ? x ?? false : c"); - - // folded, but not here. - test_same("a = x ?? false ? b : c"); - test_same("a = x ?? true ? b : c"); - - test_same("x = foo() ?? true ?? bar()"); - test("x = foo() ?? (true && bar())", "x = foo() ?? bar()"); - test_same("x = (foo() || false) ?? bar()"); - - test("a() ?? (1 ?? b())", "a() ?? 1"); - test("(a() ?? 1) ?? b()", "a() ?? 1 ?? b()"); -} - -#[test] -fn test_fold_void() { - test_same("void 0"); - test("void 1", "void 0"); - test("void x", "void 0"); - test_same("void x()"); -} - -#[test] -fn test_fold_bit_shift() { - test("x = 1 << 0", "x=1"); - test("x = -1 << 0", "x=-1"); - test("x = 1 << 1", "x=2"); - test("x = 3 << 1", "x=6"); - test("x = 1 << 8", "x=256"); - - test("x = 1 >> 0", "x=1"); - test("x = -1 >> 0", "x=-1"); - test("x = 1 >> 1", "x=0"); - test("x = 2 >> 1", "x=1"); - test("x = 5 >> 1", "x=2"); - test("x = 127 >> 3", "x=15"); - test("x = 3 >> 1", "x=1"); - test("x = 3 >> 2", "x=0"); - test("x = 10 >> 1", "x=5"); - test("x = 10 >> 2", "x=2"); - test("x = 10 >> 5", "x=0"); - - test("x = 10 >>> 1", "x=5"); - test("x = 10 >>> 2", "x=2"); - test("x = 10 >>> 5", "x=0"); - test("x = -1 >>> 1", "x=2147483647"); // 0x7fffffff - test("x = -1 >>> 0", "x=4294967295"); // 0xffffffff - test("x = -2 >>> 0", "x=4294967294"); // 0xfffffffe - test("x = 0x90000000 >>> 28", "x=9"); - - test("x = 0xffffffff << 0", "x=-1"); - test("x = 0xffffffff << 4", "x=-16"); - test("1 << 32", "1<<32"); - test("1 << -1", "1<<-1"); - test("1 >> 32", "1>>32"); -} diff --git a/crates/oxc_minifier/tests/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/tests/ast_passes/peephole_minimize_conditions.rs deleted file mode 100644 index fd83a0c625284..0000000000000 --- a/crates/oxc_minifier/tests/ast_passes/peephole_minimize_conditions.rs +++ /dev/null @@ -1,1042 +0,0 @@ -//! - -use oxc_minifier::CompressOptions; - -// TODO: handle negative cases -fn test(source_text: &str, expected: &str) { - let options = CompressOptions { - remove_syntax: true, - minimize_conditions: true, - ..CompressOptions::all_false() - }; - crate::test(source_text, expected, options); -} - -fn test_same(source_text: &str) { - test(source_text, source_text); -} - -fn fold_same(js: &str) { - test_same(js); -} - -fn fold(js: &str, expected: &str) { - test(js, expected); -} - -/** Check that removing blocks with 1 child works */ -#[test] -#[ignore] -fn test_fold_one_child_blocks() { - // late = false; - fold("function f(){if(x)a();x=3}", "function f(){x&&a();x=3}"); - fold("function f(){if(x)a?.();x=3}", "function f(){x&&a?.();x=3}"); - - fold("function f(){if(x){a()}x=3}", "function f(){x&&a();x=3}"); - fold("function f(){if(x){a?.()}x=3}", "function f(){x&&a?.();x=3}"); - - fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); - fold("function f(){if(x){a()}}", "function f(){x&&a()}"); - fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); - - // Try it out with functions - fold("function f(){if(x){foo()}}", "function f(){x&&foo()}"); - fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); - - // Try it out with properties and methods - fold("function f(){if(x){a.b=1}}", "function f(){if(x)a.b=1}"); - fold("function f(){if(x){a.b*=1}}", "function f(){x&&(a.b*=1)}"); - fold("function f(){if(x){a.b+=1}}", "function f(){x&&(a.b+=1)}"); - fold("function f(){if(x){++a.b}}", "function f(){x&&++a.b}"); - fold("function f(){if(x){a.foo()}}", "function f(){x&&a.foo()}"); - fold("function f(){if(x){a?.foo()}}", "function f(){x&&a?.foo()}"); - - // Try it out with throw/catch/finally [which should not change] - fold_same("function f(){try{foo()}catch(e){bar(e)}finally{baz()}}"); - - // Try it out with switch statements - fold_same("function f(){switch(x){case 1:break}}"); - - // Do while loops stay in a block if that's where they started - fold_same("function f(){if(e1){do foo();while(e2)}else foo2()}"); - // Test an obscure case with do and while - fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); - - // Play with nested IFs - fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); - fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); - fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); - fold( - "function f(){if(x){if(y)foo();else bar()}else{baz()}}", - "function f(){x?y?foo():bar():baz()}", - ); - - fold("if(e1){while(e2){if(e3){foo()}}}else{bar()}", "if(e1)while(e2)e3&&foo();else bar()"); - - fold("if(e1){with(e2){if(e3){foo()}}}else{bar()}", "if(e1)with(e2)e3&&foo();else bar()"); - - fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); - fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); - - // NOTE - technically we can remove the blocks since both the parent - // and child have elses. But we don't since it causes ambiguities in - // some cases where not all descendent ifs having elses - fold("if(x){ if(y){var x;}else{var z;} }else{var w}", "if(x)if(y)var x;else var z;else var w"); - fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); - - // Here's some of the ambiguous cases - fold( - "if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", - "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()", - ); - - fold("function f(){foo()}", "function f(){foo()}"); - fold("switch(x){case y: foo()}", "switch(x){case y:foo()}"); - fold("try{foo()}catch(ex){bar()}finally{baz()}", "try{foo()}catch(ex){bar()}finally{baz()}"); -} - -/** Try to minimize returns */ -#[test] -#[ignore] -fn test_fold_returns() { - fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}"); - fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}"); - fold("function f(){if(x)return;return 2}", "function f(){return x?void 0:2}"); - fold("function f(){if(x)return 1+x;else return 2-x}", "function f(){return x?1+x:2-x}"); - fold("function f(){if(x)return 1+x;return 2-x}", "function f(){return x?1+x:2-x}"); - fold( - "function f(){if(x)return y += 1;else return y += 2}", - "function f(){return x?(y+=1):(y+=2)}", - ); - - fold("function f(){if(x)return;else return 2-x}", "function f(){if(x);else return 2-x}"); - fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}"); - fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;{}}"); - fold("function f(){if(x)return x;return}", "function f(){if(x)return x}"); - - fold_same("function f(){for(var x in y) { return x.y; } return k}"); -} - -#[test] -#[ignore] -fn test_combine_ifs1() { - fold("function f() {if (x) return 1; if (y) return 1}", "function f() {if (x||y) return 1;}"); - fold( - "function f() {if (x) return 1; if (y) foo(); else return 1}", - "function f() {if ((!x)&&y) foo(); else return 1;}", - ); -} - -#[test] -#[ignore] -fn test_combine_ifs2() { - // combinable but not yet done - fold_same("function f() {if (x) throw 1; if (y) throw 1}"); - // Can't combine, side-effect - fold("function f(){ if (x) g(); if (y) g() }", "function f(){ x&&g(); y&&g() }"); - fold("function f(){ if (x) g?.(); if (y) g?.() }", "function f(){ x&&g?.(); y&&g?.() }"); - // Can't combine, side-effect - fold("function f(){ if (x) y = 0; if (y) y = 0; }", "function f(){ x&&(y = 0); y&&(y = 0); }"); -} - -#[test] -#[ignore] -fn test_combine_ifs3() { - fold_same("function f() {if (x) return 1; if (y) {g();f()}}"); -} - -/** Try to minimize assignments */ -#[test] -#[ignore] -fn test_fold_assignments() { - fold("function f(){if(x)y=3;else y=4;}", "function f(){y=x?3:4}"); - fold("function f(){if(x)y=1+a;else y=2+a;}", "function f(){y=x?1+a:2+a}"); - - // and operation assignments - fold("function f(){if(x)y+=1;else y+=2;}", "function f(){y+=x?1:2}"); - fold("function f(){if(x)y-=1;else y-=2;}", "function f(){y-=x?1:2}"); - fold("function f(){if(x)y%=1;else y%=2;}", "function f(){y%=x?1:2}"); - fold("function f(){if(x)y|=1;else y|=2;}", "function f(){y|=x?1:2}"); - - // Don't fold if the 2 ops don't match. - fold_same("function f(){x ? y-=1 : y+=2}"); - - // Don't fold if the 2 LHS don't match. - fold_same("function f(){x ? y-=1 : z-=1}"); - - // Don't fold if there are potential effects. - fold_same("function f(){x ? y().a=3 : y().a=4}"); -} - -#[test] -#[ignore] -fn test_remove_duplicate_statements() { - // enableNormalize(); - // TODO(bradfordcsmith): Stop normalizing the expected output or document why it is necessary. - // enableNormalizeExpectedOutput(); - fold("if (a) { x = 1; x++ } else { x = 2; x++ }", "x=(a) ? 1 : 2; x++"); - // fold( - // "if (a) { x = 1; x++; y += 1; z = pi; }" + " else { x = 2; x++; y += 1; z = pi; }", - // "x=(a) ? 1 : 2; x++; y += 1; z = pi;", - // ); - // fold( - // "function z() {" + "if (a) { foo(); return !0 } else { goo(); return !0 }" + "}", - // "function z() {(a) ? foo() : goo(); return !0}", - // ); - // fold( - // "function z() {if (a) { foo(); x = true; return true " - // + "} else { goo(); x = true; return true }}", - // "function z() {(a) ? foo() : goo(); x = true; return true}", - // ); - - // fold( - // "function z() {" - // + " if (a) { bar(); foo(); return true }" - // + " else { bar(); goo(); return true }" - // + "}", - // "function z() {" - // + " if (a) { bar(); foo(); }" - // + " else { bar(); goo(); }" - // + " return true;" - // + "}", - // ); -} - -#[test] -#[ignore] -fn test_fold_returns_integration2() { - // late = true; - // disableNormalize(); - - // if-then-else duplicate statement removal handles this case: - test_same("function test(a) {if (a) {const a = Math.random();if(a) {return a;}} return a; }"); -} - -#[test] -#[ignore] -fn test_dont_remove_duplicate_statements_without_normalization() { - // In the following test case, we can't remove the duplicate "alert(x);" lines since each "x" - // refers to a different variable. - // We only try removing duplicate statements if the AST is normalized and names are unique. - test_same( - "if (Math.random() < 0.5) { const x = 3; alert(x); } else { const x = 5; alert(x); }", - ); -} - -#[test] -#[ignore] -fn test_not_cond() { - fold("function f(){if(!x)foo()}", "function f(){x||foo()}"); - fold("function f(){if(!x)b=1}", "function f(){x||(b=1)}"); - fold("if(!x)z=1;else if(y)z=2", "if(x){y&&(z=2);}else{z=1;}"); - fold("if(x)y&&(z=2);else z=1;", "x ? y&&(z=2) : z=1"); - fold("function f(){if(!(x=1))a.b=1}", "function f(){(x=1)||(a.b=1)}"); -} - -#[test] -#[ignore] -fn test_and_parentheses_count() { - fold("function f(){if(x||y)a.foo()}", "function f(){(x||y)&&a.foo()}"); - fold("function f(){if(x.a)x.a=0}", "function f(){x.a&&(x.a=0)}"); - fold("function f(){if(x?.a)x.a=0}", "function f(){x?.a&&(x.a=0)}"); - fold_same("function f(){if(x()||y()){x()||y()}}"); -} - -#[test] -#[ignore] -fn test_fold_logical_op_string_compare() { - // side-effects - // There is two way to parse two &&'s and both are correct. - fold("if (foo() && false) z()", "(foo(), 0) && z()"); -} - -#[test] -#[ignore] -fn test_fold_not() { - fold("while(!(x==y)){a=b;}", "while(x!=y){a=b;}"); - fold("while(!(x!=y)){a=b;}", "while(x==y){a=b;}"); - fold("while(!(x===y)){a=b;}", "while(x!==y){a=b;}"); - fold("while(!(x!==y)){a=b;}", "while(x===y){a=b;}"); - // Because !(x=NaN don't fold < and > cases. - fold_same("while(!(x>y)){a=b;}"); - fold_same("while(!(x>=y)){a=b;}"); - fold_same("while(!(x 20)) {foo();foo()}", "if(f() > 20){foo();foo()}"); -} - -#[test] -#[ignore] -fn test_fold_loop_break_late() { - // late = true; - fold("for(;;) if (a) break", "for(;!a;);"); - fold_same("for(;;) if (a) { f(); break }"); - fold("for(;;) if (a) break; else f()", "for(;!a;) { { f(); } }"); - fold("for(;a;) if (b) break", "for(;a && !b;);"); - fold("for(;a;) { if (b) break; if (c) break; }", "for(;(a && !b);) if (c) break;"); - fold("for(;(a && !b);) if (c) break;", "for(;(a && !b) && !c;);"); - fold("for(;;) { if (foo) { break; var x; } } x;", "var x; for(;!foo;) {} x;"); - - // 'while' is normalized to 'for' - // enableNormalize(); - fold("while(true) if (a) break", "for(;1&&!a;);"); - // disableNormalize(); -} - -#[test] -#[ignore] -fn test_fold_loop_break_early() { - // late = false; - fold_same("for(;;) if (a) break"); - fold_same("for(;;) if (a) { f(); break }"); - fold_same("for(;;) if (a) break; else f()"); - fold_same("for(;a;) if (b) break"); - fold_same("for(;a;) { if (b) break; if (c) break; }"); - - fold_same("while(1) if (a) break"); - // enableNormalize(); - fold_same("for (; 1; ) if (a) break"); -} - -#[test] -#[ignore] -fn test_fold_conditional_var_declaration() { - fold("if(x) var y=1;else y=2", "var y=x?1:2"); - fold("if(x) y=1;else var y=2", "var y=x?1:2"); - - fold_same("if(x) var y = 1; z = 2"); - fold_same("if(x||y) y = 1; var z = 2"); - - fold_same("if(x) { var y = 1; print(y)} else y = 2 "); - fold_same("if(x) var y = 1; else {y = 2; print(y)}"); -} - -#[test] -#[ignore] -fn test_fold_if_with_lower_operators_inside() { - fold("if (x + (y=5)) z && (w,z);", "x + (y=5) && (z && (w,z))"); - fold("if (!(x+(y=5))) z && (w,z);", "x + (y=5) || z && (w,z)"); - fold( - "if (x + (y=5)) if (z && (w,z)) for(;;) foo();", - "if (x + (y=5) && (z && (w,z))) for(;;) foo();", - ); -} - -#[test] -#[ignore] -fn test_substitute_return() { - // late = false; - // enableNormalize(); - // TODO(bradfordcsmith): Stop normalizing the expected output or document why it is necessary. - // enableNormalizeExpectedOutput(); - - fold("function f() { while(x) { return }}", "function f() { while(x) { break }}"); - - fold_same("function f() { while(x) { return 5 } }"); - - fold_same("function f() { a: { return 5 } }"); - - fold( - "function f() { while(x) { return 5} return 5}", - "function f() { while(x) { break } return 5}", - ); - - fold( - "function f() { while(x) { return x} return x}", - "function f() { while(x) { break } return x}", - ); - - fold( - "function f() { while(x) { if (y) { return }}}", - "function f() { while(x) { if (y) { break }}}", - ); - - fold( - "function f() { while(x) { if (y) { return }} return}", - "function f() { while(x) { if (y) { break }}}", - ); - - fold( - "function f() { while(x) { if (y) { return 5 }} return 5}", - "function f() { while(x) { if (y) { break }} return 5}", - ); - - // It doesn't matter if x is changed between them. We are still returning - // x at whatever x value current holds. The whole x = 1 is skipped. - fold( - "function f() { while(x) { if (y) { return x } x = 1} return x}", - "function f() { while(x) { if (y) { break } x = 1} return x}", - ); - - fold( - "function f() { while(x) { if (y) { return x } return x} return x}", - "function f() { while(x) { if (y) {} break }return x}", - ); - - // A break here only breaks out of the inner loop. - fold_same("function f() { while(x) { while (y) { return } } }"); - - fold_same("function f() { while(1) { return 7} return 5}"); - - // fold_same("function f() {" + " try { while(x) {return f()}} catch (e) { } return f()}"); - - // fold_same("function f() {" + " try { while(x) {return f()}} finally {alert(1)} return f()}"); - - // Both returns has the same handler - // fold( - // "function f() {" + " try { while(x) { return f() } return f() } catch (e) { } }", - // "function f() {" + " try { while(x) { break } return f() } catch (e) { } }", - // ); - - // We can't fold this because it'll change the order of when foo is called. - // fold_same( - // "function f() {" - // + " try { while(x) { return foo() } } finally { alert(1) } " - // + " return foo()}", - // ); - - // This is fine, we have no side effect in the return value. - // fold( - // "function f() {" + " try { while(x) { return 1 } } finally { alert(1) } return 1}", - // "function f() {" + " try { while(x) { break } } finally { alert(1) } return 1}", - // ); - - fold_same("function f() { try{ return a } finally { a = 2 } return a; }"); - - fold( - "function f() { switch(a){ case 1: return a; default: g();} return a;}", - "function f() { switch(a){ case 1: break; default: g();} return a; }", - ); -} - -#[test] -#[ignore] -fn test_substitute_break_for_throw() { - // late = false; - // enableNormalize(); - // TODO(bradfordcsmith): Stop normalizing the expected output or document why it is necessary. - // enableNormalizeExpectedOutput(); - - fold_same("function f() { while(x) { throw Error }}"); - - fold( - "function f() { while(x) { throw Error } throw Error }", - "function f() { while(x) { break } throw Error}", - ); - fold_same("function f() { while(x) { throw Error(1) } throw Error(2)}"); - fold_same("function f() { while(x) { throw Error(1) } return Error(2)}"); - - fold_same("function f() { while(x) { throw 5 } }"); - - fold_same("function f() { a: { throw 5 } }"); - - fold( - "function f() { while(x) { throw 5} throw 5}", - "function f() { while(x) { break } throw 5}", - ); - - fold( - "function f() { while(x) { throw x} throw x}", - "function f() { while(x) { break } throw x}", - ); - - fold_same("function f() { while(x) { if (y) { throw Error }}}"); - - fold( - "function f() { while(x) { if (y) { throw Error }} throw Error}", - "function f() { while(x) { if (y) { break }} throw Error}", - ); - - fold( - "function f() { while(x) { if (y) { throw 5 }} throw 5}", - "function f() { while(x) { if (y) { break }} throw 5}", - ); - - // It doesn't matter if x is changed between them. We are still throwing - // x at whatever x value current holds. The whole x = 1 is skipped. - fold( - "function f() { while(x) { if (y) { throw x } x = 1} throw x}", - "function f() { while(x) { if (y) { break } x = 1} throw x}", - ); - - fold( - "function f() { while(x) { if (y) { throw x } throw x} throw x}", - "function f() { while(x) { if (y) {} break }throw x}", - ); - - // A break here only breaks out of the inner loop. - fold_same("function f() { while(x) { while (y) { throw Error } } }"); - - fold_same("function f() { while(1) { throw 7} throw 5}"); - - // fold_same("function f() {" + " try { while(x) {throw f()}} catch (e) { } throw f()}"); - - // fold_same("function f() {" + " try { while(x) {throw f()}} finally {alert(1)} throw f()}"); - - // Both throws has the same handler - // fold( - // "function f() {" + " try { while(x) { throw f() } throw f() } catch (e) { } }", - // "function f() {" + " try { while(x) { break } throw f() } catch (e) { } }", - // ); - - // We can't fold this because it'll change the order of when foo is called. - // fold_same( - // "function f() {" - // + " try { while(x) { throw foo() } } finally { alert(1) } " - // + " throw foo()}", - // ); - - // This is fine, we have no side effect in the throw value. - // fold( - // "function f() {" + " try { while(x) { throw 1 } } finally { alert(1) } throw 1}", - // "function f() {" + " try { while(x) { break } } finally { alert(1) } throw 1}", - // ); - - fold_same("function f() { try{ throw a } finally { a = 2 } throw a; }"); - - fold( - "function f() { switch(a){ case 1: throw a; default: g();} throw a;}", - "function f() { switch(a){ case 1: break; default: g();} throw a; }", - ); -} - -#[test] -#[ignore] -fn test_remove_duplicate_return() { - // late = false; - // enableNormalize(); - - fold("function f() { return; }", "function f(){}"); - fold_same("function f() { return a; }"); - fold("function f() { if (x) { return a } return a; }", "function f() { if (x) {} return a; }"); - fold_same("function f() { try { if (x) { return a } } catch(e) {} return a; }"); - fold_same("function f() { try { if (x) {} } catch(e) {} return 1; }"); - - // finally clauses may have side effects - fold_same("function f() { try { if (x) { return a } } finally { a++ } return a; }"); - // but they don't matter if the result doesn't have side effects and can't - // be affect by side-effects. - fold( - "function f() { try { if (x) { return 1 } } finally {} return 1; }", - "function f() { try { if (x) {} } finally {} return 1; }", - ); - - fold( - "function f() { switch(a){ case 1: return a; } return a; }", - "function f() { switch(a){ case 1: } return a; }", - ); - - // fold( - // "function f() { switch(a){ " + " case 1: return a; case 2: return a; } return a; }", - // "function f() { switch(a){ " + " case 1: break; case 2: } return a; }", - // ); -} - -#[test] -#[ignore] -fn test_remove_duplicate_throw() { - // late = false; - // enableNormalize(); - - fold_same("function f() { throw a; }"); - fold("function f() { if (x) { throw a } throw a; }", "function f() { if (x) {} throw a; }"); - fold_same("function f() { try { if (x) {throw a} } catch(e) {} throw a; }"); - fold_same("function f() { try { if (x) {throw 1} } catch(e) {f()} throw 1; }"); - fold_same("function f() { try { if (x) {throw 1} } catch(e) {f()} throw 1; }"); - fold_same("function f() { try { if (x) {throw 1} } catch(e) {throw 1}}"); - fold( - "function f() { try { if (x) {throw 1} } catch(e) {throw 1} throw 1; }", - "function f() { try { if (x) {throw 1} } catch(e) {} throw 1; }", - ); - - // finally clauses may have side effects - fold_same("function f() { try { if (x) { throw a } } finally { a++ } throw a; }"); - // but they don't matter if the result doesn't have side effects and can't - // be affect by side-effects. - fold( - "function f() { try { if (x) { throw 1 } } finally {} throw 1; }", - "function f() { try { if (x) {} } finally {} throw 1; }", - ); - - fold( - "function f() { switch(a){ case 1: throw a; } throw a; }", - "function f() { switch(a){ case 1: } throw a; }", - ); - - // fold( - // "function f() { switch(a){ " + "case 1: throw a; case 2: throw a; } throw a; }", - // "function f() { switch(a){ case 1: break; case 2: } throw a; }", - // ); -} - -#[test] -#[ignore] -fn test_nested_if_combine() { - fold("if(x)if(y){while(1){}}", "if(x&&y){while(1){}}"); - fold("if(x||z)if(y){while(1){}}", "if((x||z)&&y){while(1){}}"); - fold("if(x)if(y||z){while(1){}}", "if((x)&&(y||z)){while(1){}}"); - fold_same("if(x||z)if(y||z){while(1){}}"); - fold("if(x)if(y){if(z){while(1){}}}", "if(x&&(y&&z)){while(1){}}"); -} - -// See: http://blickly.github.io/closure-compiler-issues/#291 -#[test] -#[ignore] -fn test_issue291() { - fold("if (true) { f.onchange(); }", "if (1) f.onchange();"); - fold_same("if (f) { f.onchange(); }"); - fold_same("if (f) { f.bar(); } else { f.onchange(); }"); - fold("if (f) { f.bonchange(); }", "f && f.bonchange();"); - fold_same("if (f) { f['x'](); }"); - - // optional versions - fold("if (true) { f?.onchange(); }", "if (1) f?.onchange();"); - fold_same("if (f) { f?.onchange(); }"); - fold_same("if (f) { f?.bar(); } else { f?.onchange(); }"); - fold("if (f) { f?.bonchange(); }", "f && f?.bonchange();"); - fold_same("if (f) { f?.['x'](); }"); -} - -#[test] -#[ignore] -fn test_object_literal() { - test("({})", "1"); - test("({a:1})", "1"); - test_same("({a:foo()})"); - test_same("({'a':foo()})"); -} - -#[test] -#[ignore] -fn test_array_literal() { - test("([])", "1"); - test("([1])", "1"); - test("([a])", "1"); - test_same("([foo()])"); -} - -#[test] -#[ignore] -fn test_remove_else_cause() { - // test( - // "function f() {" + " if(x) return 1;" + " else if(x) return 2;" + " else if(x) return 3 }", - // "function f() {" + " if(x) return 1;" + "{ if(x) return 2;" + "{ if(x) return 3 } } }", - // ); -} - -#[test] -#[ignore] -fn test_remove_else_cause1() { - test("function f() { if (x) throw 1; else f() }", "function f() { if (x) throw 1; { f() } }"); -} - -#[test] -#[ignore] -fn test_remove_else_cause2() { - test("function f() { if (x) return 1; else f() }", "function f() { if (x) return 1; { f() } }"); - test("function f() { if (x) return; else f() }", "function f() { if (x) {} else { f() } }"); - // This case is handled by minimize exit points. - test_same("function f() { if (x) return; f() }"); -} - -#[test] -#[ignore] -fn test_remove_else_cause3() { - test_same("function f() { a:{if (x) break a; else f() } }"); - test_same("function f() { if (x) { a:{ break a } } else f() }"); - test_same("function f() { if (x) a:{ break a } else f() }"); -} - -#[test] -#[ignore] -fn test_remove_else_cause4() { - test_same("function f() { if (x) { if (y) { return 1; } } else f() }"); -} - -#[test] -#[ignore] -fn test_issue925() { - // test( - // "if (x[--y] === 1) {\n" + " x[y] = 0;\n" + "} else {\n" + " x[y] = 1;\n" + "}", - // "(x[--y] === 1) ? x[y] = 0 : x[y] = 1;", - // ); - - // test( - // "if (x[--y]) {\n" + " a = 0;\n" + "} else {\n" + " a = 1;\n" + "}", - // "a = (x[--y]) ? 0 : 1;", - // ); - - // test( - // lines( - // "if (x?.[--y]) {", // - // " a = 0;", - // "} else {", - // " a = 1;", - // "}", - // ), - // "a = (x?.[--y]) ? 0 : 1;", - // ); - - test("if (x++) { x += 2 } else { x += 3 }", "x++ ? x += 2 : x += 3"); - - test("if (x++) { x = x + 2 } else { x = x + 3 }", "x = x++ ? x + 2 : x + 3"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_disabled() { - // enableTypeCheck(); - test_same("var x = {}; if (x != null) throw 'a';"); - test_same("var x = {}; var y = x != null;"); - - test_same("var x = 1; if (x != 0) throw 'a';"); - test_same("var x = 1; var y = x != 0;"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_boolean_result0() { - // enableTypeCheck(); - test_same("var x = {}; var y = x != null;"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_boolean_result1() { - // enableTypeCheck(); - test_same("var x = {}; var y = x == null;"); - test_same("var x = {}; var y = x !== null;"); - test_same("var x = undefined; var y = x !== null;"); - test_same("var x = {}; var y = x === null;"); - test_same("var x = undefined; var y = x === null;"); - - test_same("var x = 1; var y = x != 0;"); - test_same("var x = 1; var y = x == 0;"); - test_same("var x = 1; var y = x !== 0;"); - test_same("var x = 1; var y = x === 0;"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_if() { - // enableTypeCheck(); - test("var x = {};\nif (x != null) throw 'a';\n", "var x={}; if (x!=null) throw 'a'"); - test_same("var x = {};\nif (x == null) throw 'a';\n"); - test_same("var x = {};\nif (x !== null) throw 'a';\n"); - test_same("var x = {};\nif (x === null) throw 'a';\n"); - test_same("var x = {};\nif (null != x) throw 'a';\n"); - test_same("var x = {};\nif (null == x) throw 'a';\n"); - test_same("var x = {};\nif (null !== x) throw 'a';\n"); - test_same("var x = {};\nif (null === x) throw 'a';\n"); - - test_same("var x = 1;\nif (x != 0) throw 'a';\n"); - test_same("var x = 1;\nif (x != 0) throw 'a';\n"); - test_same("var x = 1;\nif (x == 0) throw 'a';\n"); - test_same("var x = 1;\nif (x !== 0) throw 'a';\n"); - test_same("var x = 1;\nif (x === 0) throw 'a';\n"); - test_same("var x = 1;\nif (0 != x) throw 'a';\n"); - test_same("var x = 1;\nif (0 == x) throw 'a';\n"); - test_same("var x = 1;\nif (0 !== x) throw 'a';\n"); - test_same("var x = 1;\nif (0 === x) throw 'a';\n"); - test_same("var x = NaN;\nif (0 === x) throw 'a';\n"); - test_same("var x = NaN;\nif (x === 0) throw 'a';\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_expression() { - // enableTypeCheck(); - test_same("var x = {}; x != null && alert('b');"); - test_same("var x = 1; x != 0 && alert('b');"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_hook() { - // enableTypeCheck(); - // test_same(lines("var x = {};", "var y = x != null ? 1 : 2;")); - // test_same(lines("var x = 1;", "var y = x != 0 ? 1 : 2;")); -} - -#[test] -#[ignore] -fn test_coercion_substitution_not() { - // enableTypeCheck(); - test( - "var x = {};\nvar y = !(x != null) ? 1 : 2;\n", - "var x = {};\nvar y = (x == null) ? 1 : 2;\n", - ); - test("var x = 1;\nvar y = !(x != 0) ? 1 : 2;\n", "var x = 1;\nvar y = x == 0 ? 1 : 2;\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_while() { - // enableTypeCheck(); - test_same("var x = {}; while (x != null) throw 'a';"); - test_same("var x = 1; while (x != 0) throw 'a';"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_unknown_type() { - // enableTypeCheck(); - test_same("var x = /** @type {?} */ ({});\nif (x != null) throw 'a';\n"); - test_same("var x = /** @type {?} */ (1);\nif (x != 0) throw 'a';\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_all_type() { - // enableTypeCheck(); - test_same("var x = /** @type {*} */ ({});\nif (x != null) throw 'a';\n"); - test_same("var x = /** @type {*} */ (1);\nif (x != 0) throw 'a';\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_primitives_vs_null() { - // enableTypeCheck(); - test_same("var x = 0;\nif (x != null) throw 'a';\n"); - test_same("var x = '';\nif (x != null) throw 'a';\n"); - test_same("var x = false;\nif (x != null) throw 'a';\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_non_number_vs_zero() { - // enableTypeCheck(); - test_same("var x = {};\nif (x != 0) throw 'a';\n"); - test_same("var x = '';\nif (x != 0) throw 'a';\n"); - test_same("var x = false;\nif (x != 0) throw 'a';\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_boxed_number_vs_zero() { - // enableTypeCheck(); - test_same("var x = new Number(0);\nif (x != 0) throw 'a';\n"); -} - -#[test] -#[ignore] -fn test_coercion_substitution_boxed_primitives() { - // enableTypeCheck(); - test_same("var x = new Number(); if (x != null) throw 'a';"); - test_same("var x = new String(); if (x != null) throw 'a';"); - test_same("var x = new Boolean();\nif (x != null) throw 'a';"); -} - -#[test] -#[ignore] -fn test_minimize_if_with_new_target_condition() { - // Related to https://github.com/google/closure-compiler/issues/3097 - // test( - // lines( - // "function x() {", - // " if (new.target) {", - // " return 1;", - // " } else {", - // " return 2;", - // " }", - // "}", - // ), - // lines("function x() {", " return new.target ? 1 : 2;", "}"), - // ); -} diff --git a/crates/oxc_minifier/tests/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/tests/ast_passes/peephole_substitute_alternate_syntax.rs deleted file mode 100644 index 3def6cb1c5401..0000000000000 --- a/crates/oxc_minifier/tests/ast_passes/peephole_substitute_alternate_syntax.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! - -use crate::CompressOptions; - -fn test(source_text: &str, expected: &str) { - let options = CompressOptions::all_true(); - crate::test(source_text, expected, options); -} - -fn test_same(source_text: &str) { - test(source_text, source_text); -} - -// Oxc - -#[test] -fn cjs() { - // Bail `cjs-module-lexer`. - // Export is undefined when `enumerable` is "!0". - // https://github.com/nodejs/cjs-module-lexer/issues/64 - test_same( - "Object.defineProperty(exports, 'ConnectableObservable', { - enumerable: true, - get: function() { - return ConnectableObservable_1.ConnectableObservable; - } - });", - ); - // @babel/types/lib/index.js - test_same( - r#"Object.keys(_index6).forEach(function(key) { - if (key === "default" || key === "__esModule") return; - if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; - if (key in exports && exports[key] === _index6[key]) return; - Object.defineProperty(exports, key, { - enumerable: true, - get: function() { - return _index6[key]; - } - }); - });"#, - ); -} - -// Google Closure Compiler - -#[test] -fn fold_return_result() { - test("function f(){return !1;}", "function f(){return !1}"); - test("function f(){return null;}", "function f(){return null}"); - test("function f(){return void 0;}", "function f(){return}"); - test("function f(){return void foo();}", "function f(){return void foo()}"); - test("function f(){return undefined;}", "function f(){return}"); - test("function f(){if(a()){return undefined;}}", "function f(){if(a())return}"); -} - -#[test] -fn undefined() { - test("var x = undefined", "var x"); - test( - "var undefined = 1;function f() {var undefined=2;var x;}", - "var undefined=1;function f(){var undefined=2,x}", - ); - test("function f(undefined) {}", "function f(undefined){}"); - test("try {} catch(undefined) {}", "try{}catch(undefined){}"); - test("for (undefined in {}) {}", "for(undefined in {}){}"); - test("undefined++", "undefined++"); - test("undefined += undefined", "undefined+=void 0"); - - // shadowd - test_same("(function(undefined) { let x = typeof undefined; })()"); -} diff --git a/crates/oxc_minifier/tests/ast_passes/remove_syntax.rs b/crates/oxc_minifier/tests/ast_passes/remove_syntax.rs deleted file mode 100644 index 1c7c92ff66118..0000000000000 --- a/crates/oxc_minifier/tests/ast_passes/remove_syntax.rs +++ /dev/null @@ -1,29 +0,0 @@ -use oxc_minifier::CompressOptions; - -fn test(source_text: &str, expected: &str) { - let options = CompressOptions { remove_syntax: true, ..CompressOptions::all_false() }; - crate::test(source_text, expected, options); -} - -#[test] -fn parens() { - test("(((x)))", "x"); - test("(((a + b))) * c", "(a + b) * c"); -} - -#[test] -fn drop_console() { - let options = - CompressOptions { remove_syntax: true, drop_console: true, ..CompressOptions::all_false() }; - crate::test("console.log()", "", options); -} - -#[test] -fn drop_debugger() { - let options = CompressOptions { - remove_syntax: true, - drop_debugger: true, - ..CompressOptions::all_false() - }; - crate::test("debugger", "", options); -} diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index 03b1b40625b5a..a09c3043bacfd 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -12,10 +12,7 @@ pub(crate) fn test(source_text: &str, expected: &str, options: CompressOptions) let source_type = SourceType::default(); let result = run(source_text, source_type, Some(options)); let expected = run(expected, source_type, None); - assert_eq!( - result, expected, - "\nfor source {source_text:?}\nexpect {expected:?}\ngot {result:?}" - ); + assert_eq!(result, expected, "\nfor source\n{source_text}\nexpect\n{expected}\ngot\n{result}"); } fn run(source_text: &str, source_type: SourceType, options: Option) -> String {