UNPKG

9.15 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.evaluateTruthy = evaluateTruthy;
7exports.evaluate = evaluate;
8const VALID_CALLEES = ["String", "Number", "Math"];
9const INVALID_METHODS = ["random"];
10
11function evaluateTruthy() {
12 const res = this.evaluate();
13 if (res.confident) return !!res.value;
14}
15
16function deopt(path, state) {
17 if (!state.confident) return;
18 state.deoptPath = path;
19 state.confident = false;
20}
21
22function evaluateCached(path, state) {
23 const {
24 node
25 } = path;
26 const {
27 seen
28 } = state;
29
30 if (seen.has(node)) {
31 const existing = seen.get(node);
32
33 if (existing.resolved) {
34 return existing.value;
35 } else {
36 deopt(path, state);
37 return;
38 }
39 } else {
40 const item = {
41 resolved: false
42 };
43 seen.set(node, item);
44
45 const val = _evaluate(path, state);
46
47 if (state.confident) {
48 item.resolved = true;
49 item.value = val;
50 }
51
52 return val;
53 }
54}
55
56function _evaluate(path, state) {
57 if (!state.confident) return;
58 const {
59 node
60 } = path;
61
62 if (path.isSequenceExpression()) {
63 const exprs = path.get("expressions");
64 return evaluateCached(exprs[exprs.length - 1], state);
65 }
66
67 if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) {
68 return node.value;
69 }
70
71 if (path.isNullLiteral()) {
72 return null;
73 }
74
75 if (path.isTemplateLiteral()) {
76 return evaluateQuasis(path, node.quasis, state);
77 }
78
79 if (path.isTaggedTemplateExpression() && path.get("tag").isMemberExpression()) {
80 const object = path.get("tag.object");
81 const {
82 node: {
83 name
84 }
85 } = object;
86 const property = path.get("tag.property");
87
88 if (object.isIdentifier() && name === "String" && !path.scope.getBinding(name, true) && property.isIdentifier && property.node.name === "raw") {
89 return evaluateQuasis(path, node.quasi.quasis, state, true);
90 }
91 }
92
93 if (path.isConditionalExpression()) {
94 const testResult = evaluateCached(path.get("test"), state);
95 if (!state.confident) return;
96
97 if (testResult) {
98 return evaluateCached(path.get("consequent"), state);
99 } else {
100 return evaluateCached(path.get("alternate"), state);
101 }
102 }
103
104 if (path.isExpressionWrapper()) {
105 return evaluateCached(path.get("expression"), state);
106 }
107
108 if (path.isMemberExpression() && !path.parentPath.isCallExpression({
109 callee: node
110 })) {
111 const property = path.get("property");
112 const object = path.get("object");
113
114 if (object.isLiteral() && property.isIdentifier()) {
115 const value = object.node.value;
116 const type = typeof value;
117
118 if (type === "number" || type === "string") {
119 return value[property.node.name];
120 }
121 }
122 }
123
124 if (path.isReferencedIdentifier()) {
125 const binding = path.scope.getBinding(node.name);
126
127 if (binding && binding.constantViolations.length > 0) {
128 return deopt(binding.path, state);
129 }
130
131 if (binding && path.node.start < binding.path.node.end) {
132 return deopt(binding.path, state);
133 }
134
135 if (binding && binding.hasValue) {
136 return binding.value;
137 } else {
138 if (node.name === "undefined") {
139 return binding ? deopt(binding.path, state) : undefined;
140 } else if (node.name === "Infinity") {
141 return binding ? deopt(binding.path, state) : Infinity;
142 } else if (node.name === "NaN") {
143 return binding ? deopt(binding.path, state) : NaN;
144 }
145
146 const resolved = path.resolve();
147
148 if (resolved === path) {
149 return deopt(path, state);
150 } else {
151 return evaluateCached(resolved, state);
152 }
153 }
154 }
155
156 if (path.isUnaryExpression({
157 prefix: true
158 })) {
159 if (node.operator === "void") {
160 return undefined;
161 }
162
163 const argument = path.get("argument");
164
165 if (node.operator === "typeof" && (argument.isFunction() || argument.isClass())) {
166 return "function";
167 }
168
169 const arg = evaluateCached(argument, state);
170 if (!state.confident) return;
171
172 switch (node.operator) {
173 case "!":
174 return !arg;
175
176 case "+":
177 return +arg;
178
179 case "-":
180 return -arg;
181
182 case "~":
183 return ~arg;
184
185 case "typeof":
186 return typeof arg;
187 }
188 }
189
190 if (path.isArrayExpression()) {
191 const arr = [];
192 const elems = path.get("elements");
193
194 for (const elem of elems) {
195 const elemValue = elem.evaluate();
196
197 if (elemValue.confident) {
198 arr.push(elemValue.value);
199 } else {
200 return deopt(elem, state);
201 }
202 }
203
204 return arr;
205 }
206
207 if (path.isObjectExpression()) {
208 const obj = {};
209 const props = path.get("properties");
210
211 for (const prop of props) {
212 if (prop.isObjectMethod() || prop.isSpreadElement()) {
213 return deopt(prop, state);
214 }
215
216 const keyPath = prop.get("key");
217 let key = keyPath;
218
219 if (prop.node.computed) {
220 key = key.evaluate();
221
222 if (!key.confident) {
223 return deopt(keyPath, state);
224 }
225
226 key = key.value;
227 } else if (key.isIdentifier()) {
228 key = key.node.name;
229 } else {
230 key = key.node.value;
231 }
232
233 const valuePath = prop.get("value");
234 let value = valuePath.evaluate();
235
236 if (!value.confident) {
237 return deopt(valuePath, state);
238 }
239
240 value = value.value;
241 obj[key] = value;
242 }
243
244 return obj;
245 }
246
247 if (path.isLogicalExpression()) {
248 const wasConfident = state.confident;
249 const left = evaluateCached(path.get("left"), state);
250 const leftConfident = state.confident;
251 state.confident = wasConfident;
252 const right = evaluateCached(path.get("right"), state);
253 const rightConfident = state.confident;
254
255 switch (node.operator) {
256 case "||":
257 state.confident = leftConfident && (!!left || rightConfident);
258 if (!state.confident) return;
259 return left || right;
260
261 case "&&":
262 state.confident = leftConfident && (!left || rightConfident);
263 if (!state.confident) return;
264 return left && right;
265 }
266 }
267
268 if (path.isBinaryExpression()) {
269 const left = evaluateCached(path.get("left"), state);
270 if (!state.confident) return;
271 const right = evaluateCached(path.get("right"), state);
272 if (!state.confident) return;
273
274 switch (node.operator) {
275 case "-":
276 return left - right;
277
278 case "+":
279 return left + right;
280
281 case "/":
282 return left / right;
283
284 case "*":
285 return left * right;
286
287 case "%":
288 return left % right;
289
290 case "**":
291 return Math.pow(left, right);
292
293 case "<":
294 return left < right;
295
296 case ">":
297 return left > right;
298
299 case "<=":
300 return left <= right;
301
302 case ">=":
303 return left >= right;
304
305 case "==":
306 return left == right;
307
308 case "!=":
309 return left != right;
310
311 case "===":
312 return left === right;
313
314 case "!==":
315 return left !== right;
316
317 case "|":
318 return left | right;
319
320 case "&":
321 return left & right;
322
323 case "^":
324 return left ^ right;
325
326 case "<<":
327 return left << right;
328
329 case ">>":
330 return left >> right;
331
332 case ">>>":
333 return left >>> right;
334 }
335 }
336
337 if (path.isCallExpression()) {
338 const callee = path.get("callee");
339 let context;
340 let func;
341
342 if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) {
343 func = global[node.callee.name];
344 }
345
346 if (callee.isMemberExpression()) {
347 const object = callee.get("object");
348 const property = callee.get("property");
349
350 if (object.isIdentifier() && property.isIdentifier() && VALID_CALLEES.indexOf(object.node.name) >= 0 && INVALID_METHODS.indexOf(property.node.name) < 0) {
351 context = global[object.node.name];
352 func = context[property.node.name];
353 }
354
355 if (object.isLiteral() && property.isIdentifier()) {
356 const type = typeof object.node.value;
357
358 if (type === "string" || type === "number") {
359 context = object.node.value;
360 func = context[property.node.name];
361 }
362 }
363 }
364
365 if (func) {
366 const args = path.get("arguments").map(arg => evaluateCached(arg, state));
367 if (!state.confident) return;
368 return func.apply(context, args);
369 }
370 }
371
372 deopt(path, state);
373}
374
375function evaluateQuasis(path, quasis, state, raw = false) {
376 let str = "";
377 let i = 0;
378 const exprs = path.get("expressions");
379
380 for (const elem of quasis) {
381 if (!state.confident) break;
382 str += raw ? elem.value.raw : elem.value.cooked;
383 const expr = exprs[i++];
384 if (expr) str += String(evaluateCached(expr, state));
385 }
386
387 if (!state.confident) return;
388 return str;
389}
390
391function evaluate() {
392 const state = {
393 confident: true,
394 deoptPath: null,
395 seen: new Map()
396 };
397 let value = evaluateCached(this, state);
398 if (!state.confident) value = undefined;
399 return {
400 confident: state.confident,
401 deopt: state.deoptPath,
402 value: value
403 };
404}
\No newline at end of file