Files
bun.sh/test/regression/issue/25398.test.ts
Hamidreza Hanafi 9c96937329 fix(transpiler): preserve simplified property values in object spread expressions (#25401)
Fixes #25398

### What does this PR do?

Fixes a bug where object expressions with spread properties and nullish
coalescing to empty objects (e.g., `k?.x ?? {}`) would produce invalid
JavaScript output like `k?.x ?? ` (missing `{}`).

### Root Cause

In `src/ast/SideEffects.zig`, the `simplifyUnusedExpr` function handles
unused object expressions with spread properties. When simplifying
property values:

1. The code creates a mutable copy `prop` from the original `prop_`
2. When a property value is simplified (e.g., `k?.x ?? {}` → `k?.x`), it
updates `prop.value`
3. **Bug:** The code then wrote back `prop_` (the original) instead of
`prop` (the modified copy)

Because `simplifyUnusedExpr` mutates the AST in place when handling
nullish coalescing (setting `bin.right` to empty), the original `prop_`
now contained an expression with `bin.right` as an empty/missing
expression, resulting in invalid output.

### How did you verify your code works?
- Added regression test in `test/regression/issue/25398.test.ts`
- Verified the original reproduction case passes
- Verified existing CommonJS tests continue to pass
- Verified test fails with system bun and passes with the fix
2025-12-07 16:33:37 -08:00

55 lines
1.9 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/25398
// Bug: Object spread with nullish coalescing to empty object literal
// in unused expression statements was incorrectly simplified,
// resulting in invalid JavaScript output like `k?.x ?? ` (missing {})
test("object spread with nullish coalescing to empty object in arrow function body", async () => {
// This pattern is common in Webpack-style CommonJS chunks
using dir = tempDir("issue-25398", {
"test.js": `exports.id=1,exports.ids=[1],exports.modules={1:(a,b,c)=>{let k={};({...k,a:k?.x??{}})}};`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "test.js"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should not throw "Expected CommonJS module to have a function wrapper"
expect(stderr).not.toContain("Expected CommonJS module to have a function wrapper");
expect(exitCode).toBe(0);
});
test("object spread with nullish coalescing preserves value in simplification", async () => {
// This specifically tests that the value after ?? is preserved when used
using dir = tempDir("issue-25398-preserve", {
"test.js": `
let result;
const f = () => { let k = {x: null}; result = {...k, a: k?.x ?? {default: true}}; };
f();
console.log(JSON.stringify(result));
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "test.js"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(JSON.parse(stdout.trim())).toEqual({ x: null, a: { default: true } });
expect(exitCode).toBe(0);
});