Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/emittery/index.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
387 lines (312 sloc)
8.64 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const anyMap = new WeakMap(); | |
const eventsMap = new WeakMap(); | |
const producersMap = new WeakMap(); | |
const anyProducer = Symbol('anyProducer'); | |
const resolvedPromise = Promise.resolve(); | |
const listenerAdded = Symbol('listenerAdded'); | |
const listenerRemoved = Symbol('listenerRemoved'); | |
function assertEventName(eventName) { | |
if (typeof eventName !== 'string' && typeof eventName !== 'symbol') { | |
throw new TypeError('eventName must be a string or a symbol'); | |
} | |
} | |
function assertListener(listener) { | |
if (typeof listener !== 'function') { | |
throw new TypeError('listener must be a function'); | |
} | |
} | |
function getListeners(instance, eventName) { | |
const events = eventsMap.get(instance); | |
if (!events.has(eventName)) { | |
events.set(eventName, new Set()); | |
} | |
return events.get(eventName); | |
} | |
function getEventProducers(instance, eventName) { | |
const key = typeof eventName === 'string' ? eventName : anyProducer; | |
const producers = producersMap.get(instance); | |
if (!producers.has(key)) { | |
producers.set(key, new Set()); | |
} | |
return producers.get(key); | |
} | |
function enqueueProducers(instance, eventName, eventData) { | |
const producers = producersMap.get(instance); | |
if (producers.has(eventName)) { | |
for (const producer of producers.get(eventName)) { | |
producer.enqueue(eventData); | |
} | |
} | |
if (producers.has(anyProducer)) { | |
const item = Promise.all([eventName, eventData]); | |
for (const producer of producers.get(anyProducer)) { | |
producer.enqueue(item); | |
} | |
} | |
} | |
function iterator(instance, eventName) { | |
let isFinished = false; | |
let flush = () => {}; | |
let queue = []; | |
const producer = { | |
enqueue(item) { | |
queue.push(item); | |
flush(); | |
}, | |
finish() { | |
isFinished = true; | |
flush(); | |
} | |
}; | |
getEventProducers(instance, eventName).add(producer); | |
return { | |
async next() { | |
if (!queue) { | |
return {done: true}; | |
} | |
if (queue.length === 0) { | |
if (isFinished) { | |
queue = undefined; | |
return this.next(); | |
} | |
await new Promise(resolve => { | |
flush = resolve; | |
}); | |
return this.next(); | |
} | |
return { | |
done: false, | |
value: await queue.shift() | |
}; | |
}, | |
async return(value) { | |
queue = undefined; | |
getEventProducers(instance, eventName).delete(producer); | |
flush(); | |
return arguments.length > 0 ? | |
{done: true, value: await value} : | |
{done: true}; | |
}, | |
[Symbol.asyncIterator]() { | |
return this; | |
} | |
}; | |
} | |
function defaultMethodNamesOrAssert(methodNames) { | |
if (methodNames === undefined) { | |
return allEmitteryMethods; | |
} | |
if (!Array.isArray(methodNames)) { | |
throw new TypeError('`methodNames` must be an array of strings'); | |
} | |
for (const methodName of methodNames) { | |
if (!allEmitteryMethods.includes(methodName)) { | |
if (typeof methodName !== 'string') { | |
throw new TypeError('`methodNames` element must be a string'); | |
} | |
throw new Error(`${methodName} is not Emittery method`); | |
} | |
} | |
return methodNames; | |
} | |
const isListenerSymbol = symbol => symbol === listenerAdded || symbol === listenerRemoved; | |
class Emittery { | |
static mixin(emitteryPropertyName, methodNames) { | |
methodNames = defaultMethodNamesOrAssert(methodNames); | |
return target => { | |
if (typeof target !== 'function') { | |
throw new TypeError('`target` must be function'); | |
} | |
for (const methodName of methodNames) { | |
if (target.prototype[methodName] !== undefined) { | |
throw new Error(`The property \`${methodName}\` already exists on \`target\``); | |
} | |
} | |
function getEmitteryProperty() { | |
Object.defineProperty(this, emitteryPropertyName, { | |
enumerable: false, | |
value: new Emittery() | |
}); | |
return this[emitteryPropertyName]; | |
} | |
Object.defineProperty(target.prototype, emitteryPropertyName, { | |
enumerable: false, | |
get: getEmitteryProperty | |
}); | |
const emitteryMethodCaller = methodName => function (...args) { | |
return this[emitteryPropertyName][methodName](...args); | |
}; | |
for (const methodName of methodNames) { | |
Object.defineProperty(target.prototype, methodName, { | |
enumerable: false, | |
value: emitteryMethodCaller(methodName) | |
}); | |
} | |
return target; | |
}; | |
} | |
constructor() { | |
anyMap.set(this, new Set()); | |
eventsMap.set(this, new Map()); | |
producersMap.set(this, new Map()); | |
} | |
on(eventName, listener) { | |
assertEventName(eventName); | |
assertListener(listener); | |
getListeners(this, eventName).add(listener); | |
if (!isListenerSymbol(eventName)) { | |
this.emit(listenerAdded, {eventName, listener}); | |
} | |
return this.off.bind(this, eventName, listener); | |
} | |
off(eventName, listener) { | |
assertEventName(eventName); | |
assertListener(listener); | |
if (!isListenerSymbol(eventName)) { | |
this.emit(listenerRemoved, {eventName, listener}); | |
} | |
getListeners(this, eventName).delete(listener); | |
} | |
once(eventName) { | |
return new Promise(resolve => { | |
assertEventName(eventName); | |
const off = this.on(eventName, data => { | |
off(); | |
resolve(data); | |
}); | |
}); | |
} | |
events(eventName) { | |
assertEventName(eventName); | |
return iterator(this, eventName); | |
} | |
async emit(eventName, eventData) { | |
assertEventName(eventName); | |
enqueueProducers(this, eventName, eventData); | |
const listeners = getListeners(this, eventName); | |
const anyListeners = anyMap.get(this); | |
const staticListeners = [...listeners]; | |
const staticAnyListeners = isListenerSymbol(eventName) ? [] : [...anyListeners]; | |
await resolvedPromise; | |
return Promise.all([ | |
...staticListeners.map(async listener => { | |
if (listeners.has(listener)) { | |
return listener(eventData); | |
} | |
}), | |
...staticAnyListeners.map(async listener => { | |
if (anyListeners.has(listener)) { | |
return listener(eventName, eventData); | |
} | |
}) | |
]); | |
} | |
async emitSerial(eventName, eventData) { | |
assertEventName(eventName); | |
const listeners = getListeners(this, eventName); | |
const anyListeners = anyMap.get(this); | |
const staticListeners = [...listeners]; | |
const staticAnyListeners = [...anyListeners]; | |
await resolvedPromise; | |
/* eslint-disable no-await-in-loop */ | |
for (const listener of staticListeners) { | |
if (listeners.has(listener)) { | |
await listener(eventData); | |
} | |
} | |
for (const listener of staticAnyListeners) { | |
if (anyListeners.has(listener)) { | |
await listener(eventName, eventData); | |
} | |
} | |
/* eslint-enable no-await-in-loop */ | |
} | |
onAny(listener) { | |
assertListener(listener); | |
anyMap.get(this).add(listener); | |
this.emit(listenerAdded, {listener}); | |
return this.offAny.bind(this, listener); | |
} | |
anyEvent() { | |
return iterator(this); | |
} | |
offAny(listener) { | |
assertListener(listener); | |
this.emit(listenerRemoved, {listener}); | |
anyMap.get(this).delete(listener); | |
} | |
clearListeners(eventName) { | |
if (typeof eventName === 'string') { | |
getListeners(this, eventName).clear(); | |
const producers = getEventProducers(this, eventName); | |
for (const producer of producers) { | |
producer.finish(); | |
} | |
producers.clear(); | |
} else { | |
anyMap.get(this).clear(); | |
for (const listeners of eventsMap.get(this).values()) { | |
listeners.clear(); | |
} | |
for (const producers of producersMap.get(this).values()) { | |
for (const producer of producers) { | |
producer.finish(); | |
} | |
producers.clear(); | |
} | |
} | |
} | |
listenerCount(eventName) { | |
if (typeof eventName === 'string') { | |
return anyMap.get(this).size + getListeners(this, eventName).size + | |
getEventProducers(this, eventName).size + getEventProducers(this).size; | |
} | |
if (typeof eventName !== 'undefined') { | |
assertEventName(eventName); | |
} | |
let count = anyMap.get(this).size; | |
for (const value of eventsMap.get(this).values()) { | |
count += value.size; | |
} | |
for (const value of producersMap.get(this).values()) { | |
count += value.size; | |
} | |
return count; | |
} | |
bindMethods(target, methodNames) { | |
if (typeof target !== 'object' || target === null) { | |
throw new TypeError('`target` must be an object'); | |
} | |
methodNames = defaultMethodNamesOrAssert(methodNames); | |
for (const methodName of methodNames) { | |
if (target[methodName] !== undefined) { | |
throw new Error(`The property \`${methodName}\` already exists on \`target\``); | |
} | |
Object.defineProperty(target, methodName, { | |
enumerable: false, | |
value: this[methodName].bind(this) | |
}); | |
} | |
} | |
} | |
const allEmitteryMethods = Object.getOwnPropertyNames(Emittery.prototype).filter(v => v !== 'constructor'); | |
// Subclass used to encourage TS users to type their events. | |
Emittery.Typed = class extends Emittery {}; | |
Object.defineProperty(Emittery.Typed, 'Typed', { | |
enumerable: false, | |
value: undefined | |
}); | |
Object.defineProperty(Emittery, 'listenerAdded', { | |
value: listenerAdded, | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
Object.defineProperty(Emittery, 'listenerRemoved', { | |
value: listenerRemoved, | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
module.exports = Emittery; |