From c2cf52895321a40f680316a59bb252a15e9c1b12 Mon Sep 17 00:00:00 2001
From: dave caruso <me@paperdave.net>
Date: Thu, 1 Aug 2024 15:00:38 -0700
Subject: [PATCH] bundler: Add `--ignore-dce-annotations`, and other DCE
 annotation related stuff (#12808)

Co-authored-by: paperdave <paperdave@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
---
 docs/bundler/vs-esbuild.md            |   3 +-
 src/api/schema.zig                    |   3 +
 src/bun_js.zig                        |   2 +
 src/bundler.zig                       |   5 +
 src/bundler/bundle_v2.zig             |   9 +
 src/cli.zig                           |  13 +-
 src/cli/build_command.zig             |   6 +
 src/js_parser.zig                     |  40 ++--
 src/js_printer.zig                    |  16 +-
 src/options.zig                       |   3 +
 test/bundler/esbuild/dce.test.ts      | 302 +++++++++++++++-----------
 test/bundler/esbuild/tsconfig.test.ts |   4 +-
 test/bundler/expectBundled.ts         |   6 +
 test/cli/install/bun-run.test.ts      |  40 +++-
 14 files changed, 286 insertions(+), 166 deletions(-)

diff --git a/docs/bundler/vs-esbuild.md b/docs/bundler/vs-esbuild.md
index 83dccc4ecb0d07..da2905bf6e52f1 100644
--- a/docs/bundler/vs-esbuild.md
+++ b/docs/bundler/vs-esbuild.md
@@ -208,8 +208,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
 ---
 
 - `--ignore-annotations`
-- n/a
-- Not supported
+- `--ignore-dce-annotations`
 
 ---
 
diff --git a/src/api/schema.zig b/src/api/schema.zig
index 8bed348e9b1946..713aefc7138c45 100644
--- a/src/api/schema.zig
+++ b/src/api/schema.zig
@@ -1687,6 +1687,9 @@ pub const Api = struct {
         /// packages
         packages: ?PackagesMode = null,
 
+        /// ignore_dce_annotations
+        ignore_dce_annotations: bool,
+
         pub fn decode(reader: anytype) anyerror!TransformOptions {
             var this = std.mem.zeroes(TransformOptions);
 
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 8947e3721880fc..66cc3399364d2d 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -88,6 +88,7 @@ pub const Run = struct {
 
         b.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
         b.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
+        b.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
         b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
         b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
 
@@ -232,6 +233,7 @@ pub const Run = struct {
 
         b.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
         b.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
+        b.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
         b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
         b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
 
diff --git a/src/bundler.zig b/src/bundler.zig
index ac6a81203c9823..cd88138e94d0dc 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -1144,6 +1144,7 @@ pub const Bundler = struct {
                     .minify_identifiers = bundler.options.minify_identifiers,
                     .transform_only = bundler.options.transform_only,
                     .runtime_transpiler_cache = runtime_transpiler_cache,
+                    .print_dce_annotations = bundler.options.emit_dce_annotations,
                 },
                 enable_source_map,
             ),
@@ -1167,6 +1168,7 @@ pub const Bundler = struct {
                     .transform_only = bundler.options.transform_only,
                     .import_meta_ref = ast.import_meta_ref,
                     .runtime_transpiler_cache = runtime_transpiler_cache,
+                    .print_dce_annotations = bundler.options.emit_dce_annotations,
                 },
                 enable_source_map,
             ),
@@ -1199,6 +1201,7 @@ pub const Bundler = struct {
                         .inline_require_and_import_errors = false,
                         .import_meta_ref = ast.import_meta_ref,
                         .runtime_transpiler_cache = runtime_transpiler_cache,
+                        .print_dce_annotations = bundler.options.emit_dce_annotations,
                     },
                     enable_source_map,
                 ),
@@ -1405,6 +1408,8 @@ pub const Bundler = struct {
                 opts.features.runtime_transpiler_cache = this_parse.runtime_transpiler_cache;
                 opts.transform_only = bundler.options.transform_only;
 
+                opts.ignore_dce_annotations = bundler.options.ignore_dce_annotations;
+
                 // @bun annotation
                 opts.features.dont_bundle_twice = this_parse.dont_bundle_twice;
 
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index 74776675ce2718..7706ec1321a484 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -760,6 +760,9 @@ pub const BundleV2 = struct {
         generator.linker.options.minify_syntax = bundler.options.minify_syntax;
         generator.linker.options.minify_identifiers = bundler.options.minify_identifiers;
         generator.linker.options.minify_whitespace = bundler.options.minify_whitespace;
+        generator.linker.options.emit_dce_annotations = bundler.options.emit_dce_annotations;
+        generator.linker.options.ignore_dce_annotations = bundler.options.ignore_dce_annotations;
+
         generator.linker.options.source_maps = bundler.options.source_map;
         generator.linker.options.tree_shaking = bundler.options.tree_shaking;
         generator.linker.options.public_path = bundler.options.public_path;
@@ -1627,6 +1630,7 @@ pub const BundleV2 = struct {
                 .extension_order = &.{},
                 .env_files = &.{},
                 .conditions = config.conditions.map.keys(),
+                .ignore_dce_annotations = bundler.options.ignore_dce_annotations,
             },
             completion.env,
         );
@@ -2874,6 +2878,7 @@ pub const ParseTask = struct {
         opts.features.minify_syntax = bundler.options.minify_syntax;
         opts.features.minify_identifiers = bundler.options.minify_identifiers;
         opts.features.emit_decorator_metadata = bundler.options.emit_decorator_metadata;
+        opts.ignore_dce_annotations = bundler.options.ignore_dce_annotations and !source.index.isRuntime();
 
         opts.tree_shaking = if (source.index.isRuntime()) true else bundler.options.tree_shaking;
         opts.module_type = task.module_type;
@@ -3851,6 +3856,7 @@ pub const LinkerContext = struct {
     pub const LinkerOptions = struct {
         output_format: options.OutputFormat = .esm,
         ignore_dce_annotations: bool = false,
+        emit_dce_annotations: bool = true,
         tree_shaking: bool = true,
         minify_whitespace: bool = false,
         minify_syntax: bool = false,
@@ -6804,6 +6810,7 @@ pub const LinkerContext = struct {
                 .minify_whitespace = c.options.minify_whitespace,
                 .minify_identifiers = c.options.minify_identifiers,
                 .minify_syntax = c.options.minify_syntax,
+                .print_dce_annotations = c.options.emit_dce_annotations,
                 // .const_values = c.graph.const_values,
             };
 
@@ -7714,6 +7721,7 @@ pub const LinkerContext = struct {
             .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c),
 
             .minify_whitespace = c.options.minify_whitespace,
+            .print_dce_annotations = c.options.emit_dce_annotations,
             .minify_syntax = c.options.minify_syntax,
             // .const_values = c.graph.const_values,
         };
@@ -9015,6 +9023,7 @@ pub const LinkerContext = struct {
             .minify_whitespace = c.options.minify_whitespace,
             .minify_syntax = c.options.minify_syntax,
             .module_type = c.options.output_format,
+            .print_dce_annotations = c.options.emit_dce_annotations,
             .has_run_symbol_renamer = true,
 
             .allocator = allocator,
diff --git a/src/cli.zig b/src/cli.zig
index d7135e36122380..5de5c1e2d192b7 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -185,6 +185,7 @@ pub const Arguments = struct {
         clap.parseParam("--jsx-fragment <STR>              Changes the function called when compiling JSX fragments") catch unreachable,
         clap.parseParam("--jsx-import-source <STR>         Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable,
         clap.parseParam("--jsx-runtime <STR>               \"automatic\" (default) or \"classic\"") catch unreachable,
+        clap.parseParam("--ignore-dce-annotations          Ignore tree-shaking annotations such as @__PURE__") catch unreachable,
     };
     const runtime_params_ = [_]ParamType{
         clap.parseParam("--watch                           Automatically restart the process on file change") catch unreachable,
@@ -238,7 +239,7 @@ pub const Arguments = struct {
         clap.parseParam("--no-clear-screen                Disable clearing the terminal screen on reload when --watch is enabled") catch unreachable,
         clap.parseParam("--target <STR>                   The intended execution environment for the bundle. \"browser\", \"bun\" or \"node\"") catch unreachable,
         clap.parseParam("--outdir <STR>                   Default to \"dist\" if multiple files") catch unreachable,
-        clap.parseParam("--outfile <STR>                   Write to a file") catch unreachable,
+        clap.parseParam("--outfile <STR>                  Write to a file") catch unreachable,
         clap.parseParam("--sourcemap <STR>?               Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable,
         clap.parseParam("--format <STR>                   Specifies the module format to build to. Only \"esm\" is supported.") catch unreachable,
         clap.parseParam("--root <STR>                     Root directory used for multiple entry points") catch unreachable,
@@ -251,10 +252,11 @@ pub const Arguments = struct {
         clap.parseParam("--asset-naming <STR>             Customize asset filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable,
         clap.parseParam("--server-components              Enable React Server Components (experimental)") catch unreachable,
         clap.parseParam("--no-bundle                      Transpile file only, do not bundle") catch unreachable,
+        clap.parseParam("--emit-dce-annotations           Re-emit DCE annotations in bundles. Enabled by default unless --minify-whitespace is passed.") catch unreachable,
         clap.parseParam("--minify                         Enable all minification flags") catch unreachable,
         clap.parseParam("--minify-syntax                  Minify syntax and inline data") catch unreachable,
         clap.parseParam("--minify-whitespace              Minify whitespace") catch unreachable,
-        clap.parseParam("--minify-identifiers              Minify identifiers") catch unreachable,
+        clap.parseParam("--minify-identifiers             Minify identifiers") catch unreachable,
         clap.parseParam("--dump-environment-variables") catch unreachable,
         clap.parseParam("--conditions <STR>...            Pass custom conditions to resolve") catch unreachable,
     };
@@ -697,6 +699,8 @@ pub const Arguments = struct {
         const output_dir: ?string = null;
         const output_file: ?string = null;
 
+        ctx.bundler_options.ignore_dce_annotations = args.flag("--ignore-dce-annotations");
+
         if (cmd == .BuildCommand) {
             ctx.bundler_options.transform_only = args.flag("--no-bundle");
 
@@ -709,6 +713,9 @@ pub const Arguments = struct {
             ctx.bundler_options.minify_whitespace = minify_flag or args.flag("--minify-whitespace");
             ctx.bundler_options.minify_identifiers = minify_flag or args.flag("--minify-identifiers");
 
+            ctx.bundler_options.emit_dce_annotations = args.flag("--emit-dce-annotations") or
+                !ctx.bundler_options.minify_whitespace;
+
             if (args.options("--external").len > 0) {
                 var externals = try allocator.alloc([]u8, args.options("--external").len);
                 for (args.options("--external"), 0..) |external, i| {
@@ -1275,6 +1282,8 @@ pub const Command = struct {
             minify_syntax: bool = false,
             minify_whitespace: bool = false,
             minify_identifiers: bool = false,
+            ignore_dce_annotations: bool = false,
+            emit_dce_annotations: bool = true,
         };
 
         pub fn create(allocator: std.mem.Allocator, log: *logger.Log, comptime command: Command.Tag) anyerror!Context {
diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig
index 631278298854f7..5fee90e015fc88 100644
--- a/src/cli/build_command.zig
+++ b/src/cli/build_command.zig
@@ -114,6 +114,12 @@ pub const BuildCommand = struct {
         this_bundler.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
         this_bundler.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
 
+        this_bundler.options.emit_dce_annotations = ctx.bundler_options.emit_dce_annotations;
+        this_bundler.resolver.opts.emit_dce_annotations = ctx.bundler_options.emit_dce_annotations;
+
+        this_bundler.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
+        this_bundler.resolver.opts.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
+
         if (ctx.bundler_options.compile) {
             if (ctx.bundler_options.code_splitting) {
                 Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use --compile with --splitting", .{});
diff --git a/src/js_parser.zig b/src/js_parser.zig
index e7322655bde21a..370d6bb5792c0d 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -1935,13 +1935,14 @@ pub const SideEffects = enum(u1) {
                 }
             },
 
-            .e_call => |call| {
-
+            inline .e_call, .e_new => |call| {
                 // A call that has been marked "__PURE__" can be removed if all arguments
                 // can be removed. The annotation causes us to ignore the target.
                 if (call.can_be_unwrapped_if_unused) {
                     if (call.args.len > 0) {
                         return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, comptime simplifyUnusedExpr, p.allocator);
+                    } else {
+                        return Expr.empty;
                     }
                 }
             },
@@ -2071,23 +2072,6 @@ pub const SideEffects = enum(u1) {
                 );
             },
 
-            .e_new => |call| {
-                // A constructor call that has been marked "__PURE__" can be removed if all arguments
-                // can be removed. The annotation causes us to ignore the target.
-                if (call.can_be_unwrapped_if_unused) {
-                    if (call.args.len > 0) {
-                        return Expr.joinAllWithCommaCallback(
-                            call.args.slice(),
-                            @TypeOf(p),
-                            p,
-                            comptime simplifyUnusedExpr,
-                            p.allocator,
-                        );
-                    }
-
-                    return null;
-                }
-            },
             else => {},
         }
 
@@ -3122,6 +3106,10 @@ pub const Parser = struct {
                 hasher.update("NO_TS");
             }
 
+            if (this.ignore_dce_annotations) {
+                hasher.update("no_dce");
+            }
+
             this.features.hashForRuntimeTranspiler(hasher);
         }
 
@@ -13201,12 +13189,6 @@ fn NewParser_(
                     .e_new => |ex| {
                         ex.can_be_unwrapped_if_unused = true;
                     },
-
-                    // this is specifically added only to support our implementation
-                    // of '__require' for --target=node, for /* @__PURE__ */ import.meta.url
-                    .e_dot => |ex| {
-                        ex.can_be_removed_if_unused = true;
-                    },
                     else => {},
                 }
             }
@@ -20860,7 +20842,13 @@ fn NewParser_(
                 E.Call{
                     .target = target,
                     .args = ExprNodeList.init(args_list),
-                    .can_be_unwrapped_if_unused = all_values_are_pure,
+                    // TODO: make these fully tree-shakable. this annotation
+                    // as-is is incorrect.  This would be done by changing all
+                    // enum wrappers into `var Enum = ...` instead of two
+                    // separate statements. This way, the @__PURE__ annotation
+                    // is attached to the variable binding.
+                    //
+                    // .can_be_unwrapped_if_unused = all_values_are_pure,
                 },
                 stmt_loc,
             );
diff --git a/src/js_printer.zig b/src/js_printer.zig
index 47f431d5c860d8..6a636edffc067f 100644
--- a/src/js_printer.zig
+++ b/src/js_printer.zig
@@ -65,8 +65,8 @@ const ascii_only_always_on_unless_minifying = true;
 fn formatUnsignedIntegerBetween(comptime len: u16, buf: *[len]u8, val: u64) void {
     comptime var i: u16 = len;
     var remainder = val;
-    // Write out the number from the end to the front
 
+    // Write out the number from the end to the front
     inline while (i > 0) {
         comptime i -= 1;
         buf[comptime i] = @as(u8, @intCast((remainder % 10))) + '0';
@@ -536,6 +536,8 @@ pub const Options = struct {
     minify_whitespace: bool = false,
     minify_identifiers: bool = false,
     minify_syntax: bool = false,
+    print_dce_annotations: bool = true,
+
     transform_only: bool = false,
     inline_require_and_import_errors: bool = true,
     has_run_symbol_renamer: bool = false,
@@ -544,7 +546,7 @@ pub const Options = struct {
 
     module_type: options.OutputFormat = .preserve,
 
-    /// Used for cross-module inlining of import items when bundling
+    // /// Used for cross-module inlining of import items when bundling
     // const_values: Ast.ConstValuesMap = .{},
     ts_enums: Ast.TsEnumsMap = .{},
 
@@ -2146,8 +2148,10 @@ fn NewPrinter(
             return;
         }
 
-        // noop for now
-        pub inline fn printPure(_: *Printer) void {}
+        pub inline fn printPure(p: *Printer) void {
+            if (Environment.allow_assert) assert(p.options.print_dce_annotations);
+            p.printWhitespacer(ws("/* @__PURE__ */ "));
+        }
 
         pub fn printQuotedUTF8(p: *Printer, str: string, allow_backtick: bool) void {
             const quote = if (comptime !is_json)
@@ -2353,7 +2357,7 @@ fn NewPrinter(
                     }
                 },
                 .e_new => |e| {
-                    const has_pure_comment = e.can_be_unwrapped_if_unused;
+                    const has_pure_comment = e.can_be_unwrapped_if_unused and p.options.print_dce_annotations;
                     const wrap = level.gte(.call) or (has_pure_comment and level.gte(.postfix));
 
                     if (wrap) {
@@ -2403,7 +2407,7 @@ fn NewPrinter(
                         wrap = true;
                     }
 
-                    const has_pure_comment = e.can_be_unwrapped_if_unused;
+                    const has_pure_comment = e.can_be_unwrapped_if_unused and p.options.print_dce_annotations;
                     if (has_pure_comment and level.gte(.postfix)) {
                         wrap = true;
                     }
diff --git a/src/options.zig b/src/options.zig
index b2b60949cb701a..9a7abd576350f0 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -1515,6 +1515,9 @@ pub const BundleOptions = struct {
     minify_identifiers: bool = false,
     dead_code_elimination: bool = true,
 
+    ignore_dce_annotations: bool = false,
+    emit_dce_annotations: bool = false,
+
     code_coverage: bool = false,
     debugger: bool = false,
 
diff --git a/test/bundler/esbuild/dce.test.ts b/test/bundler/esbuild/dce.test.ts
index 69e80e8ce3885a..a4a5b84250b7aa 100644
--- a/test/bundler/esbuild/dce.test.ts
+++ b/test/bundler/esbuild/dce.test.ts
@@ -1,5 +1,6 @@
 import { itBundled, dedent } from "../expectBundled";
 import { describe, expect } from "bun:test";
+import { isWindows } from 'harness';
 
 // Tests ported from:
 // https://github.com/evanw/esbuild/blob/main/internal/bundler_tests/bundler_dce_test.go
@@ -107,7 +108,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsTrueKeepES6", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import "demo-pkg"
@@ -129,7 +129,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsTrueKeepCommonJS", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import "demo-pkg"
@@ -151,7 +150,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseKeepBareImportAndRequireES6", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import "demo-pkg"
@@ -174,7 +172,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseKeepBareImportAndRequireCommonJS", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import "demo-pkg"
@@ -197,7 +194,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseRemoveBareImportES6", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import "demo-pkg"
@@ -219,7 +215,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseRemoveBareImportCommonJS", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import "demo-pkg"
@@ -241,7 +236,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseRemoveNamedImportES6", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -263,7 +257,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseRemoveNamedImportCommonJS", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -285,7 +278,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseRemoveStarImportES6", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import * as ns from "demo-pkg"
@@ -307,7 +299,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseRemoveStarImportCommonJS", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import * as ns from "demo-pkg"
@@ -351,7 +342,7 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsArrayKeep", {
-    todo: true,
+    todo: isWindows,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -429,7 +420,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsArrayKeepMainImplicitModule", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -457,7 +447,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsArrayKeepMainImplicitMain", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -490,7 +479,7 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsArrayKeepModuleUseModule", {
-    todo: true,
+    todo: isWindows,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -518,7 +507,7 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsArrayKeepModuleUseMain", {
-    todo: true,
+    todo: isWindows,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -546,7 +535,7 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsArrayKeepModuleImplicitModule", {
-    todo: true,
+    todo: isWindows,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "demo-pkg"
@@ -778,7 +767,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseIntermediateFilesDiamond", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": /* js */ `
         import {foo} from "a"
@@ -807,7 +795,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseOneFork", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": `import("a").then(x => console.log(x.foo))`,
       "/Users/user/project/node_modules/a/index.js": `export {foo} from "b"`,
@@ -828,7 +815,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/PackageJsonSideEffectsFalseAllFork", {
-    todo: true,
     files: {
       "/Users/user/project/src/entry.js": `import("a").then(x => console.log(x.foo))`,
       "/Users/user/project/node_modules/a/index.js": `export {foo} from "b"`,
@@ -851,7 +837,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/JSONLoaderRemoveUnused", {
-    todo: true,
     files: {
       "/entry.js": /* js */ `
         import unused from "./example.json"
@@ -865,7 +850,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/TextLoaderRemoveUnused", {
-    todo: true,
     files: {
       "/entry.js": /* js */ `
         import unused from "./example.txt"
@@ -934,7 +918,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/RemoveUnusedImportMeta", {
-    todo: true,
     files: {
       "/entry.js": /* js */ `
         function foo() {
@@ -948,109 +931,119 @@ describe("bundler", () => {
       stdout: "foo is unused",
     },
   });
-  itBundled("dce/RemoveUnusedPureCommentCalls", {
-    todo: true,
-    // in this test, the bundler must drop all `_yes` variables entirely, and then
-    // preserve the pure comments in the same way esbuild does
-    files: {
-      "/entry.js": /* js */ `
-        function bar() {}
-        let bare = foo(bar);
-  
-        let at_yes = /* @__PURE__ */ foo(bar);
-        let at_no = /* @__PURE__ */ foo(bar());
-        let new_at_yes = /* @__PURE__ */ new foo(bar);
-        let new_at_no = /* @__PURE__ */ new foo(bar());
-  
-        let nospace_at_yes = /*@__PURE__*/ foo(bar);
-        let nospace_at_no = /*@__PURE__*/ foo(bar());
-        let nospace_new_at_yes = /*@__PURE__*/ new foo(bar);
-        let nospace_new_at_no = /*@__PURE__*/ new foo(bar());
-  
-        let num_yes = /* #__PURE__ */ foo(bar);
-        let num_no = /* #__PURE__ */ foo(bar());
-        let new_num_yes = /* #__PURE__ */ new foo(bar);
-        let new_num_no = /* #__PURE__ */ new foo(bar());
-  
-        let nospace_num_yes = /*#__PURE__*/ foo(bar);
-        let nospace_num_no = /*#__PURE__*/ foo(bar());
-        let nospace_new_num_yes = /*#__PURE__*/ new foo(bar);
-        let nospace_new_num_no = /*#__PURE__*/ new foo(bar());
-  
-        let dot_yes = /* @__PURE__ */ foo(sideEffect()).dot(bar);
-        let dot_no = /* @__PURE__ */ foo(sideEffect()).dot(bar());
-        let new_dot_yes = /* @__PURE__ */ new foo(sideEffect()).dot(bar);
-        let new_dot_no = /* @__PURE__ */ new foo(sideEffect()).dot(bar());
-  
-        let nested_yes = [1, /* @__PURE__ */ foo(bar), 2];
-        let nested_no = [1, /* @__PURE__ */ foo(bar()), 2];
-        let new_nested_yes = [1, /* @__PURE__ */ new foo(bar), 2];
-        let new_nested_no = [1, /* @__PURE__ */ new foo(bar()), 2];
-  
-        let single_at_yes = // @__PURE__
-          foo(bar);
-        let single_at_no = // @__PURE__
-          foo(bar());
-        let new_single_at_yes = // @__PURE__
-          new foo(bar);
-        let new_single_at_no = // @__PURE__
-          new foo(bar());
-  
-        let single_num_yes = // #__PURE__
-          foo(bar);
-        let single_num_no = // #__PURE__
-          foo(bar());
-        let new_single_num_yes = // #__PURE__
-          new foo(bar);
-        let new_single_num_no = // #__PURE__
-          new foo(bar());
-  
-        let bad_no = /* __PURE__ */ foo(bar);
-        let new_bad_no = /* __PURE__ */ new foo(bar);
-  
-        let parens_no = (/* @__PURE__ */ foo)(bar);
-        let new_parens_no = new (/* @__PURE__ */ foo)(bar);
-  
-        let exp_no = /* @__PURE__ */ foo() ** foo();
-        let new_exp_no = /* @__PURE__ */ new foo() ** foo();
-      `,
-    },
-    onAfterBundle(api) {
-      const code = api.readFile("/out.js");
-      expect(code).not.toContain("_yes"); // should not contain any *_yes variables
-      expect(code).toContain("var bare = foo(bar)"); // should contain `var bare = foo(bar)`
-      const keep = [
-        ["at_no", true],
-        ["new_at_no", true],
-        ["nospace_at_no", true],
-        ["nospace_new_at_no", true],
-        ["num_no", true],
-        ["new_num_no", true],
-        ["nospace_num_no", true],
-        ["nospace_new_num_no", true],
-        ["dot_no", true],
-        ["new_dot_no", true],
-        ["nested_no", true],
-        ["new_nested_no", true],
-        ["single_at_no", true],
-        ["new_single_at_no", true],
-        ["single_num_no", true],
-        ["new_single_num_no", true],
-        ["bad_no", false],
-        ["new_bad_no", false],
-        ["parens_no", false],
-        ["new_parens_no", false],
-        ["exp_no", true],
-        ["new_exp_no", true],
-      ];
-      for (const [name, pureComment] of keep) {
-        const regex = new RegExp(`${name}\\s*=[^\/\n]*(\\/\\*.*?\\*\\/)?`, "g");
-        const match = regex.exec(code);
-        expect(match).toBeTruthy(); // should contain ${name}
-        expect(pureComment ? !!match[1] : !match[1]).toBeTruthy(); // should contain a pure comment for ${name}
-      }
-    },
-  });
+  for (const { minify, emitDCEAnnotations, name } of [
+    { minify: false, emitDCEAnnotations: false, name: "dce/RemoveUnusedPureCommentCalls" },
+    { minify: true, emitDCEAnnotations: false, name: "dce/RemoveUnusedPureCommentCallsMinify" },
+    { minify: true, emitDCEAnnotations: true, name: "dce/RemoveUnusedPureCommentCallsMinifyExplitOn" },
+  ]) {
+    itBundled(name, {
+      // in this test, the bundler must drop all `_yes` variables entirely, and then
+      // preserve the pure comments in the same way esbuild does
+      files: {
+        "/entry.js": /* js */ `
+          function bar() {}
+          let bare = foo(bar);
+
+          let at_yes = /* @__PURE__ */ foo(bar);
+          let at_no = /* @__PURE__ */ foo(bar());
+          let new_at_yes = /* @__PURE__ */ new foo(bar);
+          let new_at_no = /* @__PURE__ */ new foo(bar());
+
+          let nospace_at_yes = /*@__PURE__*/ foo(bar);
+          let nospace_at_no = /*@__PURE__*/ foo(bar());
+          let nospace_new_at_yes = /*@__PURE__*/ new foo(bar);
+          let nospace_new_at_no = /*@__PURE__*/ new foo(bar());
+
+          let num_yes = /* #__PURE__ */ foo(bar);
+          let num_no = /* #__PURE__ */ foo(bar());
+          let new_num_yes = /* #__PURE__ */ new foo(bar);
+          let new_num_no = /* #__PURE__ */ new foo(bar());
+
+          let nospace_num_yes = /*#__PURE__*/ foo(bar);
+          let nospace_num_no = /*#__PURE__*/ foo(bar());
+          let nospace_new_num_yes = /*#__PURE__*/ new foo(bar);
+          let nospace_new_num_no = /*#__PURE__*/ new foo(bar());
+
+          let dot_yes = /* @__PURE__ */ foo(sideEffect()).dot(bar);
+          let dot_no = /* @__PURE__ */ foo(sideEffect()).dot(bar());
+          let new_dot_yes = /* @__PURE__ */ new foo(sideEffect()).dot(bar);
+          let new_dot_no = /* @__PURE__ */ new foo(sideEffect()).dot(bar());
+
+          let nested_yes = [1, /* @__PURE__ */ foo(bar), 2];
+          let nested_no = [1, /* @__PURE__ */ foo(bar()), 2];
+          let new_nested_yes = [1, /* @__PURE__ */ new foo(bar), 2];
+          let new_nested_no = [1, /* @__PURE__ */ new foo(bar()), 2];
+
+          let single_at_yes = // @__PURE__
+            foo(bar);
+          let single_at_no = // @__PURE__
+            foo(bar());
+          let new_single_at_yes = // @__PURE__
+            new foo(bar);
+          let new_single_at_no = // @__PURE__
+            new foo(bar());
+
+          let single_num_yes = // #__PURE__
+            foo(bar);
+          let single_num_no = // #__PURE__
+            foo(bar());
+          let new_single_num_yes = // #__PURE__
+            new foo(bar);
+          let new_single_num_no = // #__PURE__
+            new foo(bar());
+
+          let bad_no = /* __PURE__ */ foo(bar);
+          let new_bad_no = /* __PURE__ */ new foo(bar);
+
+          let parens_no = (/* @__PURE__ */ foo)(bar);
+          let new_parens_no = new (/* @__PURE__ */ foo)(bar);
+
+          let exp_no = /* @__PURE__ */ foo() ** foo();
+          let new_exp_no = /* @__PURE__ */ new foo() ** foo();
+        `,
+      },
+      minifyWhitespace: minify,
+      emitDCEAnnotations: emitDCEAnnotations,
+      onAfterBundle(api) {
+        const code = api.readFile("/out.js");
+        expect(code).not.toContain("_yes"); // should not contain any *_yes variables
+        expect(code).toContain(minify ? "var bare=foo(bar)" : "var bare = foo(bar)");
+        const keep = [
+          ["at_no", true],
+          ["new_at_no", true],
+          ["nospace_at_no", true],
+          ["nospace_new_at_no", true],
+          ["num_no", true],
+          ["new_num_no", true],
+          ["nospace_num_no", true],
+          ["nospace_new_num_no", true],
+          ["dot_no", true],
+          ["new_dot_no", true],
+          ["nested_no", true],
+          ["new_nested_no", true],
+          ["single_at_no", true],
+          ["new_single_at_no", true],
+          ["single_num_no", true],
+          ["new_single_num_no", true],
+          ["parens_no", false],
+          ["new_parens_no", false],
+          ["exp_no", true],
+          ["new_exp_no", true],
+        ];
+        for (const [name, pureComment] of keep) {
+          const regex = new RegExp(`${name}\\s*=[^\/\n;]*(\\/\\*[^\/\n;]*?PURE[^\/\n;]*?\\*\\/)?`, "g");
+          const match = regex.exec(code)!;
+          expect(match).toBeTruthy(); // should contain ${name}
+
+          if ((emitDCEAnnotations || !minify) && pureComment) {
+            expect(match[1], "should contain pure comment for " + name).toBeTruthy();
+          } else {
+            expect(match[1], "should not contain pure comment for " + name).toBeFalsy();
+          }
+        }
+      },
+    });
+  }
   itBundled("dce/TreeShakingReactElements", {
     files: {
       "/entry.jsx": /* jsx */ `
@@ -2608,7 +2601,6 @@ describe("bundler", () => {
     },
   });
   itBundled("dce/CrossModuleConstantFolding", {
-    todo: true,
     files: {
       "/enum-constants.ts": /* ts */ `
         export enum remove {
@@ -2718,7 +2710,6 @@ describe("bundler", () => {
     dce: true,
   });
   itBundled("dce/MultipleDeclarationTreeShaking", {
-    todo: true,
     files: {
       "/var2.js": /* js */ `
         var x = 1
@@ -2757,7 +2748,6 @@ describe("bundler", () => {
     ],
   });
   itBundled("dce/MultipleDeclarationTreeShakingMinifySyntax", {
-    todo: true,
     files: {
       "/var2.js": /* js */ `
         var x = 1
@@ -2964,6 +2954,62 @@ describe("bundler", () => {
       stdout: "foo\nbar",
     },
   });
+  itBundled("dce/CallWithNoArg", {
+    files: {
+      "/entry.js": /* js */ `
+        /* @__PURE__ */ noSideEffects();
+      `,
+    },
+    run: {
+      stdout: "",
+    },
+  });
+  itBundled("dce/ConstructWithNoArg", {
+    files: {
+      "/entry.js": /* js */ `
+        /* @__PURE__ */ new NoSideEffects();
+      `,
+    },
+    run: {
+      stdout: "",
+    },
+  });
+  itBundled("dce/IgnoreAnnotations", {
+    files: {
+      "/entry.js": /* js */ `
+        function noSideEffects() { console.log("PASS"); }
+        /* @__PURE__ */ noSideEffects(1);
+      `,
+    },
+    ignoreDCEAnnotations: true,
+    run: {
+      stdout: "PASS",
+    },
+  });
+  itBundled("dce/IgnoreAnnotationsDoesNotApplyToRuntime", {
+    files: {
+      "/entry.js": /* js */ `
+        import("./other.js");
+      `,
+      "/other.js": /* js */ `
+        export function foo() { }
+      `,
+    },
+    ignoreDCEAnnotations: true,
+    onAfterBundle(api) {
+      // These symbols technically have side effects, and we use dce annotations
+      // to let them tree-shake User-specified --ignore-annotations should not
+      // apply to our code.
+      api.expectFile("/out.js").not.toContain("__dispose");
+      api.expectFile("/out.js").not.toContain("__asyncDispose");
+      api.expectFile("/out.js").not.toContain("__require");
+
+      // This assertion catches if the bundler changes in that the runtime is no
+      // longer included. If this fails, just adjust the code snippet so some
+      // part of runtime.js is used
+      api.expectFile("/out.js").toContain("__defProp");
+    },
+  });
   // itBundled("dce/TreeShakingJSWithAssociatedCSS", {
   //   // TODO: css assertions. this should contain both button and menu
   //   files: {
diff --git a/test/bundler/esbuild/tsconfig.test.ts b/test/bundler/esbuild/tsconfig.test.ts
index bdeb28d462086d..d96d822f96bad8 100644
--- a/test/bundler/esbuild/tsconfig.test.ts
+++ b/test/bundler/esbuild/tsconfig.test.ts
@@ -393,7 +393,9 @@ describe("bundler", () => {
     onAfterBundle(api) {
       api
         .expectFile("/Users/user/project/out.js")
-        .toContain(`console.log(R.c(R.F, null, R.c(\"div\", null), R.c(\"div\", null)));\n`);
+        .toContain(
+          `console.log(/* @__PURE__ */ R.c(R.F, null, /* @__PURE__ */ R.c(\"div\", null), /* @__PURE__ */ R.c(\"div\", null)));\n`,
+        );
     },
   });
   itBundled("tsconfig/ReactJSXNotReact", {
diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts
index ce8c8131de0385..7e93a791fdc3ea 100644
--- a/test/bundler/expectBundled.ts
+++ b/test/bundler/expectBundled.ts
@@ -165,6 +165,7 @@ export interface BundlerTestInput {
   format?: "esm" | "cjs" | "iife";
   globalName?: string;
   ignoreDCEAnnotations?: boolean;
+  emitDCEAnnotations?: boolean;
   inject?: string[];
   jsx?: {
     runtime?: "automatic" | "classic";
@@ -440,6 +441,8 @@ function expectBundled(
     unsupportedCSSFeatures,
     unsupportedJSFeatures,
     useDefineForClassFields,
+    ignoreDCEAnnotations,
+    emitDCEAnnotations,
     // @ts-expect-error
     _referenceFn,
     expectExactFilesize,
@@ -645,6 +648,8 @@ function expectBundled(
               splitting && `--splitting`,
               serverComponents && "--server-components",
               outbase && `--root=${outbase}`,
+              ignoreDCEAnnotations && `--ignore-dce-annotations`,
+              emitDCEAnnotations && `--emit-dce-annotations`,
               // inject && inject.map(x => ["--inject", path.join(root, x)]),
               // jsx.preserve && "--jsx=preserve",
               // legalComments && `--legal-comments=${legalComments}`,
@@ -687,6 +692,7 @@ function expectBundled(
               sourceMap && `--sourcemap=${sourceMap}`,
               banner && `--banner:js=${banner}`,
               legalComments && `--legal-comments=${legalComments}`,
+              ignoreDCEAnnotations && `--ignore-annotations`,
               splitting && `--splitting`,
               treeShaking && `--tree-shaking`,
               outbase && `--outbase=${outbase}`,
diff --git a/test/cli/install/bun-run.test.ts b/test/cli/install/bun-run.test.ts
index 203f8f9676072f..21f48c4c9f84c8 100644
--- a/test/cli/install/bun-run.test.ts
+++ b/test/cli/install/bun-run.test.ts
@@ -1,6 +1,6 @@
 import { file, spawn, spawnSync } from "bun";
 import { afterEach, beforeEach, expect, it, describe } from "bun:test";
-import { bunEnv, bunExe, bunEnv as env, isWindows, tmpdirSync } from "harness";
+import { bunEnv, bunExe, bunEnv as env, isWindows, tempDirWithFiles, tmpdirSync } from "harness";
 import { rm, writeFile, exists, mkdir } from "fs/promises";
 import { join } from "path";
 import { readdirSorted } from "./dummy.registry";
@@ -437,3 +437,41 @@ it("should show the correct working directory when run with --cwd", async () =>
   expect(await res.exited).toBe(0);
   expect(await Bun.readableStreamToText(res.stdout)).toMatch(/subdir/);
 });
+
+it("DCE annotations are respected", () => {
+  const dir = tempDirWithFiles("test", {
+    "index.ts": `
+      /* @__PURE__ */ console.log("Hello, world!");
+    `,
+  });
+
+  const { stdout, stderr, exitCode } = spawnSync({
+    cmd: [bunExe(), "run", "index.ts"],
+    cwd: dir,
+    env: bunEnv,
+  });
+
+  expect(exitCode).toBe(0);
+
+  expect(stderr.toString()).toBe("");
+  expect(stdout.toString()).toBe("");
+});
+
+it("--ignore-dce-annotations ignores DCE annotations", () => {
+  const dir = tempDirWithFiles("test", {
+    "index.ts": `
+      /* @__PURE__ */ console.log("Hello, world!");
+    `,
+  });
+
+  const { stdout, stderr, exitCode } = spawnSync({
+    cmd: [bunExe(), "--ignore-dce-annotations", "run", "index.ts"],
+    cwd: dir,
+    env: bunEnv,
+  });
+
+  expect(exitCode).toBe(0);
+
+  expect(stderr.toString()).toBe("");
+  expect(stdout.toString()).toBe("Hello, world!\n");
+});