/
transformer.js
121 lines (107 loc) · 3.27 KB
/
transformer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
const {parse: acornParse} = require('acorn');
const {full: acornWalkFull} = require('acorn-walk');
const {compileFunction} = require('vm');
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 transformer(args, body, isAsync, isGenerator) {
let code;
let argsOffset;
if (args === null) {
code = body;
} else {
code = isAsync ? '(async function' : '(function';
if (isGenerator) code += '*';
code += ' anonymous(';
code += args;
argsOffset = code.length;
code += '\n) {\n';
code += body;
code += '\n})';
}
let ast;
try {
ast = acornParse(code, {
__proto__: null,
ecmaVersion: 2020,
allowAwaitOutsideFunction: args === null && isAsync,
allowReturnOutsideFunction: args === null
});
} catch (e) {
// Try to generate a nicer error message.
compileFunction(code);
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 RIGHT = -100;
const LEFT = 100;
acornWalkFull(ast, (node, state, type) => {
if (type === '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: RIGHT,
code: `${name}=${INTERNAL_STATE_NAME}.handleException(${name});`
});
}
}
} else if (type === 'WithStatement') {
insertions.push({
__proto__: null,
pos: node.object.start,
order: RIGHT,
code: INTERNAL_STATE_NAME + '.wrapWith('
});
insertions.push({
__proto__: null,
pos: node.object.end,
order: LEFT,
code: ')'
});
} else if (type === 'Identifier') {
if (node.name === INTERNAL_STATE_NAME) {
throw new SyntaxError('Use of internal vm2 state variable');
}
} else if (type === 'ImportExpression') {
insertions.push({
__proto__: null,
pos: node.start,
order: LEFT,
code: INTERNAL_STATE_NAME + '.'
});
} else if (type === 'Function') {
if (node.async) hasAsync = true;
}
});
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;