angular/packages/compiler/src/aot/static_reflector.ts
Chuck Jazdzewski c32e83334b fix(compiler): cache external reference resolution (#21359)
Cache reference resolution for external references as finding
the declaration of a symbol is expensive and does not change
for a program once created.

This resolves a signficant performance regression in the langauge
service.

PR Close #21359
2018-01-10 12:34:07 -08:00

1029 lines
40 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 {CompileSummaryKind} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {MetadataFactory, createAttribute, createComponent, createContentChild, createContentChildren, createDirective, createHost, createHostBinding, createHostListener, createInject, createInjectable, createInput, createNgModule, createOptional, createOutput, createPipe, createSelf, createSkipSelf, createViewChild, createViewChildren} from '../core';
import * as o from '../output/output_ast';
import {SummaryResolver} from '../summary_resolver';
import {syntaxError} from '../util';
import {FormattedMessageChain, formattedError} from './formatted_error';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_CORE = '@angular/core';
const ANGULAR_ROUTER = '@angular/router';
const HIDDEN_KEY = /^\$.*\$$/;
const IGNORE = {
__symbolic: 'ignore'
};
const USE_VALUE = 'useValue';
const PROVIDE = 'provide';
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
const TYPEGUARD_POSTFIX = 'TypeGuard';
const USE_IF = 'UseIf';
function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore';
}
/**
* A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically.
*/
export class StaticReflector implements CompileReflector {
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private staticCache = new Map<StaticSymbol, string[]>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private resolvedExternalReferences = new Map<string, StaticSymbol>();
private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol;
ROUTES: StaticSymbol;
private ANALYZE_FOR_ENTRY_COMPONENTS: StaticSymbol;
private annotationForParentClassWithSummaryKind =
new Map<CompileSummaryKind, MetadataFactory<any>[]>();
constructor(
private summaryResolver: SummaryResolver<StaticSymbol>,
private symbolResolver: StaticSymbolResolver,
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName?: string) => void) {
this.initializeConversionMap();
knownMetadataClasses.forEach(
(kc) => this._registerDecoratorOrConstructor(
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
knownMetadataFunctions.forEach(
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
this.annotationForParentClassWithSummaryKind.set(
CompileSummaryKind.Directive, [createDirective, createComponent]);
this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.Pipe, [createPipe]);
this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.NgModule, [createNgModule]);
this.annotationForParentClassWithSummaryKind.set(
CompileSummaryKind.Injectable,
[createInjectable, createPipe, createDirective, createComponent, createNgModule]);
}
componentModuleUrl(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
return this.symbolResolver.getResourcePath(staticSymbol);
}
resolveExternalReference(ref: o.ExternalReference, containingFile?: string): StaticSymbol {
let key: string|undefined = undefined;
if (!containingFile) {
key = `${ref.moduleName}:${ref.name}`;
const declarationSymbol = this.resolvedExternalReferences.get(key);
if (declarationSymbol) return declarationSymbol;
}
const refSymbol =
this.symbolResolver.getSymbolByModule(ref.moduleName !, ref.name !, containingFile);
const declarationSymbol = this.findSymbolDeclaration(refSymbol);
if (!containingFile) {
this.symbolResolver.recordModuleNameForFileName(refSymbol.filePath, ref.moduleName !);
this.symbolResolver.recordImportAs(declarationSymbol, refSymbol);
}
if (key) {
this.resolvedExternalReferences.set(key, declarationSymbol);
}
return declarationSymbol;
}
findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
return this.findSymbolDeclaration(
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
}
tryFindDeclaration(moduleUrl: string, name: string): StaticSymbol {
return this.symbolResolver.ignoreErrorsFor(() => this.findDeclaration(moduleUrl, name));
}
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol) {
let resolvedMetadata = resolvedSymbol.metadata;
if (resolvedMetadata && resolvedMetadata.__symbolic === 'resolved') {
resolvedMetadata = resolvedMetadata.symbol;
}
if (resolvedMetadata instanceof StaticSymbol) {
return this.findSymbolDeclaration(resolvedSymbol.metadata);
}
}
return symbol;
}
public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type);
if (!annotations) {
annotations = [];
const classMetadata = this.getTypeMetadata(type);
const parentType = this.findParentType(type, classMetadata);
if (parentType) {
const parentAnnotations = this.annotations(parentType);
annotations.push(...parentAnnotations);
}
let ownAnnotations: any[] = [];
if (classMetadata['decorators']) {
ownAnnotations = this.simplify(type, classMetadata['decorators']);
annotations.push(...ownAnnotations);
}
if (parentType && !this.summaryResolver.isLibraryFile(type.filePath) &&
this.summaryResolver.isLibraryFile(parentType.filePath)) {
const summary = this.summaryResolver.resolveSummary(parentType);
if (summary && summary.type) {
const requiredAnnotationTypes =
this.annotationForParentClassWithSummaryKind.get(summary.type.summaryKind !) !;
const typeHasRequiredAnnotation = requiredAnnotationTypes.some(
(requiredType) => ownAnnotations.some(ann => requiredType.isTypeOf(ann)));
if (!typeHasRequiredAnnotation) {
this.reportError(
formatMetadataError(
metadataError(
`Class ${type.name} in ${type.filePath} extends from a ${CompileSummaryKind[summary.type.summaryKind!]} in another compilation unit without duplicating the decorator`,
/* summary */ undefined,
`Please add a ${requiredAnnotationTypes.map((type) => type.ngMetadataName).join(' or ')} decorator to the class`),
type),
type);
}
}
}
this.annotationCache.set(type, annotations.filter(ann => !!ann));
}
return annotations;
}
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type);
propMetadata = {};
const parentType = this.findParentType(type, classMetadata);
if (parentType) {
const parentPropMetadata = this.propMetadata(parentType);
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata ![parentProp] = parentPropMetadata[parentProp];
});
}
const members = classMetadata['members'] || {};
Object.keys(members).forEach((propName) => {
const propData = members[propName];
const prop = (<any[]>propData)
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
const decorators: any[] = [];
if (propMetadata ![propName]) {
decorators.push(...propMetadata ![propName]);
}
propMetadata ![propName] = decorators;
if (prop && prop['decorators']) {
decorators.push(...this.simplify(type, prop['decorators']));
}
});
this.propertyCache.set(type, propMetadata);
}
return propMetadata;
}
public parameters(type: StaticSymbol): any[] {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`),
type);
return [];
}
try {
let parameters = this.parameterCache.get(type);
if (!parameters) {
const classMetadata = this.getTypeMetadata(type);
const parentType = this.findParentType(type, classMetadata);
const members = classMetadata ? classMetadata['members'] : null;
const ctorData = members ? members['__ctor__'] : null;
if (ctorData) {
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
const rawParameterTypes = <any[]>ctor['parameters'] || [];
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = [];
rawParameterTypes.forEach((rawParamType, index) => {
const nestedResult: any[] = [];
const paramType = this.trySimplify(type, rawParamType);
if (paramType) nestedResult.push(paramType);
const decorators = parameterDecorators ? parameterDecorators[index] : null;
if (decorators) {
nestedResult.push(...decorators);
}
parameters !.push(nestedResult);
});
} else if (parentType) {
parameters = this.parameters(parentType);
}
if (!parameters) {
parameters = [];
}
this.parameterCache.set(type, parameters);
}
return parameters;
} catch (e) {
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
}
}
private _methodNames(type: any): {[key: string]: boolean} {
let methodNames = this.methodCache.get(type);
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type);
methodNames = {};
const parentType = this.findParentType(type, classMetadata);
if (parentType) {
const parentMethodNames = this._methodNames(parentType);
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames ![parentProp] = parentMethodNames[parentProp];
});
}
const members = classMetadata['members'] || {};
Object.keys(members).forEach((propName) => {
const propData = members[propName];
const isMethod = (<any[]>propData).some(a => a['__symbolic'] == 'method');
methodNames ![propName] = methodNames ![propName] || isMethod;
});
this.methodCache.set(type, methodNames);
}
return methodNames;
}
private _staticMembers(type: StaticSymbol): string[] {
let staticMembers = this.staticCache.get(type);
if (!staticMembers) {
const classMetadata = this.getTypeMetadata(type);
const staticMemberData = classMetadata['statics'] || {};
staticMembers = Object.keys(staticMemberData);
this.staticCache.set(type, staticMembers);
}
return staticMembers;
}
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
return parentType;
}
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`),
type);
}
try {
return !!this._methodNames(type)[lcProperty];
} catch (e) {
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
}
}
guards(type: any): {[key: string]: StaticSymbol} {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(`guards received ${JSON.stringify(type)} which is not a StaticSymbol`), type);
return {};
}
const staticMembers = this._staticMembers(type);
const result: {[key: string]: StaticSymbol} = {};
for (let name of staticMembers) {
if (name.endsWith(TYPEGUARD_POSTFIX)) {
let property = name.substr(0, name.length - TYPEGUARD_POSTFIX.length);
let value: any;
if (property.endsWith(USE_IF)) {
property = name.substr(0, property.length - USE_IF.length);
value = USE_IF;
} else {
value = this.getStaticSymbol(type.filePath, type.name, [name]);
}
result[property] = value;
}
}
return result;
}
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
}
private _registerFunction(type: StaticSymbol, fn: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
}
private initializeConversionMap(): void {
this.injectionToken = this.findDeclaration(ANGULAR_CORE, 'InjectionToken');
this.opaqueToken = this.findDeclaration(ANGULAR_CORE, 'OpaqueToken');
this.ROUTES = this.tryFindDeclaration(ANGULAR_ROUTER, 'ROUTES');
this.ANALYZE_FOR_ENTRY_COMPONENTS =
this.findDeclaration(ANGULAR_CORE, 'ANALYZE_FOR_ENTRY_COMPONENTS');
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Injectable'), createInjectable);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Inject'), createInject);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Attribute'), createAttribute);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ContentChild'), createContentChild);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ContentChildren'), createContentChildren);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ViewChild'), createViewChild);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ViewChildren'), createViewChildren);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Input'), createInput);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Output'), createOutput);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Pipe'), createPipe);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'HostBinding'), createHostBinding);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'HostListener'), createHostListener);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Directive'), createDirective);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Component'), createComponent);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'NgModule'), createNgModule);
// Note: Some metadata classes can be used directly with Provider.deps.
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional);
}
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
}
/**
* Simplify but discard any errors
*/
private trySimplify(context: StaticSymbol, value: any): any {
const originalRecorder = this.errorRecorder;
this.errorRecorder = (error: any, fileName: string) => {};
const result = this.simplify(context, value);
this.errorRecorder = originalRecorder;
return result;
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
const self = this;
let scope = BindingScope.empty;
const calling = new Map<StaticSymbol, boolean>();
const rootContext = context;
function simplifyInContext(
context: StaticSymbol, value: any, depth: number, references: number): any {
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
return resolvedSymbol ? resolvedSymbol.metadata : null;
}
function simplifyEagerly(value: any): any {
return simplifyInContext(context, value, depth, 0);
}
function simplifyLazily(value: any): any {
return simplifyInContext(context, value, depth, references + 1);
}
function simplifyNested(nestedContext: StaticSymbol, value: any): any {
if (nestedContext === context) {
// If the context hasn't changed let the exception propagate unmodified.
return simplifyInContext(nestedContext, value, depth + 1, references);
}
try {
return simplifyInContext(nestedContext, value, depth + 1, references);
} catch (e) {
if (isMetadataError(e)) {
// Propagate the message text up but add a message to the chain that explains how we got
// here.
// e.chain implies e.symbol
const summaryMsg = e.chain ? 'references \'' + e.symbol !.name + '\'' : errorSummary(e);
const summary = `'${nestedContext.name}' ${summaryMsg}`;
const chain = {message: summary, position: e.position, next: e.chain};
// TODO(chuckj): retrieve the position information indirectly from the collectors node
// map if the metadata is from a .ts file.
self.error(
{
message: e.message,
advise: e.advise,
context: e.context, chain,
symbol: nestedContext
},
context);
} else {
// It is probably an internal error.
throw e;
}
}
}
function simplifyCall(
functionSymbol: StaticSymbol, targetFunction: any, args: any[], targetExpression: any) {
if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) {
self.error(
{
message: 'Recursion is not supported',
summary: `called '${functionSymbol.name}' recursively`,
value: targetFunction
},
functionSymbol);
}
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
args = args.map(arg => simplifyNested(context, arg))
.map(arg => shouldIgnore(arg) ? undefined : arg);
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
calling.set(functionSymbol, true);
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyNested(functionSymbol, value);
} finally {
scope = oldScope;
}
return result;
}
} finally {
calling.delete(functionSymbol);
}
}
if (depth === 0) {
// If depth is 0 we are evaluating the top level expression that is describing element
// decorator. In this case, it is a decorator we don't understand, such as a custom
// non-angular decorator, and we should just ignore it.
return IGNORE;
}
let position: Position|undefined = undefined;
if (targetExpression && targetExpression.__symbolic == 'resolved') {
const line = targetExpression.line;
const character = targetExpression.character;
const fileName = targetExpression.fileName;
if (fileName != null && line != null && character != null) {
position = {fileName, line, column: character};
}
}
self.error(
{
message: FUNCTION_CALL_NOT_SUPPORTED,
context: functionSymbol,
value: targetFunction, position
},
context);
}
function simplify(expression: any): any {
if (isPrimitive(expression)) {
return expression;
}
if (expression instanceof Array) {
const result: any[] = [];
for (const item of (<any>expression)) {
// Check for a spread expression
if (item && item.__symbolic === 'spread') {
// We call with references as 0 because we require the actual value and cannot
// tolerate a reference here.
const spreadArray = simplifyEagerly(item.expression);
if (Array.isArray(spreadArray)) {
for (const spreadItem of spreadArray) {
result.push(spreadItem);
}
continue;
}
}
const value = simplify(item);
if (shouldIgnore(value)) {
continue;
}
result.push(value);
}
return result;
}
if (expression instanceof StaticSymbol) {
// Stop simplification at builtin symbols or if we are in a reference context and
// the symbol doesn't have members.
if (expression === self.injectionToken || self.conversionMap.has(expression) ||
(references > 0 && !expression.members.length)) {
return expression;
} else {
const staticSymbol = expression;
const declarationValue = resolveReferenceValue(staticSymbol);
if (declarationValue != null) {
return simplifyNested(staticSymbol, declarationValue);
} else {
return staticSymbol;
}
}
}
if (expression) {
if (expression['__symbolic']) {
let staticSymbol: StaticSymbol;
switch (expression['__symbolic']) {
case 'binop':
let left = simplify(expression['left']);
if (shouldIgnore(left)) return left;
let right = simplify(expression['right']);
if (shouldIgnore(right)) return right;
switch (expression['operator']) {
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 '/':
return left / right;
case '%':
return left % right;
}
return null;
case 'if':
let condition = simplify(expression['condition']);
return condition ? simplify(expression['thenExpression']) :
simplify(expression['elseExpression']);
case 'pre':
let operand = simplify(expression['operand']);
if (shouldIgnore(operand)) return operand;
switch (expression['operator']) {
case '+':
return operand;
case '-':
return -operand;
case '!':
return !operand;
case '~':
return ~operand;
}
return null;
case 'index':
let indexTarget = simplifyEagerly(expression['expression']);
let index = simplifyEagerly(expression['index']);
if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null;
case 'select':
const member = expression['member'];
let selectContext = context;
let selectTarget = simplify(expression['expression']);
if (selectTarget instanceof StaticSymbol) {
const members = selectTarget.members.concat(member);
selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
const declarationValue = resolveReferenceValue(selectContext);
if (declarationValue != null) {
return simplifyNested(selectContext, declarationValue);
} else {
return selectContext;
}
}
if (selectTarget && isPrimitive(member))
return simplifyNested(selectContext, selectTarget[member]);
return null;
case 'reference':
// Note: This only has to deal with variable references, as symbol references have
// been converted into 'resolved'
// in the StaticSymbolResolver.
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
break;
case 'resolved':
try {
return simplify(expression.symbol);
} catch (e) {
// If an error is reported evaluating the symbol record the position of the
// reference in the error so it can
// be reported in the error message generated from the exception.
if (isMetadataError(e) && expression.fileName != null &&
expression.line != null && expression.character != null) {
e.position = {
fileName: expression.fileName,
line: expression.line,
column: expression.character
};
}
throw e;
}
case 'class':
return context;
case 'function':
return context;
case 'new':
case 'call':
// Determine if the function is a built-in conversion
staticSymbol = simplifyInContext(
context, expression['expression'], depth + 1, /* references */ 0);
if (staticSymbol instanceof StaticSymbol) {
if (staticSymbol === self.injectionToken || staticSymbol === self.opaqueToken) {
// if somebody calls new InjectionToken, don't create an InjectionToken,
// but rather return the symbol to which the InjectionToken is assigned to.
// OpaqueToken is supported too as it is required by the language service to
// support v4 and prior versions of Angular.
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
const args = argExpressions.map(arg => simplifyNested(context, arg))
.map(arg => shouldIgnore(arg) ? undefined : arg);
return converter(context, args);
} else {
// Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(
staticSymbol, targetFunction, argExpressions, expression['expression']);
}
}
return IGNORE;
case 'error':
let message = expression.message;
if (expression['line'] != null) {
self.error(
{
message,
context: expression.context,
value: expression,
position: {
fileName: expression['fileName'],
line: expression['line'],
column: expression['character']
}
},
context);
} else {
self.error({message, context: expression.context}, context);
}
return IGNORE;
case 'ignore':
return expression;
}
return null;
}
return mapStringMap(expression, (value, name) => {
if (REFERENCE_SET.has(name)) {
if (name === USE_VALUE && PROVIDE in expression) {
// If this is a provider expression, check for special tokens that need the value
// during analysis.
const provide = simplify(expression.provide);
if (provide === self.ROUTES || provide == self.ANALYZE_FOR_ENTRY_COMPONENTS) {
return simplify(value);
}
}
return simplifyLazily(value);
}
return simplify(value);
});
}
return IGNORE;
}
return simplify(value);
}
let result: any;
try {
result = simplifyInContext(context, value, 0, 0);
} catch (e) {
if (this.errorRecorder) {
this.reportError(e, context);
} else {
throw formatMetadataError(e, context);
}
}
if (shouldIgnore(result)) {
return undefined;
}
return result;
}
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
{__symbolic: 'class'};
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
if (this.errorRecorder) {
this.errorRecorder(
formatMetadataError(error, context), (context && context.filePath) || path);
} else {
throw error;
}
}
private error(
{message, summary, advise, position, context, value, symbol, chain}: {
message: string,
summary?: string,
advise?: string,
position?: Position,
context?: any,
value?: any,
symbol?: StaticSymbol,
chain?: MetadataMessageChain
},
reportingContext: StaticSymbol) {
this.reportError(
metadataError(message, summary, advise, position, symbol, context, chain),
reportingContext);
}
}
interface Position {
fileName: string;
line: number;
column: number;
}
interface MetadataMessageChain {
message: string;
summary?: string;
position?: Position;
context?: any;
symbol?: StaticSymbol;
next?: MetadataMessageChain;
}
type MetadataError = Error & {
position?: Position;
advise?: string;
summary?: string;
context?: any;
symbol?: StaticSymbol;
chain?: MetadataMessageChain;
};
const METADATA_ERROR = 'ngMetadataError';
function metadataError(
message: string, summary?: string, advise?: string, position?: Position, symbol?: StaticSymbol,
context?: any, chain?: MetadataMessageChain): MetadataError {
const error = syntaxError(message) as MetadataError;
(error as any)[METADATA_ERROR] = true;
if (advise) error.advise = advise;
if (position) error.position = position;
if (summary) error.summary = summary;
if (context) error.context = context;
if (chain) error.chain = chain;
if (symbol) error.symbol = symbol;
return error;
}
function isMetadataError(error: Error): error is MetadataError {
return !!(error as any)[METADATA_ERROR];
}
const REFERENCE_TO_NONEXPORTED_CLASS = 'Reference to non-exported class';
const VARIABLE_NOT_INITIALIZED = 'Variable not initialized';
const DESTRUCTURE_NOT_SUPPORTED = 'Destructuring not supported';
const COULD_NOT_RESOLVE_TYPE = 'Could not resolve type';
const FUNCTION_CALL_NOT_SUPPORTED = 'Function call not supported';
const REFERENCE_TO_LOCAL_SYMBOL = 'Reference to a local symbol';
const LAMBDA_NOT_SUPPORTED = 'Lambda not supported';
function expandedMessage(message: string, context: any): string {
switch (message) {
case REFERENCE_TO_NONEXPORTED_CLASS:
if (context && context.className) {
return `References to a non-exported class are not supported in decorators but ${context.className} was referenced.`;
}
break;
case VARIABLE_NOT_INITIALIZED:
return 'Only initialized variables and constants can be referenced in decorators because the value of this variable is needed by the template compiler';
case DESTRUCTURE_NOT_SUPPORTED:
return 'Referencing an exported destructured variable or constant is not supported in decorators and this value is needed by the template compiler';
case COULD_NOT_RESOLVE_TYPE:
if (context && context.typeName) {
return `Could not resolve type ${context.typeName}`;
}
break;
case FUNCTION_CALL_NOT_SUPPORTED:
if (context && context.name) {
return `Function calls are not supported in decorators but '${context.name}' was called`;
}
return 'Function calls are not supported in decorators';
case REFERENCE_TO_LOCAL_SYMBOL:
if (context && context.name) {
return `Reference to a local (non-exported) symbols are not supported in decorators but '${context.name}' was referenced`;
}
break;
case LAMBDA_NOT_SUPPORTED:
return `Function expressions are not supported in decorators`;
}
return message;
}
function messageAdvise(message: string, context: any): string|undefined {
switch (message) {
case REFERENCE_TO_NONEXPORTED_CLASS:
if (context && context.className) {
return `Consider exporting '${context.className}'`;
}
break;
case DESTRUCTURE_NOT_SUPPORTED:
return 'Consider simplifying to avoid destructuring';
case REFERENCE_TO_LOCAL_SYMBOL:
if (context && context.name) {
return `Consider exporting '${context.name}'`;
}
break;
case LAMBDA_NOT_SUPPORTED:
return `Consider changing the function expression into an exported function`;
}
return undefined;
}
function errorSummary(error: MetadataError): string {
if (error.summary) {
return error.summary;
}
switch (error.message) {
case REFERENCE_TO_NONEXPORTED_CLASS:
if (error.context && error.context.className) {
return `references non-exported class ${error.context.className}`;
}
break;
case VARIABLE_NOT_INITIALIZED:
return 'is not initialized';
case DESTRUCTURE_NOT_SUPPORTED:
return 'is a destructured variable';
case COULD_NOT_RESOLVE_TYPE:
return 'could not be resolved';
case FUNCTION_CALL_NOT_SUPPORTED:
if (error.context && error.context.name) {
return `calls '${error.context.name}'`;
}
return `calls a function`;
case REFERENCE_TO_LOCAL_SYMBOL:
if (error.context && error.context.name) {
return `references local variable ${error.context.name}`;
}
return `references a local variable`;
}
return 'contains the error';
}
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
{[key: string]: any} {
if (!input) return {};
const result: {[key: string]: any} = {};
Object.keys(input).forEach((key) => {
const value = transform(input[key], key);
if (!shouldIgnore(value)) {
if (HIDDEN_KEY.test(key)) {
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
} else {
result[key] = value;
}
}
});
return result;
}
function isPrimitive(o: any): boolean {
return o === null || (typeof o !== 'function' && typeof o !== 'object');
}
interface BindingScopeBuilder {
define(name: string, value: any): BindingScopeBuilder;
done(): BindingScope;
}
abstract class BindingScope {
abstract resolve(name: string): any;
public static missing = {};
public static empty: BindingScope = {resolve: name => BindingScope.missing};
public static build(): BindingScopeBuilder {
const current = new Map<string, any>();
return {
define: function(name, value) {
current.set(name, value);
return this;
},
done: function() {
return current.size > 0 ? new PopulatedScope(current) : BindingScope.empty;
}
};
}
}
class PopulatedScope extends BindingScope {
constructor(private bindings: Map<string, any>) { super(); }
resolve(name: string): any {
return this.bindings.has(name) ? this.bindings.get(name) : BindingScope.missing;
}
}
function formatMetadataMessageChain(
chain: MetadataMessageChain, advise: string | undefined): FormattedMessageChain {
const expanded = expandedMessage(chain.message, chain.context);
const nesting = chain.symbol ? ` in '${chain.symbol.name}'` : '';
const message = `${expanded}${nesting}`;
const position = chain.position;
const next: FormattedMessageChain|undefined = chain.next ?
formatMetadataMessageChain(chain.next, advise) :
advise ? {message: advise} : undefined;
return {message, position, next};
}
function formatMetadataError(e: Error, context: StaticSymbol): Error {
if (isMetadataError(e)) {
// Produce a formatted version of the and leaving enough information in the original error
// to recover the formatting information to eventually produce a diagnostic error message.
const position = e.position;
const chain: MetadataMessageChain = {
message: `Error during template compile of '${context.name}'`,
position: position,
next: {message: e.message, next: e.chain, context: e.context, symbol: e.symbol}
};
const advise = e.advise || messageAdvise(e.message, e.context);
return formattedError(formatMetadataMessageChain(chain, advise));
}
return e;
}