From 24099bdbd24a657c8ac7ff9f04942fd0c0f440b2 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 15 Nov 2016 08:49:23 -0800 Subject: [PATCH] refactor(compiler): move `findDeclaration` into the `StaticReflector` Previously, this was part of the `AotCompilerHost`. The `AotCompilerHost` is now also greatly simplified. --- modules/@angular/compiler-cli/index.ts | 4 +- modules/@angular/compiler-cli/src/codegen.ts | 38 +- .../@angular/compiler-cli/src/extractor.ts | 14 +- .../src/{reflector_host.ts => ng_host.ts} | 158 +-------- ...flector_host.ts => path_mapped_ng_host.ts} | 19 +- .../compiler-cli/src/private_import_core.ts | 6 - modules/@angular/compiler-cli/test/mocks.ts | 4 +- .../compiler-cli/test/ng_host_spec.ts | 204 +++++++++++ .../compiler-cli/test/reflector_host_spec.ts | 329 ------------------ modules/@angular/compiler/index.ts | 1 + .../compiler/src/aot/compiler_host.ts | 31 ++ .../compiler/src/aot/static_reflector.ts | 248 ++++++++----- .../test/aot/static_reflector_spec.ts | 260 ++++++++------ 13 files changed, 612 insertions(+), 704 deletions(-) rename modules/@angular/compiler-cli/src/{reflector_host.ts => ng_host.ts} (58%) rename modules/@angular/compiler-cli/src/{path_mapped_reflector_host.ts => path_mapped_ng_host.ts} (87%) create mode 100644 modules/@angular/compiler-cli/test/ng_host_spec.ts delete mode 100644 modules/@angular/compiler-cli/test/reflector_host_spec.ts create mode 100644 modules/@angular/compiler/src/aot/compiler_host.ts diff --git a/modules/@angular/compiler-cli/index.ts b/modules/@angular/compiler-cli/index.ts index 19b10e6cfc..590350abcf 100644 --- a/modules/@angular/compiler-cli/index.ts +++ b/modules/@angular/compiler-cli/index.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -export {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler'; export {CodeGenerator} from './src/codegen'; export {Extractor} from './src/extractor'; -export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host'; +export {NgHost, NgHostContext, NodeNgHostContext} from './src/ng_host'; export * from '@angular/tsc-wrapped'; diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 5c64c1955e..03255bd92a 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -17,9 +17,9 @@ import {readFileSync} from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; -import {PathMappedReflectorHost} from './path_mapped_reflector_host'; +import {NgHost, NgHostContext} from './ng_host'; +import {PathMappedNgHost} from './path_mapped_ng_host'; import {Console} from './private_import_core'; -import {ReflectorHost, ReflectorHostContext} from './reflector_host'; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; @@ -37,8 +37,7 @@ export class CodeGenerator { constructor( private options: AngularCompilerOptions, private program: ts.Program, public host: ts.CompilerHost, private staticReflector: compiler.StaticReflector, - private compiler: compiler.AotCompiler, private reflectorHost: compiler.StaticReflectorHost) { - } + private compiler: compiler.AotCompiler, private ngHost: NgHost) {} // Write codegen in a directory structure matching the sources. private calculateEmitPath(filePath: string): string { @@ -65,7 +64,7 @@ export class CodeGenerator { codegen(options: {transitiveModules: boolean}): Promise { const staticSymbols = - extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); + extractProgramSymbols(this.program, this.staticReflector, this.ngHost, this.options); return this.compiler.compileModules(staticSymbols, options).then(generatedModules => { generatedModules.forEach(generatedModule => { @@ -79,8 +78,8 @@ export class CodeGenerator { static create( options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, - compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext, - resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator { + compilerHost: ts.CompilerHost, ngHostContext?: NgHostContext, + resourceLoader?: compiler.ResourceLoader, ngHost?: NgHost): CodeGenerator { resourceLoader = resourceLoader || { get: (s: string) => { if (!compilerHost.fileExists(s)) { @@ -102,13 +101,13 @@ export class CodeGenerator { } const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); - if (!reflectorHost) { + if (!ngHost) { const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; - reflectorHost = usePathMapping ? - new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) : - new ReflectorHost(program, compilerHost, options, reflectorHostContext); + ngHost = usePathMapping ? + new PathMappedNgHost(program, compilerHost, options, ngHostContext) : + new NgHost(program, compilerHost, options, ngHostContext); } - const staticReflector = new compiler.StaticReflector(reflectorHost); + const staticReflector = new compiler.StaticReflector(ngHost); compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector); const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat); @@ -135,18 +134,15 @@ export class CodeGenerator { new compiler.ViewCompiler(config, elementSchemaRegistry), new compiler.DirectiveWrapperCompiler( config, expressionParser, elementSchemaRegistry, console), - new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost), - cliOptions.locale, cliOptions.i18nFormat, - new compiler.AnimationParser(elementSchemaRegistry)); + new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(ngHost), cliOptions.locale, + cliOptions.i18nFormat, new compiler.AnimationParser(elementSchemaRegistry)); - return new CodeGenerator( - options, program, compilerHost, staticReflector, aotCompiler, reflectorHost); + return new CodeGenerator(options, program, compilerHost, staticReflector, aotCompiler, ngHost); } } export function extractProgramSymbols( - program: ts.Program, staticReflector: compiler.StaticReflector, - reflectorHost: compiler.StaticReflectorHost, + program: ts.Program, staticReflector: compiler.StaticReflector, ngHost: NgHost, options: AngularCompilerOptions): compiler.StaticSymbol[] { // Compare with false since the default should be true const skipFileNames = @@ -157,7 +153,7 @@ export function extractProgramSymbols( program.getSourceFiles() .filter(sourceFile => !skipFileNames.test(sourceFile.fileName)) .forEach(sourceFile => { - const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName); + const absSrcPath = ngHost.getCanonicalFileName(sourceFile.fileName); const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath); if (!moduleMetadata) { @@ -176,7 +172,7 @@ export function extractProgramSymbols( // Ignore symbols that are only included to record error information. continue; } - staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath)); + staticSymbols.push(staticReflector.findDeclaration(absSrcPath, symbol, absSrcPath)); } }); diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index c956b60231..a16a388bad 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -19,18 +19,18 @@ import * as tsc from '@angular/tsc-wrapped'; import * as ts from 'typescript'; import {extractProgramSymbols} from './codegen'; -import {ReflectorHost} from './reflector_host'; +import {NgHost} from './ng_host'; export class Extractor { constructor( private options: tsc.AngularCompilerOptions, private program: ts.Program, public host: ts.CompilerHost, private staticReflector: compiler.StaticReflector, - private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost, + private messageBundle: compiler.MessageBundle, private ngHost: NgHost, private metadataResolver: compiler.CompileMetadataResolver) {} extract(): Promise { const programSymbols: compiler.StaticSymbol[] = - extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); + extractProgramSymbols(this.program, this.staticReflector, this.ngHost, this.options); const {ngModules, files} = compiler.analyzeAndValidateNgModules( programSymbols, {transitiveModules: true}, this.metadataResolver); @@ -65,12 +65,12 @@ export class Extractor { static create( options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader, - reflectorHost?: ReflectorHost): Extractor { + ngHost?: NgHost): Extractor { const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); - if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options); - const staticReflector = new compiler.StaticReflector(reflectorHost); + if (!ngHost) ngHost = new NgHost(program, compilerHost, options); + const staticReflector = new compiler.StaticReflector(ngHost); compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector); const config = new compiler.CompilerConfig({ @@ -92,6 +92,6 @@ export class Extractor { const messageBundle = new compiler.MessageBundle(htmlParser, [], {}); return new Extractor( - options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver); + options, program, compilerHost, staticReflector, messageBundle, ngHost, resolver); } } \ No newline at end of file diff --git a/modules/@angular/compiler-cli/src/reflector_host.ts b/modules/@angular/compiler-cli/src/ng_host.ts similarity index 58% rename from modules/@angular/compiler-cli/src/reflector_host.ts rename to modules/@angular/compiler-cli/src/ng_host.ts index 7c0ee729be..50c437bb04 100644 --- a/modules/@angular/compiler-cli/src/reflector_host.ts +++ b/modules/@angular/compiler-cli/src/ng_host.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AssetUrl, ImportGenerator, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +import {AotCompilerHost, AssetUrl, StaticSymbol} from '@angular/compiler'; import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped'; import * as fs from 'fs'; import * as path from 'path'; @@ -17,46 +17,42 @@ const DTS = /\.d\.ts$/; const NODE_MODULES = '/node_modules/'; const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/; -export interface ReflectorHostContext { +export interface NgHostContext { fileExists(fileName: string): boolean; directoryExists(directoryName: string): boolean; readFile(fileName: string): string; assumeFileExists(fileName: string): void; } -export class ReflectorHost implements StaticReflectorHost, ImportGenerator { +export class NgHost implements AotCompilerHost { protected metadataCollector = new MetadataCollector(); - protected context: ReflectorHostContext; + protected context: NgHostContext; private isGenDirChildOfRootDir: boolean; protected basePath: string; private genDir: string; constructor( protected program: ts.Program, protected compilerHost: ts.CompilerHost, - protected options: AngularCompilerOptions, context?: ReflectorHostContext) { + protected options: AngularCompilerOptions, context?: NgHostContext) { // normalize the path so that it never ends with '/'. this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/'); this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/'); - this.context = context || new NodeReflectorHostContext(compilerHost); + this.context = context || new NodeNgHostContext(compilerHost); const genPath: string = path.relative(this.basePath, this.genDir); this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); } - angularImportLocations() { - return { - coreDecorators: '@angular/core/src/metadata', - diDecorators: '@angular/core/src/di/metadata', - diMetadata: '@angular/core/src/di/metadata', - diOpaqueToken: '@angular/core/src/di/opaque_token', - animationMetadata: '@angular/core/src/animation/metadata', - provider: '@angular/core/src/di/provider' - }; - } - // We use absolute paths on disk as canonical. getCanonicalFileName(fileName: string): string { return fileName; } - protected resolve(m: string, containingFile: string) { + resolveImportToFile(m: string, containingFile: string) { + if (!containingFile || !containingFile.length) { + if (m.indexOf('.') === 0) { + throw new Error('Resolution of relative paths requires a containing file.'); + } + // Any containing file gives the same result for absolute imports + containingFile = path.join(this.basePath, 'index.ts'); + } m = m.replace(EXT, ''); const resolved = ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context) @@ -73,7 +69,7 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator { protected resolveAssetUrl(url: string, containingFile: string): string { const assetUrl = this.normalizeAssetUrl(url); if (assetUrl) { - return this.getCanonicalFileName(this.resolve(assetUrl, containingFile)); + return this.getCanonicalFileName(this.resolveImportToFile(assetUrl, containingFile)); } return url; } @@ -158,80 +154,8 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator { } } - findDeclaration( - module: string, symbolName: string, containingFile: string, - containingModule?: string): StaticSymbol { - if (!containingFile || !containingFile.length) { - if (module.indexOf('.') === 0) { - throw new Error('Resolution of relative paths requires a containing file.'); - } - // Any containing file gives the same result for absolute imports - containingFile = path.join(this.basePath, 'index.ts'); - } - - try { - const assetUrl = this.normalizeAssetUrl(module); - if (assetUrl) { - module = assetUrl; - } - const filePath = this.resolve(module, containingFile); - - if (!filePath) { - // If the file cannot be found the module is probably referencing a declared module - // for which there is no disambiguating file and we also don't need to track - // re-exports. Just use the module name. - return this.getStaticSymbol(module, symbolName); - } - - const tc = this.program.getTypeChecker(); - const sf = this.program.getSourceFile(filePath); - if (!sf || !(sf).symbol) { - // The source file was not needed in the compile but we do need the values from - // the corresponding .ts files stored in the .metadata.json file. Check the file - // for exports to see if the file is exported. - return this.resolveExportedSymbol(filePath, symbolName) || - this.getStaticSymbol(filePath, symbolName); - } - - let symbol = tc.getExportsOfModule((sf).symbol).find(m => m.name === symbolName); - if (!symbol) { - throw new Error(`can't find symbol ${symbolName} exported from module ${filePath}`); - } - if (symbol && - symbol.flags & ts.SymbolFlags.Alias) { // This is an alias, follow what it aliases - symbol = tc.getAliasedSymbol(symbol); - } - const declaration = symbol.getDeclarations()[0]; - const declarationFile = this.getCanonicalFileName(declaration.getSourceFile().fileName); - - return this.getStaticSymbol(declarationFile, symbol.getName()); - } catch (e) { - console.error(`can't resolve module ${module} from ${containingFile}`); - throw e; - } - } - - private typeCache = new Map(); private resolverCache = new Map(); - /** - * 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 { - const memberSuffix = members ? `.${ members.join('.')}` : ''; - const key = `"${declarationFile}".${name}${memberSuffix}`; - let result = this.typeCache.get(key); - if (!result) { - result = new StaticSymbol(declarationFile, name, members); - this.typeCache.set(key, result); - } - return result; - } - getMetadataFor(filePath: string): ModuleMetadata { if (!this.context.fileExists(filePath)) { // If the file doesn't exists then we cannot return metadata for the file. @@ -277,59 +201,9 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator { } return metadata; } - - protected resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol { - const resolveModule = (moduleName: string): string => { - const resolvedModulePath = this.getCanonicalFileName(this.resolve(moduleName, filePath)); - if (!resolvedModulePath) { - throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`); - } - return resolvedModulePath; - }; - const metadata = this.getResolverMetadata(filePath); - if (metadata) { - // If we have metadata for the symbol, this is the original exporting location. - if (metadata.metadata[symbolName]) { - return this.getStaticSymbol(filePath, symbolName); - } - - // If no, try to find the symbol in one of the re-export location - if (metadata.exports) { - // Try and find the symbol in the list of explicitly re-exported symbols. - for (const moduleExport of metadata.exports) { - if (moduleExport.export) { - const exportSymbol = moduleExport.export.find(symbol => { - if (typeof symbol === 'string') { - return symbol == symbolName; - } else { - return symbol.as == symbolName; - } - }); - if (exportSymbol) { - let symName = symbolName; - if (typeof exportSymbol !== 'string') { - symName = exportSymbol.name; - } - return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName); - } - } - } - - // Try to find the symbol via export * directives. - for (const moduleExport of metadata.exports) { - if (!moduleExport.export) { - const resolvedModule = resolveModule(moduleExport.from); - const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName); - if (candidateSymbol) return candidateSymbol; - } - } - } - } - return null; - } } -export class NodeReflectorHostContext implements ReflectorHostContext { +export class NodeNgHostContext implements NgHostContext { constructor(private host: ts.CompilerHost) {} private assumedExists: {[fileName: string]: boolean} = {}; diff --git a/modules/@angular/compiler-cli/src/path_mapped_reflector_host.ts b/modules/@angular/compiler-cli/src/path_mapped_ng_host.ts similarity index 87% rename from modules/@angular/compiler-cli/src/path_mapped_reflector_host.ts rename to modules/@angular/compiler-cli/src/path_mapped_ng_host.ts index e3d1e26ec4..863952cf7d 100644 --- a/modules/@angular/compiler-cli/src/path_mapped_reflector_host.ts +++ b/modules/@angular/compiler-cli/src/path_mapped_ng_host.ts @@ -12,22 +12,22 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; -import {ReflectorHost, ReflectorHostContext} from './reflector_host'; +import {NgHost, NgHostContext} from './ng_host'; const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const DTS = /\.d\.ts$/; /** - * This version of the reflector host expects that the program will be compiled + * This version of the AotCompilerHost expects that the program will be compiled * and executed with a "path mapped" directory structure, where generated files * are in a parallel tree with the sources, and imported using a `./` relative * import. This requires using TS `rootDirs` option and also teaching the module * loader what to do. */ -export class PathMappedReflectorHost extends ReflectorHost { +export class PathMappedNgHost extends NgHost { constructor( program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions, - context?: ReflectorHostContext) { + context?: NgHostContext) { super(program, compilerHost, options, context); } @@ -42,7 +42,14 @@ export class PathMappedReflectorHost extends ReflectorHost { return fileName; } - protected resolve(m: string, containingFile: string) { + resolveImportToFile(m: string, containingFile: string) { + if (!containingFile || !containingFile.length) { + if (m.indexOf('.') === 0) { + throw new Error('Resolution of relative paths requires a containing file.'); + } + // Any containing file gives the same result for absolute imports + containingFile = path.join(this.basePath, 'index.ts'); + } for (const root of this.options.rootDirs || ['']) { const rootedContainingFile = path.join(root, containingFile); const resolved = @@ -82,7 +89,7 @@ export class PathMappedReflectorHost extends ReflectorHost { } const resolvable = (candidate: string) => { - const resolved = this.getCanonicalFileName(this.resolve(candidate, importedFile)); + const resolved = this.getCanonicalFileName(this.resolveImportToFile(candidate, importedFile)); return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, ''); }; diff --git a/modules/@angular/compiler-cli/src/private_import_core.ts b/modules/@angular/compiler-cli/src/private_import_core.ts index 5030d3d23f..3cd53cdaf8 100644 --- a/modules/@angular/compiler-cli/src/private_import_core.ts +++ b/modules/@angular/compiler-cli/src/private_import_core.ts @@ -16,9 +16,3 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio export type Console = typeof r._Console; export var Console: typeof r.Console = r.Console; - -export var reflector: typeof r.reflector = r.reflector; - -export type SetterFn = typeof r._SetterFn; -export type GetterFn = typeof r._GetterFn; -export type MethodFn = typeof r._MethodFn; diff --git a/modules/@angular/compiler-cli/test/mocks.ts b/modules/@angular/compiler-cli/test/mocks.ts index 7573f7fce5..fddc69c866 100644 --- a/modules/@angular/compiler-cli/test/mocks.ts +++ b/modules/@angular/compiler-cli/test/mocks.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ReflectorHostContext} from '@angular/compiler-cli/src/reflector_host'; +import {NgHostContext} from '@angular/compiler-cli/src/ng_host'; import * as ts from 'typescript'; export type Entry = string | Directory; export interface Directory { [name: string]: Entry; } -export class MockContext implements ReflectorHostContext { +export class MockContext implements NgHostContext { constructor(public currentDirectory: string, private files: Entry) {} fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; } diff --git a/modules/@angular/compiler-cli/test/ng_host_spec.ts b/modules/@angular/compiler-cli/test/ng_host_spec.ts new file mode 100644 index 0000000000..e2776b5cf8 --- /dev/null +++ b/modules/@angular/compiler-cli/test/ng_host_spec.ts @@ -0,0 +1,204 @@ +/** + * @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 {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; +import * as ts from 'typescript'; + +import {NgHost} from '../src/ng_host'; + +import {Directory, Entry, MockCompilerHost, MockContext} from './mocks'; + +describe('NgHost', () => { + let context: MockContext; + let host: ts.CompilerHost; + let program: ts.Program; + let hostNestedGenDir: NgHost; + let hostSiblingGenDir: NgHost; + + beforeEach(() => { + context = new MockContext('/tmp/src', clone(FILES)); + host = new MockCompilerHost(context); + program = ts.createProgram( + ['main.ts'], { + module: ts.ModuleKind.CommonJS, + }, + host); + // Force a typecheck + const errors = program.getSemanticDiagnostics(); + if (errors && errors.length) { + throw new Error('Expected no errors'); + } + hostNestedGenDir = new NgHost( + program, host, { + genDir: '/tmp/project/src/gen/', + basePath: '/tmp/project/src', + skipMetadataEmit: false, + strictMetadataEmit: false, + skipTemplateCodegen: false, + trace: false + }, + context); + hostSiblingGenDir = new NgHost( + program, host, { + genDir: '/tmp/project/gen', + basePath: '/tmp/project/src/', + skipMetadataEmit: false, + strictMetadataEmit: false, + skipTemplateCodegen: false, + trace: false + }, + context); + }); + + describe('nestedGenDir', () => { + it('should import node_module from factory', () => { + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/gen/my.ngfactory.ts', + '/tmp/project/node_modules/@angular/core.d.ts')) + .toEqual('@angular/core'); + }); + + it('should import factory from factory', () => { + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts')) + .toEqual('./my.other.ngfactory'); + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts')) + .toEqual('../my.other.css'); + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts')) + .toEqual('./a/my.other.css.shim'); + }); + + it('should import application from factory', () => { + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) + .toEqual('../my.other'); + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) + .toEqual('../../my.other'); + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts')) + .toEqual('../a/my.other'); + }); + }); + + describe('nestedGenDir', () => { + it('should import node_module from factory', () => { + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/gen/my.ngfactory.ts', + '/tmp/project/node_modules/@angular/core.d.ts')) + .toEqual('@angular/core'); + }); + + it('should import factory from factory', () => { + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts')) + .toEqual('./my.other.ngfactory'); + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts')) + .toEqual('../my.other.css'); + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts')) + .toEqual('./a/my.other.css.shim'); + }); + + it('should import application from factory', () => { + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) + .toEqual('./my.other'); + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) + .toEqual('../my.other'); + expect(hostSiblingGenDir.getImportPath( + '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts')) + .toEqual('./a/my.other'); + }); + }); + + it('should be able to produce an import from main @angular/core', () => { + expect(hostNestedGenDir.getImportPath( + '/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts')) + .toEqual('@angular/core'); + }); + + it('should be able to produce an import from main to a sub-directory', () => { + expect(hostNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils'); + }); + + it('should be able to produce an import from to a peer file', () => { + expect(hostNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts')) + .toEqual('./collections'); + }); + + it('should be able to produce an import from to a sibling directory', () => { + expect(hostNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts')) + .toEqual('../lib/utils'); + }); + + it('should be able to read a metadata file', () => { + expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')) + .toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}); + }); + + it('should be able to read metadata from an otherwise unused .d.ts file ', () => { + expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined(); + }); + + it('should be able to read empty metadata ', () => { + expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toBeUndefined(); + }); + + it('should return undefined for missing modules', () => { + expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined(); + }); +}); + +const dummyModule = 'export let foo: any[];'; + +const FILES: Entry = { + 'tmp': { + 'src': { + 'main.ts': ` + import * as c from '@angular/core'; + import * as r from '@angular/router'; + import * as u from './lib/utils'; + import * as cs from './lib/collections'; + import * as u2 from './lib2/utils2'; + `, + 'lib': { + 'utils.ts': dummyModule, + 'collections.ts': dummyModule, + }, + 'lib2': {'utils2.ts': dummyModule}, + 'node_modules': { + '@angular': { + 'core.d.ts': dummyModule, + 'core.metadata.json': + `{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`, + 'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}}, + 'unused.d.ts': dummyModule, + 'empty.d.ts': 'export declare var a: string;', + 'empty.metadata.json': '[]', + } + } + } + } +}; + +function clone(entry: Entry): Entry { + if (typeof entry === 'string') { + return entry; + } else { + const result: Directory = {}; + for (const name in entry) { + result[name] = clone(entry[name]); + } + return result; + } +} diff --git a/modules/@angular/compiler-cli/test/reflector_host_spec.ts b/modules/@angular/compiler-cli/test/reflector_host_spec.ts deleted file mode 100644 index d47b89d05f..0000000000 --- a/modules/@angular/compiler-cli/test/reflector_host_spec.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * @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 {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; -import * as ts from 'typescript'; - -import {ReflectorHost} from '../src/reflector_host'; - -import {Directory, Entry, MockCompilerHost, MockContext} from './mocks'; - -describe('reflector_host', () => { - let context: MockContext; - let host: ts.CompilerHost; - let program: ts.Program; - let reflectorNestedGenDir: ReflectorHost; - let reflectorSiblingGenDir: ReflectorHost; - - beforeEach(() => { - context = new MockContext('/tmp/src', clone(FILES)); - host = new MockCompilerHost(context); - program = ts.createProgram( - ['main.ts'], { - module: ts.ModuleKind.CommonJS, - }, - host); - // Force a typecheck - const errors = program.getSemanticDiagnostics(); - if (errors && errors.length) { - throw new Error('Expected no errors'); - } - reflectorNestedGenDir = new ReflectorHost( - program, host, { - genDir: '/tmp/project/src/gen/', - basePath: '/tmp/project/src', - skipMetadataEmit: false, - strictMetadataEmit: false, - skipTemplateCodegen: false, - trace: false - }, - context); - reflectorSiblingGenDir = new ReflectorHost( - program, host, { - genDir: '/tmp/project/gen', - basePath: '/tmp/project/src/', - skipMetadataEmit: false, - strictMetadataEmit: false, - skipTemplateCodegen: false, - trace: false - }, - context); - }); - - describe('nestedGenDir', () => { - it('should import node_module from factory', () => { - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/gen/my.ngfactory.ts', - '/tmp/project/node_modules/@angular/core.d.ts')) - .toEqual('@angular/core'); - }); - - it('should import factory from factory', () => { - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts')) - .toEqual('./my.other.ngfactory'); - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts')) - .toEqual('../my.other.css'); - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts')) - .toEqual('./a/my.other.css.shim'); - }); - - it('should import application from factory', () => { - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) - .toEqual('../my.other'); - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) - .toEqual('../../my.other'); - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts')) - .toEqual('../a/my.other'); - }); - }); - - describe('nestedGenDir', () => { - it('should import node_module from factory', () => { - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/gen/my.ngfactory.ts', - '/tmp/project/node_modules/@angular/core.d.ts')) - .toEqual('@angular/core'); - }); - - it('should import factory from factory', () => { - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts')) - .toEqual('./my.other.ngfactory'); - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts')) - .toEqual('../my.other.css'); - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts')) - .toEqual('./a/my.other.css.shim'); - }); - - it('should import application from factory', () => { - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) - .toEqual('./my.other'); - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts')) - .toEqual('../my.other'); - expect(reflectorSiblingGenDir.getImportPath( - '/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts')) - .toEqual('./a/my.other'); - }); - }); - - it('should provide the import locations for angular', () => { - const {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} = - reflectorNestedGenDir.angularImportLocations(); - expect(coreDecorators).toEqual('@angular/core/src/metadata'); - expect(diDecorators).toEqual('@angular/core/src/di/metadata'); - expect(diMetadata).toEqual('@angular/core/src/di/metadata'); - expect(animationMetadata).toEqual('@angular/core/src/animation/metadata'); - expect(provider).toEqual('@angular/core/src/di/provider'); - }); - - it('should be able to produce an import from main @angular/core', () => { - expect(reflectorNestedGenDir.getImportPath( - '/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts')) - .toEqual('@angular/core'); - }); - - it('should be able to produce an import from main to a sub-directory', () => { - expect(reflectorNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils'); - }); - - it('should be able to produce an import from to a peer file', () => { - expect(reflectorNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts')) - .toEqual('./collections'); - }); - - it('should be able to produce an import from to a sibling directory', () => { - expect(reflectorNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts')) - .toEqual('../lib/utils'); - }); - - it('should be able to produce a symbol for an exported symbol', () => { - expect(reflectorNestedGenDir.findDeclaration('@angular/router', 'foo', 'main.ts')) - .toBeDefined(); - }); - - it('should be able to produce a symbol for values space only reference', () => { - expect(reflectorNestedGenDir.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts')) - .toBeDefined(); - }); - - - it('should be produce the same symbol if asked twice', () => { - const foo1 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo'); - const foo2 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo'); - expect(foo1).toBe(foo2); - }); - - it('should be able to produce a symbol for a module with no file', () => { - expect(reflectorNestedGenDir.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); - }); - - it('should be able to read a metadata file', () => { - expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')) - .toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}); - }); - - it('should be able to read metadata from an otherwise unused .d.ts file ', () => { - expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')) - .toBeUndefined(); - }); - - it('should be able to read empty metadata ', () => { - expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')) - .toBeUndefined(); - }); - - it('should return undefined for missing modules', () => { - expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')) - .toBeUndefined(); - }); - - it('should be able to trace a named export', () => { - const symbol = reflectorNestedGenDir.findDeclaration( - './reexport/reexport.d.ts', 'One', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('One'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); - }); - - it('should be able to trace a renamed export', () => { - const symbol = reflectorNestedGenDir.findDeclaration( - './reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('Three'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); - }); - - it('should be able to trace an export * export', () => { - const symbol = reflectorNestedGenDir.findDeclaration( - './reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('Five'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts'); - }); - - it('should be able to trace a multi-level re-export', () => { - const symbol = reflectorNestedGenDir.findDeclaration( - './reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('Thirty'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts'); - }); -}); - -const dummyModule = 'export let foo: any[];'; - -const FILES: Entry = { - 'tmp': { - 'src': { - 'main.ts': ` - import * as c from '@angular/core'; - import * as r from '@angular/router'; - import * as u from './lib/utils'; - import * as cs from './lib/collections'; - import * as u2 from './lib2/utils2'; - `, - 'lib': { - 'utils.ts': dummyModule, - 'collections.ts': dummyModule, - }, - 'lib2': {'utils2.ts': dummyModule}, - 'reexport': { - 'reexport.d.ts': ` - import * as c from '@angular/core'; - `, - 'reexport.metadata.json': JSON.stringify({ - __symbolic: 'module', - version: 1, - metadata: {}, - exports: [ - {from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]}, - {from: './src/origin5'}, {from: './src/reexport2'} - ] - }), - 'src': { - 'origin1.d.ts': ` - export class One {} - export class Two {} - export class Three {} - `, - 'origin1.metadata.json': JSON.stringify({ - __symbolic: 'module', - version: 1, - metadata: { - One: {__symbolic: 'class'}, - Two: {__symbolic: 'class'}, - Three: {__symbolic: 'class'}, - }, - }), - 'origin5.d.ts': ` - export class Five {} - `, - 'origin5.metadata.json': JSON.stringify({ - __symbolic: 'module', - version: 1, - metadata: { - Five: {__symbolic: 'class'}, - }, - }), - 'origin30.d.ts': ` - export class Thirty {} - `, - 'origin30.metadata.json': JSON.stringify({ - __symbolic: 'module', - version: 1, - metadata: { - Thirty: {__symbolic: 'class'}, - }, - }), - 'originNone.d.ts': dummyModule, - 'originNone.metadata.json': JSON.stringify({ - __symbolic: 'module', - version: 1, - metadata: {}, - }), - 'reexport2.d.ts': dummyModule, - 'reexport2.metadata.json': JSON.stringify({ - __symbolic: 'module', - version: 1, - metadata: {}, - exports: [{from: './originNone'}, {from: './origin30'}] - }) - } - }, - 'node_modules': { - '@angular': { - 'core.d.ts': dummyModule, - 'core.metadata.json': - `{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`, - 'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}}, - 'unused.d.ts': dummyModule, - 'empty.d.ts': 'export declare var a: string;', - 'empty.metadata.json': '[]', - } - } - } - } -}; - -function clone(entry: Entry): Entry { - if (typeof entry === 'string') { - return entry; - } else { - const result: Directory = {}; - for (const name in entry) { - result[name] = clone(entry[name]); - } - return result; - } -} diff --git a/modules/@angular/compiler/index.ts b/modules/@angular/compiler/index.ts index d1ae9ef26f..8b37b1e698 100644 --- a/modules/@angular/compiler/index.ts +++ b/modules/@angular/compiler/index.ts @@ -26,6 +26,7 @@ export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser'; export {CompilerConfig, RenderTypes} from './src/config'; export * from './src/compile_metadata'; export * from './src/aot/compiler'; +export * from './src/aot/compiler_host'; export * from './src/aot/static_reflector'; export * from './src/aot/static_reflection_capabilities'; export * from './src/aot/static_symbol'; diff --git a/modules/@angular/compiler/src/aot/compiler_host.ts b/modules/@angular/compiler/src/aot/compiler_host.ts new file mode 100644 index 0000000000..6c3fdf8001 --- /dev/null +++ b/modules/@angular/compiler/src/aot/compiler_host.ts @@ -0,0 +1,31 @@ +/** + * @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 {StaticSymbol} from './static_symbol'; + +/** + * The host of the AotCompiler disconnects the implementation from TypeScript / other language + * services and from underlying file systems. + */ +export interface AotCompilerHost { + /** + * Return a ModuleMetadata for the given module. + * 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. + * + * @param modulePath is a string identifier for a module as an absolute path. + * @returns the metadata for the given module. + */ + getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[]; + + /** + * Converts a module name into a file path. + */ + resolveImportToFile(moduleName: string, containingFile: string): string; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/aot/static_reflector.ts b/modules/@angular/compiler/src/aot/static_reflector.ts index f2c5b4614e..2efca94244 100644 --- a/modules/@angular/compiler/src/aot/static_reflector.ts +++ b/modules/@angular/compiler/src/aot/static_reflector.ts @@ -7,75 +7,49 @@ */ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; - +import {AssetUrl} from '../output/path_util'; import {ReflectorReader} from '../private_import_core'; - +import {AotCompilerHost} from './compiler_host'; import {StaticSymbol} from './static_symbol'; const SUPPORTED_SCHEMA_VERSION = 1; - -/** - * 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 modulePath is a string identifier for a module as an absolute path. - * @returns the metadata for the given module. - */ - getMetadataFor(modulePath: string): {[key: string]: any}|{[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(declarationFile: string, name: string, members?: string[]): StaticSymbol; - - angularImportLocations(): { - coreDecorators: string, - diDecorators: string, - diMetadata: string, - diOpaqueToken: string, - animationMetadata: string, - provider: string - }; - - getCanonicalFileName(fileName: string): string; -} +const ANGULAR_IMPORT_LOCATIONS = { + coreDecorators: '@angular/core/src/metadata', + diDecorators: '@angular/core/src/di/metadata', + diMetadata: '@angular/core/src/di/metadata', + diOpaqueToken: '@angular/core/src/di/opaque_token', + animationMetadata: '@angular/core/src/animation/metadata', + provider: '@angular/core/src/di/provider' +}; /** * A static reflector implements enough of the Reflector API that is necessary to compile * templates statically. */ export class StaticReflector implements ReflectorReader { + private typeCache = new Map(); private annotationCache = new Map(); private propertyCache = new Map(); private parameterCache = new Map(); private metadataCache = new Map(); private conversionMap = new Map any>(); + private declarationMap = new Map(); private opaqueToken: StaticSymbol; - constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } + constructor(private host: AotCompilerHost) { this.initializeConversionMap(); } importUri(typeOrFunc: StaticSymbol): string { - const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); + const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); return staticSymbol ? staticSymbol.filePath : null; } resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { - return this.host.findDeclaration(moduleUrl, name, ''); + return this.findDeclaration(moduleUrl, name, ''); } resolveEnum(enumIdentifier: any, name: string): any { const staticSymbol: StaticSymbol = enumIdentifier; - return this.host.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]); + return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]); } public annotations(type: StaticSymbol): any[] { @@ -172,59 +146,156 @@ export class StaticReflector implements ReflectorReader { private initializeConversionMap(): void { const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} = - this.host.angularImportLocations(); - this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken'); + ANGULAR_IMPORT_LOCATIONS; + this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken'); - this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host); + this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host); this.registerDecoratorOrConstructor( - this.host.findDeclaration(diDecorators, 'Injectable'), Injectable); - this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self); + this.findDeclaration(diDecorators, 'Injectable'), Injectable); + this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self); + this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf); + this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject); + this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional); this.registerDecoratorOrConstructor( - this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf); - this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject); + this.findDeclaration(coreDecorators, 'Attribute'), Attribute); this.registerDecoratorOrConstructor( - this.host.findDeclaration(diDecorators, 'Optional'), Optional); + this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute); + this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild); + this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren); + this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren); + this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input); + this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output); + this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild); + this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren); - this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input); + this.findDeclaration(coreDecorators, 'HostListener'), HostListener); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'Output'), Output); - this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe); + this.findDeclaration(coreDecorators, 'Directive'), Directive); this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding); - this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'HostListener'), HostListener); - this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'Directive'), Directive); - this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'Component'), Component); - this.registerDecoratorOrConstructor( - this.host.findDeclaration(coreDecorators, 'NgModule'), NgModule); + this.findDeclaration(coreDecorators, 'Component'), Component); + this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule); // Note: Some metadata classes can be used directly with Provider.deps. - this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host); - this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self); - this.registerDecoratorOrConstructor( - this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf); - this.registerDecoratorOrConstructor( - this.host.findDeclaration(diMetadata, 'Optional'), Optional); + this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host); + this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self); + this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf); + this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence); - this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group); + this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger); + this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state); + this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition); + this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style); + this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate); + this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes); + this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence); + this.registerFunction(this.findDeclaration(animationMetadata, 'group'), group); + } + + /** + * 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 { + const memberSuffix = members ? `.${ members.join('.')}` : ''; + const key = `"${declarationFile}".${name}${memberSuffix}`; + let result = this.typeCache.get(key); + if (!result) { + result = new StaticSymbol(declarationFile, name, members); + this.typeCache.set(key, result); + } + return result; + } + + private normalizeAssetUrl(url: string): string { + const assetUrl = AssetUrl.parse(url); + return assetUrl ? `${assetUrl.packageName}@${assetUrl.modulePath}` : null; + } + + private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol { + const resolveModule = (moduleName: string): string => { + const resolvedModulePath = this.host.resolveImportToFile(moduleName, filePath); + if (!resolvedModulePath) { + throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`); + } + return resolvedModulePath; + }; + const metadata = this.getModuleMetadata(filePath); + if (metadata) { + // If we have metadata for the symbol, this is the original exporting location. + if (metadata['metadata'][symbolName]) { + return this.getStaticSymbol(filePath, symbolName); + } + + // If no, try to find the symbol in one of the re-export location + if (metadata['exports']) { + // Try and find the symbol in the list of explicitly re-exported symbols. + for (const moduleExport of metadata['exports']) { + if (moduleExport.export) { + const exportSymbol = moduleExport.export.find((symbol: any) => { + if (typeof symbol === 'string') { + return symbol == symbolName; + } else { + return symbol.as == symbolName; + } + }); + if (exportSymbol) { + let symName = symbolName; + if (typeof exportSymbol !== 'string') { + symName = exportSymbol.name; + } + return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName); + } + } + } + + // Try to find the symbol via export * directives. + for (const moduleExport of metadata['exports']) { + if (!moduleExport.export) { + const resolvedModule = resolveModule(moduleExport.from); + const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName); + if (candidateSymbol) return candidateSymbol; + } + } + } + } + return null; + } + + findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol { + const cacheKey = `${module}|${symbolName}|${containingFile}`; + let symbol = this.declarationMap.get(cacheKey); + if (symbol) { + return symbol; + } + try { + const assetUrl = this.normalizeAssetUrl(module); + if (assetUrl) { + module = assetUrl; + } + const filePath = this.host.resolveImportToFile(module, containingFile); + + if (!filePath) { + // If the file cannot be found the module is probably referencing a declared module + // for which there is no disambiguating file and we also don't need to track + // re-exports. Just use the module name. + return this.getStaticSymbol(module, symbolName); + } + + let symbol = this.resolveExportedSymbol(filePath, symbolName) || + this.getStaticSymbol(filePath, symbolName); + this.declarationMap.set(cacheKey, symbol); + return symbol; + } catch (e) { + console.error(`can't resolve module ${module} from ${containingFile}`); + throw e; + } } /** @internal */ @@ -237,10 +308,10 @@ export class StaticReflector implements ReflectorReader { function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { let staticSymbol: StaticSymbol; if (expression['module']) { - staticSymbol = _this.host.findDeclaration( - expression['module'], expression['name'], context.filePath); + staticSymbol = + _this.findDeclaration(expression['module'], expression['name'], context.filePath); } else { - staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']); + staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']); } return staticSymbol; } @@ -449,8 +520,7 @@ export class StaticReflector implements ReflectorReader { const members = selectTarget.members ? (selectTarget.members as string[]).concat(member) : [member]; - return _this.host.getStaticSymbol( - selectTarget.filePath, selectTarget.name, members); + return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members); } } const member = simplify(expression['member']); @@ -485,10 +555,10 @@ export class StaticReflector implements ReflectorReader { // Determine if the function is a built-in conversion let target = expression['expression']; if (target['module']) { - staticSymbol = _this.host.findDeclaration( - target['module'], target['name'], context.filePath); + staticSymbol = + _this.findDeclaration(target['module'], target['name'], context.filePath); } else { - staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']); + staticSymbol = _this.getStaticSymbol(context.filePath, target['name']); } let converter = _this.conversionMap.get(staticSymbol); if (converter) { diff --git a/modules/@angular/compiler/test/aot/static_reflector_spec.ts b/modules/@angular/compiler/test/aot/static_reflector_spec.ts index 81d99f3866..219519caac 100644 --- a/modules/@angular/compiler/test/aot/static_reflector_spec.ts +++ b/modules/@angular/compiler/test/aot/static_reflector_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler'; import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {MetadataCollector} from '@angular/tsc-wrapped'; import * as ts from 'typescript'; @@ -18,11 +18,11 @@ const TS_EXT = /(^.|(?!\.d)..)\.ts$/; describe('StaticReflector', () => { const noContext = new StaticSymbol('', ''); - let host: StaticReflectorHost; + let host: AotCompilerHost; let reflector: StaticReflector; beforeEach(() => { - host = new MockReflectorHost(); + host = new MockAotCompilerHost(); reflector = new StaticReflector(host); }); @@ -31,7 +31,7 @@ describe('StaticReflector', () => { } it('should get annotations for NgFor', () => { - const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor'); + const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor'); const annotations = reflector.annotations(NgFor); expect(annotations.length).toEqual(1); const annotation = annotations[0]; @@ -40,15 +40,15 @@ describe('StaticReflector', () => { }); it('should get constructor for NgFor', () => { - const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor'); - const ViewContainerRef = - host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef'); + const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor'); + const ViewContainerRef = reflector.findDeclaration( + '@angular/core/src/linker/view_container_ref', 'ViewContainerRef'); const TemplateRef = - host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef'); - const IterableDiffers = host.findDeclaration( - 'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers'); - const ChangeDetectorRef = host.findDeclaration( - 'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef'); + reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef'); + const IterableDiffers = reflector.findDeclaration( + '@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers'); + const ChangeDetectorRef = reflector.findDeclaration( + '@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef'); const parameters = reflector.parameters(NgFor); expect(parameters).toEqual([ @@ -58,7 +58,7 @@ describe('StaticReflector', () => { it('should get annotations for HeroDetailComponent', () => { const HeroDetailComponent = - host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); + reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); const annotations = reflector.annotations(HeroDetailComponent); expect(annotations.length).toEqual(1); const annotation = annotations[0]; @@ -74,40 +74,39 @@ describe('StaticReflector', () => { }); it('should throw and exception for unsupported metadata versions', () => { - const e = host.findDeclaration('src/version-error', 'e'); - expect(() => reflector.annotations(e)) + expect(() => reflector.findDeclaration('src/version-error', 'e')) .toThrow(new Error( 'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 1')); }); it('should get and empty annotation list for an unknown class', () => { - const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass'); + const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const annotations = reflector.annotations(UnknownClass); expect(annotations).toEqual([]); }); it('should get propMetadata for HeroDetailComponent', () => { const HeroDetailComponent = - host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); + reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); const props = reflector.propMetadata(HeroDetailComponent); expect(props['hero']).toBeTruthy(); expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]); }); it('should get an empty object from propMetadata for an unknown class', () => { - const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass'); + const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const properties = reflector.propMetadata(UnknownClass); expect(properties).toEqual({}); }); it('should get empty parameters list for an unknown class ', () => { - const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass'); + const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const parameters = reflector.parameters(UnknownClass); expect(parameters).toEqual([]); }); it('should provide context for errors reported by the collector', () => { - const SomeClass = host.findDeclaration('src/error-reporting', 'SomeClass'); + const SomeClass = reflector.findDeclaration('src/error-reporting', 'SomeClass'); expect(() => reflector.annotations(SomeClass)) .toThrow(new Error( 'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts')); @@ -308,14 +307,14 @@ describe('StaticReflector', () => { expect(simplify( new StaticSymbol('/src/cases', ''), ({__symbolic: 'reference', module: './extern', name: 'nonExisting'}))) - .toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting')); + .toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting')); }); it('should simplify a function reference as a static symbol', () => { expect(simplify( new StaticSymbol('/src/cases', 'myFunction'), ({__symbolic: 'function', parameters: ['a'], value: []}))) - .toEqual(host.getStaticSymbol('/src/cases', 'myFunction')); + .toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction')); }); it('should simplify values initialized with a function call', () => { @@ -406,35 +405,35 @@ describe('StaticReflector', () => { it('should be able to get metadata for a class containing a custom decorator', () => { const props = reflector.propMetadata( - host.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo')); + reflector.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo')); expect(props).toEqual({foo: []}); }); it('should read ctor parameters with forwardRef', () => { const src = '/tmp/src/forward-ref.ts'; - const dep = host.getStaticSymbol(src, 'Dep'); - const props = reflector.parameters(host.getStaticSymbol(src, 'Forward')); + const dep = reflector.getStaticSymbol(src, 'Dep'); + const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward')); expect(props).toEqual([[dep, new Inject(dep)]]); }); it('should report an error for invalid function calls', () => { expect( - () => - reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent'))) + () => reflector.annotations( + reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent'))) .toThrow(new Error( `Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`)); }); it('should be able to get metadata for a class containing a static method call', () => { const annotations = reflector.annotations( - host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent')); + reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100}); }); it('should be able to get metadata for a class containing a static field reference', () => { - const annotations = - reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); + const annotations = reflector.annotations( + reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]); }); @@ -442,7 +441,7 @@ describe('StaticReflector', () => { it('should be able to get the metadata for a class calling a method with a conditional expression', () => { const annotations = reflector.annotations( - host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent')); + reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual([ [{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] @@ -452,50 +451,68 @@ describe('StaticReflector', () => { it('should be able to get the metadata for a class calling a method with default parameters', () => { const annotations = reflector.annotations( - host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent')); + reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual([['a', true, false]]); }); it('should be able to get metadata with a reference to a static method', () => { const annotations = reflector.annotations( - host.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference')); + reflector.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference')); expect(annotations.length).toBe(1); expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); }); + + it('should be able to produce a symbol for an exported symbol', () => { + expect(reflector.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined(); + }); + + it('should be able to produce a symbol for values space only reference', () => { + expect(reflector.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts')) + .toBeDefined(); + }); + + it('should be produce the same symbol if asked twice', () => { + const foo1 = reflector.getStaticSymbol('main.ts', 'foo'); + const foo2 = reflector.getStaticSymbol('main.ts', 'foo'); + expect(foo1).toBe(foo2); + }); + + it('should be able to produce a symbol for a module with no file', + () => { expect(reflector.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); }); + + it('should be able to trace a named export', () => { + const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts'); + expect(symbol.name).toEqual('One'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); + }); + + it('should be able to trace a renamed export', () => { + const symbol = reflector.findDeclaration('./reexport/reexport', 'Four', '/tmp/src/main.ts'); + expect(symbol.name).toEqual('Three'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); + }); + + it('should be able to trace an export * export', () => { + const symbol = reflector.findDeclaration('./reexport/reexport', 'Five', '/tmp/src/main.ts'); + expect(symbol.name).toEqual('Five'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts'); + }); + + it('should be able to trace a multi-level re-export', () => { + const symbol = reflector.findDeclaration('./reexport/reexport', 'Thirty', '/tmp/src/main.ts'); + expect(symbol.name).toEqual('Thirty'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts'); + }); }); -class MockReflectorHost implements StaticReflectorHost { - private staticTypeCache = new Map(); +class MockAotCompilerHost implements AotCompilerHost { private collector = new MetadataCollector(); constructor() {} - angularImportLocations() { - return { - coreDecorators: 'angular2/src/core/metadata', - diDecorators: 'angular2/src/core/di/metadata', - diMetadata: 'angular2/src/core/di/metadata', - diOpaqueToken: 'angular2/src/core/di/opaque_token', - animationMetadata: 'angular2/src/core/animation/metadata', - provider: 'angular2/src/core/di/provider' - }; - } - - getCanonicalFileName(fileName: string): string { return fileName; } - - getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol { - const cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`; - let result = this.staticTypeCache.get(cacheKey); - if (!result) { - result = new StaticSymbol(declarationFile, name, members); - this.staticTypeCache.set(cacheKey, result); - } - return result; - } - // In tests, assume that symbols are not re-exported - findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol { + resolveImportToFile(modulePath: string, containingFile?: string): string { function splitPath(path: string): string[] { return path.split(/\/|\\/g); } function resolvePath(pathParts: string[]): string { @@ -530,16 +547,16 @@ class MockReflectorHost implements StaticReflectorHost { const baseName = pathTo(containingFile, modulePath); const tsName = baseName + '.ts'; if (this.getMetadataFor(tsName)) { - return this.getStaticSymbol(tsName, symbolName); + return tsName; } - return this.getStaticSymbol(baseName + '.d.ts', symbolName); + return baseName + '.d.ts'; } - return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName); + return '/tmp/' + modulePath + '.d.ts'; } getMetadataFor(moduleId: string): any { const data: {[key: string]: any} = { - '/tmp/angular2/src/common/forms-deprecated/directives.d.ts': [{ + '/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{ '__symbolic': 'module', 'version': 1, 'metadata': { @@ -547,12 +564,12 @@ class MockReflectorHost implements StaticReflectorHost { { '__symbolic': 'reference', 'name': 'NgFor', - 'module': 'angular2/src/common/directives/ng_for' + 'module': '@angular/common/src/directives/ng_for' } ] } }], - '/tmp/angular2/src/common/directives/ng_for.d.ts': { + '/tmp/@angular/common/src/directives/ng_for.d.ts': { '__symbolic': 'module', 'version': 1, 'metadata': { @@ -564,7 +581,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic': 'reference', 'name': 'Directive', - 'module': '../../core/metadata' + 'module': '@angular/core/src/metadata' }, 'arguments': [ { @@ -581,22 +598,22 @@ class MockReflectorHost implements StaticReflectorHost { 'parameters': [ { '__symbolic': 'reference', - 'module': '../../core/linker/view_container_ref', + 'module': '@angular/core/src/linker/view_container_ref', 'name': 'ViewContainerRef' }, { '__symbolic': 'reference', - 'module': '../../core/linker/template_ref', + 'module': '@angular/core/src/linker/template_ref', 'name': 'TemplateRef' }, { '__symbolic': 'reference', - 'module': '../../core/change_detection/differs/iterable_differs', + 'module': '@angular/core/src/change_detection/differs/iterable_differs', 'name': 'IterableDiffers' }, { '__symbolic': 'reference', - 'module': '../../core/change_detection/change_detector_ref', + 'module': '@angular/core/src/change_detection/change_detector_ref', 'name': 'ChangeDetectorRef' } ] @@ -606,13 +623,13 @@ class MockReflectorHost implements StaticReflectorHost { } } }, - '/tmp/angular2/src/core/linker/view_container_ref.d.ts': + '/tmp/@angular/core/src/linker/view_container_ref.d.ts': {version: 1, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}}, - '/tmp/angular2/src/core/linker/template_ref.d.ts': + '/tmp/@angular/core/src/linker/template_ref.d.ts': {version: 1, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}}, - '/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts': + '/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts': {version: 1, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}}, - '/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts': + '/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts': {version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}}, '/tmp/src/app/hero-detail.component.d.ts': { '__symbolic': 'module', @@ -626,7 +643,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic': 'reference', 'name': 'Component', - 'module': 'angular2/src/core/metadata' + 'module': '@angular/core/src/metadata' }, 'arguments': [ { @@ -638,7 +655,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic': 'reference', 'name': 'trigger', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments': [ 'myAnimation', @@ -646,7 +663,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic': 'reference', 'name': 'state', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments': [ 'state1', @@ -654,7 +671,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic': 'reference', 'name': 'style', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments': [ { 'background':'white' } @@ -666,7 +683,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic':'reference', 'name':'transition', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments': [ '* => *', @@ -675,20 +692,20 @@ class MockReflectorHost implements StaticReflectorHost { 'expression':{ '__symbolic':'reference', 'name':'sequence', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments':[[{ '__symbolic': 'call', 'expression': { '__symbolic':'reference', 'name':'group', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments':[[{ '__symbolic': 'call', 'expression': { '__symbolic':'reference', 'name':'animate', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments':[ '1s 0.5s', @@ -696,13 +713,13 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic':'reference', 'name':'keyframes', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments':[[{ '__symbolic': 'call', 'expression': { '__symbolic':'reference', 'name':'style', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments':[ { 'background': 'blue'} ] }, { @@ -710,7 +727,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic':'reference', 'name':'style', - 'module': 'angular2/src/core/animation/metadata' + 'module': '@angular/core/src/animation/metadata' }, 'arguments':[ { 'background': 'red'} ] }]] @@ -736,7 +753,7 @@ class MockReflectorHost implements StaticReflectorHost { 'expression': { '__symbolic': 'reference', 'name': 'Input', - 'module': 'angular2/src/core/metadata' + 'module': '@angular/core/src/metadata' } } ] @@ -750,7 +767,7 @@ class MockReflectorHost implements StaticReflectorHost { '__symbolic': 'call', 'expression': { '__symbolic': 'reference', - 'module': 'angular2/src/core/metadata', + 'module': '@angular/core/src/metadata', 'name': 'HostListener' }, 'arguments': [ @@ -781,7 +798,7 @@ class MockReflectorHost implements StaticReflectorHost { expression: { __symbolic: 'reference', name: 'Component', - module: 'angular2/src/core/metadata' + module: '@angular/core/src/metadata' }, arguments: [ { @@ -982,8 +999,8 @@ class MockReflectorHost implements StaticReflectorHost { `, '/tmp/src/invalid-calls.ts': ` import {someFunction} from './nvalid-calll-definitions.ts'; - import {Component} from 'angular2/src/core/metadata'; - import {NgIf} from 'angular2/common'; + import {Component} from '@angular/core/src/metadata'; + import {NgIf} from '@angular/common'; @Component({ selector: 'my-component', @@ -999,7 +1016,7 @@ class MockReflectorHost implements StaticReflectorHost { export class MyOtherComponent { } `, '/tmp/src/static-method.ts': ` - import {Component} from 'angular2/src/core/metadata'; + import {Component} from '@angular/core/src/metadata'; @Component({ selector: 'stub' @@ -1017,7 +1034,7 @@ class MockReflectorHost implements StaticReflectorHost { } `, '/tmp/src/static-method-call.ts': ` - import {Component} from 'angular2/src/core/metadata'; + import {Component} from '@angular/core/src/metadata'; import {MyModule} from './static-method'; @Component({ @@ -1036,7 +1053,7 @@ class MockReflectorHost implements StaticReflectorHost { export class MyDefaultsComponent { } `, '/tmp/src/static-field.ts': ` - import {Injectable} from 'angular2/core'; + import {Injectable} from '@angular/core'; @Injectable() export class MyModule { @@ -1044,7 +1061,7 @@ class MockReflectorHost implements StaticReflectorHost { } `, '/tmp/src/static-field-reference.ts': ` - import {Component} from 'angular2/src/core/metadata'; + import {Component} from '@angular/core/src/metadata'; import {MyModule} from './static-field'; @Component({ @@ -1058,7 +1075,7 @@ class MockReflectorHost implements StaticReflectorHost { } `, '/tmp/src/static-method-ref.ts': ` - import {Component} from 'angular2/src/core/metadata'; + import {Component} from '@angular/core/src/metadata'; import {ClassWithStatics} from './static-method-def'; @Component({ @@ -1069,7 +1086,7 @@ class MockReflectorHost implements StaticReflectorHost { } `, '/tmp/src/invalid-metadata.ts': ` - import {Component} from 'angular2/src/core/metadata'; + import {Component} from '@angular/core/src/metadata'; @Component({ providers: [ { provider: 'a', useValue: (() => 1)() }] @@ -1077,9 +1094,9 @@ class MockReflectorHost implements StaticReflectorHost { export class InvalidMetadata {} `, '/tmp/src/forward-ref.ts': ` - import {forwardRef} from 'angular2/core'; - import {Component} from 'angular2/src/core/metadata'; - import {Inject} from 'angular2/src/core/di/metadata'; + import {forwardRef} from '@angular/core'; + import {Component} from '@angular/core/src/metadata'; + import {Inject} from '@angular/core/src/di/metadata'; @Component({}) export class Forward { constructor(@Inject(forwardRef(() => Dep)) d: Dep) {} @@ -1087,7 +1104,50 @@ class MockReflectorHost implements StaticReflectorHost { export class Dep { @Input f: Forward; } - ` + `, + '/tmp/src/reexport/reexport.d.ts': { + __symbolic: 'module', + version: 1, + metadata: {}, + exports: [ + {from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]}, + {from: './src/origin5'}, {from: './src/reexport2'} + ] + }, + '/tmp/src/reexport/src/origin1.d.ts': { + __symbolic: 'module', + version: 1, + metadata: { + One: {__symbolic: 'class'}, + Two: {__symbolic: 'class'}, + Three: {__symbolic: 'class'}, + }, + }, + '/tmp/src/reexport/src/origin5.d.ts': { + __symbolic: 'module', + version: 1, + metadata: { + Five: {__symbolic: 'class'}, + }, + }, + '/tmp/src/reexport/src/origin30.d.ts': { + __symbolic: 'module', + version: 1, + metadata: { + Thirty: {__symbolic: 'class'}, + }, + }, + '/tmp/src/reexport/src/originNone.d.ts': { + __symbolic: 'module', + version: 1, + metadata: {}, + }, + '/tmp/src/reexport/src/reexport2.d.ts': { + __symbolic: 'module', + version: 1, + metadata: {}, + exports: [{from: './originNone'}, {from: './origin30'}] + } };