aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jhmaster <32803471+jhmaster2000@users.noreply.github.com> 2023-09-27 23:26:03 -0300
committerGravatar GitHub <noreply@github.com> 2023-09-27 19:26:03 -0700
commit3d376842537764ceaa296f846f7cbfbeb17d3ffe (patch)
treee6f0d81e2d57bf32fc29d33d662a47290a2f7cb7
parent06a82b7725eee86781c9884ff63d75450bfdf4a9 (diff)
downloadbun-3d376842537764ceaa296f846f7cbfbeb17d3ffe.tar.gz
bun-3d376842537764ceaa296f846f7cbfbeb17d3ffe.tar.zst
bun-3d376842537764ceaa296f846f7cbfbeb17d3ffe.zip
`deadCodeElimination` toggle for Bun.Transpiler (#5932)
* add Bun.Transpiler DCE option * mark DCE toggle experimental + tests * full (hopefully) DCE toggle * update DCE toggle tests * add DCE option to types * run fmt * Removed uws submodule
-rw-r--r--packages/bun-types/bun.d.ts8
-rw-r--r--src/bun.js/api/JSTranspiler.zig6
-rw-r--r--src/bundler.zig1
-rw-r--r--src/js_parser.zig112
-rw-r--r--src/options.zig1
-rw-r--r--src/runtime.zig1
-rw-r--r--test/transpiler/transpiler.test.js24
7 files changed, 107 insertions, 46 deletions
diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts
index 35f206634..a74274667 100644
--- a/packages/bun-types/bun.d.ts
+++ b/packages/bun-types/bun.d.ts
@@ -912,6 +912,14 @@ declare module "bun" {
* Minify whitespace and comments from the output.
*/
minifyWhitespace?: boolean;
+ /**
+ * **Experimental**
+ *
+ * Enabled by default, use this to disable dead code elimination.
+ *
+ * Some other transpiler options may still do some specific dead code elimination.
+ */
+ deadCodeElimination?: boolean;
/**
* This does two things (and possibly more in the future):
diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig
index 8cec025eb..d41458acb 100644
--- a/src/bun.js/api/JSTranspiler.zig
+++ b/src/bun.js/api/JSTranspiler.zig
@@ -72,6 +72,7 @@ const TranspilerOptions = struct {
trim_unused_imports: ?bool = null,
inlining: bool = false,
+ dead_code_elimination: bool = true,
minify_whitespace: bool = false,
minify_identifiers: bool = false,
minify_syntax: bool = false,
@@ -541,6 +542,10 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
transpiler.minify_whitespace = flag.toBoolean();
}
+ if (object.get(globalThis, "deadCodeElimination")) |flag| {
+ transpiler.dead_code_elimination = flag.toBoolean();
+ }
+
if (object.getTruthy(globalThis, "minify")) |hot| {
if (hot.isBoolean()) {
transpiler.minify_whitespace = hot.coerce(bool, globalThis);
@@ -800,6 +805,7 @@ pub fn constructor(
bundler.options.macro_remap = transpiler_options.macro_map;
}
+ bundler.options.dead_code_elimination = transpiler_options.dead_code_elimination;
bundler.options.minify_whitespace = transpiler_options.minify_whitespace;
// Keep defaults for these
diff --git a/src/bundler.zig b/src/bundler.zig
index 920f658c3..cf0e0a4f8 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -1340,6 +1340,7 @@ pub const Bundler = struct {
opts.features.inject_jest_globals = this_parse.inject_jest_globals;
opts.features.minify_syntax = bundler.options.minify_syntax;
opts.features.minify_identifiers = bundler.options.minify_identifiers;
+ opts.features.dead_code_elimination = bundler.options.dead_code_elimination;
if (bundler.macro_context == null) {
bundler.macro_context = js_ast.Macro.MacroContext.init(bundler);
diff --git a/src/js_parser.zig b/src/js_parser.zig
index e4dbaf25b..13e264251 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -1600,6 +1600,7 @@ pub const SideEffects = enum(u1) {
}
pub fn simplifyBoolean(p: anytype, expr: Expr) Expr {
+ if (!p.options.features.dead_code_elimination) return expr;
switch (expr.data) {
.e_unary => |e| {
if (e.op == .un_not) {
@@ -1614,14 +1615,14 @@ pub const SideEffects = enum(u1) {
.e_binary => |e| {
switch (e.op) {
.bin_logical_and => {
- const effects = SideEffects.toBoolean(e.right.data);
+ const effects = SideEffects.toBoolean(p, e.right.data);
if (effects.ok and effects.value and effects.side_effects == .no_side_effects) {
// "if (anything && truthyNoSideEffects)" => "if (anything)"
return e.left;
}
},
.bin_logical_or => {
- const effects = SideEffects.toBoolean(e.right.data);
+ const effects = SideEffects.toBoolean(p, e.right.data);
if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) {
// "if (anything || falsyNoSideEffects)" => "if (anything)"
return e.left;
@@ -1647,6 +1648,7 @@ pub const SideEffects = enum(u1) {
}
pub fn simpifyUnusedExpr(p: anytype, expr: Expr) ?Expr {
+ if (!p.options.features.dead_code_elimination) return expr;
switch (expr.data) {
.e_null, .e_undefined, .e_missing, .e_boolean, .e_number, .e_big_int, .e_string, .e_this, .e_reg_exp, .e_function, .e_arrow, .e_import_meta => {
return null;
@@ -1909,7 +1911,8 @@ pub const SideEffects = enum(u1) {
//
// We can't trim the entire branch as dead or calling foo() will incorrectly
// assign to a global variable instead.
- pub fn shouldKeepStmtInDeadControlFlow(stmt: Stmt, allocator: Allocator) bool {
+ pub fn shouldKeepStmtInDeadControlFlow(p: anytype, stmt: Stmt, allocator: Allocator) bool {
+ if (!p.options.features.dead_code_elimination) return true;
switch (stmt.data) {
// Omit these statements entirely
.s_empty, .s_expr, .s_throw, .s_return, .s_break, .s_continue, .s_class, .s_debugger => return false,
@@ -1940,7 +1943,7 @@ pub const SideEffects = enum(u1) {
.s_block => |block| {
for (block.stmts) |child| {
- if (shouldKeepStmtInDeadControlFlow(child, allocator)) {
+ if (shouldKeepStmtInDeadControlFlow(p, child, allocator)) {
return true;
}
}
@@ -1949,43 +1952,43 @@ pub const SideEffects = enum(u1) {
},
.s_if => |_if_| {
- if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) {
+ if (shouldKeepStmtInDeadControlFlow(p, _if_.yes, allocator)) {
return true;
}
const no = _if_.no orelse return false;
- return shouldKeepStmtInDeadControlFlow(no, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, no, allocator);
},
.s_while => {
- return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, stmt.data.s_while.body, allocator);
},
.s_do_while => {
- return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, stmt.data.s_do_while.body, allocator);
},
.s_for => |__for__| {
if (__for__.init) |init_| {
- if (shouldKeepStmtInDeadControlFlow(init_, allocator)) {
+ if (shouldKeepStmtInDeadControlFlow(p, init_, allocator)) {
return true;
}
}
- return shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator);
},
.s_for_in => |__for__| {
- return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, __for__.init, allocator) or shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator);
},
.s_for_of => |__for__| {
- return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, __for__.init, allocator) or shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator);
},
.s_label => |label| {
- return shouldKeepStmtInDeadControlFlow(label.stmt, allocator);
+ return shouldKeepStmtInDeadControlFlow(p, label.stmt, allocator);
},
else => return true,
}
@@ -2091,11 +2094,15 @@ pub const SideEffects = enum(u1) {
pub const toTypeOf = Expr.Data.typeof;
- pub fn toNullOrUndefined(exp: Expr.Data) Result {
+ pub fn toNullOrUndefined(p: anytype, exp: Expr.Data) Result {
+ if (!p.options.features.dead_code_elimination) {
+ // value should not be read if ok is false, all existing calls to this function already adhere to this
+ return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
+ }
switch (exp) {
// Never null or undefined
.e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
- return Result{ .value = false, .side_effects = SideEffects.no_side_effects, .ok = true };
+ return Result{ .value = false, .side_effects = .no_side_effects, .ok = true };
},
.e_object, .e_array, .e_class => {
@@ -2179,7 +2186,7 @@ pub const SideEffects = enum(u1) {
},
.bin_comma => {
- const res = toNullOrUndefined(e.right.data);
+ const res = toNullOrUndefined(p, e.right.data);
if (res.ok) {
return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects };
}
@@ -2193,7 +2200,11 @@ pub const SideEffects = enum(u1) {
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
}
- pub fn toBoolean(exp: Expr.Data) Result {
+ pub fn toBoolean(p: anytype, exp: Expr.Data) Result {
+ if (!p.options.features.dead_code_elimination) {
+ // value should not be read if ok is false, all existing calls to this function already adhere to this
+ return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
+ }
switch (exp) {
.e_null, .e_undefined => {
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
@@ -2227,7 +2238,7 @@ pub const SideEffects = enum(u1) {
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
},
.un_not => {
- var result = toBoolean(e_.value.data);
+ var result = toBoolean(p, e_.value.data);
if (result.ok) {
result.value = !result.value;
return result;
@@ -2240,21 +2251,21 @@ pub const SideEffects = enum(u1) {
switch (e_.op) {
.bin_logical_or => {
// "anything || truthy" is truthy
- const result = toBoolean(e_.right.data);
+ const result = toBoolean(p, e_.right.data);
if (result.value and result.ok) {
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
}
},
.bin_logical_and => {
// "anything && falsy" is falsy
- const result = toBoolean(e_.right.data);
+ const result = toBoolean(p, e_.right.data);
if (!result.value and result.ok) {
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
}
},
.bin_comma => {
// "anything, truthy/falsy" is truthy/falsy
- var result = toBoolean(e_.right.data);
+ var result = toBoolean(p, e_.right.data);
if (result.ok) {
result.side_effects = .could_have_side_effects;
return result;
@@ -4952,20 +4963,20 @@ fn NewParser_(
}
},
.s_if => |if_statement| {
- const result = SideEffects.toBoolean(if_statement.test_.data);
+ const result = SideEffects.toBoolean(p, if_statement.test_.data);
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
break :can_remove_part false;
}
},
.s_while => |while_statement| {
- const result = SideEffects.toBoolean(while_statement.test_.data);
+ const result = SideEffects.toBoolean(p, while_statement.test_.data);
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
break :can_remove_part false;
}
},
.s_for => |for_statement| {
if (for_statement.test_) |expr| {
- const result = SideEffects.toBoolean(expr.data);
+ const result = SideEffects.toBoolean(p, expr.data);
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
break :can_remove_part false;
}
@@ -12573,7 +12584,7 @@ fn NewParser_(
// Remove unnecessary optional chains
if (p.options.features.minify_syntax) {
- const result = SideEffects.toNullOrUndefined(left.data);
+ const result = SideEffects.toNullOrUndefined(p, left.data);
if (result.ok and !result.value) {
optional_start = null;
}
@@ -14010,7 +14021,7 @@ fn NewParser_(
.name = part,
.name_loc = loc,
- .can_be_removed_if_unused = true,
+ .can_be_removed_if_unused = p.options.features.dead_code_elimination,
},
loc,
);
@@ -14473,6 +14484,7 @@ fn NewParser_(
}
fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool {
+ if (!p.options.features.dead_code_elimination) return false;
switch (binding.data) {
.b_array => |bi| {
for (bi.items) |*item| {
@@ -14511,6 +14523,7 @@ fn NewParser_(
}
fn stmtsCanBeRemovedIfUnused(p: *P, stmts: []Stmt) bool {
+ if (!p.options.features.dead_code_elimination) return false;
for (stmts) |stmt| {
switch (stmt.data) {
// These never have side effects
@@ -15344,7 +15357,7 @@ fn NewParser_(
// Mark the control flow as dead if the branch is never taken
switch (e_.op) {
.bin_logical_or => {
- const side_effects = SideEffects.toBoolean(e_.left.data);
+ const side_effects = SideEffects.toBoolean(p, e_.left.data);
if (side_effects.ok and side_effects.value) {
// "true || dead"
const old = p.is_control_flow_dead;
@@ -15356,7 +15369,7 @@ fn NewParser_(
}
},
.bin_logical_and => {
- const side_effects = SideEffects.toBoolean(e_.left.data);
+ const side_effects = SideEffects.toBoolean(p, e_.left.data);
if (side_effects.ok and !side_effects.value) {
// "false && dead"
const old = p.is_control_flow_dead;
@@ -15368,7 +15381,7 @@ fn NewParser_(
}
},
.bin_nullish_coalescing => {
- const side_effects = SideEffects.toNullOrUndefined(e_.left.data);
+ const side_effects = SideEffects.toNullOrUndefined(p, e_.left.data);
if (side_effects.ok and !side_effects.value) {
// "notNullOrUndefined ?? dead"
const old = p.is_control_flow_dead;
@@ -15448,7 +15461,7 @@ fn NewParser_(
}
},
.bin_nullish_coalescing => {
- const nullorUndefined = SideEffects.toNullOrUndefined(e_.left.data);
+ const nullorUndefined = SideEffects.toNullOrUndefined(p, e_.left.data);
if (nullorUndefined.ok) {
if (!nullorUndefined.value) {
return e_.left;
@@ -15465,7 +15478,7 @@ fn NewParser_(
}
},
.bin_logical_or => {
- const side_effects = SideEffects.toBoolean(e_.left.data);
+ const side_effects = SideEffects.toBoolean(p, e_.left.data);
if (side_effects.ok and side_effects.value) {
return e_.left;
} else if (side_effects.ok and side_effects.side_effects == .no_side_effects) {
@@ -15480,7 +15493,7 @@ fn NewParser_(
}
},
.bin_logical_and => {
- const side_effects = SideEffects.toBoolean(e_.left.data);
+ const side_effects = SideEffects.toBoolean(p, e_.left.data);
if (side_effects.ok) {
if (!side_effects.value) {
return e_.left;
@@ -15846,7 +15859,7 @@ fn NewParser_(
if (p.options.features.minify_syntax)
e_.value = SideEffects.simplifyBoolean(p, e_.value);
- const side_effects = SideEffects.toBoolean(e_.value.data);
+ const side_effects = SideEffects.toBoolean(p, e_.value.data);
if (side_effects.ok) {
return p.newExpr(E.Boolean{ .value = !side_effects.value }, expr.loc);
}
@@ -15998,7 +16011,7 @@ fn NewParser_(
e_.test_ = SideEffects.simplifyBoolean(p, e_.test_);
- const side_effects = SideEffects.toBoolean(e_.test_.data);
+ const side_effects = SideEffects.toBoolean(p, e_.test_.data);
if (!side_effects.ok) {
e_.yes = p.visitExpr(e_.yes);
@@ -16571,6 +16584,7 @@ fn NewParser_(
}
pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool {
+ if (!p.options.features.dead_code_elimination) return false;
if (class.extends) |*extends| {
if (!p.exprCanBeRemovedIfUnused(extends)) {
return false;
@@ -16609,6 +16623,7 @@ fn NewParser_(
// When React Fast Refresh is enabled, anything that's a JSX component should not be removable
// This is to improve the reliability of fast refresh between page loads.
pub fn exprCanBeRemovedIfUnused(p: *P, expr: *const Expr) bool {
+ if (!p.options.features.dead_code_elimination) return false;
switch (expr.data) {
.e_null,
.e_undefined,
@@ -17743,7 +17758,7 @@ fn NewParser_(
const orig_dead = p.is_control_flow_dead;
if (p.options.features.replace_exports.count() > 0) {
if (p.options.features.replace_exports.getPtr("default")) |entry| {
- p.is_control_flow_dead = entry.* != .replace;
+ p.is_control_flow_dead = p.options.features.dead_code_elimination and (entry.* != .replace);
mark_for_replace = true;
}
}
@@ -17997,7 +18012,8 @@ fn NewParser_(
}
},
.s_expr => |data| {
- const should_trim_primitive = !p.options.features.minify_syntax and !data.value.isPrimitiveLiteral();
+ const should_trim_primitive = p.options.features.dead_code_elimination and
+ (p.options.features.minify_syntax and data.value.isPrimitiveLiteral());
p.stmt_expr_value = data.value.data;
defer p.stmt_expr_value = .{ .e_missing = .{} };
@@ -18168,7 +18184,7 @@ fn NewParser_(
data.body = p.visitLoopBody(data.body);
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
- const result = SideEffects.toBoolean(data.test_.data);
+ const result = SideEffects.toBoolean(p, data.test_.data);
if (result.ok and result.side_effects == .no_side_effects) {
data.test_ = p.newExpr(E.Boolean{ .value = result.value }, data.test_.loc);
}
@@ -18186,7 +18202,7 @@ fn NewParser_(
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
}
- const effects = SideEffects.toBoolean(data.test_.data);
+ const effects = SideEffects.toBoolean(p, data.test_.data);
if (effects.ok and !effects.value) {
const old = p.is_control_flow_dead;
p.is_control_flow_dead = true;
@@ -18218,7 +18234,7 @@ fn NewParser_(
if (p.options.features.minify_syntax) {
if (effects.ok) {
if (effects.value) {
- if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) {
+ if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(p, 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_| {
@@ -18232,7 +18248,7 @@ fn NewParser_(
}
} else {
// The test is falsy
- if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) {
+ if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, 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_| {
@@ -18287,7 +18303,7 @@ fn NewParser_(
if (data.test_) |test_| {
data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(test_));
- const result = SideEffects.toBoolean(data.test_.?.data);
+ const result = SideEffects.toBoolean(p, data.test_.?.data);
if (result.ok and result.value and result.side_effects == .no_side_effects) {
data.test_ = null;
}
@@ -18428,7 +18444,8 @@ fn NewParser_(
.s_function => |data| {
// We mark it as dead, but the value may not actually be dead
// We just want to be sure to not increment the usage counts for anything in the function
- const mark_as_dead = data.func.flags.contains(.is_export) and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.func.name.?.ref.?);
+ const mark_as_dead = p.options.features.dead_code_elimination and data.func.flags.contains(.is_export) and
+ p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.func.name.?.ref.?);
const original_is_dead = p.is_control_flow_dead;
if (mark_as_dead) {
@@ -18478,7 +18495,8 @@ fn NewParser_(
return;
},
.s_class => |data| {
- const mark_as_dead = data.is_export and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.class.class_name.?.ref.?);
+ const mark_as_dead = p.options.features.dead_code_elimination and data.is_export and
+ p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.class.class_name.?.ref.?);
const original_is_dead = p.is_control_flow_dead;
if (mark_as_dead) {
@@ -18730,7 +18748,7 @@ fn NewParser_(
if (decl.binding.data == .b_identifier) {
if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decl.binding.data.b_identifier.ref))) |replacer| {
replacement = replacer;
- if (replacer.* != .replace) {
+ if (p.options.features.dead_code_elimination and (replacer.* != .replace)) {
p.is_control_flow_dead = true;
}
}
@@ -20442,7 +20460,7 @@ fn NewParser_(
if (p.is_control_flow_dead) {
var end: usize = 0;
for (visited.items) |item| {
- if (!SideEffects.shouldKeepStmtInDeadControlFlow(item, p.allocator)) {
+ if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, item, p.allocator)) {
continue;
}
@@ -20531,7 +20549,9 @@ fn NewParser_(
var output = ListManaged(Stmt).initCapacity(p.allocator, stmts.items.len) catch unreachable;
for (stmts.items) |stmt| {
- if (is_control_flow_dead and !SideEffects.shouldKeepStmtInDeadControlFlow(stmt, p.allocator)) {
+ if (is_control_flow_dead and p.options.features.dead_code_elimination and
+ !SideEffects.shouldKeepStmtInDeadControlFlow(p, stmt, p.allocator))
+ {
// Strip unnecessary statements if the control flow is dead here
continue;
}
diff --git a/src/options.zig b/src/options.zig
index 73ca9a64b..80dcc7781 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -1443,6 +1443,7 @@ pub const BundleOptions = struct {
minify_whitespace: bool = false,
minify_syntax: bool = false,
minify_identifiers: bool = false,
+ dead_code_elimination: bool = true,
code_coverage: bool = false,
debugger: bool = false,
diff --git a/src/runtime.zig b/src/runtime.zig
index a9a88f979..0c343ddff 100644
--- a/src/runtime.zig
+++ b/src/runtime.zig
@@ -289,6 +289,7 @@ pub const Runtime = struct {
minify_syntax: bool = false,
minify_identifiers: bool = false,
+ dead_code_elimination: bool = true,
set_breakpoint_on_first_line: bool = false,
diff --git a/test/transpiler/transpiler.test.js b/test/transpiler/transpiler.test.js
index a6c2dcf73..43c8c6665 100644
--- a/test/transpiler/transpiler.test.js
+++ b/test/transpiler/transpiler.test.js
@@ -1984,6 +1984,30 @@ console.log(resolve.length)
// expectParseError("\u200Ca", 'Unexpected "\\u200c"');
// expectParseError("\u200Da", 'Unexpected "\\u200d"');
});
+
+ describe("dead code elimination", () => {
+ const transpilerNoDCE = new Bun.Transpiler({ deadCodeElimination: false });
+ it("should DCE with deadCodeElimination: true or by default", () => {
+ expect(parsed("123", true, false)).toBe("");
+ expect(parsed("[-1, 2n, null]", true, false)).toBe("");
+ expect(parsed("true", true, false)).toBe("");
+ expect(parsed("!0", true, false)).toBe("");
+ expect(parsed('if (!1) "dead";', true, false)).toBe("if (false)");
+ expect(parsed("if (!1) var x = 2;", true, false)).toBe("if (false)\n var x");
+ expect(parsed("if (undefined) { let y = Math.random(); }", true, false)).toBe("if (undefined) {\n}");
+ });
+ it("should not DCE with deadCodeElimination: false", () => {
+ expect(parsed("123", true, false, transpilerNoDCE)).toBe("123");
+ expect(parsed("[1, 2n, null]", true, false, transpilerNoDCE)).toBe("[1, 2n, null]");
+ expect(parsed("true", true, false, transpilerNoDCE)).toBe("true");
+ expect(parsed("!0", true, false, transpilerNoDCE)).toBe("!0");
+ expect(parsed('if (!1) "dead";', true, false, transpilerNoDCE)).toBe('if (!1)\n "dead"');
+ expect(parsed("if (!1) var x = 2;", true, false, transpilerNoDCE)).toBe("if (!1)\n var x = 2");
+ expect(parsed("if (undefined) { let y = Math.random(); }", true, false, transpilerNoDCE)).toBe(
+ "if (undefined) {\n let y = Math.random();\n}",
+ );
+ });
+ });
});
it("private identifiers", () => {