"use strict"; /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ Object.defineProperty(exports, "__esModule", { value: true }); var ts = require("typescript"); var schema_1 = require("./schema"); // In TypeScript 2.1 the spread element kind was renamed. var spreadElementSyntaxKind = ts.SyntaxKind.SpreadElement || ts.SyntaxKind.SpreadElementExpression; function isMethodCallOf(callExpression, memberName) { var expression = callExpression.expression; if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) { var propertyAccessExpression = expression; var name_1 = propertyAccessExpression.name; if (name_1.kind == ts.SyntaxKind.Identifier) { return name_1.text === memberName; } } return false; } function isCallOf(callExpression, ident) { var expression = callExpression.expression; if (expression.kind === ts.SyntaxKind.Identifier) { var identifier = expression; return identifier.text === ident; } return false; } /** * ts.forEachChild stops iterating children when the callback return a truthy value. * This method inverts this to implement an `every` style iterator. It will return * true if every call to `cb` returns `true`. */ function everyNodeChild(node, cb) { return !ts.forEachChild(node, function (node) { return !cb(node); }); } function isPrimitive(value) { return Object(value) !== value; } exports.isPrimitive = isPrimitive; function isDefined(obj) { return obj !== undefined; } function getSourceFileOfNode(node) { while (node && node.kind != ts.SyntaxKind.SourceFile) { node = node.parent; } return node; } /* @internal */ function errorSymbol(message, node, context, sourceFile) { var result; if (node) { sourceFile = sourceFile || getSourceFileOfNode(node); if (sourceFile) { var _a = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)), line = _a.line, character = _a.character; result = { __symbolic: 'error', message: message, line: line, character: character }; } } if (!result) { result = { __symbolic: 'error', message: message }; } if (context) { result.context = context; } return result; } exports.errorSymbol = errorSymbol; /** * Produce a symbolic representation of an expression folding values into their final value when * possible. */ var Evaluator = (function () { function Evaluator(symbols, nodeMap, options) { if (options === void 0) { options = {}; } this.symbols = symbols; this.nodeMap = nodeMap; this.options = options; } Evaluator.prototype.nameOf = function (node) { if (node.kind == ts.SyntaxKind.Identifier) { return node.text; } var result = this.evaluateNode(node); if (schema_1.isMetadataError(result) || typeof result === 'string') { return result; } else { return errorSymbol('Name expected', node, { received: node.getText() }); } }; /** * Returns true if the expression represented by `node` can be folded into a literal expression. * * For example, a literal is always foldable. This means that literal expressions such as `1.2` * `"Some value"` `true` `false` are foldable. * * - An object literal is foldable if all the properties in the literal are foldable. * - An array literal is foldable if all the elements are foldable. * - A call is foldable if it is a call to a Array.prototype.concat or a call to CONST_EXPR. * - A property access is foldable if the object is foldable. * - A array index is foldable if index expression is foldable and the array is foldable. * - Binary operator expressions are foldable if the left and right expressions are foldable and * it is one of '+', '-', '*', '/', '%', '||', and '&&'. * - An identifier is foldable if a value can be found for its symbol in the evaluator symbol * table. */ Evaluator.prototype.isFoldable = function (node) { return this.isFoldableWorker(node, new Map()); }; Evaluator.prototype.isFoldableWorker = function (node, folding) { var _this = this; if (node) { switch (node.kind) { case ts.SyntaxKind.ObjectLiteralExpression: return everyNodeChild(node, function (child) { if (child.kind === ts.SyntaxKind.PropertyAssignment) { var propertyAssignment = child; return _this.isFoldableWorker(propertyAssignment.initializer, folding); } return false; }); case ts.SyntaxKind.ArrayLiteralExpression: return everyNodeChild(node, function (child) { return _this.isFoldableWorker(child, folding); }); case ts.SyntaxKind.CallExpression: var callExpression = node; // We can fold a .concat(). if (isMethodCallOf(callExpression, 'concat') && arrayOrEmpty(callExpression.arguments).length === 1) { var arrayNode = callExpression.expression.expression; if (this.isFoldableWorker(arrayNode, folding) && this.isFoldableWorker(callExpression.arguments[0], folding)) { // It needs to be an array. var arrayValue = this.evaluateNode(arrayNode); if (arrayValue && Array.isArray(arrayValue)) { return true; } } } // We can fold a call to CONST_EXPR if (isCallOf(callExpression, 'CONST_EXPR') && arrayOrEmpty(callExpression.arguments).length === 1) return this.isFoldableWorker(callExpression.arguments[0], folding); return false; case ts.SyntaxKind.NoSubstitutionTemplateLiteral: case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.NumericLiteral: case ts.SyntaxKind.NullKeyword: case ts.SyntaxKind.TrueKeyword: case ts.SyntaxKind.FalseKeyword: case ts.SyntaxKind.TemplateHead: case ts.SyntaxKind.TemplateMiddle: case ts.SyntaxKind.TemplateTail: return true; case ts.SyntaxKind.ParenthesizedExpression: var parenthesizedExpression = node; return this.isFoldableWorker(parenthesizedExpression.expression, folding); case ts.SyntaxKind.BinaryExpression: var binaryExpression = node; switch (binaryExpression.operatorToken.kind) { case ts.SyntaxKind.PlusToken: case ts.SyntaxKind.MinusToken: case ts.SyntaxKind.AsteriskToken: case ts.SyntaxKind.SlashToken: case ts.SyntaxKind.PercentToken: case ts.SyntaxKind.AmpersandAmpersandToken: case ts.SyntaxKind.BarBarToken: return this.isFoldableWorker(binaryExpression.left, folding) && this.isFoldableWorker(binaryExpression.right, folding); default: return false; } case ts.SyntaxKind.PropertyAccessExpression: var propertyAccessExpression = node; return this.isFoldableWorker(propertyAccessExpression.expression, folding); case ts.SyntaxKind.ElementAccessExpression: var elementAccessExpression = node; return this.isFoldableWorker(elementAccessExpression.expression, folding) && this.isFoldableWorker(elementAccessExpression.argumentExpression, folding); case ts.SyntaxKind.Identifier: var identifier = node; var reference = this.symbols.resolve(identifier.text); if (reference !== undefined && isPrimitive(reference)) { return true; } break; case ts.SyntaxKind.TemplateExpression: var templateExpression = node; return templateExpression.templateSpans.every(function (span) { return _this.isFoldableWorker(span.expression, folding); }); } } return false; }; /** * Produce a JSON serialiable object representing `node`. The foldable values in the expression * tree are folded. For example, a node representing `1 + 2` is folded into `3`. */ Evaluator.prototype.evaluateNode = function (node) { var _this = this; var t = this; var error; function recordEntry(entry, node) { t.nodeMap.set(entry, node); return entry; } function isFoldableError(value) { return !t.options.verboseInvalidExpression && schema_1.isMetadataError(value); } var resolveName = function (name) { var reference = _this.symbols.resolve(name); if (reference === undefined) { // Encode as a global reference. StaticReflector will check the reference. return recordEntry({ __symbolic: 'reference', name: name }, node); } return reference; }; switch (node.kind) { case ts.SyntaxKind.ObjectLiteralExpression: var obj_1 = {}; var quoted_1 = []; ts.forEachChild(node, function (child) { switch (child.kind) { case ts.SyntaxKind.ShorthandPropertyAssignment: case ts.SyntaxKind.PropertyAssignment: var assignment = child; if (assignment.name.kind == ts.SyntaxKind.StringLiteral) { var name_2 = assignment.name.text; quoted_1.push(name_2); } var propertyName = _this.nameOf(assignment.name); if (isFoldableError(propertyName)) { error = propertyName; return true; } var propertyValue = isPropertyAssignment(assignment) ? _this.evaluateNode(assignment.initializer) : resolveName(propertyName); if (isFoldableError(propertyValue)) { error = propertyValue; return true; // Stop the forEachChild. } else { obj_1[propertyName] = propertyValue; } } }); if (error) return error; if (this.options.quotedNames && quoted_1.length) { obj_1['$quoted$'] = quoted_1; } return obj_1; case ts.SyntaxKind.ArrayLiteralExpression: var arr_1 = []; ts.forEachChild(node, function (child) { var value = _this.evaluateNode(child); // Check for error if (isFoldableError(value)) { error = value; return true; // Stop the forEachChild. } // Handle spread expressions if (schema_1.isMetadataSymbolicSpreadExpression(value)) { if (Array.isArray(value.expression)) { for (var _i = 0, _a = value.expression; _i < _a.length; _i++) { var spreadValue = _a[_i]; arr_1.push(spreadValue); } return; } } arr_1.push(value); }); if (error) return error; return arr_1; case spreadElementSyntaxKind: var spreadExpression = this.evaluateNode(node.expression); return recordEntry({ __symbolic: 'spread', expression: spreadExpression }, node); case ts.SyntaxKind.CallExpression: var callExpression = node; if (isCallOf(callExpression, 'forwardRef') && arrayOrEmpty(callExpression.arguments).length === 1) { var firstArgument = callExpression.arguments[0]; if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) { var arrowFunction = firstArgument; return recordEntry(this.evaluateNode(arrowFunction.body), node); } } var args_1 = arrayOrEmpty(callExpression.arguments).map(function (arg) { return _this.evaluateNode(arg); }); if (!this.options.verboseInvalidExpression && args_1.some(schema_1.isMetadataError)) { return args_1.find(schema_1.isMetadataError); } if (this.isFoldable(callExpression)) { if (isMethodCallOf(callExpression, 'concat')) { var arrayValue = this.evaluateNode(callExpression.expression.expression); if (isFoldableError(arrayValue)) return arrayValue; return arrayValue.concat(args_1[0]); } } // Always fold a CONST_EXPR even if the argument is not foldable. if (isCallOf(callExpression, 'CONST_EXPR') && arrayOrEmpty(callExpression.arguments).length === 1) { return recordEntry(args_1[0], node); } var expression = this.evaluateNode(callExpression.expression); if (isFoldableError(expression)) { return recordEntry(expression, node); } var result = { __symbolic: 'call', expression: expression }; if (args_1 && args_1.length) { result.arguments = args_1; } return recordEntry(result, node); case ts.SyntaxKind.NewExpression: var newExpression = node; var newArgs = arrayOrEmpty(newExpression.arguments).map(function (arg) { return _this.evaluateNode(arg); }); if (!this.options.verboseInvalidExpression && newArgs.some(schema_1.isMetadataError)) { return recordEntry(newArgs.find(schema_1.isMetadataError), node); } var newTarget = this.evaluateNode(newExpression.expression); if (schema_1.isMetadataError(newTarget)) { return recordEntry(newTarget, node); } var call = { __symbolic: 'new', expression: newTarget }; if (newArgs.length) { call.arguments = newArgs; } return recordEntry(call, node); case ts.SyntaxKind.PropertyAccessExpression: { var propertyAccessExpression = node; var expression_1 = this.evaluateNode(propertyAccessExpression.expression); if (isFoldableError(expression_1)) { return recordEntry(expression_1, node); } var member = this.nameOf(propertyAccessExpression.name); if (isFoldableError(member)) { return recordEntry(member, node); } if (expression_1 && this.isFoldable(propertyAccessExpression.expression)) return expression_1[member]; if (schema_1.isMetadataModuleReferenceExpression(expression_1)) { // A select into a module refrence and be converted into a reference to the symbol // in the module return recordEntry({ __symbolic: 'reference', module: expression_1.module, name: member }, node); } return recordEntry({ __symbolic: 'select', expression: expression_1, member: member }, node); } case ts.SyntaxKind.ElementAccessExpression: { var elementAccessExpression = node; var expression_2 = this.evaluateNode(elementAccessExpression.expression); if (isFoldableError(expression_2)) { return recordEntry(expression_2, node); } var index = this.evaluateNode(elementAccessExpression.argumentExpression); if (isFoldableError(expression_2)) { return recordEntry(expression_2, node); } if (this.isFoldable(elementAccessExpression.expression) && this.isFoldable(elementAccessExpression.argumentExpression)) return expression_2[index]; return recordEntry({ __symbolic: 'index', expression: expression_2, index: index }, node); } case ts.SyntaxKind.Identifier: var identifier = node; var name_3 = identifier.text; return resolveName(name_3); case ts.SyntaxKind.TypeReference: var typeReferenceNode = node; var typeNameNode_1 = typeReferenceNode.typeName; var getReference = function (node) { if (typeNameNode_1.kind === ts.SyntaxKind.QualifiedName) { var qualifiedName = node; var left_1 = _this.evaluateNode(qualifiedName.left); if (schema_1.isMetadataModuleReferenceExpression(left_1)) { return recordEntry({ __symbolic: 'reference', module: left_1.module, name: qualifiedName.right.text }, node); } // Record a type reference to a declared type as a select. return { __symbolic: 'select', expression: left_1, member: qualifiedName.right.text }; } else { var identifier_1 = typeNameNode_1; var symbol = _this.symbols.resolve(identifier_1.text); if (isFoldableError(symbol) || schema_1.isMetadataSymbolicReferenceExpression(symbol)) { return recordEntry(symbol, node); } return recordEntry(errorSymbol('Could not resolve type', node, { typeName: identifier_1.text }), node); } }; var typeReference = getReference(typeNameNode_1); if (isFoldableError(typeReference)) { return recordEntry(typeReference, node); } if (!schema_1.isMetadataModuleReferenceExpression(typeReference) && typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) { var args_2 = typeReferenceNode.typeArguments.map(function (element) { return _this.evaluateNode(element); }); // TODO: Remove typecast when upgraded to 2.0 as it will be corretly inferred. // Some versions of 1.9 do not infer this correctly. typeReference.arguments = args_2; } return recordEntry(typeReference, node); case ts.SyntaxKind.UnionType: var unionType = node; // Remove null and undefined from the list of unions. var references = unionType.types .filter(function (n) { return n.kind != ts.SyntaxKind.NullKeyword && n.kind != ts.SyntaxKind.UndefinedKeyword; }) .map(function (n) { return _this.evaluateNode(n); }); // The remmaining reference must be the same. If two have type arguments consider them // different even if the type arguments are the same. var candidate = null; for (var i = 0; i < references.length; i++) { var reference = references[i]; if (schema_1.isMetadataSymbolicReferenceExpression(reference)) { if (candidate) { if (reference.name == candidate.name && reference.module == candidate.module && !reference.arguments) { candidate = reference; } } else { candidate = reference; } } else { return reference; } } if (candidate) return candidate; break; case ts.SyntaxKind.NoSubstitutionTemplateLiteral: case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.TemplateHead: case ts.SyntaxKind.TemplateTail: case ts.SyntaxKind.TemplateMiddle: return node.text; case ts.SyntaxKind.NumericLiteral: return parseFloat(node.text); case ts.SyntaxKind.AnyKeyword: return recordEntry({ __symbolic: 'reference', name: 'any' }, node); case ts.SyntaxKind.StringKeyword: return recordEntry({ __symbolic: 'reference', name: 'string' }, node); case ts.SyntaxKind.NumberKeyword: return recordEntry({ __symbolic: 'reference', name: 'number' }, node); case ts.SyntaxKind.BooleanKeyword: return recordEntry({ __symbolic: 'reference', name: 'boolean' }, node); case ts.SyntaxKind.ArrayType: var arrayTypeNode = node; return recordEntry({ __symbolic: 'reference', name: 'Array', arguments: [this.evaluateNode(arrayTypeNode.elementType)] }, node); case ts.SyntaxKind.NullKeyword: return null; case ts.SyntaxKind.TrueKeyword: return true; case ts.SyntaxKind.FalseKeyword: return false; case ts.SyntaxKind.ParenthesizedExpression: var parenthesizedExpression = node; return this.evaluateNode(parenthesizedExpression.expression); case ts.SyntaxKind.TypeAssertionExpression: var typeAssertion = node; return this.evaluateNode(typeAssertion.expression); case ts.SyntaxKind.PrefixUnaryExpression: var prefixUnaryExpression = node; var operand = this.evaluateNode(prefixUnaryExpression.operand); if (isDefined(operand) && isPrimitive(operand)) { switch (prefixUnaryExpression.operator) { case ts.SyntaxKind.PlusToken: return +operand; case ts.SyntaxKind.MinusToken: return -operand; case ts.SyntaxKind.TildeToken: return ~operand; case ts.SyntaxKind.ExclamationToken: return !operand; } } var operatorText = void 0; switch (prefixUnaryExpression.operator) { case ts.SyntaxKind.PlusToken: operatorText = '+'; break; case ts.SyntaxKind.MinusToken: operatorText = '-'; break; case ts.SyntaxKind.TildeToken: operatorText = '~'; break; case ts.SyntaxKind.ExclamationToken: operatorText = '!'; break; default: return undefined; } return recordEntry({ __symbolic: 'pre', operator: operatorText, operand: operand }, node); case ts.SyntaxKind.BinaryExpression: var binaryExpression = node; var left = this.evaluateNode(binaryExpression.left); var right = this.evaluateNode(binaryExpression.right); if (isDefined(left) && isDefined(right)) { if (isPrimitive(left) && isPrimitive(right)) switch (binaryExpression.operatorToken.kind) { case ts.SyntaxKind.BarBarToken: return left || right; case ts.SyntaxKind.AmpersandAmpersandToken: return left && right; case ts.SyntaxKind.AmpersandToken: return left & right; case ts.SyntaxKind.BarToken: return left | right; case ts.SyntaxKind.CaretToken: return left ^ right; case ts.SyntaxKind.EqualsEqualsToken: return left == right; case ts.SyntaxKind.ExclamationEqualsToken: return left != right; case ts.SyntaxKind.EqualsEqualsEqualsToken: return left === right; case ts.SyntaxKind.ExclamationEqualsEqualsToken: return left !== right; case ts.SyntaxKind.LessThanToken: return left < right; case ts.SyntaxKind.GreaterThanToken: return left > right; case ts.SyntaxKind.LessThanEqualsToken: return left <= right; case ts.SyntaxKind.GreaterThanEqualsToken: return left >= right; case ts.SyntaxKind.LessThanLessThanToken: return left << right; case ts.SyntaxKind.GreaterThanGreaterThanToken: return left >> right; case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; case ts.SyntaxKind.PlusToken: return left + right; case ts.SyntaxKind.MinusToken: return left - right; case ts.SyntaxKind.AsteriskToken: return left * right; case ts.SyntaxKind.SlashToken: return left / right; case ts.SyntaxKind.PercentToken: return left % right; } return recordEntry({ __symbolic: 'binop', operator: binaryExpression.operatorToken.getText(), left: left, right: right }, node); } break; case ts.SyntaxKind.ConditionalExpression: var conditionalExpression = node; var condition = this.evaluateNode(conditionalExpression.condition); var thenExpression = this.evaluateNode(conditionalExpression.whenTrue); var elseExpression = this.evaluateNode(conditionalExpression.whenFalse); if (isPrimitive(condition)) { return condition ? thenExpression : elseExpression; } return recordEntry({ __symbolic: 'if', condition: condition, thenExpression: thenExpression, elseExpression: elseExpression }, node); case ts.SyntaxKind.FunctionExpression: case ts.SyntaxKind.ArrowFunction: return recordEntry(errorSymbol('Function call not supported', node), node); case ts.SyntaxKind.TaggedTemplateExpression: return recordEntry(errorSymbol('Tagged template expressions are not supported in metadata', node), node); case ts.SyntaxKind.TemplateExpression: var templateExpression = node; if (this.isFoldable(node)) { return templateExpression.templateSpans.reduce(function (previous, current) { return previous + _this.evaluateNode(current.expression) + _this.evaluateNode(current.literal); }, this.evaluateNode(templateExpression.head)); } else { return templateExpression.templateSpans.reduce(function (previous, current) { var expr = _this.evaluateNode(current.expression); var literal = _this.evaluateNode(current.literal); if (isFoldableError(expr)) return expr; if (isFoldableError(literal)) return literal; if (typeof previous === 'string' && typeof expr === 'string' && typeof literal === 'string') { return previous + expr + literal; } var result = expr; if (previous !== '') { result = { __symbolic: 'binop', operator: '+', left: previous, right: expr }; } if (literal != '') { result = { __symbolic: 'binop', operator: '+', left: result, right: literal }; } return result; }, this.evaluateNode(templateExpression.head)); } } return recordEntry(errorSymbol('Expression form not supported', node), node); }; return Evaluator; }()); exports.Evaluator = Evaluator; function isPropertyAssignment(node) { return node.kind == ts.SyntaxKind.PropertyAssignment; } var empty = []; function arrayOrEmpty(v) { return v || empty; } //# sourceMappingURL=evaluator.js.map