angular-cn/packages/compiler-cli/src/metadata/evaluator.ts

696 lines
30 KiB
TypeScript

/**
* @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
*/
import * as ts from 'typescript';
import {CollectorOptions} from './collector';
import {ClassMetadata, FunctionMetadata, InterfaceMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSourceLocationInfo, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportDefaultReference, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
// In TypeScript 2.1 the spread element kind was renamed.
const spreadElementSyntaxKind: ts.SyntaxKind =
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
const expression = callExpression.expression;
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const propertyAccessExpression = <ts.PropertyAccessExpression>expression;
const name = propertyAccessExpression.name;
if (name.kind == ts.SyntaxKind.Identifier) {
return name.text === memberName;
}
}
return false;
}
function isCallOf(callExpression: ts.CallExpression, ident: string): boolean {
const expression = callExpression.expression;
if (expression.kind === ts.SyntaxKind.Identifier) {
const identifier = <ts.Identifier>expression;
return identifier.text === ident;
}
return false;
}
/* @internal */
export function recordMapEntry<T extends MetadataEntry>(
entry: T, node: ts.Node,
nodeMap: Map<MetadataValue|ClassMetadata|InterfaceMetadata|FunctionMetadata, ts.Node>,
sourceFile?: ts.SourceFile) {
if (!nodeMap.has(entry)) {
nodeMap.set(entry, node);
if (node && (isMetadataImportedSymbolReferenceExpression(entry) ||
isMetadataImportDefaultReference(entry)) &&
entry.line == null) {
const info = sourceInfo(node, sourceFile);
if (info.line != null) entry.line = info.line;
if (info.character != null) entry.character = info.character;
}
}
return entry;
}
/**
* 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: ts.Node, cb: (node: ts.Node) => boolean) {
return !ts.forEachChild(node, node => !cb(node));
}
export function isPrimitive(value: any): boolean {
return Object(value) !== value;
}
function isDefined(obj: any): boolean {
return obj !== undefined;
}
// import {propertyName as name} from 'place'
// import {name} from 'place'
export interface ImportSpecifierMetadata {
name: string;
propertyName?: string;
}
export interface ImportMetadata {
defaultName?: string; // import d from 'place'
namespace?: string; // import * as d from 'place'
namedImports?: ImportSpecifierMetadata[]; // import {a} from 'place'
from: string; // from 'place'
}
function getSourceFileOfNode(node: ts.Node | undefined): ts.SourceFile {
while (node && node.kind != ts.SyntaxKind.SourceFile) {
node = node.parent;
}
return <ts.SourceFile>node;
}
/* @internal */
export function sourceInfo(
node: ts.Node | undefined, sourceFile: ts.SourceFile | undefined): MetadataSourceLocationInfo {
if (node) {
sourceFile = sourceFile || getSourceFileOfNode(node);
if (sourceFile) {
return ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
}
}
return {};
}
/* @internal */
export function errorSymbol(
message: string, node?: ts.Node, context?: {[name: string]: string},
sourceFile?: ts.SourceFile): MetadataError {
const result: MetadataError = {__symbolic: 'error', message, ...sourceInfo(node, sourceFile)};
if (context) {
result.context = context;
}
return result;
}
/**
* Produce a symbolic representation of an expression folding values into their final value when
* possible.
*/
export class Evaluator {
constructor(
private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>,
private options: CollectorOptions = {},
private recordExport?: (name: string, value: MetadataValue) => void) {}
nameOf(node: ts.Node|undefined): string|MetadataError {
if (node && node.kind == ts.SyntaxKind.Identifier) {
return (<ts.Identifier>node).text;
}
const result = node && this.evaluateNode(node);
if (isMetadataError(result) || typeof result === 'string') {
return result;
} else {
return errorSymbol(
'Name expected', node, {received: (node && node.getText()) || '<missing>'});
}
}
/**
* 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.
*/
public isFoldable(node: ts.Node): boolean {
return this.isFoldableWorker(node, new Map<ts.Node, boolean>());
}
private isFoldableWorker(node: ts.Node|undefined, folding: Map<ts.Node, boolean>): boolean {
if (node) {
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
return everyNodeChild(node, child => {
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
const propertyAssignment = <ts.PropertyAssignment>child;
return this.isFoldableWorker(propertyAssignment.initializer, folding);
}
return false;
});
case ts.SyntaxKind.ArrayLiteralExpression:
return everyNodeChild(node, child => this.isFoldableWorker(child, folding));
case ts.SyntaxKind.CallExpression:
const callExpression = <ts.CallExpression>node;
// We can fold a <array>.concat(<v>).
if (isMethodCallOf(callExpression, 'concat') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
const arrayNode = (<ts.PropertyAccessExpression>callExpression.expression).expression;
if (this.isFoldableWorker(arrayNode, folding) &&
this.isFoldableWorker(callExpression.arguments[0], folding)) {
// It needs to be an array.
const 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:
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
return this.isFoldableWorker(parenthesizedExpression.expression, folding);
case ts.SyntaxKind.BinaryExpression:
const binaryExpression = <ts.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:
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
return this.isFoldableWorker(propertyAccessExpression.expression, folding);
case ts.SyntaxKind.ElementAccessExpression:
const elementAccessExpression = <ts.ElementAccessExpression>node;
return this.isFoldableWorker(elementAccessExpression.expression, folding) &&
this.isFoldableWorker(elementAccessExpression.argumentExpression, folding);
case ts.SyntaxKind.Identifier:
let identifier = <ts.Identifier>node;
let reference = this.symbols.resolve(identifier.text);
if (reference !== undefined && isPrimitive(reference)) {
return true;
}
break;
case ts.SyntaxKind.TemplateExpression:
const templateExpression = <ts.TemplateExpression>node;
return templateExpression.templateSpans.every(
span => 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`.
*/
public evaluateNode(node: ts.Node, preferReference?: boolean): MetadataValue {
const t = this;
let error: MetadataError|undefined;
function recordEntry(entry: MetadataValue, node: ts.Node): MetadataValue {
if (t.options.substituteExpression) {
const newEntry = t.options.substituteExpression(entry, node);
if (t.recordExport && newEntry != entry && isMetadataGlobalReferenceExpression(newEntry)) {
t.recordExport(newEntry.name, entry);
}
entry = newEntry;
}
return recordMapEntry(entry, node, t.nodeMap);
}
function isFoldableError(value: any): value is MetadataError {
return !t.options.verboseInvalidExpression && isMetadataError(value);
}
const resolveName = (name: string, preferReference?: boolean): MetadataValue => {
const reference = this.symbols.resolve(name, preferReference);
if (reference === undefined) {
// Encode as a global reference. StaticReflector will check the reference.
return recordEntry({__symbolic: 'reference', name}, node);
}
if (reference && isMetadataSymbolicReferenceExpression(reference)) {
return recordEntry({...reference}, node);
}
return reference;
};
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
let obj: {[name: string]: any} = {};
let quoted: string[] = [];
ts.forEachChild(node, child => {
switch (child.kind) {
case ts.SyntaxKind.ShorthandPropertyAssignment:
case ts.SyntaxKind.PropertyAssignment:
const assignment = <ts.PropertyAssignment|ts.ShorthandPropertyAssignment>child;
if (assignment.name.kind == ts.SyntaxKind.StringLiteral) {
const name = (assignment.name as ts.StringLiteral).text;
quoted.push(name);
}
const propertyName = this.nameOf(assignment.name);
if (isFoldableError(propertyName)) {
error = propertyName;
return true;
}
const propertyValue = isPropertyAssignment(assignment) ?
this.evaluateNode(assignment.initializer, /* preferReference */ true) :
resolveName(propertyName, /* preferReference */ true);
if (isFoldableError(propertyValue)) {
error = propertyValue;
return true; // Stop the forEachChild.
} else {
obj[propertyName] = isPropertyAssignment(assignment) ?
recordEntry(propertyValue, assignment.initializer) :
propertyValue;
}
}
});
if (error) return error;
if (this.options.quotedNames && quoted.length) {
obj['$quoted$'] = quoted;
}
return recordEntry(obj, node);
case ts.SyntaxKind.ArrayLiteralExpression:
let arr: MetadataValue[] = [];
ts.forEachChild(node, child => {
const value = this.evaluateNode(child, /* preferReference */ true);
// Check for error
if (isFoldableError(value)) {
error = value;
return true; // Stop the forEachChild.
}
// Handle spread expressions
if (isMetadataSymbolicSpreadExpression(value)) {
if (Array.isArray(value.expression)) {
for (const spreadValue of value.expression) {
arr.push(spreadValue);
}
return;
}
}
arr.push(value);
});
if (error) return error;
return recordEntry(arr, node);
case spreadElementSyntaxKind:
let spreadExpression = this.evaluateNode((node as any).expression);
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
case ts.SyntaxKind.CallExpression:
const callExpression = <ts.CallExpression>node;
if (isCallOf(callExpression, 'forwardRef') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
const firstArgument = callExpression.arguments[0];
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
const arrowFunction = <ts.ArrowFunction>firstArgument;
return recordEntry(this.evaluateNode(arrowFunction.body), node);
}
}
const args = arrayOrEmpty(callExpression.arguments).map(arg => this.evaluateNode(arg));
if (this.isFoldable(callExpression)) {
if (isMethodCallOf(callExpression, 'concat')) {
const arrayValue = <MetadataValue[]>this.evaluateNode(
(<ts.PropertyAccessExpression>callExpression.expression).expression);
if (isFoldableError(arrayValue)) return arrayValue;
return arrayValue.concat(args[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[0], node);
}
const expression = this.evaluateNode(callExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
let result: MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
if (args && args.length) {
result.arguments = args;
}
return recordEntry(result, node);
case ts.SyntaxKind.NewExpression:
const newExpression = <ts.NewExpression>node;
const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg));
const newTarget = this.evaluateNode(newExpression.expression);
if (isMetadataError(newTarget)) {
return recordEntry(newTarget, node);
}
const call: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
if (newArgs.length) {
call.arguments = newArgs;
}
return recordEntry(call, node);
case ts.SyntaxKind.PropertyAccessExpression: {
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
const expression = this.evaluateNode(propertyAccessExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
const member = this.nameOf(propertyAccessExpression.name);
if (isFoldableError(member)) {
return recordEntry(member, node);
}
if (expression && this.isFoldable(propertyAccessExpression.expression))
return (<any>expression)[member];
if (isMetadataModuleReferenceExpression(expression)) {
// A select into a module reference and be converted into a reference to the symbol
// in the module
return recordEntry(
{__symbolic: 'reference', module: expression.module, name: member}, node);
}
return recordEntry({__symbolic: 'select', expression, member}, node);
}
case ts.SyntaxKind.ElementAccessExpression: {
const elementAccessExpression = <ts.ElementAccessExpression>node;
const expression = this.evaluateNode(elementAccessExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
if (!elementAccessExpression.argumentExpression) {
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
if (this.isFoldable(elementAccessExpression.expression) &&
this.isFoldable(elementAccessExpression.argumentExpression))
return (<any>expression)[<string|number>index];
return recordEntry({__symbolic: 'index', expression, index}, node);
}
case ts.SyntaxKind.Identifier:
const identifier = <ts.Identifier>node;
const name = identifier.text;
return resolveName(name, preferReference);
case ts.SyntaxKind.TypeReference:
const typeReferenceNode = <ts.TypeReferenceNode>node;
const typeNameNode = typeReferenceNode.typeName;
const getReference: (typeNameNode: ts.Identifier | ts.QualifiedName) => MetadataValue =
node => {
if (typeNameNode.kind === ts.SyntaxKind.QualifiedName) {
const qualifiedName = <ts.QualifiedName>node;
const left = this.evaluateNode(qualifiedName.left);
if (isMetadataModuleReferenceExpression(left)) {
return recordEntry(
<MetadataImportedSymbolReferenceExpression>{
__symbolic: 'reference',
module: left.module,
name: qualifiedName.right.text
},
node);
}
// Record a type reference to a declared type as a select.
return {__symbolic: 'select', expression: left, member: qualifiedName.right.text};
} else {
const identifier = <ts.Identifier>typeNameNode;
const symbol = this.symbols.resolve(identifier.text);
if (isFoldableError(symbol) || isMetadataSymbolicReferenceExpression(symbol)) {
return recordEntry(symbol, node);
}
return recordEntry(
errorSymbol('Could not resolve type', node, {typeName: identifier.text}), node);
}
};
const typeReference = getReference(typeNameNode);
if (isFoldableError(typeReference)) {
return recordEntry(typeReference, node);
}
if (!isMetadataModuleReferenceExpression(typeReference) &&
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
const args = typeReferenceNode.typeArguments.map(element => this.evaluateNode(element));
// TODO: Remove typecast when upgraded to 2.0 as it will be correctly inferred.
// Some versions of 1.9 do not infer this correctly.
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
}
return recordEntry(typeReference, node);
case ts.SyntaxKind.UnionType:
const unionType = <ts.UnionTypeNode>node;
// Remove null and undefined from the list of unions.
const references = unionType.types
.filter(
n => n.kind != ts.SyntaxKind.NullKeyword &&
n.kind != ts.SyntaxKind.UndefinedKeyword)
.map(n => 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.
let candidate: any = null;
for (let i = 0; i < references.length; i++) {
const reference = references[i];
if (isMetadataSymbolicReferenceExpression(reference)) {
if (candidate) {
if ((reference as any).name == candidate.name &&
(reference as any).module == candidate.module && !(reference as any).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 (<ts.LiteralLikeNode>node).text;
case ts.SyntaxKind.NumericLiteral:
return parseFloat((<ts.LiteralExpression>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:
const arrayTypeNode = <ts.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:
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
return this.evaluateNode(parenthesizedExpression.expression);
case ts.SyntaxKind.TypeAssertionExpression:
const typeAssertion = <ts.TypeAssertion>node;
return this.evaluateNode(typeAssertion.expression);
case ts.SyntaxKind.PrefixUnaryExpression:
const prefixUnaryExpression = <ts.PrefixUnaryExpression>node;
const operand = this.evaluateNode(prefixUnaryExpression.operand);
if (isDefined(operand) && isPrimitive(operand)) {
switch (prefixUnaryExpression.operator) {
case ts.SyntaxKind.PlusToken:
return +(operand as any);
case ts.SyntaxKind.MinusToken:
return -(operand as any);
case ts.SyntaxKind.TildeToken:
return ~(operand as any);
case ts.SyntaxKind.ExclamationToken:
return !operand;
}
}
let operatorText: '+'|'-'|'~'|'!';
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:
const binaryExpression = <ts.BinaryExpression>node;
const left = this.evaluateNode(binaryExpression.left);
const right = this.evaluateNode(binaryExpression.right);
if (isDefined(left) && isDefined(right)) {
if (isPrimitive(left) && isPrimitive(right))
switch (binaryExpression.operatorToken.kind) {
case ts.SyntaxKind.BarBarToken:
return <any>left || <any>right;
case ts.SyntaxKind.AmpersandAmpersandToken:
return <any>left && <any>right;
case ts.SyntaxKind.AmpersandToken:
return <any>left & <any>right;
case ts.SyntaxKind.BarToken:
return <any>left | <any>right;
case ts.SyntaxKind.CaretToken:
return <any>left ^ <any>right;
case ts.SyntaxKind.EqualsEqualsToken:
return <any>left == <any>right;
case ts.SyntaxKind.ExclamationEqualsToken:
return <any>left != <any>right;
case ts.SyntaxKind.EqualsEqualsEqualsToken:
return <any>left === <any>right;
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
return <any>left !== <any>right;
case ts.SyntaxKind.LessThanToken:
return <any>left < <any>right;
case ts.SyntaxKind.GreaterThanToken:
return <any>left > <any>right;
case ts.SyntaxKind.LessThanEqualsToken:
return <any>left <= <any>right;
case ts.SyntaxKind.GreaterThanEqualsToken:
return <any>left >= <any>right;
case ts.SyntaxKind.LessThanLessThanToken:
return (<any>left) << (<any>right);
case ts.SyntaxKind.GreaterThanGreaterThanToken:
return <any>left >> <any>right;
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
return <any>left >>> <any>right;
case ts.SyntaxKind.PlusToken:
return <any>left + <any>right;
case ts.SyntaxKind.MinusToken:
return <any>left - <any>right;
case ts.SyntaxKind.AsteriskToken:
return <any>left * <any>right;
case ts.SyntaxKind.SlashToken:
return <any>left / <any>right;
case ts.SyntaxKind.PercentToken:
return <any>left % <any>right;
}
return recordEntry(
{
__symbolic: 'binop',
operator: binaryExpression.operatorToken.getText(),
left: left,
right: right
},
node);
}
break;
case ts.SyntaxKind.ConditionalExpression:
const conditionalExpression = <ts.ConditionalExpression>node;
const condition = this.evaluateNode(conditionalExpression.condition);
const thenExpression = this.evaluateNode(conditionalExpression.whenTrue);
const elseExpression = this.evaluateNode(conditionalExpression.whenFalse);
if (isPrimitive(condition)) {
return condition ? thenExpression : elseExpression;
}
return recordEntry({__symbolic: 'if', condition, thenExpression, elseExpression}, node);
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
return recordEntry(errorSymbol('Lambda 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:
const templateExpression = <ts.TemplateExpression>node;
if (this.isFoldable(node)) {
return templateExpression.templateSpans.reduce(
(previous, current) => previous + <string>this.evaluateNode(current.expression) +
<string>this.evaluateNode(current.literal),
this.evaluateNode(templateExpression.head));
} else {
return templateExpression.templateSpans.reduce((previous, current) => {
const expr = this.evaluateNode(current.expression);
const 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;
}
let 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));
}
case ts.SyntaxKind.AsExpression:
const asExpression = <ts.AsExpression>node;
return this.evaluateNode(asExpression.expression);
case ts.SyntaxKind.ClassExpression:
return {__symbolic: 'class'};
}
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
}
function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
return node.kind == ts.SyntaxKind.PropertyAssignment;
}
const empty = ts.createNodeArray<any>();
function arrayOrEmpty<T extends ts.Node>(v: ts.NodeArray<T>| undefined): ts.NodeArray<T> {
return v || empty;
}