aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-05-15 21:50:44 -0700
committerGravatar GitHub <noreply@github.com> 2023-05-15 21:50:44 -0700
commit9c85483a8199f67feb4bebcb88fcc1bed5687916 (patch)
treefa03b9ed2591244f9df8aaf1e4242338c4b96c05
parent29572737ab81b6d2864ce1cb835879d7f45a32ae (diff)
downloadbun-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.zig4
-rw-r--r--src/js_parser.zig122
-rw-r--r--src/js_printer.zig3
-rw-r--r--test/bundler/bundler_cjs2esm.test.ts4
-rw-r--r--test/bundler/bundler_minify.test.ts127
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",
+ },
+ });
});