Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
673c7e8f52 fix(bundler): allow define to replace optional chaining expressions
The `isDotDefineMatch` function was rejecting any expression with optional
chaining (`?.`), preventing defines from matching expressions like
`process.env?.NODE_ENV` or `process?.env?.NODE_ENV`. This removes the
`optional_chain != null` early-return checks, matching esbuild's behavior
where define matching is purely structural and ignores optional chain state.

Closes #7106

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-19 09:31:30 +00:00
2 changed files with 63 additions and 8 deletions

View File

@@ -5455,10 +5455,6 @@ pub fn NewParser_(
switch (expr.data) {
.e_dot => |ex| {
if (parts.len > 1) {
if (ex.optional_chain != null) {
return false;
}
// Intermediates must be dot expressions
const last = parts.len - 1;
const is_tail_match = strings.eql(parts[last], ex.name);
@@ -5474,10 +5470,6 @@ pub fn NewParser_(
// the intent is to handle people using this form instead of E.Dot. So we really only want to do this if the accessor can also be an identifier
.e_index => |index| {
if (parts.len > 1 and index.index.data == .e_string and index.index.data.e_string.isUTF8()) {
if (index.optional_chain != null) {
return false;
}
const last = parts.len - 1;
const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator));
return is_tail_match and p.isDotDefineMatch(index.target, parts[0..last]);

View File

@@ -0,0 +1,63 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import { join } from "path";
test("define should work with optional chaining expressions", async () => {
using dir = tempDir("issue-7106", {
"input.js": `console.log(process.env.NODE_ENV);
console.log(process.env?.NODE_ENV);
console.log(process?.env?.NODE_ENV);
console.log(process?.env.NODE_ENV);`,
});
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--transform",
"--define",
'process.env.NODE_ENV="production"',
join(String(dir), "input.js"),
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
// All optional chaining variants should be replaced by the define value
expect(stdout).toContain('"production"');
expect(stdout).not.toContain("process.env?.NODE_ENV");
expect(stdout).not.toContain("process?.env?.NODE_ENV");
expect(stdout).not.toContain("process?.env.NODE_ENV");
expect(exitCode).toBe(0);
});
test("define should work with bracket notation and optional chaining", async () => {
using dir = tempDir("issue-7106-2", {
"input.js": `console.log(a.b.c);
console.log(a?.b.c);
console.log(a.b?.c);
console.log(a?.b?.c);`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", "--transform", "--define", 'a.b.c="replaced"', join(String(dir), "input.js")],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
// All variants should be replaced
expect(stdout).not.toContain("a.b.c");
expect(stdout).not.toContain("a?.b.c");
expect(stdout).not.toContain("a.b?.c");
expect(stdout).not.toContain("a?.b?.c");
expect(exitCode).toBe(0);
});