refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

View File

@ -0,0 +1,411 @@
/**
* @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 {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
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 {syntaxError} from '../util';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerHost} from './compiler_host';
import {GeneratedFile} from './generated_file';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {serializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName} from './util';
export class AotCompiler {
constructor(
private _config: CompilerConfig, private _host: AotCompilerHost,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
private _translationFormat: string, private _symbolResolver: StaticSymbolResolver) {}
clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, false)))
.then(() => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
file.ngModules, file.injectables));
return flatten(sourceModules);
});
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
injectables: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = [];
const exportedVars: string[] = [];
const generatedFiles: GeneratedFile[] = [];
generatedFiles.push(this._createSummary(
srcFileUrl, directives, pipes, ngModules, injectables, statements, exportedVars));
// compile all ng modules
exportedVars.push(
...ngModules.map((ngModuleType) => this._compileModule(ngModuleType, statements)));
// compile components
directives.forEach((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) {
return Promise.resolve(null);
}
const ngModule = ngModuleByPipeOrDirective.get(dirType);
if (!ngModule) {
throw new Error(
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
}
_assertComponent(compMeta);
// compile styles
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
generatedFiles.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
});
// compile components
const compViewVars = this._compileComponent(
compMeta, ngModule, ngModule.transitiveModule.directives,
stylesCompileResults.componentStylesheet, fileSuffix, statements);
exportedVars.push(
this._compileComponentFactory(compMeta, ngModule, fileSuffix, statements),
compViewVars.viewClassVar, compViewVars.compRenderTypeVar);
});
if (statements.length > 0) {
const srcModule = this._codegenSourceModule(
srcFileUrl, ngfactoryFilePath(srcFileUrl), statements, exportedVars);
generatedFiles.unshift(srcModule);
}
return generatedFiles;
}
private _createSummary(
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[], injectables: StaticSymbol[], targetStatements: o.Statement[],
targetExportedVars: string[]): 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, exportAs} = serializeSummaries(
this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
exportAs.forEach((entry) => {
targetStatements.push(
o.variable(entry.exportAs).set(o.importExpr({reference: entry.symbol})).toDeclStmt());
targetExportedVars.push(entry.exportAs);
});
return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json);
}
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType);
const providers: CompileProviderMetadata[] = [];
if (this._localeId) {
providers.push({
token: createIdentifierToken(Identifiers.LOCALE_ID),
useValue: this._localeId,
});
}
if (this._translationFormat) {
providers.push({
token: createIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
useValue: this._translationFormat
});
}
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
targetStatements.push(...appCompileResult.statements);
return appCompileResult.ngModuleFactoryVar;
}
private _compileComponentFactory(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string {
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
const hostMeta = createHostComponentMeta(
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
const hostViewFactoryVar =
this._compileComponent(
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements)
.viewClassVar;
const compFactoryVar = componentFactoryName(compMeta.type.reference);
targetStatements.push(
o.variable(compFactoryVar)
.set(o.importExpr(createIdentifier(Identifiers.createComponentFactory)).callFn([
o.literal(compMeta.selector),
o.importExpr(compMeta.type),
o.variable(hostViewFactoryVar),
]))
.toDeclStmt(
o.importType(
createIdentifier(Identifiers.ComponentFactory), [o.importType(compMeta.type)],
[o.TypeModifier.Const]),
[o.StmtModifier.Final]));
return compFactoryVar;
}
private _compileComponent(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
directiveIdentifiers: CompileIdentifierMetadata[], componentStyles: CompiledStylesheet,
fileSuffix: string,
targetStatements: o.Statement[]): {viewClassVar: string, compRenderTypeVar: string} {
const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type));
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const viewResult =
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);
if (componentStyles) {
targetStatements.push(
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
}
targetStatements.push(...viewResult.statements);
return {viewClassVar: viewResult.viewClassVar, compRenderTypeVar: viewResult.rendererTypeVar};
}
private _codgenStyles(
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
_resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix);
return this._codegenSourceModule(
fileUrl, _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
stylesCompileResult.statements, [stylesCompileResult.stylesVar]);
}
private _codegenSourceModule(
srcFileUrl: string, genFileUrl: string, statements: o.Statement[],
exportedVars: string[]): GeneratedFile {
return new GeneratedFile(
srcFileUrl, genFileUrl,
this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars));
}
}
function _resolveStyleStatements(
reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
fileSuffix: string): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
_stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix), dep.name);
});
return compileResult.statements;
}
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
}
function _assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new Error(
`Could not compile '${identifierName(meta.type)}' because it is not a component.`);
}
}
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{
srcUrl: string,
directives: StaticSymbol[],
pipes: 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[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, host, metadataResolver);
return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
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}! Add ${s.name} to the NgModule to fix it.`);
throw syntaxError(messages.join('\n'));
}
return result;
}
function _analyzeNgModules(
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`,
// - a map from file to pipes `ngPipesByFile`,
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
ngModuleMetas.forEach((ngModuleMeta) => {
const srcFileUrl = ngModuleMeta.type.reference.filePath;
filePaths.add(srcFileUrl);
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
});
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
const fileUrl = pipeIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngPipesByFile.set(
fileUrl, (ngPipesByFile.get(fileUrl) || []).concat(pipeIdentifier.reference));
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
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) || [];
const injectables = ngInjectablesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules, injectables});
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
ngModules: ngModuleMetas, symbolsMissingModule
};
}
export function extractProgramSymbols(
staticSymbolResolver: StaticSymbolResolver, files: string[],
host: NgAnalyzeModulesHost): StaticSymbol[] {
const staticSymbols: StaticSymbol[] = [];
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);
}
}
});
});
return staticSymbols;
}
// Load the NgModules and check
// that all directives / pipes that are present in the program
// are also declared by a module.
function _createNgModules(
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = [];
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
return false;
}
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
if (ngModule) {
ngModules.set(ngModule.type.reference, ngModule);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
// For every input module add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.reference));
}
return !!ngModule;
};
programStaticSymbols.forEach((staticSymbol) => {
if (!addNgModule(staticSymbol) &&
(metadataResolver.isDirective(staticSymbol) || metadataResolver.isPipe(staticSymbol))) {
programPipesAndDirectives.push(staticSymbol);
}
});
// Throw an error if any of the program pipe or directives is not declared by a module
const symbolsMissingModule =
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
}

View File

@ -0,0 +1,84 @@
/**
* @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 {MissingTranslationStrategy, ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from '../directive_resolver';
import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
import {NgModuleResolver} from '../ng_module_resolver';
import {TypeScriptEmitter} from '../output/ts_emitter';
import {PipeResolver} from '../pipe_resolver';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompiler} from './compiler';
import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector';
import {StaticSymbol, 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.
*/
export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions):
{compiler: AotCompiler, reflector: StaticReflector} {
let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver();
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 console = new Console();
const htmlParser = new I18NHtmlParser(
new HtmlParser(), translations, options.i18nFormat, MissingTranslationStrategy.Warning,
console);
const config = new CompilerConfig({
defaultEncapsulation: ViewEncapsulation.Emulated,
useJit: false,
enableLegacyTemplate: options.enableLegacyTemplate !== false,
});
const normalizer = new DirectiveNormalizer(
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
const expressionParser = new Parser(new Lexer());
const elementSchemaRegistry = new DomElementSchemaRegistry();
const tmplParser =
new TemplateParser(config, expressionParser, elementSchemaRegistry, htmlParser, console, []);
const resolver = new CompileMetadataResolver(
config, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
symbolCache, staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const importResolver = {
getImportAs: (symbol: StaticSymbol) => symbolResolver.getImportAs(symbol),
fileNameToModuleName: (fileName: string, containingFilePath: string) =>
compilerHost.fileNameToModuleName(fileName, containingFilePath),
getTypeArity: (symbol: StaticSymbol) => symbolResolver.getTypeArity(symbol)
};
const viewCompiler = new ViewCompiler(config, elementSchemaRegistry);
const compiler = new AotCompiler(
config, compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver), viewCompiler,
new NgModuleCompiler(), new TypeScriptEmitter(importResolver), summaryResolver,
options.locale, options.i18nFormat, symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -0,0 +1,30 @@
/**
* @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 {StaticSymbolResolverHost} from './static_symbol_resolver';
import {AotSummaryResolverHost} from './summary_resolver';
/**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/**
* Converts a file path to a module name that can be used as an `import.
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
*
* See ImportResolver.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string
/*|null*/;
/**
* Loads a resource (e.g. html / css)
*/
loadResource(path: string): Promise<string>;
}

View File

@ -0,0 +1,14 @@
/**
* @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 interface AotCompilerOptions {
locale?: string;
i18nFormat?: string;
translations?: string;
enableLegacyTemplate?: boolean;
}

View File

@ -0,0 +1,11 @@
/**
* @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 class GeneratedFile {
constructor(public srcFileUrl: string, public genFileUrl: string, public source: string) {}
}

View File

@ -0,0 +1,59 @@
/**
* @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 {ɵGetterFn, ɵMethodFn, ɵReflectionCapabilities, ɵSetterFn, ɵreflector} from '@angular/core';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) {
ɵreflector.updateCapabilities(new StaticAndDynamicReflectionCapabilities(staticDelegate));
}
private dynamicDelegate = new ɵReflectionCapabilities();
constructor(private staticDelegate: StaticReflector) {}
isReflectionEnabled(): boolean { return true; }
factory(type: any): Function { return this.dynamicDelegate.factory(type); }
hasLifecycleHook(type: any, lcProperty: string): boolean {
return isStaticType(type) ? this.staticDelegate.hasLifecycleHook(type, lcProperty) :
this.dynamicDelegate.hasLifecycleHook(type, lcProperty);
}
parameters(type: any): any[][] {
return isStaticType(type) ? this.staticDelegate.parameters(type) :
this.dynamicDelegate.parameters(type);
}
annotations(type: any): any[] {
return isStaticType(type) ? this.staticDelegate.annotations(type) :
this.dynamicDelegate.annotations(type);
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
return isStaticType(typeOrFunc) ? this.staticDelegate.propMetadata(typeOrFunc) :
this.dynamicDelegate.propMetadata(typeOrFunc);
}
getter(name: string): ɵGetterFn { return this.dynamicDelegate.getter(name); }
setter(name: string): ɵSetterFn { return this.dynamicDelegate.setter(name); }
method(name: string): ɵMethodFn { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); }
resolveIdentifier(name: string, moduleUrl: string, members: string[], runtime: any) {
return this.staticDelegate.resolveIdentifier(name, moduleUrl, members);
}
resolveEnum(enumIdentifier: any, name: string): any {
if (isStaticType(enumIdentifier)) {
return this.staticDelegate.resolveEnum(enumIdentifier, name);
} else {
return null;
}
}
}
function isStaticType(type: any): boolean {
return typeof type === 'object' && type.name && type.filePath;
}

View File

@ -0,0 +1,700 @@
/**
* @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 {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, ɵReflectorReader} from '@angular/core';
import {syntaxError} from '../util';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_CORE = '@angular/core';
const HIDDEN_KEY = /^\$.*\$$/;
const IGNORE = {
__symbolic: 'ignore'
};
function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore';
}
/**
* A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically.
*/
export class StaticReflector implements ɵReflectorReader {
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol;
constructor(
private symbolResolver: StaticSymbolResolver,
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName: string) => void) {
this.initializeConversionMap();
knownMetadataClasses.forEach(
(kc) => this._registerDecoratorOrConstructor(
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
knownMetadataFunctions.forEach(
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
}
importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
return staticSymbol ? staticSymbol.filePath : null;
}
resolveIdentifier(name: string, moduleUrl: string, members: string[]): StaticSymbol {
const importSymbol = this.getStaticSymbol(moduleUrl, name);
const rootSymbol = this.findDeclaration(moduleUrl, name);
if (importSymbol != rootSymbol) {
this.symbolResolver.recordImportAs(rootSymbol, importSymbol);
}
if (members && members.length) {
return this.getStaticSymbol(rootSymbol.filePath, rootSymbol.name, members);
}
return rootSymbol;
}
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 {
const staticSymbol: StaticSymbol = enumIdentifier;
const members = (staticSymbol.members || []).concat(name);
return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, members);
}
public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type);
if (!annotations) {
annotations = [];
const classMetadata = this.getTypeMetadata(type);
if (classMetadata['extends']) {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType && (parentType instanceof StaticSymbol)) {
const parentAnnotations = this.annotations(parentType);
annotations.push(...parentAnnotations);
}
}
if (classMetadata['decorators']) {
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
annotations.push(...ownAnnotations);
}
this.annotationCache.set(type, annotations.filter(ann => !!ann));
}
return annotations;
}
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type);
propMetadata = {};
if (classMetadata['extends']) {
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
const parentPropMetadata = this.propMetadata(parentType);
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata[parentProp] = parentPropMetadata[parentProp];
});
}
}
const members = classMetadata['members'] || {};
Object.keys(members).forEach((propName) => {
const propData = members[propName];
const prop = (<any[]>propData)
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
const decorators: any[] = [];
if (propMetadata[propName]) {
decorators.push(...propMetadata[propName]);
}
propMetadata[propName] = decorators;
if (prop && prop['decorators']) {
decorators.push(...this.simplify(type, prop['decorators']));
}
});
this.propertyCache.set(type, propMetadata);
}
return propMetadata;
}
public parameters(type: StaticSymbol): any[] {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`),
type);
return [];
}
try {
let parameters = this.parameterCache.get(type);
if (!parameters) {
const classMetadata = this.getTypeMetadata(type);
const members = classMetadata ? classMetadata['members'] : null;
const ctorData = members ? members['__ctor__'] : null;
if (ctorData) {
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = [];
parameterTypes.forEach((paramType, index) => {
const nestedResult: any[] = [];
if (paramType) {
nestedResult.push(paramType);
}
const decorators = parameterDecorators ? parameterDecorators[index] : null;
if (decorators) {
nestedResult.push(...decorators);
}
parameters.push(nestedResult);
});
} else if (classMetadata['extends']) {
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
parameters = this.parameters(parentType);
}
}
if (!parameters) {
parameters = [];
}
this.parameterCache.set(type, parameters);
}
return parameters;
} catch (e) {
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
}
}
private _methodNames(type: any): {[key: string]: boolean} {
let methodNames = this.methodCache.get(type);
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type);
methodNames = {};
if (classMetadata['extends']) {
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
const parentMethodNames = this._methodNames(parentType);
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames[parentProp] = parentMethodNames[parentProp];
});
}
}
const members = classMetadata['members'] || {};
Object.keys(members).forEach((propName) => {
const propData = members[propName];
const isMethod = (<any[]>propData).some(a => a['__symbolic'] == 'method');
methodNames[propName] = methodNames[propName] || isMethod;
});
this.methodCache.set(type, methodNames);
}
return methodNames;
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`),
type);
}
try {
return !!this._methodNames(type)[lcProperty];
} catch (e) {
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
}
}
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
}
private _registerFunction(type: StaticSymbol, fn: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
}
private initializeConversionMap(): void {
this.injectionToken = this.findDeclaration(ANGULAR_CORE, 'InjectionToken');
this.opaqueToken = this.findDeclaration(ANGULAR_CORE, 'OpaqueToken');
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), Host);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Injectable'), Injectable);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), Self);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), SkipSelf);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Inject'), Inject);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Optional'), Optional);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Attribute'), Attribute);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ContentChild'), ContentChild);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ContentChildren'), ContentChildren);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ViewChild'), ViewChild);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'ViewChildren'), ViewChildren);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Input'), Input);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Output'), Output);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Pipe'), Pipe);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'HostBinding'), HostBinding);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'HostListener'), HostListener);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Directive'), Directive);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'Component'), Component);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'NgModule'), NgModule);
// Note: Some metadata classes can be used directly with Provider.deps.
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), Host);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), Self);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), SkipSelf);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Optional'), Optional);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'trigger'), trigger);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'state'), state);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'transition'), transition);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'style'), style);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'animate'), animate);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'keyframes'), keyframes);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'sequence'), sequence);
this._registerFunction(this.findDeclaration(ANGULAR_CORE, '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 {
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
if (this.errorRecorder) {
this.errorRecorder(error, (context && context.filePath) || path);
} else {
throw error;
}
}
/**
* Simplify but discard any errors
*/
private trySimplify(context: StaticSymbol, value: any): any {
const originalRecorder = this.errorRecorder;
this.errorRecorder = (error: any, fileName: string) => {};
const result = this.simplify(context, value);
this.errorRecorder = originalRecorder;
return result;
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
const self = this;
let scope = BindingScope.empty;
const calling = new Map<StaticSymbol, boolean>();
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
return resolvedSymbol ? resolvedSymbol.metadata : null;
}
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
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')) {
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
args = args.map(arg => simplifyInContext(context, arg, depth + 1))
.map(arg => shouldIgnore(arg) ? undefined : arg);
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;
}
} finally {
calling.delete(functionSymbol);
}
}
if (depth === 0) {
// If depth is 0 we are evaluating the top level expression that is describing element
// decorator. In this case, it is a decorator we don't understand, such as a custom
// non-angular decorator, and we should just ignore it.
return IGNORE;
}
return simplify(
{__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
}
function simplify(expression: any): any {
if (isPrimitive(expression)) {
return expression;
}
if (expression instanceof Array) {
const result: any[] = [];
for (const item of (<any>expression)) {
// Check for a spread expression
if (item && item.__symbolic === 'spread') {
const spreadArray = simplify(item.expression);
if (Array.isArray(spreadArray)) {
for (const spreadItem of spreadArray) {
result.push(spreadItem);
}
continue;
}
}
const value = simplify(item);
if (shouldIgnore(value)) {
continue;
}
result.push(value);
}
return result;
}
if (expression instanceof StaticSymbol) {
// Stop simplification at builtin symbols
if (expression === self.injectionToken || 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']) {
let staticSymbol: StaticSymbol;
switch (expression['__symbolic']) {
case 'binop':
let left = simplify(expression['left']);
if (shouldIgnore(left)) return left;
let right = simplify(expression['right']);
if (shouldIgnore(right)) return right;
switch (expression['operator']) {
case '&&':
return left && right;
case '||':
return left || right;
case '|':
return left | right;
case '^':
return left ^ right;
case '&':
return left & right;
case '==':
return left == right;
case '!=':
return left != right;
case '===':
return left === right;
case '!==':
return left !== right;
case '<':
return left < right;
case '>':
return left > right;
case '<=':
return left <= right;
case '>=':
return left >= right;
case '<<':
return left << right;
case '>>':
return left >> right;
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
case '%':
return left % right;
}
return null;
case 'if':
let condition = simplify(expression['condition']);
return condition ? simplify(expression['thenExpression']) :
simplify(expression['elseExpression']);
case 'pre':
let operand = simplify(expression['operand']);
if (shouldIgnore(operand)) return operand;
switch (expression['operator']) {
case '+':
return operand;
case '-':
return -operand;
case '!':
return !operand;
case '~':
return ~operand;
}
return null;
case 'index':
let indexTarget = simplify(expression['expression']);
let index = simplify(expression['index']);
if (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) {
const members = selectTarget.members.concat(member);
selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
const declarationValue = resolveReferenceValue(selectContext);
if (declarationValue) {
return simplifyInContext(selectContext, declarationValue, depth + 1);
} else {
return selectContext;
}
}
if (selectTarget && isPrimitive(member))
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
return null;
case 'reference':
// 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;
}
break;
case 'class':
return context;
case 'function':
return context;
case 'new':
case 'call':
// Determine if the function is a built-in conversion
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
if (staticSymbol instanceof StaticSymbol) {
if (staticSymbol === self.injectionToken || staticSymbol === self.opaqueToken) {
// if somebody calls new InjectionToken, don't create an InjectionToken,
// but rather return the symbol to which the InjectionToken is assigned to.
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1))
.map(arg => shouldIgnore(arg) ? undefined : arg);
return converter(context, args);
} else {
// Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, argExpressions);
}
}
break;
case 'error':
let message = produceErrorMessage(expression);
if (expression['line']) {
message =
`${message} (position ${expression['line']+1}:${expression['character']+1} in the original .ts file)`;
self.reportError(
positionalError(
message, context.filePath, expression['line'], expression['character']),
context);
} else {
self.reportError(new Error(message), context);
}
return IGNORE;
case 'ignore':
return expression;
}
return null;
}
return mapStringMap(expression, (value, name) => simplify(value));
}
return IGNORE;
}
try {
return simplify(value);
} catch (e) {
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);
}
throw syntaxError(message);
}
}
const recordedSimplifyInContext = (context: StaticSymbol, value: any, depth: number) => {
try {
return simplifyInContext(context, value, depth);
} catch (e) {
this.reportError(e, context);
}
};
const result = this.errorRecorder ? recordedSimplifyInContext(context, value, 0) :
simplifyInContext(context, value, 0);
if (shouldIgnore(result)) {
return undefined;
}
return result;
}
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
{__symbolic: 'class'};
}
}
function expandedMessage(error: any): string {
switch (error.message) {
case 'Reference to non-exported class':
if (error.context && error.context.className) {
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
}
break;
case 'Variable not initialized':
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
case 'Destructuring not supported':
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
case 'Could not resolve type':
if (error.context && error.context.typeName) {
return `Could not resolve type ${error.context.typeName}`;
}
break;
case 'Function call not supported':
let prefix =
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
return prefix +
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
case 'Reference to a local symbol':
if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
}
break;
}
return error.message;
}
function produceErrorMessage(error: any): string {
return `Error encountered resolving symbol values statically. ${expandedMessage(error)}`;
}
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
{[key: string]: any} {
if (!input) return {};
const result: {[key: string]: any} = {};
Object.keys(input).forEach((key) => {
const value = transform(input[key], key);
if (!shouldIgnore(value)) {
if (HIDDEN_KEY.test(key)) {
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
} else {
result[key] = value;
}
}
});
return result;
}
function isPrimitive(o: any): boolean {
return o === null || (typeof o !== 'function' && typeof o !== 'object');
}
interface BindingScopeBuilder {
define(name: string, value: any): BindingScopeBuilder;
done(): BindingScope;
}
abstract class BindingScope {
abstract resolve(name: string): any;
public static missing = {};
public static empty: BindingScope = {resolve: name => BindingScope.missing};
public static build(): BindingScopeBuilder {
const current = new Map<string, any>();
return {
define: function(name, value) {
current.set(name, value);
return this;
},
done: function() {
return current.size > 0 ? new PopulatedScope(current) : BindingScope.empty;
}
};
}
}
class PopulatedScope extends BindingScope {
constructor(private bindings: Map<string, any>) { super(); }
resolve(name: string): any {
return this.bindings.has(name) ? this.bindings.get(name) : BindingScope.missing;
}
}
function positionalError(message: string, fileName: string, line: number, column: number): Error {
const result = new Error(message);
(result as any).fileName = fileName;
(result as any).line = line;
(result as any).column = column;
return result;
}

