diff --git a/src/renamer.zig b/src/renamer.zig index 09ef18e3b6..82e7254cd5 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -272,13 +272,14 @@ pub const MinifyRenamer = struct { var slots = this.slots.getPtr(ns); sorted.clearRetainingCapacity(); try sorted.ensureUnusedCapacity(slots.items.len); - sorted.items.len = slots.items.len; - for (sorted.items, slots.items, 0..) |*elem, slot, i| { - elem.* = SlotAndCount{ + for (slots.items, 0..) |slot, i| { + // Skip symbols with zero use count - they're never used and don't need a minified name + if (slot.count == 0) continue; + sorted.appendAssumeCapacity(SlotAndCount{ .slot = @as(u32, @intCast(i)), .count = slot.count, - }; + }); } std.sort.pdq(SlotAndCount, sorted.items, {}, SlotAndCount.lessThan); diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index 0865d9a372..720d9018b7 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -1135,4 +1135,36 @@ describe("bundler", () => { ); }, }); + // Regression test for: https://github.com/oxc-project/oxc/pull/18183 + // When symbols have zero use count, we skip them during renaming. + // This tests that when nested scopes are tree-shaken, their pre-allocated + // symbol slots (which have count=0) are skipped during name assignment. + itBundled("minify/SkipUnusedSymbolSlots", { + files: { + "/entry.js": /* js */ ` + // Create nested scopes with many symbols that will be tree-shaken + function unused() { + var a = 1, b = 2, c = 3; + function inner() { + var x = 1, y = 2, z = 3, w = 4, v = 5; + return x + y + z + w + v; + } + return a + b + c + inner(); + } + // This function is used and should get short minified names + function used() { return 42; } + console.log(used()); + `, + }, + minifyIdentifiers: true, + minifyWhitespace: true, + minifySyntax: true, + onAfterBundle(api) { + const code = api.readFile("/out.js").trim(); + // The unused function and its nested scopes are tree-shaken. + // The optimization ensures we don't waste time generating names for + // the pre-allocated slots that were never used. + expect(code).toMatchInlineSnapshot(`"function j(){return 42}console.log(j());"`); + }, + }); });