/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const util = require("util"); const deprecateContext = util.deprecate(() => {}, "Hook.context is deprecated and will be removed"); const CALL_DELEGATE = function(...args) { this.call = this._createCall("sync"); return this.call(...args); }; const CALL_ASYNC_DELEGATE = function(...args) { this.callAsync = this._createCall("async"); return this.callAsync(...args); }; const PROMISE_DELEGATE = function(...args) { this.promise = this._createCall("promise"); return this.promise(...args); }; class Hook { constructor(args = [], name = undefined) { this._args = args; this.name = name; this.taps = []; this.interceptors = []; this._call = CALL_DELEGATE; this.call = CALL_DELEGATE; this._callAsync = CALL_ASYNC_DELEGATE; this.callAsync = CALL_ASYNC_DELEGATE; this._promise = PROMISE_DELEGATE; this.promise = PROMISE_DELEGATE; this._x = undefined; this.compile = this.compile; this.tap = this.tap; this.tapAsync = this.tapAsync; this.tapPromise = this.tapPromise; } compile(options) { throw new Error("Abstract: should be overridden"); } _createCall(type) { return this.compile({ taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }); } _tap(type, options, fn) { if (typeof options === "string") { options = { name: options.trim() }; } else if (typeof options !== "object" || options === null) { throw new Error("Invalid tap options"); } if (typeof options.name !== "string" || options.name === "") { throw new Error("Missing name for tap"); } if (typeof options.context !== "undefined") { deprecateContext(); } options = Object.assign({ type, fn }, options); options = this._runRegisterInterceptors(options); this._insert(options); } tap(options, fn) { this._tap("sync", options, fn); } tapAsync(options, fn) { this._tap("async", options, fn); } tapPromise(options, fn) { this._tap("promise", options, fn); } _runRegisterInterceptors(options) { for (const interceptor of this.interceptors) { if (interceptor.register) { const newOptions = interceptor.register(options); if (newOptions !== undefined) { options = newOptions; } } } return options; } withOptions(options) { const mergeOptions = opt => Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt); return { name: this.name, tap: (opt, fn) => this.tap(mergeOptions(opt), fn), tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn), tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn), intercept: interceptor => this.intercept(interceptor), isUsed: () => this.isUsed(), withOptions: opt => this.withOptions(mergeOptions(opt)) }; } isUsed() { return this.taps.length > 0 || this.interceptors.length > 0; } intercept(interceptor) { this._resetCompilation(); this.interceptors.push(Object.assign({}, interceptor)); if (interceptor.register) { for (let i = 0; i < this.taps.length; i++) { this.taps[i] = interceptor.register(this.taps[i]); } } } _resetCompilation() { this.call = this._call; this.callAsync = this._callAsync; this.promise = this._promise; } _insert(item) { this._resetCompilation(); let before; if (typeof item.before === "string") { before = new Set([item.before]); } else if (Array.isArray(item.before)) { before = new Set(item.before); } let stage = 0; if (typeof item.stage === "number") { stage = item.stage; } let i = this.taps.length; while (i > 0) { i--; const x = this.taps[i]; this.taps[i + 1] = x; const xStage = x.stage || 0; if (before) { if (before.has(x.name)) { before.delete(x.name); continue; } if (before.size > 0) { continue; } } if (xStage > stage) { continue; } i++; break; } this.taps[i] = item; } } Object.setPrototypeOf(Hook.prototype, null); module.exports = Hook;