HookCodeFactory.js 12 KB

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