123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- const {Parser: AcornParser, isNewLine: acornIsNewLine, getLineInfo: acornGetLineInfo} = require('acorn');
- const {full: acornWalkFull} = require('acorn-walk');
- const INTERNAL_STATE_NAME = 'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL';
- function assertType(node, type) {
- if (!node) throw new Error(`None existent node expected '${type}'`);
- if (node.type !== type) throw new Error(`Invalid node type '${node.type}' expected '${type}'`);
- return node;
- }
- function makeNiceSyntaxError(message, code, filename, location, tokenizer) {
- const loc = acornGetLineInfo(code, location);
- let end = location;
- while (end < code.length && !acornIsNewLine(code.charCodeAt(end))) {
- end++;
- }
- let markerEnd = tokenizer.start === location ? tokenizer.end : location + 1;
- if (!markerEnd || markerEnd > end) markerEnd = end;
- let markerLen = markerEnd - location;
- if (markerLen <= 0) markerLen = 1;
- if (message === 'Unexpected token') {
- const type = tokenizer.type;
- if (type.label === 'name' || type.label === 'privateId') {
- message = 'Unexpected identifier';
- } else if (type.label === 'eof') {
- message = 'Unexpected end of input';
- } else if (type.label === 'num') {
- message = 'Unexpected number';
- } else if (type.label === 'string') {
- message = 'Unexpected string';
- } else if (type.label === 'regexp') {
- message = 'Unexpected token \'/\'';
- markerLen = 1;
- } else {
- const token = tokenizer.value || type.label;
- message = `Unexpected token '${token}'`;
- }
- }
- const error = new SyntaxError(message);
- if (!filename) return error;
- const line = code.slice(location - loc.column, end);
- const marker = line.slice(0, loc.column).replace(/\S/g, ' ') + '^'.repeat(markerLen);
- error.stack = `${filename}:${loc.line}\n${line}\n${marker}\n\n${error.stack}`;
- return error;
- }
- function transformer(args, body, isAsync, isGenerator, filename) {
- let code;
- let argsOffset;
- if (args === null) {
- code = body;
- // Note: Keywords are not allows to contain u escapes
- if (!/\b(?:catch|import|async)\b/.test(code)) {
- return {__proto__: null, code, hasAsync: false};
- }
- } else {
- code = isAsync ? '(async function' : '(function';
- if (isGenerator) code += '*';
- code += ' anonymous(';
- code += args;
- argsOffset = code.length;
- code += '\n) {\n';
- code += body;
- code += '\n})';
- }
- const parser = new AcornParser({
- __proto__: null,
- ecmaVersion: 2022,
- allowAwaitOutsideFunction: args === null && isAsync,
- allowReturnOutsideFunction: args === null
- }, code);
- let ast;
- try {
- ast = parser.parse();
- } catch (e) {
- // Try to generate a nicer error message.
- if (e instanceof SyntaxError && e.pos !== undefined) {
- let message = e.message;
- const match = message.match(/^(.*) \(\d+:\d+\)$/);
- if (match) message = match[1];
- e = makeNiceSyntaxError(message, code, filename, e.pos, parser);
- }
- throw e;
- }
- if (args !== null) {
- const pBody = assertType(ast, 'Program').body;
- if (pBody.length !== 1) throw new SyntaxError('Single function literal required');
- const expr = pBody[0];
- if (expr.type !== 'ExpressionStatement') throw new SyntaxError('Single function literal required');
- const func = expr.expression;
- if (func.type !== 'FunctionExpression') throw new SyntaxError('Single function literal required');
- if (func.body.start !== argsOffset + 3) throw new SyntaxError('Unexpected end of arg string');
- }
- const insertions = [];
- let hasAsync = false;
- const TO_LEFT = -100;
- const TO_RIGHT = 100;
- let internStateValiable = undefined;
- acornWalkFull(ast, (node, state, type) => {
- if (type === 'Function') {
- if (node.async) hasAsync = true;
- }
- const nodeType = node.type;
- if (nodeType === 'CatchClause') {
- const param = node.param;
- if (param) {
- const name = assertType(param, 'Identifier').name;
- const cBody = assertType(node.body, 'BlockStatement');
- if (cBody.body.length > 0) {
- insertions.push({
- __proto__: null,
- pos: cBody.body[0].start,
- order: TO_LEFT,
- code: `${name}=${INTERNAL_STATE_NAME}.handleException(${name});`
- });
- }
- }
- } else if (nodeType === 'WithStatement') {
- insertions.push({
- __proto__: null,
- pos: node.object.start,
- order: TO_LEFT,
- code: INTERNAL_STATE_NAME + '.wrapWith('
- });
- insertions.push({
- __proto__: null,
- pos: node.object.end,
- order: TO_RIGHT,
- code: ')'
- });
- } else if (nodeType === 'Identifier') {
- if (node.name === INTERNAL_STATE_NAME) {
- if (internStateValiable === undefined || internStateValiable.start > node.start) {
- internStateValiable = node;
- }
- }
- } else if (nodeType === 'ImportExpression') {
- insertions.push({
- __proto__: null,
- pos: node.start,
- order: TO_RIGHT,
- code: INTERNAL_STATE_NAME + '.'
- });
- }
- });
- if (internStateValiable) {
- throw makeNiceSyntaxError('Use of internal vm2 state variable', code, filename, internStateValiable.start, {
- __proto__: null,
- start: internStateValiable.start,
- end: internStateValiable.end
- });
- }
- if (insertions.length === 0) return {__proto__: null, code, hasAsync};
- insertions.sort((a, b) => (a.pos == b.pos ? a.order - b.order : a.pos - b.pos));
- let ncode = '';
- let curr = 0;
- for (let i = 0; i < insertions.length; i++) {
- const change = insertions[i];
- ncode += code.substring(curr, change.pos) + change.code;
- curr = change.pos;
- }
- ncode += code.substring(curr);
- return {__proto__: null, code: ncode, hasAsync};
- }
- exports.INTERNAL_STATE_NAME = INTERNAL_STATE_NAME;
- exports.transformer = transformer;
|