import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import { isArray, isPresent, isBlank, isPrimitive, isStringMap, FunctionWrapper } from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import { AttributeMetadata, DirectiveMetadata, ComponentMetadata, ContentChildrenMetadata, ContentChildMetadata, InputMetadata, HostBindingMetadata, HostListenerMetadata, OutputMetadata, PipeMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata, QueryMetadata, } from 'angular2/src/core/metadata'; import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader'; import {reflector} from 'angular2/src/core/reflection/reflection'; import {Provider} from 'angular2/src/core/di/provider'; import { HostMetadata, OptionalMetadata, InjectableMetadata, SelfMetadata, SkipSelfMetadata, InjectMetadata, } from "angular2/src/core/di/metadata"; import {OpaqueToken} from 'angular2/src/core/di/opaque_token'; export class ModuleContext { constructor(public moduleId: string, public filePath: string) {} } /** * The host of the static resolver is expected to be able to provide module metadata in the form of * ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is * produced and the module has exported variables or classes with decorators. Module metadata can * also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata. */ export interface StaticReflectorHost { /** * Return a ModuleMetadata for the given module. * * @param moduleId is a string identifier for a module as an absolute path. * @returns the metadata for the given module. */ getMetadataFor(modulePath: string): {[key: string]: any}; /** * Resolve a symbol from an import statement form, to the file where it is declared. * @param module the location imported from * @param containingFile for relative imports, the path of the file containing the import */ findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol; getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol; } /** * A token representing the a reference to a static type. * * This token is unique for a moduleId and name and can be used as a hash table key. */ export class StaticSymbol implements ModuleContext { constructor(public moduleId: string, public filePath: string, public name: string) {} } /** * A static reflector implements enough of the Reflector API that is necessary to compile * templates statically. */ export class StaticReflector implements ReflectorReader { private annotationCache = new Map<StaticSymbol, any[]>(); private propertyCache = new Map<StaticSymbol, {[key: string]: any}>(); private parameterCache = new Map<StaticSymbol, any[]>(); private metadataCache = new Map<string, {[key: string]: any}>(); private conversionMap = new Map<StaticSymbol, (moduleContext: ModuleContext, args: any[]) => any>(); constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } importUri(typeOrFunc: any): string { return (<StaticSymbol>typeOrFunc).filePath; } public annotations(type: StaticSymbol): any[] { let annotations = this.annotationCache.get(type); if (!isPresent(annotations)) { let classMetadata = this.getTypeMetadata(type); if (isPresent(classMetadata['decorators'])) { annotations = this.simplify(type, classMetadata['decorators']); } else { annotations = []; } this.annotationCache.set(type, annotations.filter(ann => isPresent(ann))); } return annotations; } public propMetadata(type: StaticSymbol): {[key: string]: any} { let propMetadata = this.propertyCache.get(type); if (!isPresent(propMetadata)) { let classMetadata = this.getTypeMetadata(type); let members = isPresent(classMetadata) ? classMetadata['members'] : {}; propMetadata = mapStringMap(members, (propData, propName) => { let prop = (<any[]>propData).find(a => a['__symbolic'] == 'property'); if (isPresent(prop) && isPresent(prop['decorators'])) { return this.simplify(type, prop['decorators']); } else { return []; } }); this.propertyCache.set(type, propMetadata); } return propMetadata; } public parameters(type: StaticSymbol): any[] { try { let parameters = this.parameterCache.get(type); if (!isPresent(parameters)) { let classMetadata = this.getTypeMetadata(type); let members = isPresent(classMetadata) ? classMetadata['members'] : null; let ctorData = isPresent(members) ? members['__ctor__'] : null; if (isPresent(ctorData)) { let ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor'); let parameterTypes = <any[]>this.simplify(type, ctor['parameters']); let parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators']); parameters = []; ListWrapper.forEachWithIndex(parameterTypes, (paramType, index) => { let nestedResult = []; if (isPresent(paramType)) { nestedResult.push(paramType); } let decorators = isPresent(parameterDecorators) ? parameterDecorators[index] : null; if (isPresent(decorators)) { ListWrapper.addAll(nestedResult, decorators); } parameters.push(nestedResult); }); } if (!isPresent(parameters)) { parameters = []; } this.parameterCache.set(type, parameters); } return parameters; } catch (e) { console.error('Failed on type', type, 'with error', e); throw e; } } private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void { this.conversionMap.set(type, (moduleContext: ModuleContext, args: any[]) => { let argValues = []; ListWrapper.forEachWithIndex(args, (arg, index) => { let argValue; if (isStringMap(arg) && isBlank(arg['__symbolic'])) { argValue = mapStringMap(arg, (value, key) => this.simplify( moduleContext, value) ); } else { argValue = this.simplify(moduleContext, arg); } argValues.push(argValue); }); return FunctionWrapper.apply(reflector.factory(ctor), argValues); }); } private initializeConversionMap(): void { let coreDecorators = 'angular2/src/core/metadata'; let diDecorators = 'angular2/src/core/di/decorators'; let diMetadata = 'angular2/src/core/di/metadata'; let provider = 'angular2/src/core/di/provider'; let opaqueToken = 'angular2/src/core/di/opaque_token'; this.registerDecoratorOrConstructor(this.host.findDeclaration(provider, 'Provider'), Provider); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), HostMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Injectable'), InjectableMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), SelfMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelfMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), InjectMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Optional'), OptionalMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Attribute'), AttributeMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Query'), QueryMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ViewQuery'), ViewQueryMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChildMetadata); this.registerDecoratorOrConstructor( this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildrenMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChildMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildrenMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), InputMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Output'), OutputMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), PipeMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBindingMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'HostListener'), HostListenerMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Directive'), DirectiveMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Component'), ComponentMetadata); // Note: Some metadata classes can be used directly with Provider.deps. this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'HostMetadata'), HostMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'SelfMetadata'), SelfMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'SkipSelfMetadata'), SkipSelfMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'OptionalMetadata'), OptionalMetadata); this.registerDecoratorOrConstructor(this.host.findDeclaration(opaqueToken, 'OpaqueToken'), OpaqueToken); } /** @internal */ public simplify(moduleContext: ModuleContext, value: any): any { let _this = this; function simplify(expression: any): any { if (isPrimitive(expression)) { return expression; } if (isArray(expression)) { let result = []; for (let item of(<any>expression)) { result.push(simplify(item)); } return result; } if (isPresent(expression)) { if (isPresent(expression['__symbolic'])) { let staticSymbol; switch (expression['__symbolic']) { case "binop": let left = simplify(expression['left']); let right = simplify(expression['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 "pre": let operand = simplify(expression['operand']); switch (expression['operator']) { case '+': return operand; case '-': return -operand; case '!': return !operand; case '~': return ~operand; } return null; case "index": let indexTarget = simplify(expression['expression']); let index = simplify(expression['index']); if (isPresent(indexTarget) && isPrimitive(index)) return indexTarget[index]; return null; case "select": let selectTarget = simplify(expression['expression']); let member = simplify(expression['member']); if (isPresent(selectTarget) && isPrimitive(member)) return selectTarget[member]; return null; case "reference": if (isPresent(expression['module'])) { staticSymbol = _this.host.findDeclaration(expression['module'], expression['name'], moduleContext.filePath); } else { staticSymbol = _this.host.getStaticSymbol( moduleContext.moduleId, moduleContext.filePath, expression['name']); } let result = staticSymbol; let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath); let declarationValue = isPresent(moduleMetadata) ? moduleMetadata['metadata'][staticSymbol.name] : null; if (isPresent(declarationValue)) { if (isClassMetadata(declarationValue)) { result = staticSymbol; } else { let newModuleContext = new ModuleContext(staticSymbol.moduleId, staticSymbol.filePath); result = _this.simplify(newModuleContext, declarationValue); } } return result; case "new": case "call": let target = expression['expression']; staticSymbol = _this.host.findDeclaration(target['module'], target['name'], moduleContext.filePath); let converter = _this.conversionMap.get(staticSymbol); if (isBlank(converter)) { throw new BaseException(`Cannot convert call/new expression for ${target['name']} in ${moduleContext.filePath}`) } if (isPresent(converter)) { let args = expression['arguments']; if (isBlank(args)) { args = []; } return converter(moduleContext, args); } else { return staticSymbol; } } return null; } return mapStringMap(expression, (value, name) => simplify(value)); } return null; } return simplify(value); } /** * @param module an absolute path to a module file. */ public getModuleMetadata(module: string): {[key: string]: any} { let moduleMetadata = this.metadataCache.get(module); if (!isPresent(moduleMetadata)) { moduleMetadata = this.host.getMetadataFor(module); if (!isPresent(moduleMetadata)) { moduleMetadata = {__symbolic: "module", module: module, metadata: {}}; } this.metadataCache.set(module, moduleMetadata); } return moduleMetadata; } private getTypeMetadata(type: StaticSymbol): {[key: string]: any} { let moduleMetadata = this.getModuleMetadata(type.filePath); let result = moduleMetadata['metadata'][type.name]; if (!isPresent(result)) { result = {__symbolic: "class"}; } return result; } } function isClassMetadata(expression: any): boolean { return !isPrimitive(expression) && !isArray(expression) && expression['__symbolic'] == 'class'; } function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any): {[key: string]: any} { if (isBlank(input)) return {}; var result = {}; StringMapWrapper.keys(input).forEach((key) => { result[key] = transform(input[key], key); }); return result; }