derby
Version:
MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers.
264 lines (263 loc) • 9.81 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPathExpression = void 0;
var esprima = require("esprima-derby");
var templates_1 = require("../templates");
var util_1 = require("../templates/util");
var Syntax = esprima.Syntax;
function createPathExpression(source) {
// @ts-expect-error `parse` not declared in @types/esprima
var parsed = esprima.parse(source);
var node = parsed.expression;
return reduce(node);
}
exports.createPathExpression = createPathExpression;
function reduce(node) {
var type = node.type;
if (type === Syntax.MemberExpression) {
return reduceMemberExpression(node);
}
else if (type === Syntax.Identifier) {
return reduceIdentifier(node);
}
else if (type === Syntax.ThisExpression) {
return reduceThis(node);
}
else if (type === Syntax.CallExpression) {
return reduceCallExpression(node);
}
else if (type === Syntax.Literal) {
return reduceLiteral(node);
}
else if (type === Syntax.UnaryExpression) {
return reduceUnaryExpression(node);
}
else if (type === Syntax.BinaryExpression || type === Syntax.LogicalExpression) {
return reduceBinaryExpression(node);
}
else if (type === Syntax.ConditionalExpression) {
return reduceConditionalExpression(node);
}
else if (type === Syntax.ArrayExpression) {
return reduceArrayExpression(node);
}
else if (type === Syntax.ObjectExpression) {
return reduceObjectExpression(node);
}
else if (type === Syntax.SequenceExpression) {
return reduceSequenceExpression(node);
}
else if (type === Syntax.NewExpression) {
return reduceNewExpression(node);
}
unexpected(node);
}
function reduceMemberExpression(node, afterSegments) {
if (node.computed) {
// Square brackets
if (node.property.type === Syntax.Literal) {
return reducePath(node, node.property.value, afterSegments);
}
var before_1 = reduce(node.object);
var inside = reduce(node.property);
return new templates_1.expressions.BracketsExpression(before_1, inside, afterSegments);
}
// Dot notation
if (node.property.type === Syntax.Identifier) {
return reducePath(node, node.property.name);
}
unexpected(node);
}
function reducePath(node, segment, afterSegments) {
var segments = [segment];
if (afterSegments)
segments = segments.concat(afterSegments);
var relative = false;
while ((node = node.object)) {
if (node.type === Syntax.MemberExpression) {
if (node.computed) {
return reduceMemberExpression(node, segments);
}
else if (node.property.type === Syntax.Identifier) {
segments.unshift(node.property.name);
}
else {
unexpected(node);
}
}
else if (node.type === Syntax.Identifier) {
segments.unshift(node.name);
}
else if (node.type === Syntax.ThisExpression) {
relative = true;
}
else if (node.type === Syntax.CallExpression) {
return reduceCallExpression(node, segments);
}
else if (node.type === Syntax.SequenceExpression) {
return reduceSequenceExpression(node, segments);
}
else if (node.type === Syntax.NewExpression) {
return reduceNewExpression(node, segments);
}
else {
unexpected(node);
}
}
return (relative) ?
new templates_1.expressions.RelativePathExpression(segments) :
createSegmentsExpression(segments);
}
function reduceIdentifier(node) {
var segments = [node.name];
return createSegmentsExpression(segments);
}
function reduceThis(_node) {
var segments = [];
return new templates_1.expressions.RelativePathExpression(segments);
}
function createSegmentsExpression(segments) {
var firstSegment = segments[0];
var firstChar = firstSegment.charAt && firstSegment.charAt(0);
if (firstChar === '#') {
var alias = firstSegment;
segments.shift();
return new templates_1.expressions.AliasPathExpression(alias, segments);
}
else if (firstChar === '@') {
var attribute = firstSegment.slice(1);
segments.shift();
return new templates_1.expressions.AttributePathExpression(attribute, segments);
}
else {
return new templates_1.expressions.PathExpression(segments);
}
}
function reduceCallExpression(node, afterSegments) {
return reduceFnExpression(node, afterSegments, templates_1.expressions.FnExpression);
}
function reduceNewExpression(node, afterSegments) {
return reduceFnExpression(node, afterSegments, templates_1.expressions.NewExpression);
}
function reduceFnExpression(node, afterSegments, Constructor) {
var args = node.arguments.map(reduce);
var callee = node.callee;
if (callee.type === Syntax.Identifier) {
if (callee.name === '$at') {
return new templates_1.expressions.ScopedModelExpression(args[0]);
}
return new Constructor([callee.name], args, afterSegments);
}
else if (callee.type === Syntax.MemberExpression) {
var segments = reduceMemberExpression(callee).segments;
return new Constructor(segments, args, afterSegments);
}
else {
unexpected(node);
}
}
function reduceLiteral(node) {
return new templates_1.expressions.LiteralExpression(node.value);
}
function reduceUnaryExpression(node) {
// `-` and `+` can be either unary or binary, so all unary operators are
// postfixed with `U` to differentiate
var operator = node.operator + 'U';
var expression = reduce(node.argument);
if (expression instanceof templates_1.expressions.LiteralExpression) {
var fn = templates_1.operatorFns.get[operator];
expression.value = fn(expression.value);
return expression;
}
return new templates_1.expressions.OperatorExpression(operator, [expression]);
}
function reduceBinaryExpression(node) {
var operator = node.operator;
var left = reduce(node.left);
var right = reduce(node.right);
if (left instanceof templates_1.expressions.LiteralExpression &&
right instanceof templates_1.expressions.LiteralExpression) {
var fn = templates_1.operatorFns.get[operator];
var value = fn(left.value, right.value);
return new templates_1.expressions.LiteralExpression(value);
}
return new templates_1.expressions.OperatorExpression(operator, [left, right]);
}
function reduceConditionalExpression(node) {
var test = reduce(node.test);
var consequent = reduce(node.consequent);
var alternate = reduce(node.alternate);
if (test instanceof templates_1.expressions.LiteralExpression &&
consequent instanceof templates_1.expressions.LiteralExpression &&
alternate instanceof templates_1.expressions.LiteralExpression) {
var value = (test.value) ? consequent.value : alternate.value;
return new templates_1.expressions.LiteralExpression(value);
}
return new templates_1.expressions.OperatorExpression('?', [test, consequent, alternate]);
}
function reduceArrayExpression(node) {
var literal = [];
var items = [];
var isLiteral = true;
for (var i = 0; i < node.elements.length; i++) {
var expression = reduce(node.elements[i]);
items.push(expression);
if (isLiteral && expression instanceof templates_1.expressions.LiteralExpression) {
literal.push(expression.value);
}
else {
isLiteral = false;
}
}
return (isLiteral) ?
new templates_1.expressions.LiteralExpression(literal) :
new templates_1.expressions.ArrayExpression(items);
}
function isProperty(property) {
return property.type === Syntax.Property;
}
function reduceObjectExpression(node) {
var literal = {};
var properties = {};
var isLiteral = true;
for (var i = 0; i < node.properties.length; i++) {
var property = node.properties[i];
if (isProperty(property)) {
var key = getKeyName(property.key);
var expression = reduce(property.value);
(0, util_1.checkKeyIsSafe)(key);
properties[key] = expression;
if (isLiteral && expression instanceof templates_1.expressions.LiteralExpression) {
literal[key] = expression.value;
}
else {
isLiteral = false;
}
}
else {
// actually a estree.SpreadElement and not supported
unexpected(node);
}
}
return (isLiteral) ?
new templates_1.expressions.LiteralExpression(literal) :
new templates_1.expressions.ObjectExpression(properties);
}
function getKeyName(node) {
return (node.type === Syntax.Identifier) ? node.name :
(node.type === Syntax.Literal) ? node.value :
unexpected(node);
}
function reduceSequenceExpression(node, afterSegments) {
// Note that sequence expressions are not reduced to a literal if they only
// contain literals. There isn't any utility to such an expression, so it
// isn't worth optimizing.
//
// The fact that expressions separated by commas always parse into a sequence
// is relied upon in parsing template tags that have comma-separated
// arguments following a keyword
var args = node.expressions.map(reduce);
return new templates_1.expressions.SequenceExpression(args, afterSegments);
}
function unexpected(node) {
throw new Error('Unexpected Esprima node: ' + JSON.stringify(node, null, 2));
}