transformer.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. const {Parser: AcornParser, isNewLine: acornIsNewLine, getLineInfo: acornGetLineInfo} = require('acorn');
  2. const {full: acornWalkFull} = require('acorn-walk');
  3. const INTERNAL_STATE_NAME = 'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL';
  4. function assertType(node, type) {
  5. if (!node) throw new Error(`None existent node expected '${type}'`);
  6. if (node.type !== type) throw new Error(`Invalid node type '${node.type}' expected '${type}'`);
  7. return node;
  8. }
  9. function makeNiceSyntaxError(message, code, filename, location, tokenizer) {
  10. const loc = acornGetLineInfo(code, location);
  11. let end = location;
  12. while (end < code.length && !acornIsNewLine(code.charCodeAt(end))) {
  13. end++;
  14. }
  15. let markerEnd = tokenizer.start === location ? tokenizer.end : location + 1;
  16. if (!markerEnd || markerEnd > end) markerEnd = end;
  17. let markerLen = markerEnd - location;
  18. if (markerLen <= 0) markerLen = 1;
  19. if (message === 'Unexpected token') {
  20. const type = tokenizer.type;
  21. if (type.label === 'name' || type.label === 'privateId') {
  22. message = 'Unexpected identifier';
  23. } else if (type.label === 'eof') {
  24. message = 'Unexpected end of input';
  25. } else if (type.label === 'num') {
  26. message = 'Unexpected number';
  27. } else if (type.label === 'string') {
  28. message = 'Unexpected string';
  29. } else if (type.label === 'regexp') {
  30. message = 'Unexpected token \'/\'';
  31. markerLen = 1;
  32. } else {
  33. const token = tokenizer.value || type.label;
  34. message = `Unexpected token '${token}'`;
  35. }
  36. }
  37. const error = new SyntaxError(message);
  38. if (!filename) return error;
  39. const line = code.slice(location - loc.column, end);
  40. const marker = line.slice(0, loc.column).replace(/\S/g, ' ') + '^'.repeat(markerLen);
  41. error.stack = `${filename}:${loc.line}\n${line}\n${marker}\n\n${error.stack}`;
  42. return error;
  43. }
  44. function transformer(args, body, isAsync, isGenerator, filename) {
  45. let code;
  46. let argsOffset;
  47. if (args === null) {
  48. code = body;
  49. // Note: Keywords are not allows to contain u escapes
  50. if (!/\b(?:catch|import|async)\b/.test(code)) {
  51. return {__proto__: null, code, hasAsync: false};
  52. }
  53. } else {
  54. code = isAsync ? '(async function' : '(function';
  55. if (isGenerator) code += '*';
  56. code += ' anonymous(';
  57. code += args;
  58. argsOffset = code.length;
  59. code += '\n) {\n';
  60. code += body;
  61. code += '\n})';
  62. }
  63. const parser = new AcornParser({
  64. __proto__: null,
  65. ecmaVersion: 2022,
  66. allowAwaitOutsideFunction: args === null && isAsync,
  67. allowReturnOutsideFunction: args === null
  68. }, code);
  69. let ast;
  70. try {
  71. ast = parser.parse();
  72. } catch (e) {
  73. // Try to generate a nicer error message.
  74. if (e instanceof SyntaxError && e.pos !== undefined) {
  75. let message = e.message;
  76. const match = message.match(/^(.*) \(\d+:\d+\)$/);
  77. if (match) message = match[1];
  78. e = makeNiceSyntaxError(message, code, filename, e.pos, parser);
  79. }
  80. throw e;
  81. }
  82. if (args !== null) {
  83. const pBody = assertType(ast, 'Program').body;
  84. if (pBody.length !== 1) throw new SyntaxError('Single function literal required');
  85. const expr = pBody[0];
  86. if (expr.type !== 'ExpressionStatement') throw new SyntaxError('Single function literal required');
  87. const func = expr.expression;
  88. if (func.type !== 'FunctionExpression') throw new SyntaxError('Single function literal required');
  89. if (func.body.start !== argsOffset + 3) throw new SyntaxError('Unexpected end of arg string');
  90. }
  91. const insertions = [];
  92. let hasAsync = false;
  93. const TO_LEFT = -100;
  94. const TO_RIGHT = 100;
  95. let internStateValiable = undefined;
  96. acornWalkFull(ast, (node, state, type) => {
  97. if (type === 'Function') {
  98. if (node.async) hasAsync = true;
  99. }
  100. const nodeType = node.type;
  101. if (nodeType === 'CatchClause') {
  102. const param = node.param;
  103. if (param) {
  104. const name = assertType(param, 'Identifier').name;
  105. const cBody = assertType(node.body, 'BlockStatement');
  106. if (cBody.body.length > 0) {
  107. insertions.push({
  108. __proto__: null,
  109. pos: cBody.body[0].start,
  110. order: TO_LEFT,
  111. code: `${name}=${INTERNAL_STATE_NAME}.handleException(${name});`
  112. });
  113. }
  114. }
  115. } else if (nodeType === 'WithStatement') {
  116. insertions.push({
  117. __proto__: null,
  118. pos: node.object.start,
  119. order: TO_LEFT,
  120. code: INTERNAL_STATE_NAME + '.wrapWith('
  121. });
  122. insertions.push({
  123. __proto__: null,
  124. pos: node.object.end,
  125. order: TO_RIGHT,
  126. code: ')'
  127. });
  128. } else if (nodeType === 'Identifier') {
  129. if (node.name === INTERNAL_STATE_NAME) {
  130. if (internStateValiable === undefined || internStateValiable.start > node.start) {
  131. internStateValiable = node;
  132. }
  133. }
  134. } else if (nodeType === 'ImportExpression') {
  135. insertions.push({
  136. __proto__: null,
  137. pos: node.start,
  138. order: TO_RIGHT,
  139. code: INTERNAL_STATE_NAME + '.'
  140. });
  141. }
  142. });
  143. if (internStateValiable) {
  144. throw makeNiceSyntaxError('Use of internal vm2 state variable', code, filename, internStateValiable.start, {
  145. __proto__: null,
  146. start: internStateValiable.start,
  147. end: internStateValiable.end
  148. });
  149. }
  150. if (insertions.length === 0) return {__proto__: null, code, hasAsync};
  151. insertions.sort((a, b) => (a.pos == b.pos ? a.order - b.order : a.pos - b.pos));
  152. let ncode = '';
  153. let curr = 0;
  154. for (let i = 0; i < insertions.length; i++) {
  155. const change = insertions[i];
  156. ncode += code.substring(curr, change.pos) + change.code;
  157. curr = change.pos;
  158. }
  159. ncode += code.substring(curr);
  160. return {__proto__: null, code: ncode, hasAsync};
  161. }
  162. exports.INTERNAL_STATE_NAME = INTERNAL_STATE_NAME;
  163. exports.transformer = transformer;