UNPKG

estree-toolkit

Version:

Traverser, scope tracker, and more tools for working with ESTree AST

219 lines (218 loc) 8.91 kB
import { assertNever } from '../helpers.mjs'; const evaluateBinaryExpr = (left, right, operator) => { switch (operator) { case '==': return left == right; // eslint-disable-line eqeqeq case '!=': return left != right; // eslint-disable-line eqeqeq 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; case '**': return left ** right; case '|': return left | right; case '^': return left ^ right; case '&': return left & right; case 'in': return left in right; case 'instanceof': return; /* istanbul ignore next */ default: assertNever(operator); } }; class Evaluator { constructor() { this.cache = new Map(); } getEvaluated(path) { /* istanbul ignore if */ if (this.cache.has(path)) { const cachedResult = this.cache.get(path); return cachedResult; } else { const result = this.evaluate(path); this.cache.set(path, result); return result; } } evaluate(path) { var _a, _b; switch ( /* istanbul ignore next */(_a = path.node) === null || _a === void 0 ? void 0 : _a.type) { case 'Identifier': if (path.node.name === 'undefined') return { value: undefined }; break; case 'Literal': return { value: path.node.value }; case 'BinaryExpression': { const aPath = path; const left = this.getEvaluated(aPath.get('left')); if (left == null) return; const right = this.getEvaluated(aPath.get('right')); if (right == null) return; const value = evaluateBinaryExpr(left.value, right.value, aPath.node.operator); if (value == null) return; return { value }; } case 'UnaryExpression': { const aPath = path; const arg = this.getEvaluated(aPath.get('argument')); if (arg == null) return; const argVal = arg.value; switch (aPath.node.operator) { case '+': return { value: +argVal }; case '-': return { value: -argVal }; case '!': return { value: !argVal }; case '~': return { value: ~argVal }; case 'typeof': return { value: typeof argVal }; case 'void': return { value: undefined }; case 'delete': break; /* istanbul ignore next */ default: assertNever(aPath.node.operator); } break; } case 'LogicalExpression': { const aPath = path; const left = this.getEvaluated(aPath.get('left')); const right = this.getEvaluated(aPath.get('right')); switch (aPath.node.operator) { case '&&': { // If any of them is falsy, the expression will return `false` if (left != null && !left.value || right != null && !right.value) { return { value: false }; } // If both of them are truthy, the expression will return `true` if (left != null && left.value && right != null && right.value) { return { value: true }; } break; } case '||': { // If any of them is truthy, the expression will always return `true` if (left != null && left.value || right != null && right.value) { return { value: true }; } // If both of them are falsy, the expression will always return `false` if (left != null && !left.value && right != null && !right.value) { return { value: false }; } break; } case '??': { // Return left if it's not nullish // Return right if left is nullish if (left != null) { if (left.value != null) { return left; } else if (right != null) { return right; } } break; } /* istanbul ignore next */ default: assertNever(aPath.node.operator); } break; } case 'ObjectExpression': { const aPath = path; const object = {}; const properties = aPath.get('properties'); for (let i = 0; i < properties.length; i++) { const property = properties[i]; if (property.type === 'Property') { /* istanbul ignore if: never going to happen, but just in case */ if (property.node == null) return; if (property.node.kind !== 'init') return; const key = property.node.computed ? (_b = this.getEvaluated(property.get('key'))) === null || _b === void 0 ? void 0 : _b.value : property.node.key.name; if (key != null) { const value = this.getEvaluated(property.get('value')); if (value == null) return; object[key] = value.value; } else { return; } } else /* istanbul ignore else */ if (property.type === 'SpreadElement') { const argument = this.getEvaluated(property.get('argument')); if (argument == null) return; Object.assign(object, argument.value); } else { return; } } return { value: object }; } case 'ArrayExpression': { const aPath = path; const array = []; const elements = aPath.get('elements'); for (let i = 0; i < elements.length; i++) { const value = this.getEvaluated(elements[i]); if (value == null) return; array.push(value.value); } return { value: array }; } case 'ConditionalExpression': { const aPath = path; const test = this.getEvaluated(aPath.get('test')); if (test == null) return; return this.getEvaluated(aPath.get(test.value ? 'consequent' : 'alternate')); } } } } /** * Evaluates the given `path`. * @returns An object with property `value` if evaluation is successful, otherwise `undefined` */ export const evaluate = (path) => { const result = new Evaluator().evaluate(path); if (result != null) return { value: result.value }; }; /** * Evaluates the given path for truthiness * @returns `true` or `false` if the evaluation is successful, otherwise `undefined` */ export const evaluateTruthy = (path) => { const result = evaluate(path); if (result != null) return !!result.value; };