angular-cn/packages/compiler/src/metadata_resolver.ts

1235 lines
46 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
import {ngfactoryFilePath} from './aot/util';
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {CompilerConfig} from './config';
import {ChangeDetectionStrategy, Component, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createNgModule, createOptional, createSelf, createSkipSelf} from './core';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver, findLast} from './directive_resolver';
import {Identifiers} from './identifiers';
import {getAllLifecycleHooks} from './lifecycle_reflector';
import {HtmlParser} from './ml_parser/html_parser';
import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {CssSelector} from './selector';
import {SummaryResolver} from './summary_resolver';
import {Console, SyncAsync, ValueTransformer, isPromise, noUndefined, resolveForwardRef, stringify, syntaxError, visitValue} from './util';
export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COMPONENT_TYPE = 'ngComponentType';
// Design notes:
// - don't lazily create metadata:
// For some metadata, we need to do async work sometimes,
// so the user has to kick off this loading.
// But we want to report errors even when the async work is
// not required to check that the user would have been able
// to wait correctly.
export class CompileMetadataResolver {
private _nonNormalizedDirectiveCache =
new Map<Type, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>();
private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>();
private _summaryCache = new Map<Type, cpl.CompileTypeSummary|null>();
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>();
private _ngModuleOfTypes = new Map<Type, Type>();
private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>();
constructor(
private _config: CompilerConfig, private _htmlParser: HtmlParser,
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer, private _console: Console,
private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector,
private _errorCollector?: ErrorCollector) {}
getReflector(): CompileReflector { return this._reflector; }
clearCacheFor(type: Type) {
const dirMeta = this._directiveCache.get(type);
this._directiveCache.delete(type);
this._nonNormalizedDirectiveCache.delete(type);
this._summaryCache.delete(type);
this._pipeCache.delete(type);
this._ngModuleOfTypes.delete(type);
// Clear all of the NgModule as they contain transitive information!
this._ngModuleCache.clear();
if (dirMeta) {
this._directiveNormalizer.clearCacheFor(dirMeta);
}
}
clearCache(): void {
this._directiveCache.clear();
this._nonNormalizedDirectiveCache.clear();
this._summaryCache.clear();
this._pipeCache.clear();
this._ngModuleCache.clear();
this._ngModuleOfTypes.clear();
this._directiveNormalizer.clearCache();
}
private _createProxyClass(baseType: any, name: string): cpl.ProxyClass {
let delegate: any = null;
const proxyClass: cpl.ProxyClass = <any>function() {
if (!delegate) {
throw new Error(
`Illegal state: Class ${name} for type ${stringify(baseType)} is not compiled yet!`);
}
return delegate.apply(this, arguments);
};
proxyClass.setDelegate = (d) => {
delegate = d;
(<any>proxyClass).prototype = d.prototype;
};
// Make stringify work correctly
(<any>proxyClass).overriddenName = name;
return proxyClass;
}
private getGeneratedClass(dirType: any, name: string): StaticSymbol|cpl.ProxyClass {
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(ngfactoryFilePath(dirType.filePath), name);
} else {
return this._createProxyClass(dirType, name);
}
}
private getComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass {
return this.getGeneratedClass(dirType, cpl.viewClassName(dirType, 0));
}
getHostComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass {
return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType));
}
getHostComponentType(dirType: any): StaticSymbol|Type {
const name = `${cpl.identifierName({reference: dirType})}_Host`;
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(dirType.filePath, name);
} else {
const HostClass = <any>function HostClass() {};
HostClass.overriddenName = name;
return HostClass;
}
}
private getRendererType(dirType: any): StaticSymbol|object {
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(
ngfactoryFilePath(dirType.filePath), cpl.rendererTypeName(dirType));
} else {
// returning an object as proxy,
// that we fill later during runtime compilation.
return <any>{};
}
}
private getComponentFactory(
selector: string, dirType: any, inputs: {[key: string]: string}|null,
outputs: {[key: string]: string}): StaticSymbol|object {
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(
ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType));
} else {
const hostView = this.getHostComponentViewClass(dirType);
// Note: ngContentSelectors will be filled later once the template is
// loaded.
const createComponentFactory =
this._reflector.resolveExternalReference(Identifiers.createComponentFactory);
return createComponentFactory(selector, dirType, <any>hostView, inputs, outputs, []);
}
}
private initComponentFactory(factory: StaticSymbol|object, ngContentSelectors: string[]) {
if (!(factory instanceof StaticSymbol)) {
(factory as any).ngContentSelectors.push(...ngContentSelectors);
}
}
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary|null {
let typeSummary = this._summaryCache.get(type);
if (!typeSummary) {
const summary = this._summaryResolver.resolveSummary(type);
typeSummary = summary ? summary.type : null;
this._summaryCache.set(type, typeSummary || null);
}
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}
getHostComponentMetadata(
compMeta: cpl.CompileDirectiveMetadata,
hostViewType?: StaticSymbol|cpl.ProxyClass): cpl.CompileDirectiveMetadata {
const hostType = this.getHostComponentType(compMeta.type.reference);
if (!hostViewType) {
hostViewType = this.getHostComponentViewClass(hostType);
}
// Note: ! is ok here as this method should only be called with normalized directive
// metadata, which always fills in the selector.
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
const templateUrl = '';
const htmlAst = this._htmlParser.parse(template, templateUrl);
return cpl.CompileDirectiveMetadata.create({
isHost: true,
type: {reference: hostType, diDeps: [], lifecycleHooks: []},
template: new cpl.CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template,
templateUrl,
htmlAst,
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: [],
isInline: true,
externalStylesheets: [],
interpolation: null,
preserveWhitespaces: false,
}),
exportAs: null,
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
guards: {},
viewQueries: [],
componentViewType: hostViewType,
rendererType:
{id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}} as object,
entryComponents: [],
componentFactory: null
});
}
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
if (this._directiveCache.has(directiveType)) {
return null;
}
directiveType = resolveForwardRef(directiveType);
const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType) !;
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata | null) => {
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
isHost: false,
type: metadata.type,
isComponent: metadata.isComponent,
selector: metadata.selector,
exportAs: metadata.exportAs,
changeDetection: metadata.changeDetection,
inputs: metadata.inputs,
outputs: metadata.outputs,
hostListeners: metadata.hostListeners,
hostProperties: metadata.hostProperties,
hostAttributes: metadata.hostAttributes,
providers: metadata.providers,
viewProviders: metadata.viewProviders,
queries: metadata.queries,
guards: metadata.guards,
viewQueries: metadata.viewQueries,
entryComponents: metadata.entryComponents,
componentViewType: metadata.componentViewType,
rendererType: metadata.rendererType,
componentFactory: metadata.componentFactory,
template: templateMetadata
});
if (templateMetadata) {
this.initComponentFactory(metadata.componentFactory !, templateMetadata.ngContentSelectors);
}
this._directiveCache.set(directiveType, normalizedDirMeta);
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
return null;
};
if (metadata.isComponent) {
const template = metadata.template !;
const templateMeta = this._directiveNormalizer.normalizeTemplate({
ngModuleType,
componentType: directiveType,
moduleUrl: this._reflector.componentModuleUrl(directiveType, annotation),
encapsulation: template.encapsulation,
template: template.template,
templateUrl: template.templateUrl,
styles: template.styles,
styleUrls: template.styleUrls,
animations: template.animations,
interpolation: template.interpolation,
preserveWhitespaces: template.preserveWhitespaces
});
if (isPromise(templateMeta) && isSync) {
this._reportError(componentStillLoadingError(directiveType), directiveType);
return null;
}
return SyncAsync.then(templateMeta, createDirectiveMetadata);
} else {
// directive
createDirectiveMetadata(null);
return null;
}
}
getNonNormalizedDirectiveMetadata(directiveType: any):
{annotation: Directive, metadata: cpl.CompileDirectiveMetadata}|null {
directiveType = resolveForwardRef(directiveType);
if (!directiveType) {
return null;
}
let cacheEntry = this._nonNormalizedDirectiveCache.get(directiveType);
if (cacheEntry) {
return cacheEntry;
}
const dirMeta = this._directiveResolver.resolve(directiveType, false);
if (!dirMeta) {
return null;
}
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata = undefined !;
if (createComponent.isTypeOf(dirMeta)) {
// component
const compMeta = dirMeta as Component;
assertArrayOfStrings('styles', compMeta.styles);
assertArrayOfStrings('styleUrls', compMeta.styleUrls);
assertInterpolationSymbols('interpolation', compMeta.interpolation);
const animations = compMeta.animations;
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
encapsulation: noUndefined(compMeta.encapsulation),
template: noUndefined(compMeta.template),
templateUrl: noUndefined(compMeta.templateUrl),
htmlAst: null,
styles: compMeta.styles || [],
styleUrls: compMeta.styleUrls || [],
animations: animations || [],
interpolation: noUndefined(compMeta.interpolation),
isInline: !!compMeta.template,
externalStylesheets: [],
ngContentSelectors: [],
preserveWhitespaces: noUndefined(dirMeta.preserveWhitespaces),
});
}
let changeDetectionStrategy: ChangeDetectionStrategy = null !;
let viewProviders: cpl.CompileProviderMetadata[] = [];
let entryComponentMetadata: cpl.CompileEntryComponentMetadata[] = [];
let selector = dirMeta.selector;
if (createComponent.isTypeOf(dirMeta)) {
// Component
const compMeta = dirMeta as Component;
changeDetectionStrategy = compMeta.changeDetection !;
if (compMeta.viewProviders) {
viewProviders = this._getProvidersMetadata(
compMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringifyType(directiveType)}"`, [], directiveType);
}
if (compMeta.entryComponents) {
entryComponentMetadata = flattenAndDedupeArray(compMeta.entryComponents)
.map((type) => this._getEntryComponentMetadata(type) !)
.concat(entryComponentMetadata);
}
if (!selector) {
selector = this._schemaRegistry.getDefaultComponentElementName();
}
} else {
// Directive
if (!selector) {
this._reportError(
syntaxError(
`Directive ${stringifyType(directiveType)} has no selector, please add it!`),
directiveType);
selector = 'error';
}
}
let providers: cpl.CompileProviderMetadata[] = [];
if (dirMeta.providers != null) {
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata,
`providers for "${stringifyType(directiveType)}"`, [], directiveType);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
if (dirMeta.queries != null) {
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
}
const metadata = cpl.CompileDirectiveMetadata.create({
isHost: false,
selector: selector,
exportAs: noUndefined(dirMeta.exportAs),
isComponent: !!nonNormalizedTemplateMetadata,
type: this._getTypeMetadata(directiveType),
template: nonNormalizedTemplateMetadata,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs || [],
outputs: dirMeta.outputs || [],
host: dirMeta.host || {},
providers: providers || [],
viewProviders: viewProviders || [],
queries: queries || [],
guards: dirMeta.guards || {},
viewQueries: viewQueries || [],
entryComponents: entryComponentMetadata,
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
null,
rendererType: nonNormalizedTemplateMetadata ? this.getRendererType(directiveType) : null,
componentFactory: null
});
if (nonNormalizedTemplateMetadata) {
metadata.componentFactory =
this.getComponentFactory(selector, directiveType, metadata.inputs, metadata.outputs);
}
cacheEntry = {metadata, annotation: dirMeta};
this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry);
return cacheEntry;
}
/**
* Gets the metadata for the given directive.
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
*/
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
const dirMeta = this._directiveCache.get(directiveType) !;
if (!dirMeta) {
this._reportError(
syntaxError(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
directiveType);
}
return dirMeta;
}
getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary {
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (!dirSummary) {
this._reportError(
syntaxError(
`Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`),
dirType);
}
return dirSummary;
}
isDirective(type: any) {
return !!this._loadSummary(type, cpl.CompileSummaryKind.Directive) ||
this._directiveResolver.isDirective(type);
}
isPipe(type: any) {
return !!this._loadSummary(type, cpl.CompileSummaryKind.Pipe) ||
this._pipeResolver.isPipe(type);
}
isNgModule(type: any) {
return !!this._loadSummary(type, cpl.CompileSummaryKind.NgModule) ||
this._ngModuleResolver.isNgModule(type);
}
getNgModuleSummary(moduleType: any, alreadyCollecting: Set<any>|null = null):
cpl.CompileNgModuleSummary|null {
let moduleSummary: cpl.CompileNgModuleSummary|null =
<cpl.CompileNgModuleSummary>this._loadSummary(moduleType, cpl.CompileSummaryKind.NgModule);
if (!moduleSummary) {
const moduleMeta = this.getNgModuleMetadata(moduleType, false, alreadyCollecting);
moduleSummary = moduleMeta ? moduleMeta.toSummary() : null;
if (moduleSummary) {
this._summaryCache.set(moduleType, moduleSummary);
}
}
return moduleSummary;
}
/**
* Loads the declared directives and pipes of an NgModule.
*/
loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
Promise<any> {
const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound);
const loading: Promise<any>[] = [];
if (ngModule) {
ngModule.declaredDirectives.forEach((id) => {
const promise = this.loadDirectiveMetadata(moduleType, id.reference, isSync);
if (promise) {
loading.push(promise);
}
});
ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference));
}
return Promise.all(loading);
}
getShallowModuleMetadata(moduleType: any): cpl.CompileShallowModuleMetadata|null {
let compileMeta = this._shallowModuleCache.get(moduleType);
if (compileMeta) {
return compileMeta;
}
const ngModuleMeta =
findLast(this._reflector.shallowAnnotations(moduleType), createNgModule.isTypeOf);
compileMeta = {
type: this._getTypeMetadata(moduleType),
rawExports: ngModuleMeta.exports,
rawImports: ngModuleMeta.imports,
rawProviders: ngModuleMeta.providers,
};
this._shallowModuleCache.set(moduleType, compileMeta);
return compileMeta;
}
getNgModuleMetadata(
moduleType: any, throwIfNotFound = true,
alreadyCollecting: Set<any>|null = null): cpl.CompileNgModuleMetadata|null {
moduleType = resolveForwardRef(moduleType);
2016-09-14 18:58:18 -04:00
let compileMeta = this._ngModuleCache.get(moduleType);
if (compileMeta) {
return compileMeta;
}
const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound);
if (!meta) {
return null;
}
const declaredDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedNonModuleIdentifiers: cpl.CompileIdentifierMetadata[] = [];
const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
const importedModules: cpl.CompileNgModuleSummary[] = [];
const exportedModules: cpl.CompileNgModuleSummary[] = [];
const providers: cpl.CompileProviderMetadata[] = [];
const entryComponents: cpl.CompileEntryComponentMetadata[] = [];
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
const schemas: SchemaMetadata[] = [];
if (meta.imports) {
flattenAndDedupeArray(meta.imports).forEach((importedType) => {
let importedModuleType: Type = undefined !;
if (isValidType(importedType)) {
importedModuleType = importedType;
} else if (importedType && importedType.ngModule) {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
importedType));
}
}
2016-09-14 18:58:18 -04:00
if (importedModuleType) {
if (this._checkSelfImport(moduleType, importedModuleType)) return;
if (!alreadyCollecting) alreadyCollecting = new Set();
if (alreadyCollecting.has(importedModuleType)) {
this._reportError(
syntaxError(
`${this._getTypeDescriptor(importedModuleType)} '${stringifyType(importedType)}' is imported recursively by the module '${stringifyType(moduleType)}'.`),
moduleType);
return;
}
alreadyCollecting.add(importedModuleType);
const importedModuleSummary =
this.getNgModuleSummary(importedModuleType, alreadyCollecting);
alreadyCollecting.delete(importedModuleType);
if (!importedModuleSummary) {
this._reportError(
syntaxError(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'. Please add a @NgModule annotation.`),
moduleType);
return;
}
importedModules.push(importedModuleSummary);
} else {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
});
}
if (meta.exports) {
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(exportedType)}' exported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
if (!alreadyCollecting) alreadyCollecting = new Set();
if (alreadyCollecting.has(exportedType)) {
this._reportError(
syntaxError(
`${this._getTypeDescriptor(exportedType)} '${stringify(exportedType)}' is exported recursively by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
alreadyCollecting.add(exportedType);
const exportedModuleSummary = this.getNgModuleSummary(exportedType, alreadyCollecting);
alreadyCollecting.delete(exportedType);
if (exportedModuleSummary) {
exportedModules.push(exportedModuleSummary);
} else {
exportedNonModuleIdentifiers.push(this._getIdentifierMetadata(exportedType));
}
});
}
// Note: This will be modified later, so we rely on
// getting a new instance every time!
const transitiveModule = this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
if (meta.declarations) {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
const declaredIdentifier = this._getIdentifierMetadata(declaredType);
if (this.isDirective(declaredType)) {
transitiveModule.addDirective(declaredIdentifier);
declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
} else if (this.isPipe(declaredType)) {
transitiveModule.addPipe(declaredIdentifier);
transitiveModule.pipes.push(declaredIdentifier);
declaredPipes.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
} else {
this._reportError(
syntaxError(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'. Please add a @Pipe/@Directive/@Component annotation.`),
moduleType);
return;
}
});
}
const exportedDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedPipes: cpl.CompileIdentifierMetadata[] = [];
exportedNonModuleIdentifiers.forEach((exportedId) => {
if (transitiveModule.directivesSet.has(exportedId.reference)) {
exportedDirectives.push(exportedId);
transitiveModule.addExportedDirective(exportedId);
} else if (transitiveModule.pipesSet.has(exportedId.reference)) {
exportedPipes.push(exportedId);
transitiveModule.addExportedPipe(exportedId);
} else {
this._reportError(
syntaxError(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringifyType(exportedId.reference)} from ${stringifyType(moduleType)} as it was neither declared nor imported!`),
moduleType);
return;
}
});
// The providers of the module have to go last
// so that they overwrite any other provider we already added.
if (meta.providers) {
providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents,
`provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType));
}
2016-09-14 18:58:18 -04:00
if (meta.entryComponents) {
entryComponents.push(...flattenAndDedupeArray(meta.entryComponents)
.map(type => this._getEntryComponentMetadata(type) !));
}
2016-09-14 18:58:18 -04:00
if (meta.bootstrap) {
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
if (!isValidType(type)) {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(type)}' used in the bootstrap property of module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
bootstrapComponents.push(this._getIdentifierMetadata(type));
});
}
2016-09-14 18:58:18 -04:00
entryComponents.push(
...bootstrapComponents.map(type => this._getEntryComponentMetadata(type.reference) !));
2016-09-14 18:58:18 -04:00
if (meta.schemas) {
schemas.push(...flattenAndDedupeArray(meta.schemas));
}
compileMeta = new cpl.CompileNgModuleMetadata({
type: this._getTypeMetadata(moduleType),
providers,
entryComponents,
bootstrapComponents,
schemas,
declaredDirectives,
exportedDirectives,
declaredPipes,
exportedPipes,
importedModules,
exportedModules,
transitiveModule,
id: meta.id || null,
});
2016-09-14 18:58:18 -04:00
entryComponents.forEach((id) => transitiveModule.addEntryComponent(id));
providers.forEach((provider) => transitiveModule.addProvider(provider, compileMeta !.type));
transitiveModule.addModule(compileMeta.type);
this._ngModuleCache.set(moduleType, compileMeta);
return compileMeta;
}
private _checkSelfImport(moduleType: Type, importedModuleType: Type): boolean {
if (moduleType === importedModuleType) {
this._reportError(
syntaxError(`'${stringifyType(moduleType)}' module can't import itself`), moduleType);
return true;
}
return false;
}
private _getTypeDescriptor(type: Type): string {
if (isValidType(type)) {
if (this.isDirective(type)) {
return 'directive';
}
2016-09-14 18:58:18 -04:00
if (this.isPipe(type)) {
return 'pipe';
}
2016-09-14 18:58:18 -04:00
if (this.isNgModule(type)) {
return 'module';
}
2016-09-14 18:58:18 -04:00
}
if ((type as any).provide) {
return 'provider';
}
2016-09-14 18:58:18 -04:00
return 'value';
}
private _addTypeToModule(type: Type, moduleType: Type) {
const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) {
this._reportError(
syntaxError(
`Type ${stringifyType(type)} is part of the declarations of 2 modules: ${stringifyType(oldModule)} and ${stringifyType(moduleType)}! ` +
`Please consider moving ${stringifyType(type)} to a higher module that imports ${stringifyType(oldModule)} and ${stringifyType(moduleType)}. ` +
`You can also create a new NgModule that exports and includes ${stringifyType(type)} then import that NgModule in ${stringifyType(oldModule)} and ${stringifyType(moduleType)}.`),
moduleType);
return;
}
this._ngModuleOfTypes.set(type, moduleType);
}
private _getTransitiveNgModuleMetadata(
importedModules: cpl.CompileNgModuleSummary[],
exportedModules: cpl.CompileNgModuleSummary[]): cpl.TransitiveCompileNgModuleMetadata {
// collect `providers` / `entryComponents` from all imported and all exported modules
const result = new cpl.TransitiveCompileNgModuleMetadata();
const modulesByToken = new Map<any, Set<any>>();
importedModules.concat(exportedModules).forEach((modSummary) => {
modSummary.modules.forEach((mod) => result.addModule(mod));
modSummary.entryComponents.forEach((comp) => result.addEntryComponent(comp));
const addedTokens = new Set<any>();
modSummary.providers.forEach((entry) => {
const tokenRef = cpl.tokenReference(entry.provider.token);
let prevModules = modulesByToken.get(tokenRef);
if (!prevModules) {
prevModules = new Set<any>();
modulesByToken.set(tokenRef, prevModules);
}
const moduleRef = entry.module.reference;
// Note: the providers of one module may still contain multiple providers
// per token (e.g. for multi providers), and we need to preserve these.
if (addedTokens.has(tokenRef) || !prevModules.has(moduleRef)) {
prevModules.add(moduleRef);
addedTokens.add(tokenRef);
result.addProvider(entry.provider, entry.module);
}
});
});
exportedModules.forEach((modSummary) => {
modSummary.exportedDirectives.forEach((id) => result.addExportedDirective(id));
modSummary.exportedPipes.forEach((id) => result.addExportedPipe(id));
});
importedModules.forEach((modSummary) => {
modSummary.exportedDirectives.forEach((id) => result.addDirective(id));
modSummary.exportedPipes.forEach((id) => result.addPipe(id));
});
return result;
}
private _getIdentifierMetadata(type: Type): cpl.CompileIdentifierMetadata {
type = resolveForwardRef(type);
return {reference: type};
}
isInjectable(type: any): boolean {
const annotations = this._reflector.tryAnnotations(type);
return annotations.some(ann => createInjectable.isTypeOf(ann));
}
getInjectableSummary(type: any): cpl.CompileTypeSummary {
return {
summaryKind: cpl.CompileSummaryKind.Injectable,
type: this._getTypeMetadata(type, null, false)
};
}
getInjectableMetadata(
type: any, dependencies: any[]|null = null,
throwOnUnknownDeps: boolean = true): cpl.CompileInjectableMetadata|null {
const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable);
const typeMetadata = typeSummary ?
typeSummary.type :
this._getTypeMetadata(type, dependencies, throwOnUnknownDeps);
const annotations: Injectable[] =
this._reflector.annotations(type).filter(ann => createInjectable.isTypeOf(ann));
if (annotations.length === 0) {
return null;
}
const meta = annotations[annotations.length - 1];
return {
symbol: type,
type: typeMetadata,
providedIn: meta.providedIn,
useValue: meta.useValue,
useClass: meta.useClass,
useExisting: meta.useExisting,
useFactory: meta.useFactory,
deps: meta.deps,
};
}
private _getTypeMetadata(type: Type, dependencies: any[]|null = null, throwOnUnknownDeps = true):
cpl.CompileTypeMetadata {
const identifier = this._getIdentifierMetadata(type);
return {
reference: identifier.reference,
diDeps: this._getDependenciesMetadata(identifier.reference, dependencies, throwOnUnknownDeps),
lifecycleHooks: getAllLifecycleHooks(this._reflector, identifier.reference),
};
}
private _getFactoryMetadata(factory: Function, dependencies: any[]|null = null):
cpl.CompileFactoryMetadata {
factory = resolveForwardRef(factory);
return {reference: factory, diDeps: this._getDependenciesMetadata(factory, dependencies)};
}
/**
* Gets the metadata for the given pipe.
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
*/
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata|null {
const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
this._reportError(
syntaxError(
`Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
pipeType);
}
return pipeMeta || null;
}
2016-09-14 18:58:18 -04:00
getPipeSummary(pipeType: any): cpl.CompilePipeSummary {
const pipeSummary =
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
if (!pipeSummary) {
this._reportError(
syntaxError(
`Illegal state: Could not load the summary for pipe ${stringifyType(pipeType)}.`),
pipeType);
}
return pipeSummary;
}
getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
let pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
pipeMeta = this._loadPipeMetadata(pipeType);
}
return pipeMeta;
}
private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType);
const pipeAnnotation = this._pipeResolver.resolve(pipeType) !;
const pipeMeta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType),
name: pipeAnnotation.name,
pure: !!pipeAnnotation.pure
});
this._pipeCache.set(pipeType, pipeMeta);
this._summaryCache.set(pipeType, pipeMeta.toSummary());
return pipeMeta;
}
private _getDependenciesMetadata(
typeOrFunc: Type|Function, dependencies: any[]|null,
throwOnUnknownDeps = true): cpl.CompileDiDependencyMetadata[] {
let hasUnknownDeps = false;
const params = dependencies || this._reflector.parameters(typeOrFunc) || [];
2016-09-14 18:58:18 -04:00
const dependenciesMetadata: cpl.CompileDiDependencyMetadata[] = params.map((param) => {
let isAttribute = false;
let isHost = false;
let isSelf = false;
let isSkipSelf = false;
let isOptional = false;
let token: any = null;
2016-09-14 18:58:18 -04:00
if (Array.isArray(param)) {
param.forEach((paramEntry) => {
if (createHost.isTypeOf(paramEntry)) {
isHost = true;
} else if (createSelf.isTypeOf(paramEntry)) {
isSelf = true;
} else if (createSkipSelf.isTypeOf(paramEntry)) {
isSkipSelf = true;
} else if (createOptional.isTypeOf(paramEntry)) {
isOptional = true;
} else if (createAttribute.isTypeOf(paramEntry)) {
isAttribute = true;
token = paramEntry.attributeName;
} else if (createInject.isTypeOf(paramEntry)) {
token = paramEntry.token;
} else if (
createInjectionToken.isTypeOf(paramEntry) || paramEntry instanceof StaticSymbol) {
token = paramEntry;
} else if (isValidType(paramEntry) && token == null) {
token = paramEntry;
}
});
} else {
token = param;
}
if (token == null) {
hasUnknownDeps = true;
return null !;
}
2016-09-14 18:58:18 -04:00
return {
2016-09-14 18:58:18 -04:00
isAttribute,
isHost,
isSelf,
isSkipSelf,
isOptional,
token: this._getTokenMetadata(token)
};
});
if (hasUnknownDeps) {
const depsTokens =
dependenciesMetadata.map((dep) => dep ? stringifyType(dep.token) : '?').join(', ');
const message =
`Can't resolve all parameters for ${stringifyType(typeOrFunc)}: (${depsTokens}).`;
if (throwOnUnknownDeps || this._config.strictInjectionParameters) {
this._reportError(syntaxError(message), typeOrFunc);
} else {
this._console.warn(`Warning: ${message} This will become an error in Angular v6.x`);
}
}
return dependenciesMetadata;
}
private _getTokenMetadata(token: any): cpl.CompileTokenMetadata {
token = resolveForwardRef(token);
2016-09-14 18:58:18 -04:00
let compileToken: cpl.CompileTokenMetadata;
2016-10-19 16:42:39 -04:00
if (typeof token === 'string') {
compileToken = {value: token};
} else {
compileToken = {identifier: {reference: token}};
}
return compileToken;
}
private _getProvidersMetadata(
providers: Provider[], targetEntryComponents: cpl.CompileEntryComponentMetadata[],
debugInfo?: string, compileProviders: cpl.CompileProviderMetadata[] = [],
type?: any): cpl.CompileProviderMetadata[] {
providers.forEach((provider: any, providerIdx: number) => {
2016-09-14 18:58:18 -04:00
if (Array.isArray(provider)) {
this._getProvidersMetadata(provider, targetEntryComponents, debugInfo, compileProviders);
} else {
provider = resolveForwardRef(provider);
let providerMeta: cpl.ProviderMeta = undefined !;
if (provider && typeof provider === 'object' && provider.hasOwnProperty('provide')) {
this._validateProvider(provider);
providerMeta = new cpl.ProviderMeta(provider.provide, provider);
} else if (isValidType(provider)) {
providerMeta = new cpl.ProviderMeta(provider, {useClass: provider});
} else if (provider === void 0) {
this._reportError(syntaxError(
`Encountered undefined provider! Usually this means you have a circular dependencies (might be caused by using 'barrel' index.ts files.`));
return;
} else {
const providersInfo =
(<string[]>providers.reduce(
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
if (seenProviderIdx < providerIdx) {
soFar.push(`${stringifyType(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringifyType(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...');
}
return soFar;
},
[]))
.join(', ');
this._reportError(
syntaxError(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
type);
return;
}
if (providerMeta.token ===
this._reflector.resolveExternalReference(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS)) {
targetEntryComponents.push(...this._getEntryComponentsFromProvider(providerMeta, type));
} else {
compileProviders.push(this.getProviderMetadata(providerMeta));
}
}
});
return compileProviders;
}
private _validateProvider(provider: any): void {
if (provider.hasOwnProperty('useClass') && provider.useClass == null) {
this._reportError(syntaxError(
`Invalid provider for ${stringifyType(provider.provide)}. useClass cannot be ${provider.useClass}.
Usually it happens when:
1. There's a circular dependency (might be caused by using index.ts (barrel) files).
2. Class was used before it was declared. Use forwardRef in this case.`));
}
}
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta, type?: any):
cpl.CompileEntryComponentMetadata[] {
const components: cpl.CompileEntryComponentMetadata[] = [];
2016-09-14 18:58:18 -04:00
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
if (provider.useFactory || provider.useExisting || provider.useClass) {
this._reportError(
syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
return [];
}
2016-09-14 18:58:18 -04:00
if (!provider.multi) {
this._reportError(
syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`),
type);
return [];
}
2016-09-14 18:58:18 -04:00
extractIdentifiers(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => {
const entry = this._getEntryComponentMetadata(identifier.reference, false);
if (entry) {
components.push(entry);
}
});
return components;
}
private _getEntryComponentMetadata(dirType: any, throwIfNotFound = true):
cpl.CompileEntryComponentMetadata|null {
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
if (dirMeta && dirMeta.metadata.isComponent) {
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory !};
}
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (dirSummary && dirSummary.isComponent) {
return {componentType: dirType, componentFactory: dirSummary.componentFactory !};
}
if (throwIfNotFound) {
throw syntaxError(`${dirType.name} cannot be used as an entry component.`);
}
return null;
}
private _getInjectableTypeMetadata(type: Type, dependencies: any[]|null = null):
cpl.CompileTypeMetadata {
const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable);
if (typeSummary) {
return typeSummary.type;
}
return this._getTypeMetadata(type, dependencies);
}
getProviderMetadata(provider: cpl.ProviderMeta): cpl.CompileProviderMetadata {
let compileDeps: cpl.CompileDiDependencyMetadata[] = undefined !;
let compileTypeMetadata: cpl.CompileTypeMetadata = null !;
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null !;
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
2016-09-14 18:58:18 -04:00
if (provider.useClass) {
compileTypeMetadata =
this._getInjectableTypeMetadata(provider.useClass, provider.dependencies);
compileDeps = compileTypeMetadata.diDeps;
if (provider.token === provider.useClass) {
// use the compileTypeMetadata as it contains information about lifecycleHooks...
token = {identifier: compileTypeMetadata};
}
2016-09-14 18:58:18 -04:00
} else if (provider.useFactory) {
compileFactoryMetadata = this._getFactoryMetadata(provider.useFactory, provider.dependencies);
compileDeps = compileFactoryMetadata.diDeps;
}
return {
token: token,
useClass: compileTypeMetadata,
useValue: provider.useValue,
useFactory: compileFactoryMetadata,
useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : undefined,
deps: compileDeps,
multi: provider.multi
};
}
private _getQueriesMetadata(
queries: {[key: string]: Query}, isViewQuery: boolean,
directiveType: Type): cpl.CompileQueryMetadata[] {
2016-09-14 18:58:18 -04:00
const res: cpl.CompileQueryMetadata[] = [];
Object.keys(queries).forEach((propertyName: string) => {
const query = queries[propertyName];
if (query.isViewQuery === isViewQuery) {
res.push(this._getQueryMetadata(query, propertyName, directiveType));
}
});
2016-09-14 18:58:18 -04:00
return res;
}
2016-09-14 18:58:18 -04:00
private _queryVarBindings(selector: any): string[] { return selector.split(/\s*,\s*/); }
private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type|Function):
cpl.CompileQueryMetadata {
let selectors: cpl.CompileTokenMetadata[];
2016-09-14 18:58:18 -04:00
if (typeof q.selector === 'string') {
selectors =
this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName));
} else {
2016-09-14 18:58:18 -04:00
if (!q.selector) {
this._reportError(
syntaxError(
`Can't construct a query for the property "${propertyName}" of "${stringifyType(typeOrFunc)}" since the query selector wasn't defined.`),
typeOrFunc);
selectors = [];
} else {
selectors = [this._getTokenMetadata(q.selector)];
}
}
2016-09-14 18:58:18 -04:00
return {
2016-09-14 18:58:18 -04:00
selectors,
first: q.first,
2016-09-14 18:58:18 -04:00
descendants: q.descendants, propertyName,
read: q.read ? this._getTokenMetadata(q.read) : null !
};
}
private _reportError(error: any, type?: any, otherType?: any) {
if (this._errorCollector) {
this._errorCollector(error, type);
if (otherType) {
this._errorCollector(error, otherType);
}
} else {
throw error;
}
}
}
function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
if (tree) {
2016-09-14 18:58:18 -04:00
for (let i = 0; i < tree.length; i++) {
const item = resolveForwardRef(tree[i]);
if (Array.isArray(item)) {
flattenArray(item, out);
} else {
out.push(item);
}
}
}
return out;
}
function dedupeArray(array: any[]): Array<any> {
if (array) {
return Array.from(new Set(array));
}
return [];
}
function flattenAndDedupeArray(tree: any[]): Array<any> {
return dedupeArray(flattenArray(tree));
}
function isValidType(value: any): boolean {
return (value instanceof StaticSymbol) || (value instanceof Type);
}
function extractIdentifiers(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]) {
visitValue(value, new _CompileValueConverter(), targetIdentifiers);
}
class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
targetIdentifiers.push({reference: value});
}
}
function stringifyType(type: any): string {
if (type instanceof StaticSymbol) {
return `${type.name} in ${type.filePath}`;
} else {
return stringify(type);
}
}
/**
* Indicates that a component is still being loaded in a synchronous compile.
*/
function componentStillLoadingError(compType: Type) {
const error =
Error(`Can't compile synchronously as ${stringify(compType)} is still being loaded!`);
(error as any)[ERROR_COMPONENT_TYPE] = compType;
return error;
}