Files
bun.sh/test/regression/issue/comma-operator-this-binding.test.ts
robobun 584946b0ce Fix comma operator optimization to preserve 'this' binding semantics (#21653)
## Summary
- Fix transpiler bug where comma expressions like `(0, obj.method)()`
were incorrectly optimized to `obj.method()`
- This preserved the `this` binding instead of stripping it as per
JavaScript semantics
- Add comprehensive regression test to prevent future issues

## Root Cause
The comma operator optimization in `src/js_parser.zig:7281` was directly
returning the right operand when the left operand had no side effects,
without checking if the expression was being used as a call target.

## Solution
- Added the same `is_call_target` check that other operators (nullish
coalescing, logical OR/AND) use
- When a comma expression is used as a call target AND the right operand
has a value for `this`, preserve the comma expression to strip the
`this` binding
- Follows existing patterns in the codebase for consistent behavior

## Test Plan
- [x] Reproduce the original bug: `(0, obj.method)()` incorrectly
preserved `this`
- [x] Verify fix: comma expressions now correctly strip `this` binding
in function calls
- [x] All existing transpiler tests continue to pass
- [x] Added regression test covering various comma expression scenarios
- [x] Tested edge cases: nested comma expressions, side effects,
different operand types

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-09 05:11:50 -07:00

45 lines
1.1 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("comma operator should strip 'this' binding in function calls", async () => {
const dir = tempDirWithFiles("comma-operator-test", {
"test.js": `
const doThing = () => {};
const cool = {
value: "beans",
logValue() {
console.log(this?.value || "undefined");
}
}
// Direct call - should preserve 'this'
cool.logValue();
// Comma operator calls - should strip 'this'
(0, cool.logValue)();
(doThing(), cool.logValue)();
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: bunEnv,
cwd: dir,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
// Should output: beans, undefined, undefined
const lines = stdout.trim().split("\n");
expect(lines).toHaveLength(3);
expect(lines[0]).toBe("beans");
expect(lines[1]).toBe("undefined");
expect(lines[2]).toBe("undefined");
});