Files
scriptc/tests/corpus/6453-constructor-function-emitter.cjs
T
Malte UblandClaude Opus 5.5 1fb1d9c829 Let constructor functions inherit from EventEmitter and the stream classes
Bundled npm code (glob, yazl, from2, multistream) subclasses Node's
classes with constructor functions: util.inherits(Ctor, EventEmitter) and
EventEmitter.call(this). util.inherits received an identity token and
threw ERR_INVALID_ARG_TYPE, EventEmitter.call(this) and
EventEmitter.prototype were refused, and `this instanceof Ctor` against
a function was refused.

A new runtime unit (scr_dyn_builtin_class.c, linked by the new
dynBuiltinClass feature) provides EventEmitter, the legacy Stream and
the stream classes as untyped constructor functions with Node's
prototype chain. EventEmitter's prototype ports lib/events.js over the
object's own _events, _eventsCount and _maxListeners properties; Stream
adds the legacy pipe. util.inherits, Base.call/apply(this, ...),
Base.prototype, instanceof on untyped values and JavaScript value reads
of these classes use them. Constructing over a stream class throws
SC2020 where it runs. instanceof against a compiled constructor function
compares its prototype with the value's prototype chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:49:36 -07:00

115 lines
3.8 KiB
JavaScript

// Constructor functions that inherit from EventEmitter the pre-class way:
// util.inherits and EventEmitter.call(this), the call-without-new guard,
// prototype methods that emit, and the listener API over the instance.
"use strict";
const util = require("util");
const { EventEmitter } = require("events");
util.inherits(Bus, EventEmitter);
function Bus(name) {
if (!(this instanceof Bus)) return new Bus(name);
EventEmitter.call(this);
this.name = name;
this.entries = [];
}
Bus.prototype.add = function (entry) {
this.entries.push(entry);
this.emit("added", entry, this.entries.length);
return this;
};
// A second level: Sub inherits Bus, and Bus's constructor runs through call.
function Sub(name) {
Bus.call(this, name);
this.sub = true;
}
util.inherits(Sub, Bus);
Sub.prototype.add = function (entry) {
return Bus.prototype.add.call(this, entry.toUpperCase());
};
// Object.setPrototypeOf instead of util.inherits.
function Legacy() {
EventEmitter.call(this);
}
Object.setPrototypeOf(Legacy.prototype, EventEmitter.prototype);
function main() {
const bus = Bus("main");
console.log(bus instanceof Bus, bus instanceof EventEmitter, Bus.super_ === EventEmitter);
console.log(Object.keys(bus).join(","), bus._eventsCount, bus._maxListeners);
bus.on("added", function (entry, count) {
console.log("added", entry, count, this === bus);
});
bus.once("added", (entry) => console.log("first only", entry));
bus.prependListener("added", () => console.log("prepended"));
bus.prependOnceListener("added", () => console.log("prepended once"));
bus.add("a").add("b");
console.log(bus.listenerCount("added"), bus.eventNames(), bus.getMaxListeners());
const handler = () => console.log("never");
bus.on("x", handler);
bus.on("x", () => console.log("kept"));
bus.off("x", handler);
console.log(bus.emit("x"), bus.emit("nothing"), bus.listeners("x").length, bus.rawListeners("added").length);
try {
bus.emit("error", new Error("boom"));
} catch (err) {
console.log("caught", err.message);
}
try {
bus.emit("error", "plain");
} catch (err) {
console.log("caught", err.code, err.message);
}
bus.on("error", (err) => console.log("handled", err.message));
bus.emit("error", new Error("quiet"));
bus.on("newListener", (event, listener) => console.log("newListener", event, typeof listener));
bus.on("removeListener", (event) => console.log("removeListener", event));
bus.on("later", () => {});
bus.removeAllListeners("added");
console.log(bus.listenerCount("added"), bus.listenerCount("later"));
bus.removeAllListeners();
console.log(bus.eventNames().length);
const sub = new Sub("child");
sub.on("added", (entry) => console.log("sub added", entry));
sub.add("c");
console.log(sub instanceof Sub, sub instanceof Bus, sub instanceof EventEmitter, sub.entries);
const legacy = new Legacy();
console.log("legacy", legacy instanceof EventEmitter, legacy.getMaxListeners());
try {
legacy.setMaxListeners(-1);
} catch (err) {
console.log(err.name, err.code);
}
try {
legacy.on("bad", "not a function");
} catch (err) {
console.log(err.name, err.code);
}
console.log(new Legacy());
// The leak warning goes to stderr with the process id: run it in a child
// and print its output with the id normalized.
const { spawnSync } = require("child_process");
const child = spawnSync(process.execPath, [process.argv[1], "leak"], { encoding: "utf8" });
console.log(child.stdout + child.stderr.replace(/\(node:\d+\)/g, "(node:<pid>)"));
}
function leak() {
const legacy = new Legacy();
legacy.setMaxListeners(1);
legacy.on("many", () => {});
legacy.on("many", () => {});
legacy.on("many", () => {});
console.log("leak", legacy.listenerCount("many"));
setTimeout(() => console.log("leak done"), 1);
}
if (process.argv[2] === "leak") leak();
else main();