refactor(compiler): store metadata of top level symbols also in summaries (#13289)

This allows a build using summaries to not need .metadata.json files at all
any more.

Part of #12787
This commit is contained in:
Tobias Bosch
2016-12-15 09:12:40 -08:00
committed by Igor Minar
parent 01ca2db6ae
commit 33910ddfc9
52 changed files with 1702 additions and 1011 deletions

View File

@ -20,34 +20,34 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerOptions} from './compiler_options';
import {AotCompilerHost} from './compiler_host';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {AotSummaryResolver} from './summary_resolver';
import {filterFileByPatterns} from './utils';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
import {serializeSummaries, summaryFileName} from './summary_serializer';
export class AotCompiler {
private _animationCompiler = new AnimationCompiler();
constructor(
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _host: AotCompilerHost, private _metadataResolver: CompileMetadataResolver,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: AotSummaryResolver, private _localeId: string,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
private _translationFormat: string, private _animationParser: AnimationParser,
private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {}
private _symbolResolver: StaticSymbolResolver) {}
clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -56,27 +56,21 @@ export class AotCompiler {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
file.ngModules));
file.ngModules, file.injectables));
return ListWrapper.flatten(sourceModules);
});
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[]): GeneratedFile[] {
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
injectables: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = [];
const exportedVars: string[] = [];
const generatedFiles: GeneratedFile[] = [];
// write summary files
const summaries: CompileTypeSummary[] = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref))
];
generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries));
generatedFiles.push(this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables));
// compile all ng modules
exportedVars.push(
@ -121,6 +115,22 @@ export class AotCompiler {
return generatedFiles;
}
private _createSummary(
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[], injectables: StaticSymbol[]): GeneratedFile {
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl)
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
const typeSummaries = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)),
...injectables.map(ref => this._metadataResolver.getInjectableSummary(ref))
];
const json = serializeSummaries(
this._host, this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json);
}
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType);
const providers: CompileProviderMetadata[] = [];
@ -142,7 +152,7 @@ export class AotCompiler {
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
appCompileResult.dependencies.forEach((dep) => {
dep.placeholder.reference = this._staticReflector.getStaticSymbol(
dep.placeholder.reference = this._symbolResolver.getStaticSymbol(
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
});
@ -163,7 +173,7 @@ export class AotCompiler {
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string {
const hostMeta = createHostComponentMeta(
this._staticReflector.getStaticSymbol(
this._symbolResolver.getStaticSymbol(
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
compMeta);
const hostViewFactoryVar = this._compileComponent(
@ -206,16 +216,16 @@ export class AotCompiler {
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
if (componentStyles) {
targetStatements.push(
..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix));
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
}
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult));
targetStatements.push(..._resolveViewStatements(this._symbolResolver, viewResult));
return viewResult.viewClassVar;
}
private _codgenStyles(
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
_resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix);
_resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix);
return this._codegenSourceModule(
fileUrl, _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
@ -232,7 +242,7 @@ export class AotCompiler {
}
function _resolveViewStatements(
reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] {
reflector: StaticSymbolResolver, compileResult: ViewCompileResult): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
if (dep instanceof ViewClassDependency) {
const vfd = <ViewClassDependency>dep;
@ -253,7 +263,7 @@ function _resolveViewStatements(
function _resolveStyleStatements(
reflector: StaticReflector, compileResult: CompiledStylesheet,
reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
fileSuffix: string): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
@ -303,26 +313,27 @@ export interface NgAnalyzedModules {
srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[]
ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}>;
symbolsMissingModule?: StaticSymbol[];
}
export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; }
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule);
_createNgModules(programStaticSymbols, host, metadataResolver);
return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
const result = analyzeNgModules(programStaticSymbols, host, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
@ -332,16 +343,27 @@ export function analyzeAndValidateNgModules(
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
programSymbols: StaticSymbol[], ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[],
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const ngPipesByFile = new Map<string, StaticSymbol[]>();
const ngInjectablesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Make sure we produce an analyzed file for each input file
programSymbols.forEach((symbol) => {
const filePath = symbol.filePath;
filePaths.add(filePath);
if (metadataResolver.isInjectable(symbol)) {
ngInjectablesByFile.set(filePath, (ngInjectablesByFile.get(filePath) || []).concat(symbol));
}
});
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
@ -369,17 +391,20 @@ function _analyzeNgModules(
});
});
const files:
{srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[]}[] = [];
const files: {
srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const pipes = ngPipesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules});
const injectables = ngInjectablesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules, injectables});
});
return {
@ -392,29 +417,20 @@ function _analyzeNgModules(
}
export function extractProgramSymbols(
staticReflector: StaticReflector, files: string[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] {
staticSymbolResolver: StaticSymbolResolver, files: string[],
host: NgAnalyzeModulesHost): StaticSymbol[] {
const staticSymbols: StaticSymbol[] = [];
files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => {
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile);
if (!moduleMetadata) {
console.error(`WARNING: no metadata found for ${sourceFile}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => {
staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => {
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
const symbolMeta = resolvedSymbol.metadata;
if (symbolMeta) {
if (symbolMeta.__symbolic != 'error') {
// Ignore symbols that are only included to record error information.
staticSymbols.push(resolvedSymbol.symbol);
}
}
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
}
});
});
return staticSymbols;
@ -424,8 +440,7 @@ export function extractProgramSymbols(
// that all directives / pipes that are present in the program
// are also declared by a module.
function _createNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
@ -433,7 +448,7 @@ function _createNgModules(
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) {
if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
return false;
}
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);

View File

@ -34,8 +34,11 @@ import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector';
import {StaticSymbolCache} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {AotSummaryResolver} from './summary_resolver';
/**
* Creates a new AotCompiler based on options and a host.
*/
@ -44,7 +47,10 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(compilerHost);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(symbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
const config = new CompilerConfig({
@ -60,17 +66,16 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const console = new Console();
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const compiler = new AotCompiler(
resolver, tmplParser, new StyleCompiler(urlResolver),
compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
options.i18nFormat, new AnimationParser(elementSchemaRegistry), symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -8,16 +8,17 @@
import {ImportResolver} from '../output/path_util';
import {StaticReflectorHost} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolverHost} from './static_symbol_resolver';
import {AotSummaryResolverHost} from './summary_resolver';
import {AotSummarySerializerHost} from './summary_serializer';
/**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver,
AotSummaryResolverHost {
export interface AotCompilerHost extends StaticSymbolResolverHost, ImportResolver,
AotSummaryResolverHost, AotSummarySerializerHost {
/**
* Loads a resource (e.g. html / css)
*/

View File

@ -11,6 +11,4 @@ export interface AotCompilerOptions {
locale?: string;
i18nFormat?: string;
translations?: string;
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}

View File

@ -8,6 +8,7 @@
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) {
@ -42,7 +43,7 @@ export class StaticAndDynamicReflectionCapabilities {
method(name: string): MethodFn { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); }
resolveIdentifier(name: string, moduleUrl: string, runtime: any) {
return this.staticDelegate.resolveIdentifier(name, moduleUrl, runtime);
return this.staticDelegate.resolveIdentifier(name, moduleUrl);
}
resolveEnum(enumIdentifier: any, name: string): any {
if (isStaticType(enumIdentifier)) {

View File

@ -7,10 +7,12 @@
*/
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 {ReflectorReader} from '../private_import_core';
import {StaticSymbol} from './static_symbol';
const SUPPORTED_SCHEMA_VERSION = 3;
import {ReflectorReader} from '../private_import_core';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
@ -22,66 +24,20 @@ const ANGULAR_IMPORT_LOCATIONS = {
const HIDDEN_KEY = /^\$.*\$$/;
/**
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface StaticReflectorHost {
/**
* 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}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile: string): string;
}
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}
/**
* A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically.
*/
export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private metadataCache = new Map<string, {[key: string]: any}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol;
constructor(
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
private symbolResolver: StaticSymbolResolver,
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName: string) => void) {
@ -94,12 +50,26 @@ export class StaticReflector implements ReflectorReader {
}
importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
return staticSymbol ? staticSymbol.filePath : null;
}
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.findDeclaration(moduleUrl, name, '');
resolveIdentifier(name: string, moduleUrl: string): StaticSymbol {
return this.findDeclaration(moduleUrl, name);
}
findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
return this.findSymbolDeclaration(
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
}
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
return this.findSymbolDeclaration(resolvedSymbol.metadata);
} else {
return symbol;
}
}
resolveEnum(enumIdentifier: any, name: string): any {
@ -128,7 +98,7 @@ export class StaticReflector implements ReflectorReader {
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type) || {};
const classMetadata = this.getTypeMetadata(type);
propMetadata = {};
if (classMetadata['extends']) {
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
@ -203,7 +173,7 @@ export class StaticReflector implements ReflectorReader {
private _methodNames(type: any): {[key: string]: boolean} {
let methodNames = this.methodCache.get(type);
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type) || {};
const classMetadata = this.getTypeMetadata(type);
methodNames = {};
if (classMetadata['extends']) {
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
@ -306,7 +276,7 @@ export class StaticReflector implements ReflectorReader {
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
@ -317,96 +287,6 @@ export class StaticReflector implements ReflectorReader {
}
}
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
if (!resolvedModulePath) {
this.reportError(
new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`),
null, filePath);
}
return resolvedModulePath;
};
const cacheKey = `${filePath}|${symbolName}`;
let staticSymbol = this.declarationCache.get(cacheKey);
if (staticSymbol) {
return staticSymbol;
}
const metadata = this.getModuleMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata['metadata'][symbolName]) {
staticSymbol = this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (!staticSymbol && 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;
}
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
staticSymbol =
this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
break;
}
}
}
}
if (!staticSymbol) {
// Try to find the symbol via export * directives.
for (const moduleExport of metadata['exports']) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) {
staticSymbol = candidateSymbol;
break;
}
}
}
}
}
}
}
this.declarationCache.set(cacheKey, staticSymbol);
return staticSymbol;
}
findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol {
try {
const filePath = this.host.moduleNameToFileName(module, containingFile);
let symbol: StaticSymbol;
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.
symbol = this.getStaticSymbol(module, symbolName);
} else {
symbol = this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
return symbol;
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
const self = this;
@ -414,93 +294,41 @@ export class StaticReflector implements ReflectorReader {
const calling = new Map<StaticSymbol, boolean>();
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol;
if (expression['module']) {
staticSymbol =
self.findDeclaration(expression['module'], expression['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
}
return staticSymbol;
}
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
const declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue;
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
return resolvedSymbol ? resolvedSymbol.metadata : null;
}
function isOpaqueToken(context: StaticSymbol, value: any): boolean {
if (value && value.__symbolic === 'new' && value.expression) {
const target = value.expression;
if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), self.opaqueToken);
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
}
return false;
}
function simplifyCall(expression: any) {
let callContext: {[name: string]: string}|undefined = undefined;
if (expression['__symbolic'] == 'call') {
const target = expression['expression'];
let functionSymbol: StaticSymbol;
let targetFunction: any;
if (target) {
switch (target.__symbolic) {
case 'reference':
// Find the function to call.
callContext = {name: target.name};
functionSymbol = resolveReference(context, target);
targetFunction = resolveReferenceValue(functionSymbol);
break;
case 'select':
// Find the static method to call
if (target.expression.__symbolic == 'reference') {
functionSymbol = resolveReference(context, target.expression);
const classData = resolveReferenceValue(functionSymbol);
if (classData && classData.statics) {
targetFunction = classData.statics[target.member];
}
}
break;
}
}
if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
// Determine the arguments
const args: any[] =
(expression['arguments'] || []).map((arg: any) => simplify(arg));
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyInContext(functionSymbol, value, depth + 1);
} finally {
scope = oldScope;
}
return result;
calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
} finally {
calling.delete(functionSymbol);
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyInContext(functionSymbol, value, depth + 1);
} finally {
scope = oldScope;
}
return result;
}
} finally {
calling.delete(functionSymbol);
}
}
@ -511,7 +339,7 @@ export class StaticReflector implements ReflectorReader {
return {__symbolic: 'ignore'};
}
return simplify(
{__symbolic: 'error', message: 'Function call not supported', context: callContext});
{__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
}
function simplify(expression: any): any {
@ -540,7 +368,18 @@ export class StaticReflector implements ReflectorReader {
return result;
}
if (expression instanceof StaticSymbol) {
return expression;
// Stop simplification at builtin symbols
if (expression === self.opaqueToken || self.conversionMap.has(expression)) {
return expression;
} else {
const staticSymbol = expression;
const declarationValue = resolveReferenceValue(staticSymbol);
if (declarationValue) {
return simplifyInContext(staticSymbol, declarationValue, depth + 1);
} else {
return staticSymbol;
}
}
}
if (expression) {
if (expression['__symbolic']) {
@ -618,50 +457,33 @@ export class StaticReflector implements ReflectorReader {
if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null;
case 'select':
const member = expression['member'];
let selectContext = context;
let selectTarget = simplify(expression['expression']);
if (selectTarget instanceof StaticSymbol) {
// Access to a static instance variable
const member: string = expression['member'];
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
const declarationValue = resolveReferenceValue(selectTarget);
const members = selectTarget.members.concat(member);
selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
if (declarationValue && declarationValue.statics) {
selectTarget = declarationValue.statics;
const declarationValue = resolveReferenceValue(selectContext);
if (declarationValue) {
return simplifyInContext(selectContext, declarationValue, depth + 1);
} else {
return selectContext;
}
}
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
if (selectTarget && isPrimitive(member))
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
return null;
case 'reference':
if (!expression['name']) {
return context;
// Note: This only has to deal with variable references,
// as symbol references have been converted into StaticSymbols already
// in the StaticSymbolResolver!
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
if (!expression.module) {
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
}
staticSymbol = resolveReference(context, expression);
let result: any = staticSymbol;
let declarationValue = resolveReferenceValue(result);
if (declarationValue) {
if (isOpaqueToken(staticSymbol, declarationValue)) {
// If the referenced symbol is initalized by a new OpaqueToken we can keep the
// reference to the symbol.
return staticSymbol;
}
result = simplifyInContext(staticSymbol, declarationValue, depth + 1);
}
return result;
break;
case 'class':
return context;
case 'function':
@ -669,26 +491,26 @@ export class StaticReflector implements ReflectorReader {
case 'new':
case 'call':
// Determine if the function is a built-in conversion
let target = expression['expression'];
if (target['module']) {
staticSymbol =
self.findDeclaration(target['module'], target['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
}
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
let args: any[] = expression['arguments'];
if (!args) {
args = [];
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
if (staticSymbol instanceof StaticSymbol) {
if (staticSymbol === self.opaqueToken) {
// if somebody calls new OpaqueToken, don't create an OpaqueToken,
// but rather return the symbol to which the OpaqueToken is assigned to.
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
return converter(context, args);
} else {
// Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, args);
}
return converter(
context, args.map(arg => simplifyInContext(context, arg, depth + 1)));
}
// Determine if the function is one we can simplify.
return simplifyCall(expression);
break;
case 'error':
let message = produceErrorMessage(expression);
if (expression['line']) {
@ -709,7 +531,9 @@ export class StaticReflector implements ReflectorReader {
try {
return simplify(value);
} catch (e) {
const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`;
const members = context.members.length ? `.${context.members.join('.')}` : '';
const message =
`${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`;
if (e.fileName) {
throw positionalError(message, e.fileName, e.line, e.column);
}
@ -733,40 +557,10 @@ export class StaticReflector implements ReflectorReader {
return result;
}
/**
* @param module an absolute path to a module file.
*/
public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
const errorMessage = moduleMetadata['version'] == 2 ?
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
this.reportError(new Error(errorMessage), null);
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
const moduleMetadata = this.getModuleMetadata(type.filePath);
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
{__symbolic: 'class'};
}
}
@ -858,7 +652,7 @@ class PopulatedScope extends BindingScope {
}
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
return a === b || (a.name == b.name && a.filePath == b.filePath);
return a === b;
}
function shouldIgnore(value: any): boolean {

View File

@ -14,3 +14,23 @@
export class StaticSymbol {
constructor(public filePath: string, public name: string, public members?: string[]) {}
}
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
members = members || [];
const memberSuffix = members.length ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}

View File

@ -0,0 +1,289 @@
/**
* @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 {SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
export class ResolvedStaticSymbol {
constructor(public symbol: StaticSymbol, public metadata: any) {}
}
/**
* The host of the SymbolResolverHost disconnects the implementation from TypeScript / other
* language
* services and from underlying file systems.
*/
export interface StaticSymbolResolverHost {
/**
* 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}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile: string): string /*|null*/;
}
const SUPPORTED_SCHEMA_VERSION = 3;
/**
* This class is responsible for loading metadata per symbol,
* and normalizing references between symbols.
*/
export class StaticSymbolResolver {
private metadataCache = new Map<string, {[key: string]: any}>();
private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
private resolvedFilePaths = new Set<string>();
constructor(
private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache,
private summaryResolver: SummaryResolver<StaticSymbol>,
private errorRecorder?: (error: any, fileName: string) => void) {}
resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
if (staticSymbol.members.length > 0) {
return this._resolveSymbolMembers(staticSymbol);
}
let result = this._resolveSymbolFromSummary(staticSymbol);
if (!result) {
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
// have summaries, only .d.ts files. So we always need to check both, the summary
// and metadata.
this._createSymbolsOf(staticSymbol.filePath);
result = this.resolvedSymbols.get(staticSymbol);
}
return result;
}
private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
const members = staticSymbol.members;
const baseResolvedSymbol =
this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name));
if (!baseResolvedSymbol) {
return null;
}
const baseMetadata = baseResolvedSymbol.metadata;
if (baseMetadata instanceof StaticSymbol) {
return new ResolvedStaticSymbol(
staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members));
} else if (baseMetadata && baseMetadata.__symbolic === 'class') {
if (baseMetadata.statics && members.length === 1) {
return new ResolvedStaticSymbol(staticSymbol, baseMetadata.statics[members[0]]);
}
} else {
let value = baseMetadata;
for (var i = 0; i < members.length && value; i++) {
value = value[members[i]];
}
return new ResolvedStaticSymbol(staticSymbol, value);
}
return null;
}
private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
const summary = this.summaryResolver.resolveSummary(staticSymbol);
return summary ? new ResolvedStaticSymbol(staticSymbol, summary.metadata) : null;
}
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
}
getSymbolsOf(filePath: string): StaticSymbol[] {
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
// have summaries, only .d.ts files. So we always need to check both, the summary
// and metadata.
let symbols = new Set<StaticSymbol>(this.summaryResolver.getSymbolsOf(filePath));
this._createSymbolsOf(filePath);
this.resolvedSymbols.forEach((resolvedSymbol) => {
if (resolvedSymbol.symbol.filePath === filePath) {
symbols.add(resolvedSymbol.symbol);
}
});
return Array.from(symbols);
}
private _createSymbolsOf(filePath: string) {
if (this.resolvedFilePaths.has(filePath)) {
return;
}
this.resolvedFilePaths.add(filePath);
const resolvedSymbols: ResolvedStaticSymbol[] = [];
const metadata = this.getModuleMetadata(filePath);
if (metadata['metadata']) {
// handle direct declarations of the symbol
Object.keys(metadata['metadata']).forEach((symbolName) => {
const symbolMeta = metadata['metadata'][symbolName];
resolvedSymbols.push(
this.createResolvedSymbol(this.getStaticSymbol(filePath, symbolName), symbolMeta));
});
}
// handle the symbols in one of the re-export location
if (metadata['exports']) {
for (const moduleExport of metadata['exports']) {
// handle the symbols in the list of explicitly re-exported symbols.
if (moduleExport.export) {
moduleExport.export.forEach((exportSymbol: any) => {
let symbolName: string;
if (typeof exportSymbol === 'string') {
symbolName = exportSymbol;
} else {
symbolName = exportSymbol.as;
}
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) {
const targetSymbol = this.getStaticSymbol(resolvedModule, symName);
const sourceSymbol = this.getStaticSymbol(filePath, symbolName);
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
}
});
} else {
// handle the symbols via export * directives.
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) {
const nestedExports = this.getSymbolsOf(resolvedModule);
nestedExports.forEach((targetSymbol) => {
const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
});
}
}
}
}
resolvedSymbols.forEach(
(resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
}
private createResolvedSymbol(sourceSymbol: StaticSymbol, metadata: any): ResolvedStaticSymbol {
const self = this;
class ReferenceTransformer extends ValueTransformer {
visitStringMap(map: {[key: string]: any}, functionParams: string[]): any {
const symbolic = map['__symbolic'];
if (symbolic === 'function') {
const oldLen = functionParams.length;
functionParams.push(...(map['parameters'] || []));
const result = super.visitStringMap(map, functionParams);
functionParams.length = oldLen;
return result;
} else if (symbolic === 'reference') {
const module = map['module'];
const name = map['name'];
if (!name) {
return null;
}
let filePath: string;
if (module) {
filePath = self.resolveModule(module, sourceSymbol.filePath);
if (!filePath) {
return {
__symbolic: 'error',
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
};
}
} else {
const isFunctionParam = functionParams.indexOf(name) >= 0;
if (!isFunctionParam) {
filePath = sourceSymbol.filePath;
}
}
if (filePath) {
return self.getStaticSymbol(filePath, name);
} else {
// reference to a function parameter
return {__symbolic: 'reference', name: name};
}
} else {
return super.visitStringMap(map, functionParams);
}
}
}
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
if (this.errorRecorder) {
this.errorRecorder(error, (context && context.filePath) || path);
} else {
throw error;
}
}
/**
* @param module an absolute path to a module file.
*/
private getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
const errorMessage = moduleMetadata['version'] == 2 ?
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
this.reportError(new Error(errorMessage), null);
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
const filePath = this.resolveModule(module, containingFile);
if (!filePath) {
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
}
return this.getStaticSymbol(filePath, symbolName);
}
private resolveModule(module: string, containingFile: string): string {
try {
return this.host.moduleNameToFileName(module, containingFile);
} catch (e) {
console.error(`Could not resolve module '${module}' relative to file ${containingFile}`);
this.reportError(new e, null, containingFile);
}
}
}

View File

@ -5,13 +5,12 @@
* 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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {SummaryResolver} from '../summary_resolver';
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {filterFileByPatterns} from './utils';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol} from './static_symbol_resolver';
import {deserializeSummaries, summaryFileName} from './summary_serializer';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
@ -19,106 +18,60 @@ export interface AotSummaryResolverHost {
/**
* Loads an NgModule/Directive/Pipe summary file
*/
loadSummary(filePath: string): string;
loadSummary(filePath: string): string /*|null*/;
/**
* Returns the output file path of a source file.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
* Returns whether a file is a source file or not.
*/
getOutputFileName(sourceFilePath: string): string;
isSourceFile(sourceFilePath: string): boolean;
}
export interface AotSummaryResolverOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
private loadedFilePaths = new Set<string>();
export class AotSummaryResolver implements SummaryResolver {
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
constructor(
private host: AotSummaryResolverHost, private staticReflector: StaticReflector,
private options: AotSummaryResolverOptions) {}
serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile {
const jsonReplacer = (key: string, value: any) => {
if (value instanceof StaticSymbol) {
// We convert the source filenames into output filenames,
// as the generated summary file will be used when the current
// compilation unit is used as a library
return {
'__symbolic__': 'symbol',
'name': value.name,
'path': this.host.getOutputFileName(value.filePath),
'members': value.members
};
}
return value;
};
const allSummaries = summaries.slice();
summaries.forEach((summary) => {
if (summary.summaryKind === CompileSummaryKind.NgModule) {
const moduleMeta = <CompileNgModuleSummary>summary;
moduleMeta.exportedDirectives.concat(moduleMeta.exportedPipes).forEach((id) => {
if (!filterFileByPatterns(id.reference.filePath, this.options)) {
allSummaries.push(this.resolveSummary(id.reference));
}
});
}
});
return new GeneratedFile(
srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(allSummaries, jsonReplacer));
private _assertNoMembers(symbol: StaticSymbol) {
if (symbol.members.length) {
throw new Error(
`Internal state: StaticSymbols in summaries can't have members! ${JSON.stringify(symbol)}`);
}
}
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; }
resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
this._assertNoMembers(staticSymbol);
let summary = this.summaryCache.get(staticSymbol);
if (!summary) {
this._loadSummaryFile(staticSymbol.filePath);
summary = this.summaryCache.get(staticSymbol);
}
return summary;
}
resolveSummary(staticSymbol: StaticSymbol): any {
const filePath = staticSymbol.filePath;
const name = staticSymbol.name;
const cacheKey = this._cacheKey(staticSymbol);
if (!filterFileByPatterns(filePath, this.options)) {
let summary = this.summaryCache[cacheKey];
getSymbolsOf(filePath: string): StaticSymbol[] {
this._loadSummaryFile(filePath);
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
}
private _loadSummaryFile(filePath: string) {
if (this.loadedFilePaths.has(filePath)) {
return;
}
this.loadedFilePaths.add(filePath);
if (!this.host.isSourceFile(filePath)) {
const summaryFilePath = summaryFileName(filePath);
if (!summary) {
try {
const jsonReviver = (key: string, value: any) => {
if (value && value['__symbolic__'] === 'symbol') {
// Note: We can't use staticReflector.findDeclaration here:
// Summary files can contain symbols of transitive compilation units
// (via the providers), and findDeclaration needs .metadata.json / .d.ts files,
// but we don't want to depend on these for transitive dependencies.
return this.staticReflector.getStaticSymbol(
value['path'], value['name'], value['members']);
} else {
return value;
}
};
const readSummaries: CompileTypeSummary[] =
JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver);
readSummaries.forEach((summary) => {
const filePath = summary.type.reference.filePath;
this.summaryCache[this._cacheKey(summary.type.reference)] = summary;
});
summary = this.summaryCache[cacheKey];
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
let json: string;
try {
json = this.host.loadSummary(summaryFilePath);
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
if (!summary) {
throw new Error(
`Could not find the symbol ${name} in the summary file ${summaryFilePath}!`);
if (json) {
const readSummaries = deserializeSummaries(this.staticSymbolCache, json);
readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, summary); });
}
return summary;
} else {
return null;
}
}
}
function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}

View File

@ -0,0 +1,183 @@
/**
* @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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {GeneratedFile} from './generated_file';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface AotSummarySerializerHost {
/**
* Returns the output file path of a source file.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
*/
getOutputFileName(sourceFilePath: string): string;
/**
* Returns whether a file is a source file or not.
*/
isSourceFile(sourceFilePath: string): boolean;
}
export function serializeSummaries(
host: AotSummarySerializerHost, summaryResolver: SummaryResolver<StaticSymbol>,
symbolResolver: StaticSymbolResolver,
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string {
const serializer = new Serializer(host);
// for symbols, we use everything except for the class metadata itself
// (we keep the statics though), as the class metadata is contained in the
// CompileTypeSummary.
symbols.forEach(
(resolvedSymbol) => serializer.addOrMergeSummary(
{symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}));
// Add summaries that are referenced by the given symbols (transitively)
// Note: the serializer.symbols array might be growing while
// we execute the loop!
for (let processedIndex = 0; processedIndex < serializer.symbols.length; processedIndex++) {
const symbol = serializer.symbols[processedIndex];
if (!host.isSourceFile(symbol.filePath)) {
let summary = summaryResolver.resolveSummary(symbol);
if (!summary) {
// some symbols might originate from a plain typescript library
// that just exported .d.ts and .metadata.json files, i.e. where no summary
// files were created.
const resolvedSymbol = symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol) {
summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata};
}
}
if (summary) {
serializer.addOrMergeSummary(summary);
}
}
}
// Add type summaries.
// Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols,
// as the type summaries already contain the transitive data that they require
// (in a minimal way).
types.forEach((typeSummary) => {
serializer.addOrMergeSummary(
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
const symbol: StaticSymbol = id.reference;
if (!host.isSourceFile(symbol.filePath)) {
serializer.addOrMergeSummary(summaryResolver.resolveSummary(symbol));
}
});
}
});
return serializer.serialize();
}
export function deserializeSummaries(
symbolCache: StaticSymbolCache, json: string): Summary<StaticSymbol>[] {
const deserializer = new Deserializer(symbolCache);
return deserializer.deserialize(json);
}
export function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}
class Serializer extends ValueTransformer {
symbols: StaticSymbol[] = [];
private indexBySymbol = new Map<StaticSymbol, number>();
// This now contains a `__symbol: number` in the place of
// StaticSymbols, but otherwise has the same shape as the original objects.
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
private processedSummaries: any[] = [];
constructor(private host: AotSummarySerializerHost) { super(); }
addOrMergeSummary(summary: Summary<StaticSymbol>) {
let symbolMeta = summary.metadata;
if (symbolMeta && symbolMeta.__symbolic === 'class') {
// For classes, we only keep their statics, but not the metadata
// of the class itself as that has been captured already via other summaries
// (e.g. DirectiveSummary, ...).
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics};
}
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
if (!processedSummary) {
processedSummary = this.processValue({symbol: summary.symbol});
this.processedSummaries.push(processedSummary);
this.processedSummaryBySymbol.set(summary.symbol, processedSummary);
}
// Note: == by purpose to compare with undefined!
if (processedSummary.metadata == null && symbolMeta != null) {
processedSummary.metadata = this.processValue(symbolMeta);
}
// Note: == by purpose to compare with undefined!
if (processedSummary.type == null && summary.type != null) {
processedSummary.type = this.processValue(summary.type);
}
}
serialize(): string {
return JSON.stringify({
summaries: this.processedSummaries,
symbols: this.symbols.map((symbol, index) => {
return {
__symbol: index,
name: symbol.name,
// We convert the source filenames tinto output filenames,
// as the generated summary file will be used when teh current
// compilation unit is used as a library
filePath: this.host.getOutputFileName(symbol.filePath)
};
})
});
}
private processValue(value: any): any { return visitValue(value, this, null); }
visitOther(value: any, context: any): any {
if (value instanceof StaticSymbol) {
let index = this.indexBySymbol.get(value);
// Note: == by purpose to compare with undefined!
if (index == null) {
index = this.indexBySymbol.size;
this.indexBySymbol.set(value, index);
this.symbols.push(value);
}
return {__symbol: index};
}
}
}
class Deserializer extends ValueTransformer {
private symbols: StaticSymbol[];
constructor(private symbolCache: StaticSymbolCache) { super(); }
deserialize(json: string): Summary<StaticSymbol>[] {
const data: {summaries: any[], symbols: any[]} = JSON.parse(json);
this.symbols = data.symbols.map(
serializedSymbol => this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name));
return visitValue(data.summaries, this, null);
}
visitStringMap(map: {[key: string]: any}, context: any): any {
if ('__symbol' in map) {
return this.symbols[map['__symbol']];
} else {
return super.visitStringMap(map, context);
}
}
}

View File

@ -1,19 +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
*/
export function filterFileByPatterns(
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
let match = true;
if (options.includeFilePattern) {
match = match && !!options.includeFilePattern.exec(fileName);
}
if (options.excludeFilePattern) {
match = match && !options.excludeFilePattern.exec(fileName);
}
return match;
}