/** * @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(); private _directiveCache = new Map(); private _summaryCache = new Map(); private _pipeCache = new Map(); private _ngModuleCache = new Map(); private _ngModuleOfTypes = new Map(); private _shallowModuleCache = new Map(); constructor( private _config: CompilerConfig, private _htmlParser: HtmlParser, private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, 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 = 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; (proxyClass).prototype = d.prototype; }; // Make stringify work correctly (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 = 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 {}; } } 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, 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 { 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 = 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|null = null): cpl.CompileNgModuleSummary|null { let moduleSummary: cpl.CompileNgModuleSummary|null = 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 { const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound); const loading: Promise[] = []; 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|null = null): cpl.CompileNgModuleMetadata|null { moduleType = resolveForwardRef(moduleType); 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)); } } 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)); } if (meta.entryComponents) { entryComponents.push(...flattenAndDedupeArray(meta.entryComponents) .map(type => this._getEntryComponentMetadata(type) !)); } 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)); }); } entryComponents.push( ...bootstrapComponents.map(type => this._getEntryComponentMetadata(type.reference) !)); 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, }); 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'; } if (this.isPipe(type)) { return 'pipe'; } if (this.isNgModule(type)) { return 'module'; } } if ((type as any).provide) { return 'provider'; } 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>(); importedModules.concat(exportedModules).forEach((modSummary) => { modSummary.modules.forEach((mod) => result.addModule(mod)); modSummary.entryComponents.forEach((comp) => result.addEntryComponent(comp)); const addedTokens = new Set(); modSummary.providers.forEach((entry) => { const tokenRef = cpl.tokenReference(entry.provider.token); let prevModules = modulesByToken.get(tokenRef); if (!prevModules) { prevModules = new Set(); 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; } getPipeSummary(pipeType: any): cpl.CompilePipeSummary { const pipeSummary = 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) || []; 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; 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 !; } return { 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); let compileToken: cpl.CompileTokenMetadata; 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) => { 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. This might be caused by using 'barrel' index.ts files.`)); return; } else { const providersInfo = (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[] = []; 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 []; } if (!provider.multi) { this._reportError( syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`), type); return []; } 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 = 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); 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}; } } 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[] { 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)); } }); return res; } 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[]; if (typeof q.selector === 'string') { selectors = this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName)); } else { 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)]; } } return { selectors, first: q.first, 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 = []): Array { if (tree) { 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 { if (array) { return Array.from(new Set(array)); } return []; } function flattenAndDedupeArray(tree: any[]): Array { 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; }