| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.evaluateTruthy = evaluateTruthy; |
| exports.evaluate = evaluate; |
| const VALID_CALLEES = ["String", "Number", "Math"]; |
| const INVALID_METHODS = ["random"]; |
| |
| function evaluateTruthy() { |
| const res = this.evaluate(); |
| if (res.confident) return !!res.value; |
| } |
| |
| function deopt(path, state) { |
| if (!state.confident) return; |
| state.deoptPath = path; |
| state.confident = false; |
| } |
| |
| function evaluateCached(path, state) { |
| const { |
| node |
| } = path; |
| const { |
| seen |
| } = state; |
| |
| if (seen.has(node)) { |
| const existing = seen.get(node); |
| |
| if (existing.resolved) { |
| return existing.value; |
| } else { |
| deopt(path, state); |
| return; |
| } |
| } else { |
| const item = { |
| resolved: false |
| }; |
| seen.set(node, item); |
| |
| const val = _evaluate(path, state); |
| |
| if (state.confident) { |
| item.resolved = true; |
| item.value = val; |
| } |
| |
| return val; |
| } |
| } |
| |
| function _evaluate(path, state) { |
| if (!state.confident) return; |
| const { |
| node |
| } = path; |
| |
| if (path.isSequenceExpression()) { |
| const exprs = path.get("expressions"); |
| return evaluateCached(exprs[exprs.length - 1], state); |
| } |
| |
| if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) { |
| return node.value; |
| } |
| |
| if (path.isNullLiteral()) { |
| return null; |
| } |
| |
| if (path.isTemplateLiteral()) { |
| return evaluateQuasis(path, node.quasis, state); |
| } |
| |
| if (path.isTaggedTemplateExpression() && path.get("tag").isMemberExpression()) { |
| const object = path.get("tag.object"); |
| const { |
| node: { |
| name |
| } |
| } = object; |
| const property = path.get("tag.property"); |
| |
| if (object.isIdentifier() && name === "String" && !path.scope.getBinding(name, true) && property.isIdentifier && property.node.name === "raw") { |
| return evaluateQuasis(path, node.quasi.quasis, state, true); |
| } |
| } |
| |
| if (path.isConditionalExpression()) { |
| const testResult = evaluateCached(path.get("test"), state); |
| if (!state.confident) return; |
| |
| if (testResult) { |
| return evaluateCached(path.get("consequent"), state); |
| } else { |
| return evaluateCached(path.get("alternate"), state); |
| } |
| } |
| |
| if (path.isExpressionWrapper()) { |
| return evaluateCached(path.get("expression"), state); |
| } |
| |
| if (path.isMemberExpression() && !path.parentPath.isCallExpression({ |
| callee: node |
| })) { |
| const property = path.get("property"); |
| const object = path.get("object"); |
| |
| if (object.isLiteral() && property.isIdentifier()) { |
| const value = object.node.value; |
| const type = typeof value; |
| |
| if (type === "number" || type === "string") { |
| return value[property.node.name]; |
| } |
| } |
| } |
| |
| if (path.isReferencedIdentifier()) { |
| const binding = path.scope.getBinding(node.name); |
| |
| if (binding && binding.constantViolations.length > 0) { |
| return deopt(binding.path, state); |
| } |
| |
| if (binding && path.node.start < binding.path.node.end) { |
| return deopt(binding.path, state); |
| } |
| |
| if (binding && binding.hasValue) { |
| return binding.value; |
| } else { |
| if (node.name === "undefined") { |
| return binding ? deopt(binding.path, state) : undefined; |
| } else if (node.name === "Infinity") { |
| return binding ? deopt(binding.path, state) : Infinity; |
| } else if (node.name === "NaN") { |
| return binding ? deopt(binding.path, state) : NaN; |
| } |
| |
| const resolved = path.resolve(); |
| |
| if (resolved === path) { |
| return deopt(path, state); |
| } else { |
| return evaluateCached(resolved, state); |
| } |
| } |
| } |
| |
| if (path.isUnaryExpression({ |
| prefix: true |
| })) { |
| if (node.operator === "void") { |
| return undefined; |
| } |
| |
| const argument = path.get("argument"); |
| |
| if (node.operator === "typeof" && (argument.isFunction() || argument.isClass())) { |
| return "function"; |
| } |
| |
| const arg = evaluateCached(argument, state); |
| if (!state.confident) return; |
| |
| switch (node.operator) { |
| case "!": |
| return !arg; |
| |
| case "+": |
| return +arg; |
| |
| case "-": |
| return -arg; |
| |
| case "~": |
| return ~arg; |
| |
| case "typeof": |
| return typeof arg; |
| } |
| } |
| |
| if (path.isArrayExpression()) { |
| const arr = []; |
| const elems = path.get("elements"); |
| |
| for (const elem of elems) { |
| const elemValue = elem.evaluate(); |
| |
| if (elemValue.confident) { |
| arr.push(elemValue.value); |
| } else { |
| return deopt(elem, state); |
| } |
| } |
| |
| return arr; |
| } |
| |
| if (path.isObjectExpression()) { |
| const obj = {}; |
| const props = path.get("properties"); |
| |
| for (const prop of props) { |
| if (prop.isObjectMethod() || prop.isSpreadElement()) { |
| return deopt(prop, state); |
| } |
| |
| const keyPath = prop.get("key"); |
| let key = keyPath; |
| |
| if (prop.node.computed) { |
| key = key.evaluate(); |
| |
| if (!key.confident) { |
| return deopt(keyPath, state); |
| } |
| |
| key = key.value; |
| } else if (key.isIdentifier()) { |
| key = key.node.name; |
| } else { |
| key = key.node.value; |
| } |
| |
| const valuePath = prop.get("value"); |
| let value = valuePath.evaluate(); |
| |
| if (!value.confident) { |
| return deopt(valuePath, state); |
| } |
| |
| value = value.value; |
| obj[key] = value; |
| } |
| |
| return obj; |
| } |
| |
| if (path.isLogicalExpression()) { |
| const wasConfident = state.confident; |
| const left = evaluateCached(path.get("left"), state); |
| const leftConfident = state.confident; |
| state.confident = wasConfident; |
| const right = evaluateCached(path.get("right"), state); |
| const rightConfident = state.confident; |
| |
| switch (node.operator) { |
| case "||": |
| state.confident = leftConfident && (!!left || rightConfident); |
| if (!state.confident) return; |
| return left || right; |
| |
| case "&&": |
| state.confident = leftConfident && (!left || rightConfident); |
| if (!state.confident) return; |
| return left && right; |
| } |
| } |
| |
| if (path.isBinaryExpression()) { |
| const left = evaluateCached(path.get("left"), state); |
| if (!state.confident) return; |
| const right = evaluateCached(path.get("right"), state); |
| if (!state.confident) return; |
| |
| switch (node.operator) { |
| case "-": |
| return left - right; |
| |
| case "+": |
| return left + right; |
| |
| case "/": |
| return left / right; |
| |
| case "*": |
| return left * right; |
| |
| case "%": |
| return left % right; |
| |
| case "**": |
| return Math.pow(left, right); |
| |
| case "<": |
| return left < right; |
| |
| case ">": |
| return left > right; |
| |
| case "<=": |
| return left <= right; |
| |
| case ">=": |
| return left >= right; |
| |
| case "==": |
| return left == right; |
| |
| case "!=": |
| return left != right; |
| |
| case "===": |
| return left === right; |
| |
| case "!==": |
| return left !== right; |
| |
| case "|": |
| return left | right; |
| |
| case "&": |
| return left & right; |
| |
| case "^": |
| return left ^ right; |
| |
| case "<<": |
| return left << right; |
| |
| case ">>": |
| return left >> right; |
| |
| case ">>>": |
| return left >>> right; |
| } |
| } |
| |
| if (path.isCallExpression()) { |
| const callee = path.get("callee"); |
| let context; |
| let func; |
| |
| if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) { |
| func = global[node.callee.name]; |
| } |
| |
| if (callee.isMemberExpression()) { |
| const object = callee.get("object"); |
| const property = callee.get("property"); |
| |
| if (object.isIdentifier() && property.isIdentifier() && VALID_CALLEES.indexOf(object.node.name) >= 0 && INVALID_METHODS.indexOf(property.node.name) < 0) { |
| context = global[object.node.name]; |
| func = context[property.node.name]; |
| } |
| |
| if (object.isLiteral() && property.isIdentifier()) { |
| const type = typeof object.node.value; |
| |
| if (type === "string" || type === "number") { |
| context = object.node.value; |
| func = context[property.node.name]; |
| } |
| } |
| } |
| |
| if (func) { |
| const args = path.get("arguments").map(arg => evaluateCached(arg, state)); |
| if (!state.confident) return; |
| return func.apply(context, args); |
| } |
| } |
| |
| deopt(path, state); |
| } |
| |
| function evaluateQuasis(path, quasis, state, raw = false) { |
| let str = ""; |
| let i = 0; |
| const exprs = path.get("expressions"); |
| |
| for (const elem of quasis) { |
| if (!state.confident) break; |
| str += raw ? elem.value.raw : elem.value.cooked; |
| const expr = exprs[i++]; |
| if (expr) str += String(evaluateCached(expr, state)); |
| } |
| |
| if (!state.confident) return; |
| return str; |
| } |
| |
| function evaluate() { |
| const state = { |
| confident: true, |
| deoptPath: null, |
| seen: new Map() |
| }; |
| let value = evaluateCached(this, state); |
| if (!state.confident) value = undefined; |
| return { |
| confident: state.confident, |
| deopt: state.deoptPath, |
| value: value |
| }; |
| } |