diff options
author | 2023-09-27 23:26:03 -0300 | |
---|---|---|
committer | 2023-09-27 19:26:03 -0700 | |
commit | 3d376842537764ceaa296f846f7cbfbeb17d3ffe (patch) | |
tree | e6f0d81e2d57bf32fc29d33d662a47290a2f7cb7 | |
parent | 06a82b7725eee86781c9884ff63d75450bfdf4a9 (diff) | |
download | bun-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.ts | 8 | ||||
-rw-r--r-- | src/bun.js/api/JSTranspiler.zig | 6 | ||||
-rw-r--r-- | src/bundler.zig | 1 | ||||
-rw-r--r-- | src/js_parser.zig | 112 | ||||
-rw-r--r-- | src/options.zig | 1 | ||||
-rw-r--r-- | src/runtime.zig | 1 | ||||
-rw-r--r-- | test/transpiler/transpiler.test.js | 24 |
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", () => { |