refactor(compiler): move `findDeclaration` into the `StaticReflector`

Previously, this was part of the `AotCompilerHost`.
The `AotCompilerHost` is now also greatly simplified.
This commit is contained in:
Tobias Bosch 2016-11-15 08:49:23 -08:00 committed by Chuck Jazdzewski
parent 912ca44979
commit 24099bdbd2
13 changed files with 612 additions and 704 deletions

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * 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 {CodeGenerator} from './src/codegen';
export {Extractor} from './src/extractor'; 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'; export * from '@angular/tsc-wrapped';

View File

@ -17,9 +17,9 @@ import {readFileSync} from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; 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 {Console} from './private_import_core';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.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( constructor(
private options: AngularCompilerOptions, private program: ts.Program, private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: compiler.StaticReflector, 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. // Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string { private calculateEmitPath(filePath: string): string {
@ -65,7 +64,7 @@ export class CodeGenerator {
codegen(options: {transitiveModules: boolean}): Promise<any> { codegen(options: {transitiveModules: boolean}): Promise<any> {
const staticSymbols = 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 => { return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {
@ -79,8 +78,8 @@ export class CodeGenerator {
static create( static create(
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext, compilerHost: ts.CompilerHost, ngHostContext?: NgHostContext,
resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator { resourceLoader?: compiler.ResourceLoader, ngHost?: NgHost): CodeGenerator {
resourceLoader = resourceLoader || { resourceLoader = resourceLoader || {
get: (s: string) => { get: (s: string) => {
if (!compilerHost.fileExists(s)) { if (!compilerHost.fileExists(s)) {
@ -102,13 +101,13 @@ export class CodeGenerator {
} }
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
if (!reflectorHost) { if (!ngHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
reflectorHost = usePathMapping ? ngHost = usePathMapping ?
new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) : new PathMappedNgHost(program, compilerHost, options, ngHostContext) :
new ReflectorHost(program, compilerHost, options, reflectorHostContext); new NgHost(program, compilerHost, options, ngHostContext);
} }
const staticReflector = new compiler.StaticReflector(reflectorHost); const staticReflector = new compiler.StaticReflector(ngHost);
compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector); compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = const htmlParser =
new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat); new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat);
@ -135,18 +134,15 @@ export class CodeGenerator {
new compiler.ViewCompiler(config, elementSchemaRegistry), new compiler.ViewCompiler(config, elementSchemaRegistry),
new compiler.DirectiveWrapperCompiler( new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console), config, expressionParser, elementSchemaRegistry, console),
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost), new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(ngHost), cliOptions.locale,
cliOptions.locale, cliOptions.i18nFormat, cliOptions.i18nFormat, new compiler.AnimationParser(elementSchemaRegistry));
new compiler.AnimationParser(elementSchemaRegistry));
return new CodeGenerator( return new CodeGenerator(options, program, compilerHost, staticReflector, aotCompiler, ngHost);
options, program, compilerHost, staticReflector, aotCompiler, reflectorHost);
} }
} }
export function extractProgramSymbols( export function extractProgramSymbols(
program: ts.Program, staticReflector: compiler.StaticReflector, program: ts.Program, staticReflector: compiler.StaticReflector, ngHost: NgHost,
reflectorHost: compiler.StaticReflectorHost,
options: AngularCompilerOptions): compiler.StaticSymbol[] { options: AngularCompilerOptions): compiler.StaticSymbol[] {
// Compare with false since the default should be true // Compare with false since the default should be true
const skipFileNames = const skipFileNames =
@ -157,7 +153,7 @@ export function extractProgramSymbols(
program.getSourceFiles() program.getSourceFiles()
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName)) .filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
.forEach(sourceFile => { .forEach(sourceFile => {
const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName); const absSrcPath = ngHost.getCanonicalFileName(sourceFile.fileName);
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath); const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
if (!moduleMetadata) { if (!moduleMetadata) {
@ -176,7 +172,7 @@ export function extractProgramSymbols(
// Ignore symbols that are only included to record error information. // Ignore symbols that are only included to record error information.
continue; continue;
} }
staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath)); staticSymbols.push(staticReflector.findDeclaration(absSrcPath, symbol, absSrcPath));
} }
}); });

View File

@ -19,18 +19,18 @@ import * as tsc from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {extractProgramSymbols} from './codegen'; import {extractProgramSymbols} from './codegen';
import {ReflectorHost} from './reflector_host'; import {NgHost} from './ng_host';
export class Extractor { export class Extractor {
constructor( constructor(
private options: tsc.AngularCompilerOptions, private program: ts.Program, private options: tsc.AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: compiler.StaticReflector, 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) {} private metadataResolver: compiler.CompileMetadataResolver) {}
extract(): Promise<compiler.MessageBundle> { extract(): Promise<compiler.MessageBundle> {
const programSymbols: compiler.StaticSymbol[] = 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( const {ngModules, files} = compiler.analyzeAndValidateNgModules(
programSymbols, {transitiveModules: true}, this.metadataResolver); programSymbols, {transitiveModules: true}, this.metadataResolver);
@ -65,12 +65,12 @@ export class Extractor {
static create( static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader, compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
reflectorHost?: ReflectorHost): Extractor { ngHost?: NgHost): Extractor {
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options); if (!ngHost) ngHost = new NgHost(program, compilerHost, options);
const staticReflector = new compiler.StaticReflector(reflectorHost); const staticReflector = new compiler.StaticReflector(ngHost);
compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector); compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config = new compiler.CompilerConfig({ const config = new compiler.CompilerConfig({
@ -92,6 +92,6 @@ export class Extractor {
const messageBundle = new compiler.MessageBundle(htmlParser, [], {}); const messageBundle = new compiler.MessageBundle(htmlParser, [], {});
return new Extractor( return new Extractor(
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver); options, program, compilerHost, staticReflector, messageBundle, ngHost, resolver);
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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 {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
@ -17,46 +17,42 @@ const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/'; const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/; const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
export interface ReflectorHostContext { export interface NgHostContext {
fileExists(fileName: string): boolean; fileExists(fileName: string): boolean;
directoryExists(directoryName: string): boolean; directoryExists(directoryName: string): boolean;
readFile(fileName: string): string; readFile(fileName: string): string;
assumeFileExists(fileName: string): void; assumeFileExists(fileName: string): void;
} }
export class ReflectorHost implements StaticReflectorHost, ImportGenerator { export class NgHost implements AotCompilerHost {
protected metadataCollector = new MetadataCollector(); protected metadataCollector = new MetadataCollector();
protected context: ReflectorHostContext; protected context: NgHostContext;
private isGenDirChildOfRootDir: boolean; private isGenDirChildOfRootDir: boolean;
protected basePath: string; protected basePath: string;
private genDir: string; private genDir: string;
constructor( constructor(
protected program: ts.Program, protected compilerHost: ts.CompilerHost, 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 '/'. // normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/'); this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir, '.')).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); const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); 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. // We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; } 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, ''); m = m.replace(EXT, '');
const resolved = const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context) 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 { protected resolveAssetUrl(url: string, containingFile: string): string {
const assetUrl = this.normalizeAssetUrl(url); const assetUrl = this.normalizeAssetUrl(url);
if (assetUrl) { if (assetUrl) {
return this.getCanonicalFileName(this.resolve(assetUrl, containingFile)); return this.getCanonicalFileName(this.resolveImportToFile(assetUrl, containingFile));
} }
return url; 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 || !(<any>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((<any>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<string, StaticSymbol>();
private resolverCache = new Map<string, ModuleMetadata>(); private resolverCache = new Map<string, ModuleMetadata>();
/**
* 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 { getMetadataFor(filePath: string): ModuleMetadata {
if (!this.context.fileExists(filePath)) { if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file. // 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; 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) {} constructor(private host: ts.CompilerHost) {}
private assumedExists: {[fileName: string]: boolean} = {}; private assumedExists: {[fileName: string]: boolean} = {};

View File

@ -12,22 +12,22 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; 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 EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/; 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 * and executed with a "path mapped" directory structure, where generated files
* are in a parallel tree with the sources, and imported using a `./` relative * 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 * import. This requires using TS `rootDirs` option and also teaching the module
* loader what to do. * loader what to do.
*/ */
export class PathMappedReflectorHost extends ReflectorHost { export class PathMappedNgHost extends NgHost {
constructor( constructor(
program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions, program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions,
context?: ReflectorHostContext) { context?: NgHostContext) {
super(program, compilerHost, options, context); super(program, compilerHost, options, context);
} }
@ -42,7 +42,14 @@ export class PathMappedReflectorHost extends ReflectorHost {
return fileName; 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 || ['']) { for (const root of this.options.rootDirs || ['']) {
const rootedContainingFile = path.join(root, containingFile); const rootedContainingFile = path.join(root, containingFile);
const resolved = const resolved =
@ -82,7 +89,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
} }
const resolvable = (candidate: string) => { 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, ''); return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
}; };

View File

@ -16,9 +16,3 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio
export type Console = typeof r._Console; export type Console = typeof r._Console;
export var Console: typeof r.Console = 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;

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license * 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'; import * as ts from 'typescript';
export type Entry = string | Directory; export type Entry = string | Directory;
export interface Directory { [name: string]: Entry; } export interface Directory { [name: string]: Entry; }
export class MockContext implements ReflectorHostContext { export class MockContext implements NgHostContext {
constructor(public currentDirectory: string, private files: Entry) {} constructor(public currentDirectory: string, private files: Entry) {}
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; } fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -26,6 +26,7 @@ export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
export {CompilerConfig, RenderTypes} from './src/config'; export {CompilerConfig, RenderTypes} from './src/config';
export * from './src/compile_metadata'; export * from './src/compile_metadata';
export * from './src/aot/compiler'; export * from './src/aot/compiler';
export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector'; export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities'; export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol'; export * from './src/aot/static_symbol';

View File

@ -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;
}

View File

@ -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 {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 {ReflectorReader} from '../private_import_core';
import {AotCompilerHost} from './compiler_host';
import {StaticSymbol} from './static_symbol'; import {StaticSymbol} from './static_symbol';
const SUPPORTED_SCHEMA_VERSION = 1; const SUPPORTED_SCHEMA_VERSION = 1;
const ANGULAR_IMPORT_LOCATIONS = {
/** coreDecorators: '@angular/core/src/metadata',
* The host of the static resolver is expected to be able to provide module metadata in the form of diDecorators: '@angular/core/src/di/metadata',
* ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is diMetadata: '@angular/core/src/di/metadata',
* produced and the module has exported variables or classes with decorators. Module metadata can diOpaqueToken: '@angular/core/src/di/opaque_token',
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata. animationMetadata: '@angular/core/src/animation/metadata',
*/ provider: '@angular/core/src/di/provider'
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;
}
/** /**
* A static reflector implements enough of the Reflector API that is necessary to compile * A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically. * templates statically.
*/ */
export class StaticReflector implements ReflectorReader { export class StaticReflector implements ReflectorReader {
private typeCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>(); private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>(); private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
private parameterCache = new Map<StaticSymbol, any[]>(); private parameterCache = new Map<StaticSymbol, any[]>();
private metadataCache = new Map<string, {[key: string]: any}>(); private metadataCache = new Map<string, {[key: string]: any}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>(); private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private declarationMap = new Map<string, StaticSymbol>();
private opaqueToken: StaticSymbol; private opaqueToken: StaticSymbol;
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } constructor(private host: AotCompilerHost) { this.initializeConversionMap(); }
importUri(typeOrFunc: StaticSymbol): string { 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; return staticSymbol ? staticSymbol.filePath : null;
} }
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { 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 { resolveEnum(enumIdentifier: any, name: string): any {
const staticSymbol: StaticSymbol = enumIdentifier; 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[] { public annotations(type: StaticSymbol): any[] {
@ -172,59 +146,156 @@ export class StaticReflector implements ReflectorReader {
private initializeConversionMap(): void { private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} = const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
this.host.angularImportLocations(); ANGULAR_IMPORT_LOCATIONS;
this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken'); this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host); this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Injectable'), Injectable); this.findDeclaration(diDecorators, 'Injectable'), Injectable);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self); 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.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf); this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Optional'), Optional); this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute); this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild); this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this.registerDecoratorOrConstructor( 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.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild); this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren); this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Output'), Output); this.findDeclaration(coreDecorators, 'Directive'), Directive);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding); this.findDeclaration(coreDecorators, 'Component'), Component);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
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);
// Note: Some metadata classes can be used directly with Provider.deps. // Note: Some metadata classes can be used directly with Provider.deps.
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host); this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self); this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf); this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger); this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state); this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition); this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style); this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate); this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes); this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence); this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group); 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 */ /** @internal */
@ -237,10 +308,10 @@ export class StaticReflector implements ReflectorReader {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol; let staticSymbol: StaticSymbol;
if (expression['module']) { if (expression['module']) {
staticSymbol = _this.host.findDeclaration( staticSymbol =
expression['module'], expression['name'], context.filePath); _this.findDeclaration(expression['module'], expression['name'], context.filePath);
} else { } else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']); staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
} }
return staticSymbol; return staticSymbol;
} }
@ -449,8 +520,7 @@ export class StaticReflector implements ReflectorReader {
const members = selectTarget.members ? const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) : (selectTarget.members as string[]).concat(member) :
[member]; [member];
return _this.host.getStaticSymbol( return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
selectTarget.filePath, selectTarget.name, members);
} }
} }
const member = simplify(expression['member']); const member = simplify(expression['member']);
@ -485,10 +555,10 @@ export class StaticReflector implements ReflectorReader {
// Determine if the function is a built-in conversion // Determine if the function is a built-in conversion
let target = expression['expression']; let target = expression['expression'];
if (target['module']) { if (target['module']) {
staticSymbol = _this.host.findDeclaration( staticSymbol =
target['module'], target['name'], context.filePath); _this.findDeclaration(target['module'], target['name'], context.filePath);
} else { } else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']); staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
} }
let converter = _this.conversionMap.get(staticSymbol); let converter = _this.conversionMap.get(staticSymbol);
if (converter) { if (converter) {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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 {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MetadataCollector} from '@angular/tsc-wrapped'; import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -18,11 +18,11 @@ const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
describe('StaticReflector', () => { describe('StaticReflector', () => {
const noContext = new StaticSymbol('', ''); const noContext = new StaticSymbol('', '');
let host: StaticReflectorHost; let host: AotCompilerHost;
let reflector: StaticReflector; let reflector: StaticReflector;
beforeEach(() => { beforeEach(() => {
host = new MockReflectorHost(); host = new MockAotCompilerHost();
reflector = new StaticReflector(host); reflector = new StaticReflector(host);
}); });
@ -31,7 +31,7 @@ describe('StaticReflector', () => {
} }
it('should get annotations for NgFor', () => { 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); const annotations = reflector.annotations(NgFor);
expect(annotations.length).toEqual(1); expect(annotations.length).toEqual(1);
const annotation = annotations[0]; const annotation = annotations[0];
@ -40,15 +40,15 @@ describe('StaticReflector', () => {
}); });
it('should get constructor for NgFor', () => { it('should get constructor 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 ViewContainerRef = const ViewContainerRef = reflector.findDeclaration(
host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef'); '@angular/core/src/linker/view_container_ref', 'ViewContainerRef');
const TemplateRef = const TemplateRef =
host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef'); reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef');
const IterableDiffers = host.findDeclaration( const IterableDiffers = reflector.findDeclaration(
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers'); '@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers');
const ChangeDetectorRef = host.findDeclaration( const ChangeDetectorRef = reflector.findDeclaration(
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef'); '@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef');
const parameters = reflector.parameters(NgFor); const parameters = reflector.parameters(NgFor);
expect(parameters).toEqual([ expect(parameters).toEqual([
@ -58,7 +58,7 @@ describe('StaticReflector', () => {
it('should get annotations for HeroDetailComponent', () => { it('should get annotations for HeroDetailComponent', () => {
const HeroDetailComponent = const HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const annotations = reflector.annotations(HeroDetailComponent); const annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1); expect(annotations.length).toEqual(1);
const annotation = annotations[0]; const annotation = annotations[0];
@ -74,40 +74,39 @@ describe('StaticReflector', () => {
}); });
it('should throw and exception for unsupported metadata versions', () => { it('should throw and exception for unsupported metadata versions', () => {
const e = host.findDeclaration('src/version-error', 'e'); expect(() => reflector.findDeclaration('src/version-error', 'e'))
expect(() => reflector.annotations(e))
.toThrow(new Error( .toThrow(new Error(
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 1')); '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', () => { 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); const annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]); expect(annotations).toEqual([]);
}); });
it('should get propMetadata for HeroDetailComponent', () => { it('should get propMetadata for HeroDetailComponent', () => {
const HeroDetailComponent = const HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const props = reflector.propMetadata(HeroDetailComponent); const props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy(); expect(props['hero']).toBeTruthy();
expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]); expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]);
}); });
it('should get an empty object from propMetadata for an unknown class', () => { 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); const properties = reflector.propMetadata(UnknownClass);
expect(properties).toEqual({}); expect(properties).toEqual({});
}); });
it('should get empty parameters list for an unknown class ', () => { 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); const parameters = reflector.parameters(UnknownClass);
expect(parameters).toEqual([]); expect(parameters).toEqual([]);
}); });
it('should provide context for errors reported by the collector', () => { 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)) expect(() => reflector.annotations(SomeClass))
.toThrow(new Error( .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')); '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( expect(simplify(
new StaticSymbol('/src/cases', ''), new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'}))) ({__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', () => { it('should simplify a function reference as a static symbol', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'), new StaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []}))) ({__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', () => { 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', () => { it('should be able to get metadata for a class containing a custom decorator', () => {
const props = reflector.propMetadata( 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: []}); expect(props).toEqual({foo: []});
}); });
it('should read ctor parameters with forwardRef', () => { it('should read ctor parameters with forwardRef', () => {
const src = '/tmp/src/forward-ref.ts'; const src = '/tmp/src/forward-ref.ts';
const dep = host.getStaticSymbol(src, 'Dep'); const dep = reflector.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(host.getStaticSymbol(src, 'Forward')); const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward'));
expect(props).toEqual([[dep, new Inject(dep)]]); expect(props).toEqual([[dep, new Inject(dep)]]);
}); });
it('should report an error for invalid function calls', () => { it('should report an error for invalid function calls', () => {
expect( expect(
() => () => reflector.annotations(
reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent'))) reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
.toThrow(new Error( .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`)); `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', () => { it('should be able to get metadata for a class containing a static method call', () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100}); expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100});
}); });
it('should be able to get metadata for a class containing a static field reference', () => { it('should be able to get metadata for a class containing a static field reference', () => {
const annotations = const annotations = reflector.annotations(
reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]); 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', it('should be able to get the metadata for a class calling a method with a conditional expression',
() => { () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers).toEqual([ expect(annotations[0].providers).toEqual([
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] [{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', it('should be able to get the metadata for a class calling a method with default parameters',
() => { () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers).toEqual([['a', true, false]]); expect(annotations[0].providers).toEqual([['a', true, false]]);
}); });
it('should be able to get metadata with a reference to a static method', () => { it('should be able to get metadata with a reference to a static method', () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); 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 { class MockAotCompilerHost implements AotCompilerHost {
private staticTypeCache = new Map<string, StaticSymbol>();
private collector = new MetadataCollector(); private collector = new MetadataCollector();
constructor() {} 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 // 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 splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string { function resolvePath(pathParts: string[]): string {
@ -530,16 +547,16 @@ class MockReflectorHost implements StaticReflectorHost {
const baseName = pathTo(containingFile, modulePath); const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts'; const tsName = baseName + '.ts';
if (this.getMetadataFor(tsName)) { 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 { getMetadataFor(moduleId: string): any {
const data: {[key: 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', '__symbolic': 'module',
'version': 1, 'version': 1,
'metadata': { 'metadata': {
@ -547,12 +564,12 @@ class MockReflectorHost implements StaticReflectorHost {
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'NgFor', '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', '__symbolic': 'module',
'version': 1, 'version': 1,
'metadata': { 'metadata': {
@ -564,7 +581,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Directive', 'name': 'Directive',
'module': '../../core/metadata' 'module': '@angular/core/src/metadata'
}, },
'arguments': [ 'arguments': [
{ {
@ -581,22 +598,22 @@ class MockReflectorHost implements StaticReflectorHost {
'parameters': [ 'parameters': [
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/linker/view_container_ref', 'module': '@angular/core/src/linker/view_container_ref',
'name': 'ViewContainerRef' 'name': 'ViewContainerRef'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/linker/template_ref', 'module': '@angular/core/src/linker/template_ref',
'name': 'TemplateRef' 'name': 'TemplateRef'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/change_detection/differs/iterable_differs', 'module': '@angular/core/src/change_detection/differs/iterable_differs',
'name': 'IterableDiffers' 'name': 'IterableDiffers'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/change_detection/change_detector_ref', 'module': '@angular/core/src/change_detection/change_detector_ref',
'name': 'ChangeDetectorRef' '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'}}}, {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'}}}, {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'}}}, {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'}}}, {version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/src/app/hero-detail.component.d.ts': { '/tmp/src/app/hero-detail.component.d.ts': {
'__symbolic': 'module', '__symbolic': 'module',
@ -626,7 +643,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Component', 'name': 'Component',
'module': 'angular2/src/core/metadata' 'module': '@angular/core/src/metadata'
}, },
'arguments': [ 'arguments': [
{ {
@ -638,7 +655,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'trigger', 'name': 'trigger',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
'myAnimation', 'myAnimation',
@ -646,7 +663,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'state', 'name': 'state',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
'state1', 'state1',
@ -654,7 +671,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'style', 'name': 'style',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
{ 'background':'white' } { 'background':'white' }
@ -666,7 +683,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'transition', 'name':'transition',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
'* => *', '* => *',
@ -675,20 +692,20 @@ class MockReflectorHost implements StaticReflectorHost {
'expression':{ 'expression':{
'__symbolic':'reference', '__symbolic':'reference',
'name':'sequence', 'name':'sequence',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[[{ '__symbolic': 'call', 'arguments':[[{ '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'group', 'name':'group',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[[{ 'arguments':[[{
'__symbolic': 'call', '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'animate', 'name':'animate',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[ 'arguments':[
'1s 0.5s', '1s 0.5s',
@ -696,13 +713,13 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'keyframes', 'name':'keyframes',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[[{ '__symbolic': 'call', 'arguments':[[{ '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'style', 'name':'style',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[ { 'background': 'blue'} ] 'arguments':[ { 'background': 'blue'} ]
}, { }, {
@ -710,7 +727,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'style', 'name':'style',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[ { 'background': 'red'} ] 'arguments':[ { 'background': 'red'} ]
}]] }]]
@ -736,7 +753,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Input', 'name': 'Input',
'module': 'angular2/src/core/metadata' 'module': '@angular/core/src/metadata'
} }
} }
] ]
@ -750,7 +767,7 @@ class MockReflectorHost implements StaticReflectorHost {
'__symbolic': 'call', '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': 'angular2/src/core/metadata', 'module': '@angular/core/src/metadata',
'name': 'HostListener' 'name': 'HostListener'
}, },
'arguments': [ 'arguments': [
@ -781,7 +798,7 @@ class MockReflectorHost implements StaticReflectorHost {
expression: { expression: {
__symbolic: 'reference', __symbolic: 'reference',
name: 'Component', name: 'Component',
module: 'angular2/src/core/metadata' module: '@angular/core/src/metadata'
}, },
arguments: [ arguments: [
{ {
@ -982,8 +999,8 @@ class MockReflectorHost implements StaticReflectorHost {
`, `,
'/tmp/src/invalid-calls.ts': ` '/tmp/src/invalid-calls.ts': `
import {someFunction} from './nvalid-calll-definitions.ts'; import {someFunction} from './nvalid-calll-definitions.ts';
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
import {NgIf} from 'angular2/common'; import {NgIf} from '@angular/common';
@Component({ @Component({
selector: 'my-component', selector: 'my-component',
@ -999,7 +1016,7 @@ class MockReflectorHost implements StaticReflectorHost {
export class MyOtherComponent { } export class MyOtherComponent { }
`, `,
'/tmp/src/static-method.ts': ` '/tmp/src/static-method.ts': `
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
@Component({ @Component({
selector: 'stub' selector: 'stub'
@ -1017,7 +1034,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-method-call.ts': ` '/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'; import {MyModule} from './static-method';
@Component({ @Component({
@ -1036,7 +1053,7 @@ class MockReflectorHost implements StaticReflectorHost {
export class MyDefaultsComponent { } export class MyDefaultsComponent { }
`, `,
'/tmp/src/static-field.ts': ` '/tmp/src/static-field.ts': `
import {Injectable} from 'angular2/core'; import {Injectable} from '@angular/core';
@Injectable() @Injectable()
export class MyModule { export class MyModule {
@ -1044,7 +1061,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-field-reference.ts': ` '/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'; import {MyModule} from './static-field';
@Component({ @Component({
@ -1058,7 +1075,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-method-ref.ts': ` '/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'; import {ClassWithStatics} from './static-method-def';
@Component({ @Component({
@ -1069,7 +1086,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/invalid-metadata.ts': ` '/tmp/src/invalid-metadata.ts': `
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
@Component({ @Component({
providers: [ { provider: 'a', useValue: (() => 1)() }] providers: [ { provider: 'a', useValue: (() => 1)() }]
@ -1077,9 +1094,9 @@ class MockReflectorHost implements StaticReflectorHost {
export class InvalidMetadata {} export class InvalidMetadata {}
`, `,
'/tmp/src/forward-ref.ts': ` '/tmp/src/forward-ref.ts': `
import {forwardRef} from 'angular2/core'; import {forwardRef} from '@angular/core';
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
import {Inject} from 'angular2/src/core/di/metadata'; import {Inject} from '@angular/core/src/di/metadata';
@Component({}) @Component({})
export class Forward { export class Forward {
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {} constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
@ -1087,7 +1104,50 @@ class MockReflectorHost implements StaticReflectorHost {
export class Dep { export class Dep {
@Input f: Forward; @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'}]
}
}; };