diff --git a/packages/compiler/src/node-islands/effect-gate.test.ts b/packages/compiler/src/node-islands/effect-gate.test.ts index 3e3e16fbe..d8c5a19af 100644 --- a/packages/compiler/src/node-islands/effect-gate.test.ts +++ b/packages/compiler/src/node-islands/effect-gate.test.ts @@ -79,6 +79,12 @@ const v = C.x;`, "a read through a class expression's static getter", `const C = class { static get x() { return 1; } }; const v = C.x;`, + ], + [ + "a read through an alias of a class with a static getter", + `class C { static get x() { return require("fs").readFileSync("/etc/hostname"); } } +const D = (C); +const v = D.x;`, ], [ "a computed key that converts an object", diff --git a/packages/compiler/src/node-islands/effect-gate.ts b/packages/compiler/src/node-islands/effect-gate.ts index d6a1a7d2a..6515424a1 100644 --- a/packages/compiler/src/node-islands/effect-gate.ts +++ b/packages/compiler/src/node-islands/effect-gate.ts @@ -280,6 +280,28 @@ function checkFile(parser: typeof ts, file: string, load: (file: string) => void else if (nb) for (const el of nb.elements) locals.set(el.name.text, "module"); } } + // Aliasing a class (`const B = A`) gives another name for the same + // static members, so a read through the alias runs the same accessor. + // Propagate to a fixed point: an alias chain can be any length, and a + // class binding is initialized before any alias of it (its TDZ). + const aliases: [string, string][] = []; + for (const stmt of sf.statements) { + if (!parser.isVariableStatement(stmt)) continue; + for (const d of stmt.declarationList.declarations) { + if (!parser.isIdentifier(d.name) || d.initializer === undefined) continue; + let init: ts.Expression = d.initializer; + while (parser.isParenthesizedExpression(init)) init = init.expression; + if (parser.isIdentifier(init)) aliases.push([d.name.text, init.text]); + } + } + for (let changed = true; changed; ) { + changed = false; + for (const [name, source] of aliases) + if (locals.get(source) === "accessors" && locals.get(name) !== "accessors") { + locals.set(name, "accessors"); + changed = true; + } + } function bindNames(name: ts.BindingName, kind: "module" | "value"): void { if (parser.isIdentifier(name)) locals.set(name.text, kind); else