From 20f2c46b05ab0c1c5d7d418a99c1a2b6a2b4bfa4 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:58:27 +0000 Subject: [PATCH] feat(minifier): `for (;;) { var x }` -> `for (;;) var x;` (#8847) --- .../collapse_variable_declarations.rs | 5 +- .../src/peephole/minimize_conditions.rs | 4 +- .../src/peephole/remove_dead_code.rs | 52 +++++++++++-------- .../peephole/substitute_alternate_syntax.rs | 2 +- crates/oxc_minifier/tests/peephole/esbuild.rs | 6 +-- tasks/minsize/minsize.snap | 6 +-- 6 files changed, 43 insertions(+), 32 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs b/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs index 933d9b78fee58..7149079b6ce0f 100644 --- a/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs @@ -47,7 +47,10 @@ mod test { #[test] fn test_aggressive_redeclaration_in_for() { test_same("for(var x = 1; x = 2; x = 3) x = 4"); - test_same("for(var x = 1; y = 2; z = 3) {var a = 4}"); + test( + "for(var x = 1; y = 2; z = 3) {var a = 4}", + "for(var x = 1; y = 2; z = 3) var a = 4", + ); test_same("var x; for(x = 1; x = 2; z = 3) x = 4"); } diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index 0b60c933b5531..8c6e0618d8830 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -1545,8 +1545,8 @@ mod test { test("if (x) ;else { foo }", "x || foo"); test("if (x) {;} else { foo }", "x || foo"); - test("if (x) { var foo } else { bar }", "if (x) { var foo } else bar"); - test_same("if (x) foo; else { var bar }"); + test("if (x) { var foo } else { bar }", "if (x) var foo; else bar"); + test("if (x) foo; else { var bar }", "if (x) foo; else var bar"); } #[test] diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index 0daba721fb297..b02732eb75e10 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -127,26 +127,34 @@ impl<'a, 'b> PeepholeOptimizations { stmt: &mut BlockStatement<'a>, ctx: Ctx<'a, 'b>, ) -> Option> { - // Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different - // semantics according to AnnexB, which lead to different semantics. - if stmt.body.len() == 1 && !stmt.body[0].is_declaration() { - return Some(stmt.body.remove(0)); - } - if stmt.body.len() == 0 { - let parent = ctx.parent(); - if parent.is_while_statement() - || parent.is_do_while_statement() - || parent.is_for_statement() - || parent.is_for_in_statement() - || parent.is_for_of_statement() - || parent.is_block_statement() - || parent.is_program() - { - // Remove the block if it is empty and the parent is a block statement. - return Some(ctx.ast.statement_empty(stmt.span)); + match stmt.body.len() { + 0 => { + let parent = ctx.parent(); + if parent.is_while_statement() + || parent.is_do_while_statement() + || parent.is_for_statement() + || parent.is_for_in_statement() + || parent.is_for_of_statement() + || parent.is_block_statement() + || parent.is_program() + { + // Remove the block if it is empty and the parent is a block statement. + return Some(ctx.ast.statement_empty(stmt.span)); + } + None } + 1 => { + let s = &stmt.body[0]; + if matches!(s, Statement::VariableDeclaration(decl) if !decl.kind.is_var()) + || matches!(s, Statement::ClassDeclaration(_)) + || matches!(s, Statement::FunctionDeclaration(_)) + { + return None; + } + Some(stmt.body.remove(0)) + } + _ => None, } - None } fn try_fold_if( @@ -664,8 +672,8 @@ mod test { test("a: { break a; console.log('unreachable'); }", ""); test("a: { break a; var x = 1; } x = 2;", "var x; x = 2;"); - test_same("b: { var x = 1; } x = 2;"); - test_same("a: b: { var x = 1; } x = 2;"); + test("b: { var x = 1; } x = 2;", "b: var x = 1; x = 2;"); + test("a: b: { var x = 1; } x = 2;", "a: b: var x = 1; x = 2;"); test("foo:;", ""); } @@ -784,8 +792,8 @@ mod test { test("try {} catch (e) { foo() } finally {}", ""); test("try {} finally { foo() }", "foo()"); test("try {} catch (e) { foo() } finally { bar() }", "bar()"); - test("try {} finally { var x = foo() }", "{ var x = foo() }"); - test("try {} catch (e) { foo() } finally { var x = bar() }", "{ var x = bar() }"); + test("try {} finally { var x = foo() }", "var x = foo()"); + test("try {} catch (e) { foo() } finally { var x = bar() }", "var x = bar()"); test("try {} finally { let x = foo() }", "{ let x = foo() }"); test("try {} catch (e) { foo() } finally { let x = bar() }", "{ let x = bar();}"); test("try {} catch (e) { } finally {}", ""); diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 62d1ad0fcf2dd..8dbd44e6aa3dd 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -1418,7 +1418,7 @@ mod test { "typeof x !== 'undefined'; function foo() { var x }", "typeof x < 'u'; function foo() { var x }", ); - test("typeof x !== 'undefined'; { var x }", "x !== void 0; { var x }"); + test("typeof x !== 'undefined'; { var x }", "x !== void 0; var x;"); test("typeof x !== 'undefined'; { let x }", "typeof x < 'u'; { let x }"); test("typeof x !== 'undefined'; var x", "x !== void 0; var x"); // input and output both errors with same TDZ error diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index 70ab4d933991b..d45f9cd7f93fd 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -256,7 +256,7 @@ fn js_parser_test() { test("x?.['y']()", "x?.y();"); test("x?.['y z']()", "x?.['y z']();"); test("x['y' + 'z']", "x.yz;"); - // test("x?.['y' + 'z']", "x?.['yz'];"); + test("x?.['y' + 'z']", "x?.yz;"); test("x['0']", "x[0];"); test("x['123']", "x[123];"); test("x['-123']", "x[-123];"); @@ -267,12 +267,12 @@ fn js_parser_test() { test("x['-0x1']", "x['-0x1'];"); test("x['2147483647']", "x[2147483647];"); test("x['2147483648']", "x['2147483648'];"); - // test("x['-2147483648']", "x[-2147483648];"); + test("x['-2147483648']", "x[-2147483648];"); test("x['-2147483649']", "x['-2147483649'];"); test("while(1) { while (1) {} }", "for (;;) for (;;) ;"); test("while(1) { const x = y; }", "for (;;) { let x = y;}"); test("while(1) { let x; }", "for (;;) { let x;}"); - // test("while(1) { var x; }", "for (;;) var x;"); + test("while(1) { var x; }", "for (;;) var x;"); test("while(1) { class X {} }", "for (;;) { class X { }}"); // test("while(1) { function x() {} }", "for (;;) var x = function() { };"); test("while(1) { function* x() {} }", "for (;;) { function* x() { }}"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 05f4788f2d943..8bcf48d9e8bf4 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -9,15 +9,15 @@ Original | minified | minified | gzip | gzip | Fixture 342.15 kB | 117.67 kB | 118.14 kB | 43.48 kB | 44.37 kB | vue.js -544.10 kB | 71.42 kB | 72.48 kB | 25.86 kB | 26.20 kB | lodash.js +544.10 kB | 71.40 kB | 72.48 kB | 25.86 kB | 26.20 kB | lodash.js -555.77 kB | 271.24 kB | 270.13 kB | 88.34 kB | 90.80 kB | d3.js +555.77 kB | 271.24 kB | 270.13 kB | 88.33 kB | 90.80 kB | d3.js 1.01 MB | 440.93 kB | 458.89 kB | 122.53 kB | 126.71 kB | bundle.min.js 1.25 MB | 650.33 kB | 646.76 kB | 160.96 kB | 163.73 kB | three.js -2.14 MB | 717.08 kB | 724.14 kB | 162.06 kB | 181.07 kB | victory.js +2.14 MB | 717.07 kB | 724.14 kB | 162.06 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 324.41 kB | 331.56 kB | echarts.js