Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ node_modules/
!tests/fixtures/npm-static/node_modules/*/node_modules/
!tests/fixtures/npm/store/*/node_modules/
!tests/fixtures/npm/node_modules/*/node_modules/
!tests/fixtures/npm/workspace/*/node_modules/
!tests/fixtures/npm/cases/*/node_modules/
!tests/library-mode/*/node_modules/
!tests/fixtures/provenance/node_modules/
Expand Down
14 changes: 14 additions & 0 deletions packages/compiler/src/frontend/lowering/lower-inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type { ClassInfo } from "./lower-classes.js";
import { isSafeToDiscard, isSafeToRepeat } from "./expressions/evaluation-safety.js";
import { symbolFieldDisplayName } from "./symbol-fields.js";
import { eventTargetRootOf } from "./lower-event-target.js";
import { builtinClassRefOf } from "./builtins/class-values.js";
import { lowerSystemErrorMap } from "./lower-system-errors.js";
import { isPerfEntryType, perfEntryJson } from "./builtins/performance.js";
import { recordMayKeepSource, recordSourceTest } from "./record-sources.js";
Expand Down Expand Up @@ -2545,6 +2546,19 @@ function lowerUtilCallableCall(
// Program classes keep static prototypes: there is no function object
// whose prototype chain the runtime could relink or wrap.
const classArgs = member === "inherits" ? 2 : member === "deprecate" ? 1 : 0;
// A runtime-provided parent (`util.inherits(Ctor, stream.Readable)`, the
// from2 and multistream shape) has no prototype object the runtime could
// link a constructor function's prototype to.
const parent = member === "inherits" ? expr.arguments.nodes[1] : undefined;
if (parent !== undefined && !ts.isSpreadElement(parent)) {
const ref = builtinClassRefOf(lowerer, parent);
if (ref !== null)
lowerer.noLowering(
`util.inherits onto the runtime-provided class '${ref.name}'`,
parent,
"declare the subclass with extends",
);
}
for (const arg of expr.arguments.nodes.slice(0, classArgs)) {
if (lowerer.mapTypeOf(lowerer.typeOf(arg))?.kind === "classval")
lowerer.noLowering(
Expand Down
74 changes: 74 additions & 0 deletions packages/compiler/src/frontend/npm-static-bundled-esm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,77 @@ test("files without __esm initializers and ES-module bundles are left alone", ()
lower(`${helpers}var init_a = __esm({ "a.ts"() {} });\ninit_a();\nexport {};\n`),
).toBeNull();
});

test("a class an initializer defines into a module binding becomes a class declaration", () => {
const lowered = expectSameBehavior(`"use strict";
${helpers}var import_stream, Shout, Loud, Plain;
var init_shout = __esm({
"src/shout.ts"() {
"use strict";
import_stream = require("stream");
console.log("shout init");
Shout = class extends import_stream.Transform {
_transform(chunk, encoding, callback) {
callback(null, String(chunk).toUpperCase());
}
};
Loud = class Loud extends Shout {
static of() { return new Loud(); }
}, Plain = class {
label() { return "plain"; }
};
console.log("after", typeof Shout, Loud.name, new Plain().label());
}
});
function main() {
init_shout();
const s = Loud.of();
s.on("data", (c) => console.log("data", String(c)));
s.end("hi");
}
console.log("start");
main();
`);
expect(lowered).toContain('class Shout extends require("stream").Transform');
expect(lowered).toContain("class Loud extends Shout");
expect(lowered).toContain("class Plain ");
// The bindings' declarators are gone with the interop binding's.
expect(lowered).not.toMatch(/var [^;=]*\b(?:import_stream|Shout|Loud|Plain)\b/);
});

test("classes whose definition or binding is observable stay assignments", () => {
const lowered = expectSameBehavior(`${helpers}var A, B, C, D;
var init_m = __esm({
"src/m.ts"() {
A = class { static made = console.log("static field runs at definition") };
B = class extends Array {};
console.log(typeof D);
D = class {};
C = class {};
}
});
console.log(typeof C);
init_m();
console.log(typeof A, typeof B, typeof C, typeof D);
`);
expect(lowered).toContain("A = class");
expect(lowered).toContain("B = class");
expect(lowered).toContain("C = class");
expect(lowered).toContain("D = class");
});

test("an interop binding a new expression constructs through stays the callee", () => {
const lowered = expectSameBehavior(`${helpers}function make() {
return new import_events.EventEmitter();
}
var import_events;
var init_m = __esm({
"src/m.ts"() {
import_events = require("events");
}
});
init_m();
console.log(typeof make().on);
`);
expect(lowered).toContain('new (require("events")).EventEmitter()');
});
Loading
Loading