Skip to content

Commit 9337971

Browse files
alexander-akaitclaude
authored andcommitted
fix(javascript): keep a class's name read in its own computed key in the minifier
Claude-Session: https://claude.ai/code/session_01UgQAw35dRJd9GKMF9p5CcQ
1 parent 0c96a66 commit 9337971

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

‎lib/javascript/syntax-printer.js‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16895,6 +16895,63 @@ const installCorrect = (modules) => {
1689516895
!value.generator))
1689616896
);
1689716897
};
16898+
/**
16899+
* Whether a name read is its class's own name, read in that class's
16900+
* heritage or a computed key, where the binding is not yet set.
16901+
* @param {Node} self a name read
16902+
* @param {CompressorShape} compressor the compressor
16903+
* @returns {boolean} true for `x` in `class x { [x] }`
16904+
*/
16905+
const readsUnsetClassName = (self, compressor) => {
16906+
const { definition } = self;
16907+
if (!definition) return false;
16908+
let child = compressor.self();
16909+
let inKey = false;
16910+
for (let i = 0; ; i++) {
16911+
const node = compressor.parent(i);
16912+
if (!node || A.isLambdaNode(node)) return false;
16913+
if (A.isClassNode(node)) {
16914+
return (
16915+
(inKey || child === node.superClass) &&
16916+
Boolean(node.id) &&
16917+
node.id.name === self.name &&
16918+
definition.identifiers.includes(node.id)
16919+
);
16920+
}
16921+
if (node.key === child && node.computed) inKey = true;
16922+
else if (node.key !== undefined) return false;
16923+
child = node;
16924+
}
16925+
};
16926+
for (const wrapAnswer of [ast.wrapHasSideEffects, ast.wrapMayThrow]) {
16927+
wrapAnswer(
16928+
"Identifier",
16929+
(
16930+
/** @type {(self: Node, compressor: CompressorShape) => boolean} */ original
16931+
) =>
16932+
(/** @type {Node} */ self, /** @type {CompressorShape} */ compressor) =>
16933+
original(self, compressor) ||
16934+
(corrections.enabled &&
16935+
A.isSymbolRefNode(self) &&
16936+
readsUnsetClassName(self, compressor))
16937+
);
16938+
}
16939+
ast.wrapDropSideEffectFree(
16940+
"Identifier",
16941+
(
16942+
/** @type {(self: Node, compressor: CompressorShape, firstInStatement?: boolean) => Node | null} */ original
16943+
) =>
16944+
(
16945+
/** @type {Node} */ self,
16946+
/** @type {CompressorShape} */ compressor,
16947+
/** @type {boolean=} */ firstInStatement
16948+
) =>
16949+
corrections.enabled &&
16950+
A.isSymbolRefNode(self) &&
16951+
readsUnsetClassName(self, compressor)
16952+
? self
16953+
: original(self, compressor, firstInStatement)
16954+
);
1689816955
for (const type of ["ClassDeclaration", "ClassExpression"]) {
1689916956
for (const wrapAnswer of [ast.wrapHasSideEffects, ast.wrapMayThrow]) {
1690016957
wrapAnswer(

‎test/unitCases/syntax-printer.unittest.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,11 @@ const CORRECTED_CASES = [
13891389
"function m(v) { v = 2; return arguments[0]; } console.log(m());",
13901390
{ compress: { arguments: true }, mangle: false }
13911391
],
1392+
[
1393+
"a class's name read in its own computed key, before it is set",
1394+
"try { class x { [!{ x }]; } new x(); console.log('no error'); } catch (e) { console.log(e.name); }",
1395+
{ compress: {}, mangle: false }
1396+
],
13921397
[
13931398
"a `const` assigned itself, which throws",
13941399
"try { const x = 0; x = x; console.log('no error'); } catch (e) { console.log(e.name); }",

0 commit comments

Comments
 (0)