View File

@ -0,0 +1,43 @@
/**
* @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
*/
/**
* A token representing the a reference to a static type.
*
* This token is unique for a filePath and name and can be used as a hash table key.
*/
export class StaticSymbol {
constructor(public filePath: string, public name: string, public members: string[]) {}
assertNoMembers() {
if (this.members.length) {
throw new Error(
`Illegal state: symbol without members expected, but got ${JSON.stringify(this)}.`);
}
}
}
/**
* 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,391 @@
/**
* @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';
import {isNgFactoryFile} from './util';
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 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.
*
* Internally, it only uses symbols without members,
* and deduces the values for symbols with members based
* on these symbols.
*/
export class StaticSymbolResolver {
private metadataCache = new Map<string, {[key: string]: any}>();
// Note: this will only contain StaticSymbols without members!
private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
private resolvedFilePaths = new Set<string>();
// Note: this will only contain StaticSymbols without members!
private importAs = new Map<StaticSymbol, StaticSymbol>();
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.resolvedSymbols.get(staticSymbol);
if (result) {
return result;
}
result = this._resolveSymbolFromSummary(staticSymbol);
if (result) {
return 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;
}
/**
* getImportAs produces a symbol that can be used to import the given symbol.
* The import might be different than the symbol if the symbol is exported from
* a library with a summary; in which case we want to import the symbol from the
* ngfactory re-export instead of directly to avoid introducing a direct dependency
* on an otherwise indirect dependency.
*
* @param staticSymbol the symbol for which to generate a import symbol
*/
getImportAs(staticSymbol: StaticSymbol): StaticSymbol {
if (staticSymbol.members.length) {
const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name);
const baseImportAs = this.getImportAs(baseSymbol);
return baseImportAs ?
this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) :
null;
}
let result = this.summaryResolver.getImportAs(staticSymbol);
if (!result) {
result = this.importAs.get(staticSymbol);
}
return result;
}
/**
* getTypeArity returns the number of generic type parameters the given symbol
* has. If the symbol is not a type the result is null.
*/
getTypeArity(staticSymbol: StaticSymbol): number /*|null*/ {
// If the file is a factory file, don't resolve the symbol as doing so would
// cause the metadata for an factory file to be loaded which doesn't exist.
// All references to generated classes must include the correct arity whenever
// generating code.
if (isNgFactoryFile(staticSymbol.filePath)) {
return null;
}
let resolvedSymbol = this.resolveSymbol(staticSymbol);
while (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
resolvedSymbol = this.resolveSymbol(resolvedSymbol.metadata);
}
return (resolvedSymbol && resolvedSymbol.metadata && resolvedSymbol.metadata.arity) || null;
}
recordImportAs(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol) {
sourceSymbol.assertNoMembers();
targetSymbol.assertNoMembers();
this.importAs.set(sourceSymbol, targetSymbol);
}
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 (let 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.
* @param members a symbol for a static member of the named 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
const topLevelSymbolNames =
new Set<string>(Object.keys(metadata['metadata']).map(unescapeIdentifier));
Object.keys(metadata['metadata']).forEach((metadataKey) => {
const symbolMeta = metadata['metadata'][metadataKey];
const name = unescapeIdentifier(metadataKey);
const canonicalSymbol = this.getStaticSymbol(filePath, name);
if (metadata['importAs']) {
// Index bundle indexes should use the importAs module name instead of a reference
// to the .d.ts file directly.
const importSymbol = this.getStaticSymbol(metadata['importAs'], name);
this.recordImportAs(canonicalSymbol, importSymbol);
}
resolvedSymbols.push(
this.createResolvedSymbol(canonicalSymbol, topLevelSymbolNames, 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;
}
symbolName = unescapeIdentifier(symbolName);
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = unescapeIdentifier(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(this.createExport(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(this.createExport(sourceSymbol, targetSymbol));
});
}
}
}
}
resolvedSymbols.forEach(
(resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
}
private createResolvedSymbol(
sourceSymbol: StaticSymbol, topLevelSymbolNames: Set<string>,
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'] ? unescapeIdentifier(map['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}.`
};
}
return self.getStaticSymbol(filePath, name);
} else if (functionParams.indexOf(name) >= 0) {
// reference to a function parameter
return {__symbolic: 'reference', name: name};
} else {
if (topLevelSymbolNames.has(name)) {
return self.getStaticSymbol(sourceSymbol.filePath, name);
}
// ambient value
null;
}
} else {
return super.visitStringMap(map, functionParams);
}
}
}
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
if (transformedMeta instanceof StaticSymbol) {
return this.createExport(sourceSymbol, transformedMeta);
}
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
}
private createExport(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol):
ResolvedStaticSymbol {
sourceSymbol.assertNoMembers();
targetSymbol.assertNoMembers();
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath)) {
// This case is for an ng library importing symbols from a plain ts library
// transitively.
// Note: We rely on the fact that we discover symbols in the direction
// from source files to library files
this.importAs.set(targetSymbol, this.getImportAs(sourceSymbol) || sourceSymbol);
}
return new ResolvedStaticSymbol(sourceSymbol, targetSymbol);
}
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) {
this.reportError(
new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ {
containingFile
} `: ''}`),
null);
return this.getStaticSymbol(`ERROR:${module}`, symbolName);
}
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(e, null, containingFile);
}
}
}
// Remove extra underscore from escaped identifier.
// See https://github.com/Microsoft/TypeScript/blob/master/src/compiler/utilities.ts
export function unescapeIdentifier(identifier: string): string {
return identifier.startsWith('___') ? identifier.substr(1) : identifier;
}

View File

@ -0,0 +1,96 @@
/**
* @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 {Summary, SummaryResolver} from '../summary_resolver';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {deserializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, stripNgFactory, summaryFileName} from './util';
export interface AotSummaryResolverHost {
/**
* Loads an NgModule/Directive/Pipe summary file
*/
loadSummary(filePath: string): string /*|null*/;
/**
* Returns whether a file is a source file or not.
*/
isSourceFile(sourceFilePath: string): boolean;
/**
* Returns the output file path of a source file.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
*/
getOutputFileName(sourceFilePath: string): string;
}
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
// Note: this will only contain StaticSymbols without members!
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
private loadedFilePaths = new Set<string>();
// Note: this will only contain StaticSymbols without members!
private importAs = new Map<StaticSymbol, StaticSymbol>();
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
isLibraryFile(filePath: string): boolean {
// Note: We need to strip the .ngfactory. file path,
// so this method also works for generated files
// (for which host.isSourceFile will always return false).
return !this.host.isSourceFile(stripNgFactory(filePath));
}
getLibraryFileName(filePath: string) { return this.host.getOutputFileName(filePath); }
resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
staticSymbol.assertNoMembers();
let summary = this.summaryCache.get(staticSymbol);
if (!summary) {
this._loadSummaryFile(staticSymbol.filePath);
summary = this.summaryCache.get(staticSymbol);
}
return summary;
}
getSymbolsOf(filePath: string): StaticSymbol[] {
this._loadSummaryFile(filePath);
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
}
getImportAs(staticSymbol: StaticSymbol): StaticSymbol {
staticSymbol.assertNoMembers();
return this.importAs.get(staticSymbol);
}
private _loadSummaryFile(filePath: string) {
if (this.loadedFilePaths.has(filePath)) {
return;
}
this.loadedFilePaths.add(filePath);
if (this.isLibraryFile(filePath)) {
const summaryFilePath = summaryFileName(filePath);
let json: string;
try {
json = this.host.loadSummary(summaryFilePath);
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
if (json) {
const {summaries, importAs} = deserializeSummaries(this.staticSymbolCache, json);
summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary));
importAs.forEach((importAs) => {
this.importAs.set(
importAs.symbol,
this.staticSymbolCache.get(ngfactoryFilePath(filePath), importAs.importAs));
});
}
}
}
}

View File

@ -0,0 +1,192 @@
/**
* @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 {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
export function serializeSummaries(
summaryResolver: SummaryResolver<StaticSymbol>, symbolResolver: StaticSymbolResolver,
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]):
{json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
const serializer = new Serializer(symbolResolver, summaryResolver);
// 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 (summaryResolver.isLibraryFile(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 (summaryResolver.isLibraryFile(symbol.filePath)) {
const summary = summaryResolver.resolveSummary(symbol);
if (summary) {
serializer.addOrMergeSummary(summary);
}
}
});
}
});
return serializer.serialize();
}
export function deserializeSummaries(symbolCache: StaticSymbolCache, json: string):
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} {
const deserializer = new Deserializer(symbolCache);
return deserializer.deserialize(json);
}
class Serializer extends ValueTransformer {
// Note: This only contains symbols without members.
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 symbolResolver: StaticSymbolResolver,
private summaryResolver: SummaryResolver<StaticSymbol>) {
super();
}
addOrMergeSummary(summary: Summary<StaticSymbol>) {
let symbolMeta = summary.metadata;
if (symbolMeta && symbolMeta.__symbolic === 'class') {
// For classes, we only keep their statics and arity, 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, arity: symbolMeta.arity};
}
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: == on purpose to compare with undefined!
if (processedSummary.metadata == null && symbolMeta != null) {
processedSummary.metadata = this.processValue(symbolMeta);
}
// Note: == on purpose to compare with undefined!
if (processedSummary.type == null && summary.type != null) {
processedSummary.type = this.processValue(summary.type);
}
}
serialize(): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
const exportAs: {symbol: StaticSymbol, exportAs: string}[] = [];
const json = JSON.stringify({
summaries: this.processedSummaries,
symbols: this.symbols.map((symbol, index) => {
symbol.assertNoMembers();
let importAs: string;
if (this.summaryResolver.isLibraryFile(symbol.filePath)) {
importAs = `${symbol.name}_${index}`;
exportAs.push({symbol, exportAs: importAs});
}
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.summaryResolver.getLibraryFileName(symbol.filePath),
importAs: importAs
};
})
});
return {json, exportAs};
}
private processValue(value: any): any { return visitValue(value, this, null); }
visitOther(value: any, context: any): any {
if (value instanceof StaticSymbol) {
const baseSymbol = this.symbolResolver.getStaticSymbol(value.filePath, value.name);
let index = this.indexBySymbol.get(baseSymbol);
// Note: == on purpose to compare with undefined!
if (index == null) {
index = this.indexBySymbol.size;
this.indexBySymbol.set(baseSymbol, index);
this.symbols.push(baseSymbol);
}
return {__symbol: index, members: value.members};
}
}
}
class Deserializer extends ValueTransformer {
private symbols: StaticSymbol[];
constructor(private symbolCache: StaticSymbolCache) { super(); }
deserialize(json: string):
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} {
const data: {summaries: any[], symbols: any[]} = JSON.parse(json);
const importAs: {symbol: StaticSymbol, importAs: string}[] = [];
this.symbols = [];
data.symbols.forEach((serializedSymbol) => {
const symbol = this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name);
this.symbols.push(symbol);
if (serializedSymbol.importAs) {
importAs.push({symbol: symbol, importAs: serializedSymbol.importAs});
}
});
const summaries = visitValue(data.summaries, this, null);
return {summaries, importAs};
}
visitStringMap(map: {[key: string]: any}, context: any): any {
if ('__symbol' in map) {
const baseSymbol = this.symbols[map['__symbol']];
const members = map['members'];
return members.length ? this.symbolCache.get(baseSymbol.filePath, baseSymbol.name, members) :
baseSymbol;
} else {
return super.visitStringMap(map, context);
}
}
}

View File

@ -0,0 +1,42 @@
/**
* @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
*/
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const NG_FACTORY = /\.ngfactory\./;
export function ngfactoryFilePath(filePath: string): string {
const urlWithSuffix = splitTypescriptSuffix(filePath);
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
}
export function stripNgFactory(filePath: string): string {
return filePath.replace(NG_FACTORY, '.');
}
export function isNgFactoryFile(filePath: string): boolean {
return NG_FACTORY.test(filePath);
}
export function splitTypescriptSuffix(path: string): string[] {
if (path.endsWith('.d.ts')) {
return [path.slice(0, -5), '.ts'];
}
const lastDot = path.lastIndexOf('.');
if (lastDot !== -1) {
return [path.substring(0, lastDot), path.substring(lastDot)];
}
return [path, ''];
}
export function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}