Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
643f5d86d5 fix(transpiler): ignore @__PURE__ annotations during runtime execution
@__PURE__ / #__PURE__ annotations are hints for bundlers and tree-shakers
to indicate that a call expression has no side effects and can be removed
if its result is unused. These annotations should only affect bundling,
not runtime execution.

Previously, when running `bun run file.js`, the transpiler would honor
@__PURE__ annotations and strip annotated call expressions, causing
unexpected behavior like `process.exit(0)` being silently removed.

Set `ignore_dce_annotations = true` in:
- Runtime execution paths (bun run, standalone)
- Test runner (bun test)
- Non-bundle build mode (bun build --no-bundle)

Bundler mode (bun build) continues to respect @__PURE__ annotations.

Closes #19355

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-19 09:55:19 +00:00
4 changed files with 99 additions and 3 deletions

View File

@@ -63,7 +63,10 @@ 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;
// @__PURE__ annotations are hints for bundlers/tree-shakers and should
// not cause code removal during runtime execution.
// See: https://github.com/oven-sh/bun/issues/19355
b.options.ignore_dce_annotations = true;
b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
@@ -222,7 +225,10 @@ 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;
// @__PURE__ annotations are hints for bundlers/tree-shakers and should
// not cause code removal during runtime execution.
// See: https://github.com/oven-sh/bun/issues/19355
b.options.ignore_dce_annotations = true;
b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;

View File

@@ -78,7 +78,10 @@ pub const BuildCommand = struct {
this_transpiler.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
this_transpiler.options.keep_names = ctx.bundler_options.keep_names;
this_transpiler.options.emit_dce_annotations = ctx.bundler_options.emit_dce_annotations;
this_transpiler.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
// When not bundling (--no-bundle), @__PURE__ annotations should be
// ignored since they are bundler tree-shaking hints.
// See: https://github.com/oven-sh/bun/issues/19355
this_transpiler.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations or ctx.bundler_options.transform_only;
this_transpiler.options.banner = ctx.bundler_options.banner;
this_transpiler.options.footer = ctx.bundler_options.footer;

View File

@@ -1419,6 +1419,10 @@ pub const TestCommand = struct {
vm.preload = ctx.preloads;
vm.transpiler.options.rewrite_jest_for_tests = true;
vm.transpiler.options.env.behavior = .load_all_without_inlining;
// @__PURE__ annotations are hints for bundlers/tree-shakers and should
// not cause code removal during runtime execution (including tests).
// See: https://github.com/oven-sh/bun/issues/19355
vm.transpiler.options.ignore_dce_annotations = true;
const node_env_entry = try env_loader.map.getOrPutWithoutValue("NODE_ENV");
if (!node_env_entry.found_existing) {

View File

@@ -0,0 +1,83 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/19355
// @__PURE__ annotations inside line comments should not cause
// the next call expression to be removed during runtime execution.
// @__PURE__ is a bundler tree-shaking hint, not a runtime directive.
test("@__PURE__ inside line comment does not strip next call", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `// /* @__PURE__ */\n//\nprocess.exit(0);\nconsole.log("hiii");`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("");
expect(exitCode).toBe(0);
});
test("// @__PURE__ does not strip next call at runtime", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `// @__PURE__\nprocess.exit(0);\nconsole.log("hiii");`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("");
expect(exitCode).toBe(0);
});
test("// #__PURE__ does not strip next call at runtime", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `// #__PURE__\nprocess.exit(0);\nconsole.log("hiii");`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("");
expect(exitCode).toBe(0);
});
test("/* @__PURE__ */ does not strip next call at runtime", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `/* @__PURE__ */ process.exit(0);\nconsole.log("hiii");`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("");
expect(exitCode).toBe(0);
});
test("@__PURE__ is ignored in --no-bundle mode", async () => {
const dir = tempDir("pure-no-bundle", {
"index.js": `// @__PURE__\nconsole.log("hello");\nconsole.log("world");`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", "--no-bundle", dir + "/index.js"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// With --no-bundle, @__PURE__ should be ignored (not strip the call)
expect(stdout).toContain("hello");
expect(stdout).toContain("world");
expect(exitCode).toBe(0);
});