Files
bun-src/test/bundler/transpiler/assign-to-import.test.ts
2026-08-27 21:09:14 +00:00

105 lines
3.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// Writing to an imported binding is a run-time TypeError per ECMA-262
// (SetMutableBinding on an immutable indirect binding), not an early error.
// The module must load and evaluate; only a reached write throws.
describe("assigning to an imported binding", () => {
const mod = `export let x = 1;\nexport default 2;\n`;
test.concurrent.each([
["simple assignment", `x = 5`],
["update expression", `x++`],
["compound assignment", `x += 1`],
["array destructuring", `[x] = [9]`],
["object destructuring", `({ x } = { x: 9 })`],
["for-of target", `for (x of [9]) {}`],
["default import", `d = 5`],
["namespace import", `ns = 5`],
["computed namespace property", `ns[k] = 5`],
])("is a run-time TypeError: %s", async (_name, write) => {
using dir = tempDir("assign-to-import", {
"m.mjs": mod,
"entry.mjs": `
import d, { x } from "./m.mjs";
import * as ns from "./m.mjs";
const k = "x";
void d; void ns; void k;
function never() { ${write} }
void never;
try { ${write} } catch (e) { console.log("threw:" + e.constructor.name) }
console.log("ran:" + x);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "entry.mjs"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout).toBe("threw:TypeError\nran:1\n");
expect(exitCode).toBe(0);
});
test.concurrent("unreached write does not throw", async () => {
using dir = tempDir("assign-to-import-dead", {
"m.mjs": mod,
"entry.mjs": `
import { x } from "./m.mjs";
if (false) x = 5;
function never() { x = 7 }
void never;
console.log("ran:" + x);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "entry.mjs"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout).toBe("ran:1\n");
expect(exitCode).toBe(0);
});
test.concurrent.each([
["direct binding", `import { x } from "./m.mjs"; x = 5;\n`, 'Cannot assign to import "x"'],
[
"computed namespace property",
`import * as ns from "./m.mjs"; const k = "x"; ns[k] = 5;\n`,
'Cannot assign to property on import "ns"',
],
])("bun build still rejects it: %s", async (_name, entry, diagnostic) => {
using dir = tempDir("assign-to-import-build", {
"m.mjs": mod,
"entry.mjs": entry,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", "entry.mjs"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain(diagnostic);
expect(stdout).toBe("");
expect(exitCode).toBe(1);
});
});