Files
scriptc/tests/fixtures/npm-static/node_modules/ee-first/index.js
T
Malte UblandClaude Opus 5.5 ae91df908e Admit require cycles whose modules settle their exports first
A require cycle was always refused (SC1016). Node answers a require of a
module that is still evaluating with its exports as they stand, while the
compiled bindings alias the final declarations; the two agree when every
module of the cycle assigns all of its exports before its first statement
that can run another module's code, every other use of its export object
is a plain member read, and no module's top-level code can reach its
binding of another member. Each module then initializes exactly as it
would outside the cycle. This admits semver's comparator/range cycle
("hoisted class for cyclic dependency"); other cycles keep SC1016 with a
reason naming the offending statement.

The require reachability scan no longer treats a declaration's own name
as a read (esbuild's `var X = class {...}` before its requires), treats a
plain write of an export member as running only a setter published for it
(on-finished's `module.exports.isFinished = isFinished`), and keeps the
conservative scan for writes of __proto__, __esModule and members of a
replaced export object.

Evidence: corpus 6145 (the cycle), 6142 gains the esbuild layout; the
npm-static harness compiles the published fs.realpath@1.0.0,
on-finished@2.4.1 (with ee-first@1.1.1) and statuses@2.0.1 statically;
program.test.ts pins the TDZ analysis through var bindings plus the cycle
admission and refusals. Preflight/order baselines recorded for the new
fixtures; a full re-record changed no other fixture. Dependencies and
limitations docs describe the supported require forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:24:04 -07:00

96 lines
1.6 KiB
JavaScript

/*!
* ee-first
* Copyright(c) 2014 Jonathan Ong
* MIT Licensed
*/
'use strict'
/**
* Module exports.
* @public
*/
module.exports = first
/**
* Get the first event in a set of event emitters and event pairs.
*
* @param {array} stuff
* @param {function} done
* @public
*/
function first(stuff, done) {
if (!Array.isArray(stuff))
throw new TypeError('arg must be an array of [ee, events...] arrays')
var cleanups = []
for (var i = 0; i < stuff.length; i++) {
var arr = stuff[i]
if (!Array.isArray(arr) || arr.length < 2)
throw new TypeError('each array member must be [ee, events...]')
var ee = arr[0]
for (var j = 1; j < arr.length; j++) {
var event = arr[j]
var fn = listener(event, callback)
// listen to the event
ee.on(event, fn)
// push this listener to the list of cleanups
cleanups.push({
ee: ee,
event: event,
fn: fn,
})
}
}
function callback() {
cleanup()
done.apply(null, arguments)
}
function cleanup() {
var x
for (var i = 0; i < cleanups.length; i++) {
x = cleanups[i]
x.ee.removeListener(x.event, x.fn)
}
}
function thunk(fn) {
done = fn
}
thunk.cancel = cleanup
return thunk
}
/**
* Create the event listener.
* @private
*/
function listener(event, done) {
return function onevent(arg1) {
var args = new Array(arguments.length)
var ee = this
var err = event === 'error'
? arg1
: null
// copy args to prevent arguments escaping scope
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i]
}
done(err, ee, event, args)
}
}