[6a3a178] | 1 | /*
|
---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
| 3 | Author Tobias Koppers @sokra
|
---|
| 4 | */
|
---|
| 5 | "use strict";
|
---|
| 6 |
|
---|
| 7 | class HookCodeFactory {
|
---|
| 8 | constructor(config) {
|
---|
| 9 | this.config = config;
|
---|
| 10 | this.options = undefined;
|
---|
| 11 | this._args = undefined;
|
---|
| 12 | }
|
---|
| 13 |
|
---|
| 14 | create(options) {
|
---|
| 15 | this.init(options);
|
---|
| 16 | let fn;
|
---|
| 17 | switch (this.options.type) {
|
---|
| 18 | case "sync":
|
---|
| 19 | fn = new Function(
|
---|
| 20 | this.args(),
|
---|
| 21 | '"use strict";\n' +
|
---|
| 22 | this.header() +
|
---|
| 23 | this.contentWithInterceptors({
|
---|
| 24 | onError: err => `throw ${err};\n`,
|
---|
| 25 | onResult: result => `return ${result};\n`,
|
---|
| 26 | resultReturns: true,
|
---|
| 27 | onDone: () => "",
|
---|
| 28 | rethrowIfPossible: true
|
---|
| 29 | })
|
---|
| 30 | );
|
---|
| 31 | break;
|
---|
| 32 | case "async":
|
---|
| 33 | fn = new Function(
|
---|
| 34 | this.args({
|
---|
| 35 | after: "_callback"
|
---|
| 36 | }),
|
---|
| 37 | '"use strict";\n' +
|
---|
| 38 | this.header() +
|
---|
| 39 | this.contentWithInterceptors({
|
---|
| 40 | onError: err => `_callback(${err});\n`,
|
---|
| 41 | onResult: result => `_callback(null, ${result});\n`,
|
---|
| 42 | onDone: () => "_callback();\n"
|
---|
| 43 | })
|
---|
| 44 | );
|
---|
| 45 | break;
|
---|
| 46 | case "promise":
|
---|
| 47 | let errorHelperUsed = false;
|
---|
| 48 | const content = this.contentWithInterceptors({
|
---|
| 49 | onError: err => {
|
---|
| 50 | errorHelperUsed = true;
|
---|
| 51 | return `_error(${err});\n`;
|
---|
| 52 | },
|
---|
| 53 | onResult: result => `_resolve(${result});\n`,
|
---|
| 54 | onDone: () => "_resolve();\n"
|
---|
| 55 | });
|
---|
| 56 | let code = "";
|
---|
| 57 | code += '"use strict";\n';
|
---|
| 58 | code += this.header();
|
---|
| 59 | code += "return new Promise((function(_resolve, _reject) {\n";
|
---|
| 60 | if (errorHelperUsed) {
|
---|
| 61 | code += "var _sync = true;\n";
|
---|
| 62 | code += "function _error(_err) {\n";
|
---|
| 63 | code += "if(_sync)\n";
|
---|
| 64 | code +=
|
---|
| 65 | "_resolve(Promise.resolve().then((function() { throw _err; })));\n";
|
---|
| 66 | code += "else\n";
|
---|
| 67 | code += "_reject(_err);\n";
|
---|
| 68 | code += "};\n";
|
---|
| 69 | }
|
---|
| 70 | code += content;
|
---|
| 71 | if (errorHelperUsed) {
|
---|
| 72 | code += "_sync = false;\n";
|
---|
| 73 | }
|
---|
| 74 | code += "}));\n";
|
---|
| 75 | fn = new Function(this.args(), code);
|
---|
| 76 | break;
|
---|
| 77 | }
|
---|
| 78 | this.deinit();
|
---|
| 79 | return fn;
|
---|
| 80 | }
|
---|
| 81 |
|
---|
| 82 | setup(instance, options) {
|
---|
| 83 | instance._x = options.taps.map(t => t.fn);
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | /**
|
---|
| 87 | * @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> }} options
|
---|
| 88 | */
|
---|
| 89 | init(options) {
|
---|
| 90 | this.options = options;
|
---|
| 91 | this._args = options.args.slice();
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | deinit() {
|
---|
| 95 | this.options = undefined;
|
---|
| 96 | this._args = undefined;
|
---|
| 97 | }
|
---|
| 98 |
|
---|
| 99 | contentWithInterceptors(options) {
|
---|
| 100 | if (this.options.interceptors.length > 0) {
|
---|
| 101 | const onError = options.onError;
|
---|
| 102 | const onResult = options.onResult;
|
---|
| 103 | const onDone = options.onDone;
|
---|
| 104 | let code = "";
|
---|
| 105 | for (let i = 0; i < this.options.interceptors.length; i++) {
|
---|
| 106 | const interceptor = this.options.interceptors[i];
|
---|
| 107 | if (interceptor.call) {
|
---|
| 108 | code += `${this.getInterceptor(i)}.call(${this.args({
|
---|
| 109 | before: interceptor.context ? "_context" : undefined
|
---|
| 110 | })});\n`;
|
---|
| 111 | }
|
---|
| 112 | }
|
---|
| 113 | code += this.content(
|
---|
| 114 | Object.assign(options, {
|
---|
| 115 | onError:
|
---|
| 116 | onError &&
|
---|
| 117 | (err => {
|
---|
| 118 | let code = "";
|
---|
| 119 | for (let i = 0; i < this.options.interceptors.length; i++) {
|
---|
| 120 | const interceptor = this.options.interceptors[i];
|
---|
| 121 | if (interceptor.error) {
|
---|
| 122 | code += `${this.getInterceptor(i)}.error(${err});\n`;
|
---|
| 123 | }
|
---|
| 124 | }
|
---|
| 125 | code += onError(err);
|
---|
| 126 | return code;
|
---|
| 127 | }),
|
---|
| 128 | onResult:
|
---|
| 129 | onResult &&
|
---|
| 130 | (result => {
|
---|
| 131 | let code = "";
|
---|
| 132 | for (let i = 0; i < this.options.interceptors.length; i++) {
|
---|
| 133 | const interceptor = this.options.interceptors[i];
|
---|
| 134 | if (interceptor.result) {
|
---|
| 135 | code += `${this.getInterceptor(i)}.result(${result});\n`;
|
---|
| 136 | }
|
---|
| 137 | }
|
---|
| 138 | code += onResult(result);
|
---|
| 139 | return code;
|
---|
| 140 | }),
|
---|
| 141 | onDone:
|
---|
| 142 | onDone &&
|
---|
| 143 | (() => {
|
---|
| 144 | let code = "";
|
---|
| 145 | for (let i = 0; i < this.options.interceptors.length; i++) {
|
---|
| 146 | const interceptor = this.options.interceptors[i];
|
---|
| 147 | if (interceptor.done) {
|
---|
| 148 | code += `${this.getInterceptor(i)}.done();\n`;
|
---|
| 149 | }
|
---|
| 150 | }
|
---|
| 151 | code += onDone();
|
---|
| 152 | return code;
|
---|
| 153 | })
|
---|
| 154 | })
|
---|
| 155 | );
|
---|
| 156 | return code;
|
---|
| 157 | } else {
|
---|
| 158 | return this.content(options);
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | header() {
|
---|
| 163 | let code = "";
|
---|
| 164 | if (this.needContext()) {
|
---|
| 165 | code += "var _context = {};\n";
|
---|
| 166 | } else {
|
---|
| 167 | code += "var _context;\n";
|
---|
| 168 | }
|
---|
| 169 | code += "var _x = this._x;\n";
|
---|
| 170 | if (this.options.interceptors.length > 0) {
|
---|
| 171 | code += "var _taps = this.taps;\n";
|
---|
| 172 | code += "var _interceptors = this.interceptors;\n";
|
---|
| 173 | }
|
---|
| 174 | return code;
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | needContext() {
|
---|
| 178 | for (const tap of this.options.taps) if (tap.context) return true;
|
---|
| 179 | return false;
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 | callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
|
---|
| 183 | let code = "";
|
---|
| 184 | let hasTapCached = false;
|
---|
| 185 | for (let i = 0; i < this.options.interceptors.length; i++) {
|
---|
| 186 | const interceptor = this.options.interceptors[i];
|
---|
| 187 | if (interceptor.tap) {
|
---|
| 188 | if (!hasTapCached) {
|
---|
| 189 | code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
|
---|
| 190 | hasTapCached = true;
|
---|
| 191 | }
|
---|
| 192 | code += `${this.getInterceptor(i)}.tap(${
|
---|
| 193 | interceptor.context ? "_context, " : ""
|
---|
| 194 | }_tap${tapIndex});\n`;
|
---|
| 195 | }
|
---|
| 196 | }
|
---|
| 197 | code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
|
---|
| 198 | const tap = this.options.taps[tapIndex];
|
---|
| 199 | switch (tap.type) {
|
---|
| 200 | case "sync":
|
---|
| 201 | if (!rethrowIfPossible) {
|
---|
| 202 | code += `var _hasError${tapIndex} = false;\n`;
|
---|
| 203 | code += "try {\n";
|
---|
| 204 | }
|
---|
| 205 | if (onResult) {
|
---|
| 206 | code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
|
---|
| 207 | before: tap.context ? "_context" : undefined
|
---|
| 208 | })});\n`;
|
---|
| 209 | } else {
|
---|
| 210 | code += `_fn${tapIndex}(${this.args({
|
---|
| 211 | before: tap.context ? "_context" : undefined
|
---|
| 212 | })});\n`;
|
---|
| 213 | }
|
---|
| 214 | if (!rethrowIfPossible) {
|
---|
| 215 | code += "} catch(_err) {\n";
|
---|
| 216 | code += `_hasError${tapIndex} = true;\n`;
|
---|
| 217 | code += onError("_err");
|
---|
| 218 | code += "}\n";
|
---|
| 219 | code += `if(!_hasError${tapIndex}) {\n`;
|
---|
| 220 | }
|
---|
| 221 | if (onResult) {
|
---|
| 222 | code += onResult(`_result${tapIndex}`);
|
---|
| 223 | }
|
---|
| 224 | if (onDone) {
|
---|
| 225 | code += onDone();
|
---|
| 226 | }
|
---|
| 227 | if (!rethrowIfPossible) {
|
---|
| 228 | code += "}\n";
|
---|
| 229 | }
|
---|
| 230 | break;
|
---|
| 231 | case "async":
|
---|
| 232 | let cbCode = "";
|
---|
| 233 | if (onResult)
|
---|
| 234 | cbCode += `(function(_err${tapIndex}, _result${tapIndex}) {\n`;
|
---|
| 235 | else cbCode += `(function(_err${tapIndex}) {\n`;
|
---|
| 236 | cbCode += `if(_err${tapIndex}) {\n`;
|
---|
| 237 | cbCode += onError(`_err${tapIndex}`);
|
---|
| 238 | cbCode += "} else {\n";
|
---|
| 239 | if (onResult) {
|
---|
| 240 | cbCode += onResult(`_result${tapIndex}`);
|
---|
| 241 | }
|
---|
| 242 | if (onDone) {
|
---|
| 243 | cbCode += onDone();
|
---|
| 244 | }
|
---|
| 245 | cbCode += "}\n";
|
---|
| 246 | cbCode += "})";
|
---|
| 247 | code += `_fn${tapIndex}(${this.args({
|
---|
| 248 | before: tap.context ? "_context" : undefined,
|
---|
| 249 | after: cbCode
|
---|
| 250 | })});\n`;
|
---|
| 251 | break;
|
---|
| 252 | case "promise":
|
---|
| 253 | code += `var _hasResult${tapIndex} = false;\n`;
|
---|
| 254 | code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
|
---|
| 255 | before: tap.context ? "_context" : undefined
|
---|
| 256 | })});\n`;
|
---|
| 257 | code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
|
---|
| 258 | code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
|
---|
| 259 | code += `_promise${tapIndex}.then((function(_result${tapIndex}) {\n`;
|
---|
| 260 | code += `_hasResult${tapIndex} = true;\n`;
|
---|
| 261 | if (onResult) {
|
---|
| 262 | code += onResult(`_result${tapIndex}`);
|
---|
| 263 | }
|
---|
| 264 | if (onDone) {
|
---|
| 265 | code += onDone();
|
---|
| 266 | }
|
---|
| 267 | code += `}), function(_err${tapIndex}) {\n`;
|
---|
| 268 | code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
|
---|
| 269 | code += onError(`_err${tapIndex}`);
|
---|
| 270 | code += "});\n";
|
---|
| 271 | break;
|
---|
| 272 | }
|
---|
| 273 | return code;
|
---|
| 274 | }
|
---|
| 275 |
|
---|
| 276 | callTapsSeries({
|
---|
| 277 | onError,
|
---|
| 278 | onResult,
|
---|
| 279 | resultReturns,
|
---|
| 280 | onDone,
|
---|
| 281 | doneReturns,
|
---|
| 282 | rethrowIfPossible
|
---|
| 283 | }) {
|
---|
| 284 | if (this.options.taps.length === 0) return onDone();
|
---|
| 285 | const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
|
---|
| 286 | const somethingReturns = resultReturns || doneReturns;
|
---|
| 287 | let code = "";
|
---|
| 288 | let current = onDone;
|
---|
| 289 | let unrollCounter = 0;
|
---|
| 290 | for (let j = this.options.taps.length - 1; j >= 0; j--) {
|
---|
| 291 | const i = j;
|
---|
| 292 | const unroll =
|
---|
| 293 | current !== onDone &&
|
---|
| 294 | (this.options.taps[i].type !== "sync" || unrollCounter++ > 20);
|
---|
| 295 | if (unroll) {
|
---|
| 296 | unrollCounter = 0;
|
---|
| 297 | code += `function _next${i}() {\n`;
|
---|
| 298 | code += current();
|
---|
| 299 | code += `}\n`;
|
---|
| 300 | current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
|
---|
| 301 | }
|
---|
| 302 | const done = current;
|
---|
| 303 | const doneBreak = skipDone => {
|
---|
| 304 | if (skipDone) return "";
|
---|
| 305 | return onDone();
|
---|
| 306 | };
|
---|
| 307 | const content = this.callTap(i, {
|
---|
| 308 | onError: error => onError(i, error, done, doneBreak),
|
---|
| 309 | onResult:
|
---|
| 310 | onResult &&
|
---|
| 311 | (result => {
|
---|
| 312 | return onResult(i, result, done, doneBreak);
|
---|
| 313 | }),
|
---|
| 314 | onDone: !onResult && done,
|
---|
| 315 | rethrowIfPossible:
|
---|
| 316 | rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
|
---|
| 317 | });
|
---|
| 318 | current = () => content;
|
---|
| 319 | }
|
---|
| 320 | code += current();
|
---|
| 321 | return code;
|
---|
| 322 | }
|
---|
| 323 |
|
---|
| 324 | callTapsLooping({ onError, onDone, rethrowIfPossible }) {
|
---|
| 325 | if (this.options.taps.length === 0) return onDone();
|
---|
| 326 | const syncOnly = this.options.taps.every(t => t.type === "sync");
|
---|
| 327 | let code = "";
|
---|
| 328 | if (!syncOnly) {
|
---|
| 329 | code += "var _looper = (function() {\n";
|
---|
| 330 | code += "var _loopAsync = false;\n";
|
---|
| 331 | }
|
---|
| 332 | code += "var _loop;\n";
|
---|
| 333 | code += "do {\n";
|
---|
| 334 | code += "_loop = false;\n";
|
---|
| 335 | for (let i = 0; i < this.options.interceptors.length; i++) {
|
---|
| 336 | const interceptor = this.options.interceptors[i];
|
---|
| 337 | if (interceptor.loop) {
|
---|
| 338 | code += `${this.getInterceptor(i)}.loop(${this.args({
|
---|
| 339 | before: interceptor.context ? "_context" : undefined
|
---|
| 340 | })});\n`;
|
---|
| 341 | }
|
---|
| 342 | }
|
---|
| 343 | code += this.callTapsSeries({
|
---|
| 344 | onError,
|
---|
| 345 | onResult: (i, result, next, doneBreak) => {
|
---|
| 346 | let code = "";
|
---|
| 347 | code += `if(${result} !== undefined) {\n`;
|
---|
| 348 | code += "_loop = true;\n";
|
---|
| 349 | if (!syncOnly) code += "if(_loopAsync) _looper();\n";
|
---|
| 350 | code += doneBreak(true);
|
---|
| 351 | code += `} else {\n`;
|
---|
| 352 | code += next();
|
---|
| 353 | code += `}\n`;
|
---|
| 354 | return code;
|
---|
| 355 | },
|
---|
| 356 | onDone:
|
---|
| 357 | onDone &&
|
---|
| 358 | (() => {
|
---|
| 359 | let code = "";
|
---|
| 360 | code += "if(!_loop) {\n";
|
---|
| 361 | code += onDone();
|
---|
| 362 | code += "}\n";
|
---|
| 363 | return code;
|
---|
| 364 | }),
|
---|
| 365 | rethrowIfPossible: rethrowIfPossible && syncOnly
|
---|
| 366 | });
|
---|
| 367 | code += "} while(_loop);\n";
|
---|
| 368 | if (!syncOnly) {
|
---|
| 369 | code += "_loopAsync = true;\n";
|
---|
| 370 | code += "});\n";
|
---|
| 371 | code += "_looper();\n";
|
---|
| 372 | }
|
---|
| 373 | return code;
|
---|
| 374 | }
|
---|
| 375 |
|
---|
| 376 | callTapsParallel({
|
---|
| 377 | onError,
|
---|
| 378 | onResult,
|
---|
| 379 | onDone,
|
---|
| 380 | rethrowIfPossible,
|
---|
| 381 | onTap = (i, run) => run()
|
---|
| 382 | }) {
|
---|
| 383 | if (this.options.taps.length <= 1) {
|
---|
| 384 | return this.callTapsSeries({
|
---|
| 385 | onError,
|
---|
| 386 | onResult,
|
---|
| 387 | onDone,
|
---|
| 388 | rethrowIfPossible
|
---|
| 389 | });
|
---|
| 390 | }
|
---|
| 391 | let code = "";
|
---|
| 392 | code += "do {\n";
|
---|
| 393 | code += `var _counter = ${this.options.taps.length};\n`;
|
---|
| 394 | if (onDone) {
|
---|
| 395 | code += "var _done = (function() {\n";
|
---|
| 396 | code += onDone();
|
---|
| 397 | code += "});\n";
|
---|
| 398 | }
|
---|
| 399 | for (let i = 0; i < this.options.taps.length; i++) {
|
---|
| 400 | const done = () => {
|
---|
| 401 | if (onDone) return "if(--_counter === 0) _done();\n";
|
---|
| 402 | else return "--_counter;";
|
---|
| 403 | };
|
---|
| 404 | const doneBreak = skipDone => {
|
---|
| 405 | if (skipDone || !onDone) return "_counter = 0;\n";
|
---|
| 406 | else return "_counter = 0;\n_done();\n";
|
---|
| 407 | };
|
---|
| 408 | code += "if(_counter <= 0) break;\n";
|
---|
| 409 | code += onTap(
|
---|
| 410 | i,
|
---|
| 411 | () =>
|
---|
| 412 | this.callTap(i, {
|
---|
| 413 | onError: error => {
|
---|
| 414 | let code = "";
|
---|
| 415 | code += "if(_counter > 0) {\n";
|
---|
| 416 | code += onError(i, error, done, doneBreak);
|
---|
| 417 | code += "}\n";
|
---|
| 418 | return code;
|
---|
| 419 | },
|
---|
| 420 | onResult:
|
---|
| 421 | onResult &&
|
---|
| 422 | (result => {
|
---|
| 423 | let code = "";
|
---|
| 424 | code += "if(_counter > 0) {\n";
|
---|
| 425 | code += onResult(i, result, done, doneBreak);
|
---|
| 426 | code += "}\n";
|
---|
| 427 | return code;
|
---|
| 428 | }),
|
---|
| 429 | onDone:
|
---|
| 430 | !onResult &&
|
---|
| 431 | (() => {
|
---|
| 432 | return done();
|
---|
| 433 | }),
|
---|
| 434 | rethrowIfPossible
|
---|
| 435 | }),
|
---|
| 436 | done,
|
---|
| 437 | doneBreak
|
---|
| 438 | );
|
---|
| 439 | }
|
---|
| 440 | code += "} while(false);\n";
|
---|
| 441 | return code;
|
---|
| 442 | }
|
---|
| 443 |
|
---|
| 444 | args({ before, after } = {}) {
|
---|
| 445 | let allArgs = this._args;
|
---|
| 446 | if (before) allArgs = [before].concat(allArgs);
|
---|
| 447 | if (after) allArgs = allArgs.concat(after);
|
---|
| 448 | if (allArgs.length === 0) {
|
---|
| 449 | return "";
|
---|
| 450 | } else {
|
---|
| 451 | return allArgs.join(", ");
|
---|
| 452 | }
|
---|
| 453 | }
|
---|
| 454 |
|
---|
| 455 | getTapFn(idx) {
|
---|
| 456 | return `_x[${idx}]`;
|
---|
| 457 | }
|
---|
| 458 |
|
---|
| 459 | getTap(idx) {
|
---|
| 460 | return `_taps[${idx}]`;
|
---|
| 461 | }
|
---|
| 462 |
|
---|
| 463 | getInterceptor(idx) {
|
---|
| 464 | return `_interceptors[${idx}]`;
|
---|
| 465 | }
|
---|
| 466 | }
|
---|
| 467 |
|
---|
| 468 | module.exports = HookCodeFactory;
|
---|