From 0e5df9274714aa5c64226ce1448ee8c5c3103d71 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:23:33 +0000 Subject: [PATCH 1/5] js_parser: relocate module-level vars from brace-less bodies for the bundler and the REPL A body of `if`, `else`, `while` or `do` without braces opens no scope, so `maybe_relocate_vars_to_top_level` took a module-level `var` in it for a top-level statement and left it in place. The linker's `__esm` wrapper and the REPL transform move only top-level declarations out of their closure, so the `var` stayed inside: `ReferenceError: root is not defined` in a bundle, and a lost variable in `bun repl`. For those two readers (a bundle that is not for the dev server, and the REPL): - such a `var` becomes an assignment and the top level declares the name (port of evanw/esbuild#4348); - a TypeScript enum in such a body declares its name at the top level; - the head of `for (var i; ...)` without initializer is cleared, as in esbuild. A file that has a top-level `function f` and `var f` keeps its brace-less bodies as they are: a bundle is module code, which rejects that pair. Transpile-only output and dev server output do not change. --- src/js_parser/fold.rs | 3 +- src/js_parser/p.rs | 25 +- src/js_parser/visit/visit_stmt.rs | 3 + test/bundler/bundler_edgecase.test.ts | 332 ++++++++++++++++++ test/bundler/esbuild/extra.test.ts | 31 ++ test/bundler/transpiler/transpiler.test.js | 10 + test/js/bun/repl/repl.test.ts | 32 ++ test/js/bun/transpiler/repl-transform.test.ts | 20 ++ 8 files changed, 454 insertions(+), 2 deletions(-) diff --git a/src/js_parser/fold.rs b/src/js_parser/fold.rs index 1e01b7280c76..71b612826198 100644 --- a/src/js_parser/fold.rs +++ b/src/js_parser/fold.rs @@ -56,7 +56,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ) -> RelocateVars { let p = self; // Only do this when the scope is not already top-level and when we're not inside a function. - if p.current_scope == p.module_scope { + // A brace-less body opens no scope, so there the scope does not say "top-level". + if p.current_scope == p.module_scope && !p.relocates_single_stmt_body_decls() { return RelocateVars { ok: false, ..Default::default() diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index b5795f0a802d..06b21d4bc0b4 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -645,6 +645,7 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> { // The old "var" statements are turned into regular assignments instead. This // makes it easier to quickly scan the top-level statements for "var" locals // with the guarantee that all will be found. + // Not all yet: see `relocates_single_stmt_body_decls`, and a lowered top-level `using` keeps them in its `try` (#38284). pub(crate) relocated_top_level_vars: List<'a, js_ast::LocRef>, // ArrowFunction is a special case in the grammar. Although it appears to be @@ -6890,7 +6891,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O value: None, }]); - if self.current_scope == self.module_scope { + if self.current_scope == self.module_scope && self.relocates_single_stmt_body_decls() { + // `if (c) enum E {}`: `append_part` declares the "var" at the top level. + self.relocated_top_level_vars.push(LocRef { + loc: name_loc, + ref_: name_ref, + }); + } else if self.current_scope == self.module_scope { // Top-level namespace: "var" stmts.push(self.s( S::Local { @@ -8025,6 +8032,22 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O Expr::init_identifier(r#ref, loc) } + /// The linker's `__esm` wrapper and the REPL transform move top-level declarations out of their closure, and only those. + #[inline] + pub(crate) fn lifts_top_level_decls(&self) -> bool { + self.options.repl_mode + || (self.options.bundle && !self.options.features.hot_module_reloading) + } + + /// A brace-less `if`/`else`/`while`/`do` body opens no scope, so `lifts_top_level_decls` needs its declarations relocated too. + #[inline] + pub(crate) fn relocates_single_stmt_body_decls(&self) -> bool { + self.is_inside_single_stmt_body + && self.lifts_top_level_decls() + // A bundle is module code, which rejects a top-level "var f" beside "function f" (#44001). + && !(self.options.bundle && self.has_top_level_function_merged_with_var) + } + // One statement could potentially expand to several statements pub(crate) fn stmts_to_single_stmt( &mut self, diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index f399550709e3..04d4f954625f 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -1909,6 +1909,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ); if let Some(relocated) = relocate.stmt { data.init = Some(relocated); + } else if relocate.ok && p.lifts_top_level_decls() { + // `for (var i; ...)`: a "var" left here declares a second `i` inside the wrapper. + data.init = None; } } } diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 1e421e3b7038..d32456652240 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2101,6 +2101,338 @@ describe("bundler", () => { stdout: "123", }, }); + // A module that `import()` or `require()` loads runs inside `__esm(() => {...})`, and its + // functions and exports are read from outside of it. The body of an `if`, `else`, `while` or + // `do` without braces opens no scope, so a module-level `var` in it must still be declared + // outside the wrapper. Each expected value is what node prints for the source. + itBundled("edgecase/EsmWrapperVarInBracelessBody", { + files: { + "/entry.js": ` + const lib = await import("./lib.js"); + console.log("root.Object is a " + lib.hasRoot() + ", mode is " + lib.getMode()); + `, + "/lib.js": ` + if (typeof window !== "undefined") var root = window; else var root = globalThis; + var mode = "default"; + if (globalThis) var mode = "custom"; + export function hasRoot() { return typeof root.Object; } + export function getMode() { return mode; } + `, + }, + run: { stdout: "root.Object is a function, mode is custom" }, + }); + const varsInBracelessBodies = ` + var n = 1; + if (globalThis) var a = "a"; + if (!globalThis) {} else var b = "b"; + while (n--) var c = "c"; + do var d = "d"; while (0); + if (globalThis) if (globalThis) var e = "e"; + if (!globalThis) {} else if (globalThis) var f = "f"; + if (globalThis) var { g, h: [h] } = { g: "g", h: ["h"] }; + if (globalThis) var i; + i = "i"; + if (!globalThis) var notTaken = "j"; + if (globalThis) var k = "k"; + var readInsideTheWrapper = a + typeof notTaken; + export { k }; + export function get() { + return [a, b, c, d, e, f, g, h, i, typeof notTaken, readInsideTheWrapper].join(" "); + } + `; + const varsInBracelessBodiesOutput = "a b c d e f g h i undefined aundefined k"; + for (const backend of ["cli", "api"] as const) { + itBundled(`edgecase/EsmWrapperVarInBracelessBodyImport/${backend}`, { + files: { + "/entry.js": ` + const lib = await import("./lib.js"); + console.log(lib.get(), lib.k); + `, + "/lib.js": varsInBracelessBodies, + }, + backend, + run: { stdout: varsInBracelessBodiesOutput }, + }); + } + itBundled("edgecase/EsmWrapperVarInBracelessBodyRequire", { + files: { + "/entry.js": ` + const lib = require("./lib.js"); + console.log(lib.get(), lib.k); + `, + "/lib.js": varsInBracelessBodies, + }, + format: "cjs", + run: { stdout: varsInBracelessBodiesOutput }, + }); + itBundled("edgecase/EsmWrapperVarInBracelessBodyMinify", { + files: { + "/entry.js": ` + const lib = await import("./lib.js"); + console.log(lib.get(), lib.k); + `, + "/lib.js": varsInBracelessBodies, + }, + minifySyntax: true, + minifyIdentifiers: true, + minifyWhitespace: true, + run: { stdout: varsInBracelessBodiesOutput }, + }); + itBundled("edgecase/EsmWrapperVarInBracelessBodyOfDependency", { + files: { + "/entry.js": ` + const lazy = await import("./lazy.js"); + console.log(lazy.fromEsm, lazy.fromCjs); + `, + // Both dependencies run inside a wrapper, because their importer does. + "/lazy.js": ` + import { getX } from "./esm.js"; + import { getV } from "./cjs.cjs"; + export const fromEsm = getX(); + export const fromCjs = getV(); + `, + "/esm.js": ` + if (globalThis) var x = "esm"; + export function getX() { return x; } + `, + "/cjs.cjs": ` + if (globalThis) var v = "cjs"; + exports.getV = function () { return v; }; + `, + }, + run: { stdout: "esm cjs" }, + }); + itBundled("edgecase/EsmWrapperVarInBracelessBodyImportCycle", { + files: { + "/entry.js": ` + const a = await import("./a.js"); + console.log(a.beforeTheBody, a.getX()); + `, + "/a.js": ` + import { beforeTheBody } from "./b.js"; + if (globalThis) var x = 1; + export function getX() { return x; } + export { beforeTheBody }; + `, + // Runs before the body of a.js: the "var" is declared and not yet assigned. + "/b.js": ` + import { getX } from "./a.js"; + export const beforeTheBody = typeof getX(); + `, + }, + run: { stdout: "undefined 1" }, + }); + // Pins the "var" of the body without braces only. A plain top-level "var" beside a lowered "using" stays in its try. + itBundled("edgecase/EsmWrapperVarInBracelessBodyWithLoweredUsing", { + files: { + "/entry.js": ` + const lib = await import("./lib.js"); + console.log(lib.get()); + `, + "/lib.js": ` + using resource = { [Symbol.dispose]() {} }; + if (globalThis) var x = "x"; + export function get() { return x; } + `, + }, + run: { stdout: "x" }, + }); + // A bundle is module code, and module code rejects a top-level "var f" beside "function f". + // This "var" keeps its statement, so the bundle loads as it does without the relocation. + itBundled("edgecase/EsmWrapperVarInBracelessBodyWithTheNameOfAFunction", { + files: { + "/entry.js": ` + await import("./script.js"); + console.log(globalThis.typeofF); + `, + "/script.js": ` + function f() {} + if (globalThis) var f = 2; + globalThis.typeofF = typeof f; + `, + }, + run: { stdout: "number" }, + }); + // The statement that follows "for (var i; ...)" reads the "i" of the top level too. + itBundled("edgecase/EsmWrapperVarInForHeadWithoutInitializer", { + files: { + "/entry.js": ` + const lib = await import("./lib.js"); + console.log(lib.getI(), lib.getCount()); + `, + "/lib.js": ` + for (var i; !i;) i = 7; + var count = 0; + for (var j, k; count < 3; count++) j = k; + export function getI() { return i; } + export function getCount() { return count + typeof j; } + `, + }, + run: { stdout: "7 3undefined" }, + }); + itBundled("edgecase/EsmWrapperEnumInBracelessBody", { + files: { + "/entry.ts": ` + const lib = await import("./lib.ts"); + console.log(lib.keys(), lib.getFoo()); + `, + "/lib.ts": ` + import { Foo } from "./types"; + var n = 1; + if (globalThis) enum A { a = 1 } + if (!globalThis) {} else enum B { b = 2 } + while (n--) enum C { c = 3 } + do enum D { d = 4 } while (0); + if (globalThis) var Foo = 5; + export function keys() { return [A, B, C, D].map(e => Object.keys(e).join("")).join(" "); } + export function getFoo() { return Foo; } + `, + "/types.ts": `export type Foo = number;`, + }, + run: { stdout: "1a 2b 3c 4d 5" }, + }); + // A "var" in a body without braces and a top-level "var" of the same name are one variable. + itBundled("edgecase/EsmWrapperVarInBracelessBodySharesTheTopLevelVariable", { + files: { + "/entry.js": ` + const results = {}; + for (const [name, lib] of Object.entries({ + varThenIf: await import("./var-then-if.js"), + ifReadsVar: await import("./if-reads-var.js"), + ifReadsAndResetsVar: await import("./if-reads-and-resets-var.js"), + ifThenExportedPattern: await import("./if-then-exported-pattern.js"), + })) { + results[name] = lib.get(); + } + console.log(JSON.stringify(results)); + `, + "/var-then-if.js": ` + var x = 2; + if (globalThis) var x = 1; + export function get() { return x; } + `, + "/if-reads-var.js": ` + var x = 10; + if (globalThis) var x = x + 1; + export function get() { return x; } + `, + "/if-reads-and-resets-var.js": ` + var count = 10; + if (globalThis) var count = (globalThis.countWas = count, 0); + export function get() { return [globalThis.countWas, count]; } + `, + "/if-then-exported-pattern.js": ` + if (globalThis) var x = 1; + export var { x, z } = { x: 2, z: 9 }, w = x + 1; + export function get() { return [x, z, w]; } + `, + }, + run: { + stdout: JSON.stringify({ + varThenIf: 1, + ifReadsVar: 11, + ifReadsAndResetsVar: [10, 0], + ifThenExportedPattern: [2, 9, 3], + }), + }, + }); + // A branch that never runs still declares its "var": a read gives undefined and does not throw. + const varsInDeadBracelessBodies = ` + if (false) var a = 1; + if (true) {} else var b = 2; + if (process.env.NODE_ENV !== "production") var c = 3; + if (false) if (globalThis) var d = 4; + if (false) var { e, f: [f] } = { e: 5, f: [6] }; + if (false) var g = 7; + g = "assigned"; + export function get() { return [a, b, c, d, e, f, g]; } + `; + for (const minifySyntax of [false, true]) { + itBundled(`edgecase/EsmWrapperVarInDeadBracelessBodyImport${minifySyntax ? "MinifySyntax" : ""}`, { + files: { + "/entry.js": ` + const lib = await import("./lib.js"); + console.log(JSON.stringify(lib.get())); + `, + "/lib.js": varsInDeadBracelessBodies, + }, + define: { "process.env.NODE_ENV": '"production"' }, + minifySyntax, + run: { stdout: '[null,null,null,null,null,null,"assigned"]' }, + }); + itBundled(`edgecase/EsmWrapperVarInDeadBracelessBodyRequire${minifySyntax ? "MinifySyntax" : ""}`, { + files: { + "/entry.js": ` + const lib = require("./lib.js"); + console.log(JSON.stringify(lib.get())); + `, + "/lib.js": varsInDeadBracelessBodies, + }, + define: { "process.env.NODE_ENV": '"production"' }, + format: "cjs", + minifySyntax, + run: { stdout: '[null,null,null,null,null,null,"assigned"]' }, + }); + } + itBundled("edgecase/EsmWrapperVarInBracelessBodySplitting", { + files: { + "/a.js": `console.log("a", require("./lib.js").getX());`, + "/b.js": `console.log("b", require("./lib.js").getX());`, + "/lib.js": ` + if (globalThis) var x = 1; + export function getX() { return x; } + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + target: "node", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a 1" }, + { file: "/out/b.js", stdout: "b 1" }, + ], + }); + // Without a wrapper the same relocation runs, and the program does not change. + for (const minify of [false, true]) { + itBundled(`edgecase/VarInBracelessBodyWithoutWrapper${minify ? "Minify" : ""}`, { + files: { + "/entry.js": ` + import { get } from "./lib.js"; + console.log(get()); + `, + "/lib.js": ` + if (globalThis) var a = "a"; + for (var i; !i;) i = 7; + export function get() { return a + i; } + `, + }, + minifySyntax: minify, + minifyIdentifiers: minify, + minifyWhitespace: minify, + onAfterBundle(api) { + const output = api.readFile("/out.js"); + expect(output).not.toContain("__esm"); + if (!minify) expect(output).toContain("for (;!i; )"); + }, + run: { stdout: "a7" }, + }); + } + // The dev server runs each module as a function and lifts nothing out of it: its output keeps these declarations. + itBundled("edgecase/VarInBracelessBodyDevServerFormat", { + format: "internal_bake_dev", + files: { + "/entry.js": ` + if (globalThis) var inIf = 1; + for (var inFor; !inFor;) inFor = 2; + console.log(inIf, inFor); + `, + }, + onAfterBundle(api) { + const output = api.readFile("/out.js"); + expect(output).toContain("var inIf = 1"); + expect(output).toContain("for (var inFor;"); + }, + }); itBundled("edgecase/TsEnumTreeShakingUseAndInlineClass", { files: { "/entry.ts": ` diff --git a/test/bundler/esbuild/extra.test.ts b/test/bundler/esbuild/extra.test.ts index 7cd5621a0b63..3d9ecf7d395f 100644 --- a/test/bundler/esbuild/extra.test.ts +++ b/test/bundler/esbuild/extra.test.ts @@ -1245,6 +1245,37 @@ describe("bundler", () => { }, run: true, }); + // The same for a "var" in an "if", "else", "while" or "do" body without braces: + // https://github.com/evanw/esbuild/issues/4348 + for (const minify of [false, true]) { + itBundled(`extra/NestedVarHoistingESBuildIssue4348${minify ? "Minify" : ""}`, { + files: { + "in.js": ` + var foo = Object.entries({ ...require('./foo') }).join('|') + if (foo !== 'a,a|b,b|c,c|d,d|e,e|f,f|g,g|h,h|i,i|j,j') throw 'fail: ' + foo + `, + "foo.js": ` + var a = 'a' + for (var b = 'b'; 0; ) ; + if (true) { var c = 'c' } + if (true) var d = 'd' + if (false) {} else var e = 'e' + var x = 1 + while (x--) var f = 'f' + do var g = 'g'; while (0); + for (; x++; ) var h = 'h' + for (var y in 'y') var i = 'i' + for (var y of 'y') var j = 'j' + export { a, b, c, d, e, f, g, h, i, j } + `, + }, + format: "cjs", + minifySyntax: minify, + minifyIdentifiers: minify, + minifyWhitespace: minify, + run: true, + }); + } // Test tree shaking itBundled(`extra/TreeShaking1`, { diff --git a/test/bundler/transpiler/transpiler.test.js b/test/bundler/transpiler/transpiler.test.js index 37cf46afb05c..7b068321f64e 100644 --- a/test/bundler/transpiler/transpiler.test.js +++ b/test/bundler/transpiler/transpiler.test.js @@ -3686,6 +3686,16 @@ console.log(resolve.length) expectPrintedNoTrim("for (const {a} of y) ;", "for (const { a } of y)\n ;\n"); }); + // Only the bundler and the REPL relocate these: this output is what the runtime transpiler cache stores. + it("a var in a for head or in a body without braces keeps its place", () => { + expectPrintedNoTrim("for (var x;;) ;", "for (var x;; )\n ;\nvar x;\n"); + expectPrintedNoTrim("for (var j, k;;) break;", "for (var j, k;; )\n break;\nvar j;\nvar k;\n"); + expectPrintedNoTrim("if (x) var a = 1;", "if (x)\n var a = 1;\n"); + expect(ts.transpiledOutput("if (x) enum E { A = 1 }")).toBe( + 'if (x) {\n var E;\n ((E) => {\n E[E["A"] = 1] = "A";\n })(E ||= {});\n}\n', + ); + }); + it("regexp", () => { expectPrinted("/x/g", "/x/g"); expectPrinted("/x/i", "/x/i"); diff --git a/test/js/bun/repl/repl.test.ts b/test/js/bun/repl/repl.test.ts index 5947a8d30a3f..c8cda7599ca3 100644 --- a/test/js/bun/repl/repl.test.ts +++ b/test/js/bun/repl/repl.test.ts @@ -741,6 +741,38 @@ describe.concurrent("Bun REPL", () => { expect(exitCode).toBe(0); }); + test("a var in a body without braces persists across evaluations", async () => { + const { outputs, stderr, exitCode } = await runRepl([ + "if (true) var b = 2", + "b", + "var a = 1", + "if (true) var a = 2", + "a", + "var i = 0; while (i < 2) var w = i++", + "w", + "if (false) var notTaken = 1", + "typeof notTaken", + "for (var k; !k;) k = 7", + "k", + ".exit", + ]); + expect(outputs).toEqual([ + "undefined", + "2", + "1", + "undefined", + "2", + "undefined", + "1", + "undefined", + '"undefined"', + "undefined", + "7", + ]); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); + }); + test("destructured declarations persist across evaluations", async () => { const { outputs, stderr, exitCode } = await runRepl([ "const [a, b, c] = [10, 20, 30]", diff --git a/test/js/bun/transpiler/repl-transform.test.ts b/test/js/bun/transpiler/repl-transform.test.ts index 4f811b4762e3..b23eefc97aef 100644 --- a/test/js/bun/transpiler/repl-transform.test.ts +++ b/test/js/bun/transpiler/repl-transform.test.ts @@ -150,6 +150,26 @@ describe("Bun.Transpiler replMode", () => { expect(context.x).toBe(20); }); + test("var in a body without braces, and in a for head without initializer, persists", async () => { + const { results } = await runReplSession([ + "if (true) var a = 1", + "if (false) {} else var b = 2", + "var n = 1; while (n--) var c = 3", + "do var d = 4; while (0)", + "if (false) var notTaken = 5", + "for (var e; !e;) e = 6", + "[a, b, c, d, typeof notTaken, e]", + ]); + + expect(results.at(-1)).toEqual([1, 2, 3, 4, "undefined", 6]); + }); + + test("enum in a body without braces persists", async () => { + const { results } = await runReplSession(["if (true) enum Color { Red = 1 }", "Color.Red"]); + + expect(results[1]).toBe(1); + }); + test("let persists with await", async () => { const { results } = await runReplSession(["let y = await Promise.resolve(100)", "y * 2"]); From ac0555573ac6fd6152dcb7f35d476dd33e8f5bad Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:24:43 +0000 Subject: [PATCH 2/5] Keep the stores of a relocated brace-less var inside the __esm wrapper The linker lifts a top-level `var x = ` above the `__esm` wrapper. After the relocation of a `var` from a body without braces, or from a `for` head without initializer, the wrapper stores into the same top-level variable, so the lifted store ran first: `if (c) var x = "body"; var x = 2;` gave "body" to a reader outside. The parser marks such a `var` on the root of its symbol (`VAR_STORES_STAY_IN_WRAPPER`). The linker then declares the name outside and keeps every store of it in the wrapper, in source order, and `needs_wrapper_ref` counts such a store. `SymbolFlags` grows to `u16`; `Symbol` stays 48 bytes. --- src/ast/symbol.rs | 6 +- .../generateCodeForFileInChunkJS.rs | 10 +- src/js_parser/fold.rs | 4 + src/js_parser/p.rs | 33 ++++- src/js_parser/visit/visit_stmt.rs | 2 + test/bundler/bundler_edgecase.test.ts | 113 +++++++++++------- 6 files changed, 117 insertions(+), 51 deletions(-) diff --git a/src/ast/symbol.rs b/src/ast/symbol.rs index 0baff6d2b7b0..e60d9ac6d6e3 100644 --- a/src/ast/symbol.rs +++ b/src/ast/symbol.rs @@ -108,7 +108,7 @@ pub struct Symbol { bitflags::bitflags! { #[derive(Copy, Clone, Eq, PartialEq, Default, Debug)] - pub struct SymbolFlags: u8 { + pub struct SymbolFlags: u16 { const MUST_START_WITH_CAPITAL_LETTER_FOR_JSX = 1 << 1; /// Certain symbols must not be renamed or minified. For example, the @@ -131,6 +131,9 @@ bitflags::bitflags! { /// A call of this function declaration or lifted export ignores `this`. const CALL_IGNORES_THIS = 1 << 7; + + /// The `__esm` wrapper stores into this `var`, so no store of it is lifted above the wrapper. Set on the root of the link chain. + const VAR_STORES_STAY_IN_WRAPPER = 1 << 8; } } @@ -159,6 +162,7 @@ symbol_flag_accessors! { has_been_assigned_to, set_has_been_assigned_to => HAS_BEEN_ASSIGNED_TO; called_as_method, set_called_as_method => CALLED_AS_METHOD; call_ignores_this, set_call_ignores_this => CALL_IGNORES_THIS; + var_stores_stay_in_wrapper, set_var_stores_stay_in_wrapper => VAR_STORES_STAY_IN_WRAPPER; } const _: () = assert!(core::mem::size_of::>>() == 8); diff --git a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs index f7ba8e07ca3c..36a9fd6d01a8 100644 --- a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs +++ b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs @@ -677,6 +677,13 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( Expr::init_identifier(ref_, loc) } + fn var_stores_stay_in_wrapper(&self, ref_: Ref) -> bool { + let symbols = self.symbols.get(); + symbols + .get_const(symbols.follow(ref_)) + .is_some_and(|symbol| symbol.var_stores_stay_in_wrapper()) + } + /// The pattern without the locals the linker bound to exports /// (`const { a } = await import(…)`): assigning one would /// overwrite the export. @@ -762,7 +769,8 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( if let Some(initializer) = decl.value { // Keep in sync with `needs_wrapper_ref` in the parser. if initializer.can_be_moved() - && matches!(decl.binding.data, B::B::BIdentifier(_)) + && let B::B::BIdentifier(id) = decl.binding.data + && !hoist.var_stores_stay_in_wrapper(id.get().r#ref) { // ie `const main = class { static main() {} }` => `var main = class { static main() {} }` hoist.decls.push(G::Decl { diff --git a/src/js_parser/fold.rs b/src/js_parser/fold.rs index 71b612826198..d11a352e48cd 100644 --- a/src/js_parser/fold.rs +++ b/src/js_parser/fold.rs @@ -80,6 +80,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O }; } + let first_relocated = p.relocated_top_level_vars.len(); let mut value: Expr = Expr::EMPTY; for decl in decls { // Derive `*mut P` from the live `&mut Self` so the trampoline's @@ -93,6 +94,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O value = Expr::join_with_comma(value, binding); } } + if p.current_scope == p.module_scope { + p.keep_var_stores_in_wrapper(first_relocated); + } if matches!(value.data, js_ast::ExprData::EMissing(_)) { return RelocateVars { diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 06b21d4bc0b4..133e230a5728 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -8048,6 +8048,27 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O && !(self.options.bundle && self.has_top_level_function_merged_with_var) } + /// The wrapper now stores into `relocated_top_level_vars[first..]`, and a store that the linker lifts above the wrapper would run first. + pub(crate) fn keep_var_stores_in_wrapper(&mut self, first: usize) { + if self.is_control_flow_dead { + return; + } + for i in first..self.relocated_top_level_vars.len() { + let root = self.follow_symbol_links(self.relocated_top_level_vars[i].ref_); + self.symbols[root.inner_index() as usize].set_var_stores_stay_in_wrapper(true); + } + } + + fn follow_symbol_links(&self, mut r#ref: Ref) -> Ref { + loop { + let symbol = &self.symbols[r#ref.inner_index() as usize]; + if !symbol.has_link() { + return r#ref; + } + r#ref = symbol.link.get(); + } + } + // One statement could potentially expand to several statements pub(crate) fn stmts_to_single_stmt( &mut self, @@ -9536,10 +9557,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // The linker keeps a pattern inside the wrapper. if !matches!(value.data, js_ast::ExprData::EMissing(_)) && (!value.can_be_moved() - || !matches!( - decl.binding.data, - js_ast::b::B::BIdentifier(_) - )) + || match decl.binding.data { + js_ast::b::B::BIdentifier(id) => self.symbols[self + .follow_symbol_links(id.r#ref) + .inner_index() + as usize] + .var_stores_stay_in_wrapper(), + _ => true, + }) { return true; } diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index 04d4f954625f..38f3133072cc 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -1903,6 +1903,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Potentially relocate "var" declarations to the top level. Note that this // must be done inside the scope of the for loop or they won't be relocated. if local.kind == S::Kind::KVar { + let first_relocated = p.relocated_top_level_vars.len(); let relocate = p.maybe_relocate_vars_to_top_level( local.decls.slice(), RelocateVarsMode::Normal, @@ -1912,6 +1913,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } else if relocate.ok && p.lifts_top_level_decls() { // `for (var i; ...)`: a "var" left here declares a second `i` inside the wrapper. data.init = None; + p.keep_var_stores_in_wrapper(first_relocated); } } } diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index d32456652240..27579b6c6927 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2291,51 +2291,74 @@ describe("bundler", () => { }, run: { stdout: "1a 2b 3c 4d 5" }, }); - // A "var" in a body without braces and a top-level "var" of the same name are one variable. - itBundled("edgecase/EsmWrapperVarInBracelessBodySharesTheTopLevelVariable", { - files: { - "/entry.js": ` - const results = {}; - for (const [name, lib] of Object.entries({ - varThenIf: await import("./var-then-if.js"), - ifReadsVar: await import("./if-reads-var.js"), - ifReadsAndResetsVar: await import("./if-reads-and-resets-var.js"), - ifThenExportedPattern: await import("./if-then-exported-pattern.js"), - })) { - results[name] = lib.get(); - } - console.log(JSON.stringify(results)); - `, - "/var-then-if.js": ` - var x = 2; - if (globalThis) var x = 1; - export function get() { return x; } - `, - "/if-reads-var.js": ` - var x = 10; - if (globalThis) var x = x + 1; - export function get() { return x; } - `, - "/if-reads-and-resets-var.js": ` - var count = 10; - if (globalThis) var count = (globalThis.countWas = count, 0); - export function get() { return [globalThis.countWas, count]; } - `, - "/if-then-exported-pattern.js": ` - if (globalThis) var x = 1; - export var { x, z } = { x: 2, z: 9 }, w = x + 1; - export function get() { return [x, z, w]; } - `, - }, - run: { - stdout: JSON.stringify({ - varThenIf: 1, - ifReadsVar: 11, - ifReadsAndResetsVar: [10, 0], - ifThenExportedPattern: [2, 9, 3], - }), - }, - }); + // A "var" in a body without braces and a top-level "var" of the same name are one variable, so the + // wrapper makes every store of that name, in source order. The linker lifts no store of it above the wrapper. + const storeOrderModules: Record = { + "if-then-var": [`if (globalThis) var x = "body"; var x = 2; export function get() { return x; }`, 2], + "else-then-var": [`if (!globalThis) {} else var x = "body"; var x = 2; export function get() { return x; }`, 2], + "while-then-var": [`var n = 1; while (n--) var x = "body"; var x = 2; export function get() { return x; }`, 2], + "do-then-var": [`do var x = "body"; while (0); var x = 2; export function get() { return x; }`, 2], + "if-then-function": [ + `if (globalThis) var x = "body"; var x = function () {}; export function get() { return typeof x; }`, + "function", + ], + "if-then-arrow": [ + `if (globalThis) var x = "body"; var x = () => {}; export function get() { return typeof x; }`, + "function", + ], + "if-then-class": [ + `if (globalThis) var x = "body"; var x = class {}; export function get() { return typeof x; }`, + "function", + ], + "if-then-export-var": [ + `if (globalThis) var x = "body"; export var x = null; export function get() { return x; }`, + null, + ], + "if-then-two-vars": [ + `if (globalThis) var x = "body"; var x = 2, y = 3; var y = 4; export function get() { return [x, y]; }`, + [2, 4], + ], + "if-then-store-then-var": [`if (globalThis) var x; x = "store"; var x = 2; export function get() { return x; }`, 2], + "if-without-value-then-var": [`if (globalThis) var x; var x = 2; export function get() { return x; }`, 2], + "for-then-var": [`for (var x; !x; ) x = "body"; var x = 0; export function get() { return x; }`, 0], + "read-then-var-then-if": [ + `globalThis.typeofXBeforeItsVar = typeof x; var x = 2; if (globalThis) var x = "body"; + export function get() { return [globalThis.typeofXBeforeItsVar, x]; }`, + ["undefined", "body"], + ], + "var-then-if": [`var x = 2; if (globalThis) var x = "body"; export function get() { return x; }`, "body"], + "if-reads-var": [`var x = 10; if (globalThis) var x = x + 1; export function get() { return x; }`, 11], + "if-reads-and-resets-var": [ + `var count = 10; if (globalThis) var count = (globalThis.countWas = count, 0); + export function get() { return [globalThis.countWas, count]; }`, + [10, 0], + ], + "if-then-exported-pattern": [ + `if (globalThis) var x = 1; export var { x, z } = { x: 2, z: 9 }, w = x + 1; + export function get() { return [x, z, w]; }`, + [2, 9, 3], + ], + }; + for (const minifySyntax of [false, true]) { + itBundled(`edgecase/EsmWrapperVarInBracelessBodyStoreOrder${minifySyntax ? "MinifySyntax" : ""}`, { + files: { + "/entry.js": ` + const results = {}; + ${Object.keys(storeOrderModules) + .map(name => `results[${JSON.stringify(name)}] = (await import("./${name}.js")).get();`) + .join("\n")} + console.log(JSON.stringify(results)); + `, + ...Object.fromEntries(Object.entries(storeOrderModules).map(([name, [source]]) => [`/${name}.js`, source])), + }, + minifySyntax, + run: { + stdout: JSON.stringify( + Object.fromEntries(Object.entries(storeOrderModules).map(([name, [, expected]]) => [name, expected])), + ), + }, + }); + } // A branch that never runs still declares its "var": a read gives undefined and does not throw. const varsInDeadBracelessBodies = ` if (false) var a = 1; From 76f5a6ddd88ea4c0fda167c064e714ea32e57aad Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:04:16 +0000 Subject: [PATCH 3/5] Mark a relocated brace-less var in dead code too, and keep its Fast Refresh registration A `var` in a dead brace-less body is relocated as well, so the wrapper's other reads and stores of the name reach the top-level variable. Its later top-level store must stay in the wrapper too: `if (false) var x; x = "store"; var x = 2;` gave "store". With `--react-fast-refresh`, a component declared by a brace-less module-level `var` is registered again after its relocation. The registration loop of `s_local` moves into `register_react_refresh_decls`. --- src/js_parser/p.rs | 5 +- src/js_parser/visit/visit_stmt.rs | 78 +++++++++++-------- test/bundler/bundler_edgecase.test.ts | 29 ++++++- test/js/bun/transpiler/repl-transform.test.ts | 6 ++ 4 files changed, 79 insertions(+), 39 deletions(-) diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 133e230a5728..8c5028f2cdab 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -8048,11 +8048,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O && !(self.options.bundle && self.has_top_level_function_merged_with_var) } - /// The wrapper now stores into `relocated_top_level_vars[first..]`, and a store that the linker lifts above the wrapper would run first. + /// The wrapper now reads and stores `relocated_top_level_vars[first..]`, so a store that the linker lifts above it would run first. pub(crate) fn keep_var_stores_in_wrapper(&mut self, first: usize) { - if self.is_control_flow_dead { - return; - } for i in first..self.relocated_top_level_vars.len() { let root = self.follow_symbol_links(self.relocated_top_level_vars[i].ref_); self.symbols[root.inner_index() as usize].set_var_stores_stay_in_wrapper(true); diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index 38f3133072cc..43b15ab7be29 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -1206,6 +1206,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if let Some(new_stmt) = relocated.stmt { stmts.push(new_stmt); } + if p.options.features.react_fast_refresh && p.current_scope == p.module_scope { + Self::register_react_refresh_decls(p, stmts, data.decls.slice())?; + } return Ok(()); } @@ -1216,39 +1219,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O stmts.push(*stmt); if p.options.features.react_fast_refresh && p.current_scope == p.module_scope { - for decl in data.decls.slice() { - 'try_register: { - let Some(val) = decl.value else { - break 'try_register; - }; - match val.data { - // Assigning a component to a local. - js_ast::ExprData::EArrow(_) | js_ast::ExprData::EFunction(_) => {} - - // A wrapped component. - js_ast::ExprData::ECall(call) => match call.target.data { - js_ast::ExprData::EIdentifier(id) => { - if id.ref_ != p.react_refresh.latest_signature_ref { - break 'try_register; - } - } - _ => break 'try_register, - }, - _ => break 'try_register, - } - let id = match decl.binding.data { - js_ast::binding::Data::BIdentifier(b) => b.r#ref, - _ => break 'try_register, - }; - let original_name = p.symbols[id.inner_index() as usize].original_name.slice(); - p.handle_react_refresh_register( - stmts, - original_name, - id, - ReactRefreshExportKind::Named, - )?; - } - } + Self::register_react_refresh_decls(p, stmts, data.decls.slice())?; } if data.is_export && p.options.features.server_components.wraps_exports() { @@ -1271,6 +1242,47 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O Ok(()) } + fn register_react_refresh_decls( + p: &mut Self, + stmts: &mut StmtList<'a>, + decls: &[G::Decl], + ) -> Result<(), Error> { + for decl in decls { + 'try_register: { + let Some(val) = decl.value else { + break 'try_register; + }; + match val.data { + // Assigning a component to a local. + js_ast::ExprData::EArrow(_) | js_ast::ExprData::EFunction(_) => {} + + // A wrapped component. + js_ast::ExprData::ECall(call) => match call.target.data { + js_ast::ExprData::EIdentifier(id) => { + if id.ref_ != p.react_refresh.latest_signature_ref { + break 'try_register; + } + } + _ => break 'try_register, + }, + _ => break 'try_register, + } + let id = match decl.binding.data { + js_ast::binding::Data::BIdentifier(b) => b.r#ref, + _ => break 'try_register, + }; + let original_name = p.symbols[id.inner_index() as usize].original_name.slice(); + p.handle_react_refresh_register( + stmts, + original_name, + id, + ReactRefreshExportKind::Named, + )?; + } + } + Ok(()) + } + // ─── control-flow / scope visitors ────────────────────────────────────── fn s_break( diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 27579b6c6927..3716b1c5a9b2 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2258,7 +2258,7 @@ describe("bundler", () => { files: { "/entry.js": ` const lib = await import("./lib.js"); - console.log(lib.getI(), lib.getCount()); + console.log(lib.getI(), lib.getCount(), lib.inAFunction()); `, "/lib.js": ` for (var i; !i;) i = 7; @@ -2266,9 +2266,10 @@ describe("bundler", () => { for (var j, k; count < 3; count++) j = k; export function getI() { return i; } export function getCount() { return count + typeof j; } + export function inAFunction() { for (var n; !n;) n = 3; return n; } `, }, - run: { stdout: "7 3undefined" }, + run: { stdout: "7 3undefined 3" }, }); itBundled("edgecase/EsmWrapperEnumInBracelessBody", { files: { @@ -2326,7 +2327,18 @@ describe("bundler", () => { export function get() { return [globalThis.typeofXBeforeItsVar, x]; }`, ["undefined", "body"], ], + "dead-if-then-store-then-var": [`if (false) var x; x = "store"; var x = 2; export function get() { return x; }`, 2], + "dead-for-then-store-then-var": [ + `if (false) for (var x; ; ) break; x = "store"; var x = 2; export function get() { return x; }`, + 2, + ], + "read-then-dead-if-then-var": [ + `globalThis.typeofXBeforeItsDeadVar = typeof x; if (false) var x = 1; var x = 2; + export function get() { return [globalThis.typeofXBeforeItsDeadVar, x]; }`, + ["undefined", 2], + ], "var-then-if": [`var x = 2; if (globalThis) var x = "body"; export function get() { return x; }`, "body"], + "var-then-if-without-value": [`var x = 2; if (globalThis) var x; export function get() { return x; }`, 2], "if-reads-var": [`var x = 10; if (globalThis) var x = x + 1; export function get() { return x; }`, 11], "if-reads-and-resets-var": [ `var count = 10; if (globalThis) var count = (globalThis.countWas = count, 0); @@ -2456,6 +2468,19 @@ describe("bundler", () => { expect(output).toContain("for (var inFor;"); }, }); + itBundled("edgecase/VarInBracelessBodyIsRegisteredForFastRefresh", { + files: { + "/entry.jsx": ` + if (globalThis) var InIf = () => null; + export { InIf }; + `, + }, + external: ["react-refresh/runtime"], + reactFastRefresh: true, + onAfterBundle(api) { + expect(api.readFile("/out.js")).toContain('$RefreshReg$(InIf, "entry.jsx:InIf")'); + }, + }); itBundled("edgecase/TsEnumTreeShakingUseAndInlineClass", { files: { "/entry.ts": ` diff --git a/test/js/bun/transpiler/repl-transform.test.ts b/test/js/bun/transpiler/repl-transform.test.ts index b23eefc97aef..d15a294065e4 100644 --- a/test/js/bun/transpiler/repl-transform.test.ts +++ b/test/js/bun/transpiler/repl-transform.test.ts @@ -164,6 +164,12 @@ describe("Bun.Transpiler replMode", () => { expect(results.at(-1)).toEqual([1, 2, 3, 4, "undefined", 6]); }); + test("var in a body without braces persists beside a function and a var of one name", async () => { + const { results } = await runReplSession(["function f() {} var f; if (true) var g = 5;", "typeof g"]); + + expect(results[1]).toBe("number"); + }); + test("enum in a body without braces persists", async () => { const { results } = await runReplSession(["if (true) enum Color { Red = 1 }", "Color.Red"]); From c8f8598a14814ded306e3a8452e011656f1ac109 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 21 Oct 2026 20:28:31 +0000 Subject: [PATCH 4/5] Follow symbol links with symbol::follow_symbols for the relocated var mark --- src/js_parser/p.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 04b6c6322aac..ad197d394a5f 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -8042,21 +8042,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O /// The wrapper now reads and stores `relocated_top_level_vars[first..]`, so a store that the linker lifts above it would run first. pub(crate) fn keep_var_stores_in_wrapper(&mut self, first: usize) { for i in first..self.relocated_top_level_vars.len() { - let root = self.follow_symbol_links(self.relocated_top_level_vars[i].ref_); + let root = js_ast::symbol::follow_symbols( + &self.symbols, + self.relocated_top_level_vars[i].ref_, + ); self.symbols[root.inner_index() as usize].set_var_stores_stay_in_wrapper(true); } } - fn follow_symbol_links(&self, mut r#ref: Ref) -> Ref { - loop { - let symbol = &self.symbols[r#ref.inner_index() as usize]; - if !symbol.has_link() { - return r#ref; - } - r#ref = symbol.link.get(); - } - } - // One statement could potentially expand to several statements pub(crate) fn stmts_to_single_stmt( &mut self, @@ -9545,11 +9538,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if !matches!(value.data, js_ast::ExprData::EMissing(_)) && (!value.can_be_moved() || match decl.binding.data { - js_ast::b::B::BIdentifier(id) => self.symbols[self - .follow_symbol_links(id.r#ref) - .inner_index() - as usize] - .var_stores_stay_in_wrapper(), + js_ast::b::B::BIdentifier(id) => { + let root = js_ast::symbol::follow_symbols( + &self.symbols, + id.r#ref, + ); + self.symbols[root.inner_index() as usize] + .var_stores_stay_in_wrapper() + } _ => true, }) { From 117deac93dddc887d1acc6b0b14eaf6c6639ceca Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:36:04 +0000 Subject: [PATCH 5/5] test: tell undefined from null in the dead brace-less body cases --- test/bundler/bundler_edgecase.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 17b382d87117..d72b778411ae 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2487,7 +2487,7 @@ describe("bundler", () => { if (false) var { e, f: [f] } = { e: 5, f: [6] }; if (false) var g = 7; g = "assigned"; - export function get() { return [a, b, c, d, e, f, g]; } + export function get() { return [a, b, c, d, e, f, g].map(String); } `; for (const minifySyntax of [false, true]) { itBundled(`edgecase/EsmWrapperVarInDeadBracelessBodyImport${minifySyntax ? "MinifySyntax" : ""}`, { @@ -2500,7 +2500,7 @@ describe("bundler", () => { }, define: { "process.env.NODE_ENV": '"production"' }, minifySyntax, - run: { stdout: '[null,null,null,null,null,null,"assigned"]' }, + run: { stdout: '["undefined","undefined","undefined","undefined","undefined","undefined","assigned"]' }, }); itBundled(`edgecase/EsmWrapperVarInDeadBracelessBodyRequire${minifySyntax ? "MinifySyntax" : ""}`, { files: { @@ -2513,7 +2513,7 @@ describe("bundler", () => { define: { "process.env.NODE_ENV": '"production"' }, format: "cjs", minifySyntax, - run: { stdout: '[null,null,null,null,null,null,"assigned"]' }, + run: { stdout: '["undefined","undefined","undefined","undefined","undefined","undefined","assigned"]' }, }); } itBundled("edgecase/EsmWrapperVarInBracelessBodySplitting", {