/** * @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(); private propertyCache = new Map(); private parameterCache = new Map(); private methodCache = new Map(); private staticCache = new Map(); private conversionMap = new Map any>(); private resolvedExternalReferences = new Map(); private injectionToken: StaticSymbol; private opaqueToken: StaticSymbol; ROUTES: StaticSymbol; private ANALYZE_FOR_ENTRY_COMPONENTS: StaticSymbol; private annotationForParentClassWithSummaryKind = new Map[]>(); constructor( private summaryResolver: SummaryResolver, 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 = (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 = (ctorData).find(a => a['__symbolic'] == 'constructor'); const rawParameterTypes = ctor['parameters'] || []; const parameterDecorators = 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 = (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(); 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 (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(); 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) { 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; }