diff --git a/modules/angular2/src/compiler/static_reflector.ts b/modules/angular2/src/compiler/static_reflector.ts index 413f2a4f7d..7092ef120f 100644 --- a/modules/angular2/src/compiler/static_reflector.ts +++ b/modules/angular2/src/compiler/static_reflector.ts @@ -1,12 +1,8 @@ -import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; +import {StringMapWrapper} from 'angular2/src/facade/collection'; import { isArray, - isBlank, - isNumber, isPresent, isPrimitive, - isString, - Type } from 'angular2/src/facade/lang'; import { AttributeMetadata, @@ -35,13 +31,19 @@ import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader'; */ export interface StaticReflectorHost { /** - * Return a ModuleMetadata for the give module. + * Return a ModuleMetadata for the given module. * - * @param moduleId is a string identifier for a module in the form that would expected in a - * module import of an import statement. + * @param moduleId is a string identifier for a module as an absolute path. * @returns the metadata for the given module. */ getMetadataFor(moduleId: string): {[key: string]: any}; + + /** + * Resolve a module from an import statement form to an absolute path. + * @param moduleName the location imported from + * @param containingFile for relative imports, the path of the file containing the import + */ + resolveModule(moduleName: string, containingFile?: string): string; } /** @@ -68,10 +70,10 @@ export class StaticReflector implements ReflectorReader { importUri(typeOrFunc: any): string { return (typeOrFunc).moduleId; } /** - * getStatictype produces a Type whose metadata is known but whose implementation is not loaded. + * getStaticType 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 moduleId the module identifier as would be passed to an import statement. + * @param moduleId the module identifier as an absolute path. * @param name the name of the type. */ public getStaticType(moduleId: string, name: string): StaticType { @@ -137,7 +139,7 @@ export class StaticReflector implements ReflectorReader { private conversionMap = new Map any>(); private initializeConversionMap(): any { - let core_metadata = 'angular2/src/core/metadata'; + let core_metadata = this.host.resolveModule('angular2/src/core/metadata'); let conversionMap = this.conversionMap; conversionMap.set(this.getStaticType(core_metadata, 'Directive'), (moduleContext, expression) => { @@ -272,7 +274,7 @@ export class StaticReflector implements ReflectorReader { if (isMetadataSymbolicCallExpression(expression)) { let target = expression['expression']; if (isMetadataSymbolicReferenceExpression(target)) { - let moduleId = this.normalizeModuleName(moduleContext, target['module']); + let moduleId = this.host.resolveModule(target['module'], moduleContext); return this.getStaticType(moduleId, target['name']); } } @@ -417,7 +419,7 @@ export class StaticReflector implements ReflectorReader { return null; case "reference": let referenceModuleName = - _this.normalizeModuleName(moduleContext, expression['module']); + _this.host.resolveModule(expression['module'], moduleContext); let referenceModule = _this.getModuleMetadata(referenceModuleName); let referenceValue = referenceModule['metadata'][expression['name']]; if (isClassMetadata(referenceValue)) { @@ -440,6 +442,9 @@ export class StaticReflector implements ReflectorReader { 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)) { @@ -460,13 +465,6 @@ export class StaticReflector implements ReflectorReader { } return result; } - - private normalizeModuleName(from: string, to: string): string { - if (to.startsWith('.')) { - return pathTo(from, to); - } - return to; - } } function isMetadataSymbolicCallExpression(expression: any): boolean { @@ -481,35 +479,3 @@ function isMetadataSymbolicReferenceExpression(expression: any): boolean { function isClassMetadata(expression: any): boolean { return !isPrimitive(expression) && !isArray(expression) && expression['__symbolic'] == 'class'; } - -function splitPath(path: string): string[] { - return path.split(/\/|\\/g); -} - -function resolvePath(pathParts: string[]): string { - let result = []; - ListWrapper.forEachWithIndex(pathParts, (part, index) => { - switch (part) { - case '': - case '.': - if (index > 0) return; - break; - case '..': - if (index > 0 && result.length != 0) result.pop(); - return; - } - result.push(part); - }); - return result.join('/'); -} - -function pathTo(from: string, to: string): string { - let result = to; - if (to.startsWith('.')) { - let fromParts = splitPath(from); - fromParts.pop(); // remove the file name. - let toParts = splitPath(to); - result = resolvePath(fromParts.concat(toParts)); - } - return result; -} diff --git a/modules/angular2/test/compiler/static_reflector_spec.ts b/modules/angular2/test/compiler/static_reflector_spec.ts index e9ce0868f9..db4ebf939d 100644 --- a/modules/angular2/test/compiler/static_reflector_spec.ts +++ b/modules/angular2/test/compiler/static_reflector_spec.ts @@ -1,27 +1,20 @@ import { - ddescribe, describe, - xdescribe, it, - iit, - xit, expect, - beforeEach, - afterEach, - AsyncTestCompleter, - inject, - beforeEachProviders } from 'angular2/testing_internal'; +import {ListWrapper} from 'angular2/src/facade/collection'; import {StaticReflector, StaticReflectorHost} from 'angular2/src/compiler/static_reflector'; export function main() { - describe('StaticRefelector', () => { + describe('StaticReflector', () => { it('should get annotations for NgFor', () => { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); - let NgFor = reflector.getStaticType('angular2/src/common/directives/ng_for', 'NgFor'); + let NgFor = reflector.getStaticType( + host.resolveModule('angular2/src/common/directives/ng_for'), 'NgFor'); let annotations = reflector.annotations(NgFor); expect(annotations.length).toEqual(1); let annotation = annotations[0]; @@ -33,15 +26,18 @@ export function main() { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); - let NgFor = reflector.getStaticType('angular2/src/common/directives/ng_for', 'NgFor'); - let ViewContainerRef = reflector.getStaticType('angular2/src/core/linker/view_container_ref', - 'ViewContainerRef'); - let TemplateRef = - reflector.getStaticType('angular2/src/core/linker/template_ref', 'TemplateRef'); + let NgFor = reflector.getStaticType( + host.resolveModule('angular2/src/common/directives/ng_for'), 'NgFor'); + let ViewContainerRef = reflector.getStaticType( + host.resolveModule('angular2/src/core/linker/view_container_ref'), 'ViewContainerRef'); + let TemplateRef = reflector.getStaticType( + host.resolveModule('angular2/src/core/linker/template_ref'), 'TemplateRef'); let IterableDiffers = reflector.getStaticType( - 'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers'); + host.resolveModule('angular2/src/core/change_detection/differs/iterable_differs'), + 'IterableDiffers'); let ChangeDetectorRef = reflector.getStaticType( - 'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef'); + host.resolveModule('angular2/src/core/change_detection/change_detector_ref'), + 'ChangeDetectorRef'); let parameters = reflector.parameters(NgFor); expect(parameters) @@ -53,7 +49,7 @@ export function main() { let reflector = new StaticReflector(host); let HeroDetailComponent = - reflector.getStaticType('./app/hero-detail.component', 'HeroDetailComponent'); + reflector.getStaticType('/src/app/hero-detail.component.ts', 'HeroDetailComponent'); let annotations = reflector.annotations(HeroDetailComponent); expect(annotations.length).toEqual(1); let annotation = annotations[0]; @@ -64,7 +60,7 @@ export function main() { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); - let UnknownClass = reflector.getStaticType('./app/app.component', 'UnknownClass'); + let UnknownClass = reflector.getStaticType('/src/app/app.component.ts', 'UnknownClass'); let annotations = reflector.annotations(UnknownClass); expect(annotations).toEqual([]); }); @@ -74,7 +70,7 @@ export function main() { let reflector = new StaticReflector(host); let HeroDetailComponent = - reflector.getStaticType('./app/hero-detail.component', 'HeroDetailComponent'); + reflector.getStaticType('/src/app/hero-detail.component.ts', 'HeroDetailComponent'); let props = reflector.propMetadata(HeroDetailComponent); expect(props['hero']).toBeTruthy(); }); @@ -83,7 +79,7 @@ export function main() { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); - let UnknownClass = reflector.getStaticType('./app/app.component', 'UnknownClass'); + let UnknownClass = reflector.getStaticType('/src/app/app.component.ts', 'UnknownClass'); let properties = reflector.propMetadata(UnknownClass); expect(properties).toEqual({}); }); @@ -92,7 +88,7 @@ export function main() { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); - let UnknownClass = reflector.getStaticType('./app/app.component', 'UnknownClass'); + let UnknownClass = reflector.getStaticType('/src/app/app.component.ts', 'UnknownClass'); let parameters = reflector.parameters(UnknownClass); expect(parameters).toEqual([]); }); @@ -301,7 +297,7 @@ export function main() { expect(reflector.simplify('', ({ __symbolic: 'pre', operator: '!', operand: false}))).toBe(!false); }); - it('should simpify an array index', () => { + it('should simplify an array index', () => { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); @@ -320,20 +316,56 @@ export function main() { let host = new MockReflectorHost(); let reflector = new StaticReflector(host); - expect( - reflector.simplify('./cases', ({__symbolic: "reference", module: "./extern", name: "s"}))) + expect(reflector.simplify('/src/cases', + ({__symbolic: "reference", module: "./extern", name: "s"}))) .toEqual("s"); }); }); } class MockReflectorHost implements StaticReflectorHost { + resolveModule(moduleName: string, containingFile?: string): string { + function splitPath(path: string): string[] { return path.split(/\/|\\/g); } + + function resolvePath(pathParts: string[]): string { + let result = []; + ListWrapper.forEachWithIndex(pathParts, (part, index) => { + switch (part) { + case '': + case '.': + if (index > 0) return; + break; + case '..': + if (index > 0 && result.length != 0) result.pop(); + return; + } + result.push(part); + }); + return result.join('/'); + } + + function pathTo(from: string, to: string): string { + let result = to; + if (to.startsWith('.')) { + let fromParts = splitPath(from); + fromParts.pop(); // remove the file name. + let toParts = splitPath(to); + result = resolvePath(fromParts.concat(toParts)); + } + return result; + } + + if (moduleName.indexOf('.') === 0) { + return pathTo(containingFile, moduleName) + '.d.ts'; + } + return '/tmp/' + moduleName + '.d.ts'; + } + getMetadataFor(moduleId: string): any { return { - 'angular2/src/common/directives/ng_for': + '/tmp/angular2/src/common/directives/ng_for.d.ts': { "__symbolic": "module", - "module": "./ng_for", "metadata": { "NgFor": @@ -393,27 +425,17 @@ class MockReflectorHost implements StaticReflectorHost { } } }, - 'angular2/src/core/linker/view_container_ref': - { - "module": "./view_container_ref", - "metadata": {"ViewContainerRef": {"__symbolic": "class"}} - }, - 'angular2/src/core/linker/template_ref': + '/tmp/angular2/src/core/linker/view_container_ref.d.ts': + {"metadata": {"ViewContainerRef": {"__symbolic": "class"}}}, + '/tmp/angular2/src/core/linker/template_ref.d.ts': {"module": "./template_ref", "metadata": {"TemplateRef": {"__symbolic": "class"}}}, - 'angular2/src/core/change_detection/differs/iterable_differs': - { - "module": "./iterable_differs", - "metadata": {"IterableDiffers": {"__symbolic": "class"}} - }, - 'angular2/src/core/change_detection/change_detector_ref': - { - "module": "./change_detector_ref", - "metadata": {"ChangeDetectorRef": {"__symbolic": "class"}} - }, - './app/hero-detail.component': + '/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts': + {"metadata": {"IterableDiffers": {"__symbolic": "class"}}}, + '/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts': + {"metadata": {"ChangeDetectorRef": {"__symbolic": "class"}}}, + '/src/app/hero-detail.component.ts': { "__symbolic": "module", - "module": "./hero-detail.component", "metadata": { "HeroDetailComponent": @@ -459,8 +481,8 @@ class MockReflectorHost implements StaticReflectorHost { } } }, - './extern': { - "__symbolic": "module", module: './extern', metadata: { s: "s" } + '/src/extern.d.ts': { + "__symbolic": "module", metadata: { s: "s" } } } [moduleId]; diff --git a/tools/broccoli/broccoli-typescript.ts b/tools/broccoli/broccoli-typescript.ts index 9d4ff9f4de..637b519a05 100644 --- a/tools/broccoli/broccoli-typescript.ts +++ b/tools/broccoli/broccoli-typescript.ts @@ -88,16 +88,7 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin { this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths, this.fileRegistry, this.inputPath); this.tsService = ts.createLanguageService(this.tsServiceHost, ts.createDocumentRegistry()); - this.metadataCollector = new MetadataCollector({ - // Since our code isn't under a node_modules directory, we need to reverse the module - // resolution to get metadata rooted at 'angular2'. - // see https://github.com/angular/angular/issues/8144 - reverseModuleResolution(fileName: string) { - if (/\.tmp\/angular2/.test(fileName)) { - return fileName.substr(fileName.lastIndexOf('.tmp/angular2/') + 5).replace(/\.ts$/, ''); - } - } - }); + this.metadataCollector = new MetadataCollector(); } diff --git a/tools/metadata/src/collector.ts b/tools/metadata/src/collector.ts index 49a349a715..2c7764515a 100644 --- a/tools/metadata/src/collector.ts +++ b/tools/metadata/src/collector.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import {Evaluator} from './evaluator'; +import {Evaluator, ImportMetadata, ImportSpecifierMetadata} from './evaluator'; import {Symbols} from './symbols'; import { ClassMetadata, @@ -13,46 +13,56 @@ import { MethodMetadata } from './schema'; -import * as path from 'path'; - -const EXT_REGEX = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; -const NODE_MODULES = '/node_modules/'; -const NODE_MODULES_PREFIX = 'node_modules/'; - -function pathTo(from: string, to: string): string { - var result = path.relative(path.dirname(from), to); - if (path.dirname(result) === '.') { - result = '.' + path.sep + result; - } - return result; -} - -export interface MetadataCollectorHost { - reverseModuleResolution: (moduleFileName: string) => string; -} - -const nodeModuleResolutionHost: MetadataCollectorHost = { - // Reverse moduleResolution=node for packages resolved in node_modules - reverseModuleResolution(fileName: string) { - // Remove the extension - const moduleFileName = fileName.replace(EXT_REGEX, ''); - // Check for node_modules - const nodeModulesIndex = moduleFileName.lastIndexOf(NODE_MODULES); - if (nodeModulesIndex >= 0) { - return moduleFileName.substr(nodeModulesIndex + NODE_MODULES.length); - } - if (moduleFileName.lastIndexOf(NODE_MODULES_PREFIX, NODE_MODULES_PREFIX.length) !== -1) { - return moduleFileName.substr(NODE_MODULES_PREFIX.length); - } - return null; - } -}; - /** * Collect decorator metadata from a TypeScript module. */ export class MetadataCollector { - constructor(private host: MetadataCollectorHost = nodeModuleResolutionHost) {} + constructor() {} + + collectImports(sourceFile: ts.SourceFile) { + let imports: ImportMetadata[] = []; + const stripQuotes = (s: string) => s.replace(/^['"]|['"]$/g, ''); + function visit(node: ts.Node) { + switch (node.kind) { + case ts.SyntaxKind.ImportDeclaration: + const importDecl = node; + const from = stripQuotes(importDecl.moduleSpecifier.getText()); + const newImport = {from}; + if (!importDecl.importClause) { + // Bare imports do not bring symbols into scope, so we don't need to record them + break; + } + if (importDecl.importClause.name) { + newImport['defaultName'] = importDecl.importClause.name.text; + } + const bindings = importDecl.importClause.namedBindings; + if (bindings) { + switch (bindings.kind) { + case ts.SyntaxKind.NamedImports: + const namedImports: ImportSpecifierMetadata[] = []; + (bindings) + .elements.forEach(i => { + const namedImport = {name: i.name.text}; + if (i.propertyName) { + namedImport['propertyName'] = i.propertyName.text; + } + namedImports.push(namedImport); + }); + newImport['namedImports'] = namedImports; + break; + case ts.SyntaxKind.NamespaceImport: + newImport['namespace'] = (bindings).name.text; + break; + } + } + imports.push(newImport); + break; + } + ts.forEachChild(node, visit); + } + ts.forEachChild(sourceFile, visit); + return imports; + } /** * Returns a JSON.stringify friendly form describing the decorators of the exported classes from @@ -60,17 +70,7 @@ export class MetadataCollector { */ public getMetadata(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): ModuleMetadata { const locals = new Symbols(); - const moduleNameOf = (fileName: string) => { - // If the module was resolved with TS moduleResolution, reverse that mapping - const hostResolved = this.host.reverseModuleResolution(fileName); - if (hostResolved) { - return hostResolved; - } - // Construct a simplified path from the file to the module - return pathTo(sourceFile.fileName, fileName).replace(EXT_REGEX, ''); - }; - - const evaluator = new Evaluator(typeChecker, locals, moduleNameOf); + const evaluator = new Evaluator(typeChecker, locals, this.collectImports(sourceFile)); function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression { return evaluator.evaluateNode(decoratorNode.expression); @@ -80,18 +80,7 @@ export class MetadataCollector { if (type) { let symbol = type.getSymbol(); if (symbol) { - if (symbol.flags & ts.SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } - if (symbol.declarations.length) { - const declaration = symbol.declarations[0]; - const sourceFile = declaration.getSourceFile(); - return { - __symbolic: "reference", - module: moduleNameOf(sourceFile.fileName), - name: symbol.name - }; - } + return evaluator.symbolReference(symbol); } } } @@ -206,6 +195,7 @@ export class MetadataCollector { } } } - return metadata && {__symbolic: "module", module: moduleNameOf(sourceFile.fileName), metadata}; + + return metadata && {__symbolic: "module", metadata}; } } diff --git a/tools/metadata/src/evaluator.ts b/tools/metadata/src/evaluator.ts index d654b95140..19fbca0158 100644 --- a/tools/metadata/src/evaluator.ts +++ b/tools/metadata/src/evaluator.ts @@ -3,7 +3,6 @@ import {Symbols} from './symbols'; import { MetadataValue, - MetadataObject, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression } from './schema'; @@ -58,40 +57,72 @@ 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' +} + /** * Produce a symbolic representation of an expression folding values into their final value when * possible. */ export class Evaluator { constructor(private typeChecker: ts.TypeChecker, private symbols: Symbols, - private moduleNameOf: (fileName: string) => string) {} + private imports: ImportMetadata[]) {} - // TODO: Determine if the first declaration is deterministic. - private symbolFileName(symbol: ts.Symbol): string { + symbolReference(symbol: ts.Symbol): MetadataSymbolicReferenceExpression { if (symbol) { - if (symbol.flags & ts.SymbolFlags.Alias) { - symbol = this.typeChecker.getAliasedSymbol(symbol); - } - const declarations = symbol.getDeclarations(); - if (declarations && declarations.length > 0) { - const sourceFile = declarations[0].getSourceFile(); - if (sourceFile) { - return sourceFile.fileName; + let module: string; + let name = symbol.name; + for (const eachImport of this.imports) { + if (symbol.name === eachImport.defaultName) { + module = eachImport.from; + name = undefined; + } + if (eachImport.namedImports) { + for (const named of eachImport.namedImports) { + if (symbol.name === named.name) { + name = named.propertyName ? named.propertyName : named.name; + module = eachImport.from; + break; + } + } } } - } - return undefined; - } - - private symbolReference(symbol: ts.Symbol): MetadataSymbolicReferenceExpression { - if (symbol) { - const name = symbol.name; - const module = this.moduleNameOf(this.symbolFileName(symbol)); return {__symbolic: "reference", name, module}; } } + private findImportNamespace(node: ts.Node) { + if (node.kind === ts.SyntaxKind.PropertyAccessExpression) { + const lhs = (node).expression; + if (lhs.kind === ts.SyntaxKind.Identifier) { + // TOOD: Use Array.find when tools directory is upgraded to support es6 target + for (const eachImport of this.imports) { + if (eachImport.namespace === (lhs).text) { + return eachImport; + } + } + } + } + } + private nodeSymbolReference(node: ts.Node): MetadataSymbolicReferenceExpression { + const importNamespace = this.findImportNamespace(node); + if (importNamespace) { + const result = this.symbolReference( + this.typeChecker.getSymbolAtLocation((node).name)); + result.module = importNamespace.from; + return result; + } return this.symbolReference(this.typeChecker.getSymbolAtLocation(node)); } @@ -115,7 +146,7 @@ export class Evaluator { * - 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 is in the evaluator symbol + * - An identifier is foldable if a value can be found for its symbol in the evaluator symbol * table. */ public isFoldable(node: ts.Node): boolean { @@ -129,7 +160,7 @@ export class Evaluator { return everyNodeChild(node, child => { if (child.kind === ts.SyntaxKind.PropertyAssignment) { const propertyAssignment = child; - return this.isFoldableWorker(propertyAssignment.initializer, folding) + return this.isFoldableWorker(propertyAssignment.initializer, folding); } return false; }); @@ -272,6 +303,9 @@ export class Evaluator { const expression = this.evaluateNode(propertyAccessExpression.expression); const member = this.nameOf(propertyAccessExpression.name); if (this.isFoldable(propertyAccessExpression.expression)) return expression[member]; + if (this.findImportNamespace(propertyAccessExpression)) { + return this.nodeSymbolReference(propertyAccessExpression); + } if (isDefined(expression)) { return {__symbolic: "select", expression, member}; } diff --git a/tools/metadata/src/schema.ts b/tools/metadata/src/schema.ts index 746c4d6d98..f6bd27f113 100644 --- a/tools/metadata/src/schema.ts +++ b/tools/metadata/src/schema.ts @@ -1,8 +1,5 @@ -// TODO: fix typings for __symbolic once angular moves to 1.8 - export interface ModuleMetadata { - __symbolic: string; // "module"; - module: string; + __symbolic: "module"; metadata: {[name: string]: (ClassMetadata | MetadataValue)}; } export function isModuleMetadata(value: any): value is ModuleMetadata { @@ -10,7 +7,7 @@ export function isModuleMetadata(value: any): value is ModuleMetadata { } export interface ClassMetadata { - __symbolic: string; // "class"; + __symbolic: "class"; decorators?: MetadataSymbolicExpression[]; members?: MetadataMap; } @@ -21,7 +18,7 @@ export function isClassMetadata(value: any): value is ClassMetadata { export interface MetadataMap { [name: string]: MemberMetadata[]; } export interface MemberMetadata { - __symbolic: string; // "constructor" | "method" | "property"; + __symbolic: "constructor" | "method" | "property"; decorators?: MetadataSymbolicExpression[]; } export function isMemberMetadata(value: any): value is MemberMetadata { @@ -37,7 +34,7 @@ export function isMemberMetadata(value: any): value is MemberMetadata { } export interface MethodMetadata extends MemberMetadata { - // __symbolic: "constructor" | "method"; + __symbolic: "constructor" | "method"; parameterDecorators?: MetadataSymbolicExpression[][]; } export function isMethodMetadata(value: any): value is MemberMetadata { @@ -45,7 +42,7 @@ export function isMethodMetadata(value: any): value is MemberMetadata { } export interface ConstructorMetadata extends MethodMetadata { - // __symbolic: "constructor"; + __symbolic: "constructor"; parameters?: MetadataSymbolicExpression[]; } export function isConstructorMetadata(value: any): value is ConstructorMetadata { @@ -60,7 +57,7 @@ export interface MetadataObject { [name: string]: MetadataValue; } export interface MetadataArray { [name: number]: MetadataValue; } export interface MetadataSymbolicExpression { - __symbolic: string; // "binary" | "call" | "index" | "pre" | "reference" | "select" + __symbolic: "binary" | "call" | "index" | "pre" | "reference" | "select" } export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression { if (value) { @@ -78,10 +75,9 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo } export interface MetadataSymbolicBinaryExpression extends MetadataSymbolicExpression { - // __symbolic: "binary"; - operator: string; // "&&" | "||" | "|" | "^" | "&" | "==" | "!=" | "===" | "!==" | "<" | ">" | - // "<=" | ">=" | "instanceof" | "in" | "as" | "<<" | ">>" | ">>>" | "+" | "-" | - // "*" | "/" | "%" | "**"; + __symbolic: "binary"; + operator: "&&" | "||" | "|" | "^" | "&" | "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">=" | + "instanceof" | "in" | "as" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "**"; left: MetadataValue; right: MetadataValue; } @@ -91,7 +87,7 @@ export function isMetadataSymbolicBinaryExpression( } export interface MetadataSymbolicIndexExpression extends MetadataSymbolicExpression { - // __symbolic: "index"; + __symbolic: "index"; expression: MetadataValue; index: MetadataValue; } @@ -101,7 +97,7 @@ export function isMetadataSymbolicIndexExpression( } export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression { - // __symbolic: "call"; + __symbolic: "call"; expression: MetadataValue; arguments?: MetadataValue[]; } @@ -111,8 +107,8 @@ export function isMetadataSymbolicCallExpression( } export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression { - // __symbolic: "pre"; - operator: string; // "+" | "-" | "~" | "!"; + __symbolic: "pre"; + operator: "+" | "-" | "~" | "!"; operand: MetadataValue; } export function isMetadataSymbolicPrefixExpression( @@ -121,7 +117,7 @@ export function isMetadataSymbolicPrefixExpression( } export interface MetadataSymbolicReferenceExpression extends MetadataSymbolicExpression { - // __symbolic: "reference"; + __symbolic: "reference"; name: string; module: string; } @@ -131,7 +127,7 @@ export function isMetadataSymbolicReferenceExpression( } export interface MetadataSymbolicSelectExpression extends MetadataSymbolicExpression { - // __symbolic: "select"; + __symbolic: "select"; expression: MetadataValue; name: string; } diff --git a/tools/metadata/test/collector.spec.ts b/tools/metadata/test/collector.spec.ts index 652a0ea024..42f9b4d294 100644 --- a/tools/metadata/test/collector.spec.ts +++ b/tools/metadata/test/collector.spec.ts @@ -29,12 +29,26 @@ describe('Collector', () => { expect(metadata).toBeUndefined(); }); + it("should be able to collect import statements", () => { + const sourceFile = program.getSourceFile('app/app.component.ts'); + expect(collector.collectImports(sourceFile)) + .toEqual([ + { + from: 'angular2/core', + namedImports: [{name: 'MyComponent', propertyName: 'Component'}, {name: 'OnInit'}] + }, + {from: 'angular2/common', namespace: 'common'}, + {from: './hero', namedImports: [{name: 'Hero'}]}, + {from: './hero-detail.component', namedImports: [{name: 'HeroDetailComponent'}]}, + {from: './hero.service', defaultName: 'HeroService'} + ]); + }); + it("should be able to collect a simple component's metadata", () => { const sourceFile = program.getSourceFile('app/hero-detail.component.ts'); const metadata = collector.getMetadata(sourceFile, typeChecker); expect(metadata).toEqual({ __symbolic: 'module', - module: './hero-detail.component', metadata: { HeroDetailComponent: { __symbolic: 'class', @@ -83,7 +97,6 @@ describe('Collector', () => { const metadata = collector.getMetadata(sourceFile, typeChecker); expect(metadata).toEqual({ __symbolic: 'module', - module: './app.component', metadata: { AppComponent: { __symbolic: 'class', @@ -113,8 +126,7 @@ describe('Collector', () => { }, {__symbolic: 'reference', name: 'NgFor', module: 'angular2/common'} ], - providers: - [{__symbolic: 'reference', name: 'HeroService', module: './hero.service'}], + providers: [{__symbolic: 'reference', name: undefined, module: './hero.service'}], pipes: [ {__symbolic: 'reference', name: 'LowerCasePipe', module: 'angular2/common'}, { @@ -131,9 +143,8 @@ describe('Collector', () => { __ctor__: [ { __symbolic: 'constructor', - parameters: [ - {__symbolic: 'reference', module: './hero.service', name: 'HeroService'} - ] + parameters: + [{__symbolic: 'reference', name: undefined, module: './hero.service'}] } ] } @@ -147,7 +158,6 @@ describe('Collector', () => { const metadata = collector.getMetadata(sourceFile, typeChecker); expect(metadata).toEqual({ __symbolic: 'module', - module: './mock-heroes', metadata: { HEROES: [ {"id": 11, "name": "Mr. Nice"}, @@ -187,7 +197,7 @@ describe('Collector', () => { expect(ctorData).toEqual([{__symbolic: 'constructor', parameters: [null]}]); }); - it('should record annotations on set and get declartions', () => { + it('should record annotations on set and get declarations', () => { const propertyData = { name: [ { @@ -215,13 +225,15 @@ describe('Collector', () => { const FILES: Directory = { 'app': { 'app.component.ts': ` - import {Component, OnInit} from 'angular2/core'; - import {NgFor, LowerCasePipe, UpperCasePipe} from 'angular2/common'; + import {Component as MyComponent, OnInit} from 'angular2/core'; + import * as common from 'angular2/common'; import {Hero} from './hero'; import {HeroDetailComponent} from './hero-detail.component'; - import {HeroService} from './hero.service'; - - @Component({ + import HeroService from './hero.service'; + // thrown away + import 'angular2/core'; + + @MyComponent({ selector: 'my-app', template:` + "`" + `

My Heroes

@@ -235,9 +247,9 @@ const FILES: Directory = { ` + "`" + `, - directives: [HeroDetailComponent, NgFor], + directives: [HeroDetailComponent, common.NgFor], providers: [HeroService], - pipes: [LowerCasePipe, UpperCasePipe] + pipes: [common.LowerCasePipe, common.UpperCasePipe] }) export class AppComponent implements OnInit { public title = 'Tour of Heroes'; @@ -282,7 +294,7 @@ const FILES: Directory = { @Input() public hero: Hero; }`, 'mock-heroes.ts': ` - import {Hero} from './hero'; + import {Hero as Hero} from './hero'; export const HEROES: Hero[] = [ {"id": 11, "name": "Mr. Nice"}, @@ -296,13 +308,17 @@ const FILES: Directory = { {"id": 19, "name": "Magma"}, {"id": 20, "name": "Tornado"} ];`, + 'default-exporter.ts': ` + let a: string; + export default a; + `, 'hero.service.ts': ` import {Injectable} from 'angular2/core'; import {HEROES} from './mock-heroes'; import {Hero} from './hero'; @Injectable() - export class HeroService { + class HeroService { getHeros() { return Promise.resolve(HEROES); } @@ -311,7 +327,8 @@ const FILES: Directory = { return new Promise(resolve => setTimeout(()=>resolve(HEROES), 2000)); // 2 seconds } - }`, + } + export default HeroService;`, 'cases-data.ts': ` import {Injectable, Input} from 'angular2/core'; @@ -348,7 +365,7 @@ const FILES: Directory = { } `, 'cases-no-data.ts': ` - import {HeroService} from './hero.service'; + import HeroService from './hero.service'; export class CaseCtor { constructor(private _heroService: HeroService) { } diff --git a/tools/metadata/test/evaluator.spec.ts b/tools/metadata/test/evaluator.spec.ts index f52a03982b..5a5e6be090 100644 --- a/tools/metadata/test/evaluator.spec.ts +++ b/tools/metadata/test/evaluator.spec.ts @@ -18,7 +18,7 @@ describe('Evaluator', () => { program = service.getProgram(); typeChecker = program.getTypeChecker(); symbols = new Symbols(); - evaluator = new Evaluator(typeChecker, symbols, f => f); + evaluator = new Evaluator(typeChecker, symbols, []); }); it('should not have typescript errors in test data', () => { @@ -97,9 +97,9 @@ describe('Evaluator', () => { it('should report recursive references as symbolic', () => { var expressions = program.getSourceFile('expressions.ts'); expect(evaluator.evaluateNode(findVar(expressions, 'recursiveA').initializer)) - .toEqual({__symbolic: "reference", name: "recursiveB", module: "expressions.ts"}); + .toEqual({__symbolic: "reference", name: "recursiveB", module: undefined}); expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer)) - .toEqual({__symbolic: "reference", name: "recursiveA", module: "expressions.ts"}); + .toEqual({__symbolic: "reference", name: "recursiveA", module: undefined}); }); }); @@ -160,4 +160,4 @@ const FILES: Directory = { @Pipe({name: someName, pure: someBool}) export class B {}` -} +};