diff options
| author | 2023-05-15 21:50:44 -0700 | |
|---|---|---|
| committer | 2023-05-15 21:50:44 -0700 | |
| commit | 9c85483a8199f67feb4bebcb88fcc1bed5687916 (patch) | |
| tree | fa03b9ed2591244f9df8aaf1e4242338c4b96c05 | |
| parent | 29572737ab81b6d2864ce1cb835879d7f45a32ae (diff) | |
| download | bun-9c85483a8199f67feb4bebcb88fcc1bed5687916.tar.gz bun-9c85483a8199f67feb4bebcb88fcc1bed5687916.tar.zst bun-9c85483a8199f67feb4bebcb88fcc1bed5687916.zip | |
handle printing missing expressions and add tests (#2872)
* handle missing expressions and add tests
* minify missing expression blocks in parser
| -rw-r--r-- | src/js_ast.zig | 4 | ||||
| -rw-r--r-- | src/js_parser.zig | 122 | ||||
| -rw-r--r-- | src/js_printer.zig | 3 | ||||
| -rw-r--r-- | test/bundler/bundler_cjs2esm.test.ts | 4 | ||||
| -rw-r--r-- | test/bundler/bundler_minify.test.ts | 127 |
5 files changed, 220 insertions, 40 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig index 4b5c3b812..58f5efae2 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2594,6 +2594,10 @@ pub const Stmt = struct { return self.data == .s_expr and self.data.s_expr.value.data == .e_call and self.data.s_expr.value.data.e_call.target.data == .e_super; } + pub fn isMissingExpr(self: Stmt) bool { + return self.data == .s_expr and self.data.s_expr.value.data == .e_missing; + } + pub fn empty() Stmt { return Stmt{ .data = .{ .s_empty = None }, .loc = logger.Loc{} }; } diff --git a/src/js_parser.zig b/src/js_parser.zig index f78e5b160..79a0549f0 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -18004,6 +18004,7 @@ fn NewParser_( } }, .s_expr => |data| { + const should_trim_primitive = !p.options.features.minify_syntax and !data.value.isPrimitiveLiteral(); p.stmt_expr_value = data.value.data; const is_top_level = p.current_scope == p.module_scope; if (comptime FeatureFlags.unwrap_commonjs_to_esm) { @@ -18015,8 +18016,12 @@ fn NewParser_( data.value = p.visitExpr(data.value); + if (should_trim_primitive and data.value.isPrimitiveLiteral()) { + return; + } + // simplify unused - data.value = SideEffects.simpifyUnusedExpr(p, data.value) orelse data.value.toEmpty(); + data.value = SideEffects.simpifyUnusedExpr(p, data.value) orelse return; if (comptime FeatureFlags.unwrap_commonjs_to_esm) { if (is_top_level) { @@ -18130,17 +18135,17 @@ fn NewParser_( p.popScope(); } - // // trim empty statements - if (data.stmts.len == 0) { - stmts.append(Stmt{ .data = Prefill.Data.SEmpty, .loc = stmt.loc }) catch unreachable; - return; - } else if (data.stmts.len == 1 and !statementCaresAboutScope(data.stmts[0])) { - // Unwrap blocks containing a single statement - stmts.append(data.stmts[0]) catch unreachable; - return; + if (p.options.features.minify_syntax) { + // // trim empty statements + if (data.stmts.len == 0) { + stmts.append(Stmt{ .data = Prefill.Data.SEmpty, .loc = stmt.loc }) catch unreachable; + return; + } else if (data.stmts.len == 1 and !statementCaresAboutScope(data.stmts[0])) { + // Unwrap blocks containing a single statement + stmts.append(data.stmts[0]) catch unreachable; + return; + } } - stmts.append(stmt.*) catch unreachable; - return; }, .s_with => |data| { // using with is forbidden in strict mode @@ -18169,7 +18174,11 @@ fn NewParser_( data.test_ = SideEffects.simplifyBoolean(p, data.test_); }, .s_if => |data| { - data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(data.test_)); + data.test_ = p.visitExpr(data.test_); + + if (p.options.features.minify_syntax) { + data.test_ = SideEffects.simplifyBoolean(p, data.test_); + } const effects = SideEffects.toBoolean(data.test_.data); if (effects.ok and !effects.value) { @@ -18193,41 +18202,69 @@ fn NewParser_( } // Trim unnecessary "else" clauses - if (data.no != null and @as(Stmt.Tag, data.no.?.data) == .s_empty) { - data.no = null; + if (p.options.features.minify_syntax) { + if (data.no != null and @as(Stmt.Tag, data.no.?.data) == .s_empty) { + data.no = null; + } } } - if (effects.ok) { - if (effects.value) { - if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) { - if (effects.side_effects == .could_have_side_effects) { - // Keep the condition if it could have side effects (but is still known to be truthy) - if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { - stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; + if (p.options.features.minify_syntax) { + if (effects.ok) { + if (effects.value) { + if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) { + if (effects.side_effects == .could_have_side_effects) { + // Keep the condition if it could have side effects (but is still known to be truthy) + if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { + stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; + } } - } - return try p.appendIfBodyPreservingScope(stmts, data.yes); + return try p.appendIfBodyPreservingScope(stmts, data.yes); + } else { + // We have to keep the "no" branch + } } else { - // We have to keep the "no" branch - } - } else { - // The test is falsy - if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) { - if (effects.side_effects == .could_have_side_effects) { - // Keep the condition if it could have side effects (but is still known to be truthy) - if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { - stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; + // The test is falsy + if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) { + if (effects.side_effects == .could_have_side_effects) { + // Keep the condition if it could have side effects (but is still known to be truthy) + if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { + stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; + } } + + if (data.no == null) { + return; + } + + return try p.appendIfBodyPreservingScope(stmts, data.no.?); } + } + } + // TODO: more if statement syntax minification + const can_remove_test = p.exprCanBeRemovedIfUnused(&data.test_); + switch (data.yes.data) { + .s_expr => |yes_expr| { + if (yes_expr.value.isMissing()) { + if (data.no == null and can_remove_test) { + return; + } else if (data.no.?.isMissingExpr() and can_remove_test) { + return; + } + } + }, + .s_empty => { if (data.no == null) { + if (can_remove_test) { + return; + } + } else if (data.no.?.isMissingExpr() and can_remove_test) { return; } - - return try p.appendIfBodyPreservingScope(stmts, data.no.?); - } + }, + else => {}, } } }, @@ -19551,6 +19588,21 @@ fn NewParser_( } fn visitSingleStmt(p: *P, stmt: Stmt, kind: StmtsKind) Stmt { + if (stmt.data == .s_block) { + var new_stmt = stmt; + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + var stmts = ListManaged(Stmt).initCapacity(p.allocator, stmt.data.s_block.stmts.len) catch unreachable; + stmts.appendSlice(stmt.data.s_block.stmts) catch unreachable; + p.visitStmts(&stmts, kind) catch unreachable; + p.popScope(); + new_stmt.data.s_block.stmts = stmts.items; + if (p.options.features.minify_syntax) { + new_stmt = p.stmtsToSingleStmt(stmt.loc, stmts.items); + } + + return new_stmt; + } + const has_if_scope = switch (stmt.data) { .s_function => stmt.data.s_function.func.flags.contains(.has_if_scope), else => false, diff --git a/src/js_printer.zig b/src/js_printer.zig index 41c91e2f2..2a68135c1 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -988,9 +988,6 @@ fn NewPrinter( p.printNewline(); p.options.indent += 1; p.printStmt(stmt) catch unreachable; - if (stmt.data == .s_expr and stmt.data.s_expr.value.data == .e_missing) { - p.printSemicolonIfNeeded(); - } p.options.unindent(); }, } diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 51a8e00d0..2c498a4c6 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -152,7 +152,7 @@ describe("bundler", () => { `, }, cjs2esm: true, - dce: true, + minifySyntax: true, env: { NODE_ENV: "production", }, @@ -181,7 +181,7 @@ describe("bundler", () => { `, }, cjs2esm: true, - dce: true, + minifySyntax: true, env: { NODE_ENV: "development", }, diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index 8f0481905..89d49fac6 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -157,4 +157,131 @@ describe("bundler", () => { stdout: "PASS", }, }); + itBundled("minify/MissingExpressionBlocks", { + files: { + "/entry.js": /* js */ ` + var r = 1; + var g; + g = () => { + if (r) { + undefined; + } + }; + + g = () => { + if (r) { + } else if (r) { + undefined; + } + }; + + g = () => { + if (r) { + undefined; + } else if (r) { + undefined; + } + }; + + g = () => { + if (r) { + } else if (r) { + } else { + undefined; + } + }; + + g = () => { + if (r) { + } else if (r) { + undefined; + } else { + } + }; + + g = () => { + if (r) { + undefined; + } else if (r) { + } else { + } + }; + + g = () => { + if (r) { + undefined; + } else if (r) { + undefined; + } else { + } + }; + + g = () => { + if (r) { + undefined; + } else if (r) { + undefined; + } else { + undefined; + } + }; + + g = () => { + if (r) { + undefined; + } else if (r) { + } else { + undefined; + } + }; + + g = () => { + while (r) { + undefined; + } + }; + + g = () => { + do undefined; + while (r); + }; + + g = () => { + for (;;) undefined; + }; + + g = () => { + for (let i = 0; i < 10; i++) undefined; + }; + g = () => { + for (let i in [1, 2, 3]) undefined; + }; + g = () => { + for (let i of [1, 2, 3]) undefined; + }; + + g = () => { + switch (r) { + case 1: + undefined; + case 23: { + undefined; + } + } + }; + + g = () => { + let gg; + gg = () => undefined; + }; + + console.log("PASS"); + `, + }, + minifyWhitespace: true, + minifySyntax: true, + run: { + stdout: "PASS", + }, + }); }); |
