/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {StaticSymbol} from './aot/static_symbol'; import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core'; import {LifecycleHooks} from './lifecycle_reflector'; import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser'; import {splitAtColon, stringify} from './util'; // group 0: "[prop] or (event) or @trigger" // group 1: "prop" from "[prop]" // group 2: "event" from "(event)" // group 3: "@trigger" from "@trigger" const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/; function _sanitizeIdentifier(name: string): string { return name.replace(/\W/g, '_'); } let _anonymousTypeIndex = 0; export function identifierName(compileIdentifier: CompileIdentifierMetadata | null | undefined): string|null { if (!compileIdentifier || !compileIdentifier.reference) { return null; } const ref = compileIdentifier.reference; if (ref instanceof StaticSymbol) { return ref.name; } if (ref['__anonymousType']) { return ref['__anonymousType']; } let identifier = stringify(ref); if (identifier.indexOf('(') >= 0) { // case: anonymous functions! identifier = `anonymous_${_anonymousTypeIndex++}`; ref['__anonymousType'] = identifier; } else { identifier = _sanitizeIdentifier(identifier); } return identifier; } export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata): string { const ref = compileIdentifier.reference; if (ref instanceof StaticSymbol) { return ref.filePath; } // Runtime type return `./${stringify(ref)}`; } export function viewClassName(compType: any, embeddedTemplateIndex: number): string { return `View_${identifierName({reference: compType})}_${embeddedTemplateIndex}`; } export function rendererTypeName(compType: any): string { return `RenderType_${identifierName({reference: compType})}`; } export function hostViewClassName(compType: any): string { return `HostView_${identifierName({reference: compType})}`; } export function componentFactoryName(compType: any): string { return `${identifierName({reference: compType})}NgFactory`; } export interface ProxyClass { setDelegate(delegate: any): void; } export interface CompileIdentifierMetadata { reference: any; } export enum CompileSummaryKind { Pipe, Directive, NgModule, Injectable } /** * A CompileSummary is the data needed to use a directive / pipe / module * in other modules / components. However, this data is not enough to compile * the directive / module itself. */ export interface CompileTypeSummary { summaryKind: CompileSummaryKind|null; type: CompileTypeMetadata; } export interface CompileDiDependencyMetadata { isAttribute?: boolean; isSelf?: boolean; isHost?: boolean; isSkipSelf?: boolean; isOptional?: boolean; isValue?: boolean; token?: CompileTokenMetadata; value?: any; } export interface CompileProviderMetadata { token: CompileTokenMetadata; useClass?: CompileTypeMetadata; useValue?: any; useExisting?: CompileTokenMetadata; useFactory?: CompileFactoryMetadata; deps?: CompileDiDependencyMetadata[]; multi?: boolean; } export interface CompileFactoryMetadata extends CompileIdentifierMetadata { diDeps: CompileDiDependencyMetadata[]; reference: any; } export function tokenName(token: CompileTokenMetadata) { return token.value != null ? _sanitizeIdentifier(token.value) : identifierName(token.identifier); } export function tokenReference(token: CompileTokenMetadata) { if (token.identifier != null) { return token.identifier.reference; } else { return token.value; } } export interface CompileTokenMetadata { value?: any; identifier?: CompileIdentifierMetadata|CompileTypeMetadata; } /** * Metadata regarding compilation of a type. */ export interface CompileTypeMetadata extends CompileIdentifierMetadata { diDeps: CompileDiDependencyMetadata[]; lifecycleHooks: LifecycleHooks[]; reference: any; } export interface CompileQueryMetadata { selectors: Array; descendants: boolean; first: boolean; propertyName: string; read: CompileTokenMetadata; } /** * Metadata about a stylesheet */ export class CompileStylesheetMetadata { moduleUrl: string|null; styles: string[]; styleUrls: string[]; constructor( {moduleUrl, styles, styleUrls}: {moduleUrl?: string, styles?: string[], styleUrls?: string[]} = {}) { this.moduleUrl = moduleUrl || null; this.styles = _normalizeArray(styles); this.styleUrls = _normalizeArray(styleUrls); } } /** * Summary Metadata regarding compilation of a template. */ export interface CompileTemplateSummary { ngContentSelectors: string[]; encapsulation: ViewEncapsulation|null; } /** * Metadata regarding compilation of a template. */ export class CompileTemplateMetadata { encapsulation: ViewEncapsulation|null; template: string|null; templateUrl: string|null; htmlAst: HtmlParseTreeResult|null; isInline: boolean; styles: string[]; styleUrls: string[]; externalStylesheets: CompileStylesheetMetadata[]; animations: any[]; ngContentSelectors: string[]; interpolation: [string, string]|null; preserveWhitespaces: boolean; constructor({encapsulation, template, templateUrl, htmlAst, styles, styleUrls, externalStylesheets, animations, ngContentSelectors, interpolation, isInline, preserveWhitespaces}: { encapsulation: ViewEncapsulation | null, template: string|null, templateUrl: string|null, htmlAst: HtmlParseTreeResult|null, styles: string[], styleUrls: string[], externalStylesheets: CompileStylesheetMetadata[], ngContentSelectors: string[], animations: any[], interpolation: [string, string]|null, isInline: boolean, preserveWhitespaces: boolean }) { this.encapsulation = encapsulation; this.template = template; this.templateUrl = templateUrl; this.htmlAst = htmlAst; this.styles = _normalizeArray(styles); this.styleUrls = _normalizeArray(styleUrls); this.externalStylesheets = _normalizeArray(externalStylesheets); this.animations = animations ? flatten(animations) : []; this.ngContentSelectors = ngContentSelectors || []; if (interpolation && interpolation.length != 2) { throw new Error(`'interpolation' should have a start and an end symbol.`); } this.interpolation = interpolation; this.isInline = isInline; this.preserveWhitespaces = preserveWhitespaces; } toSummary(): CompileTemplateSummary { return { ngContentSelectors: this.ngContentSelectors, encapsulation: this.encapsulation, }; } } export interface CompileEntryComponentMetadata { componentType: any; componentFactory: StaticSymbol|object; } // Note: This should only use interfaces as nested data types // as we need to be able to serialize this from/to JSON! export interface CompileDirectiveSummary extends CompileTypeSummary { type: CompileTypeMetadata; isComponent: boolean; selector: string|null; exportAs: string|null; inputs: {[key: string]: string}; outputs: {[key: string]: string}; hostListeners: {[key: string]: string}; hostProperties: {[key: string]: string}; hostAttributes: {[key: string]: string}; providers: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[]; queries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[]; entryComponents: CompileEntryComponentMetadata[]; changeDetection: ChangeDetectionStrategy|null; template: CompileTemplateSummary|null; componentViewType: StaticSymbol|ProxyClass|null; rendererType: StaticSymbol|object|null; componentFactory: StaticSymbol|object|null; } /** * Metadata regarding compilation of a directive. */ export class CompileDirectiveMetadata { static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, providers, viewProviders, queries, viewQueries, entryComponents, template, componentViewType, rendererType, componentFactory}: { isHost: boolean, type: CompileTypeMetadata, isComponent: boolean, selector: string|null, exportAs: string|null, changeDetection: ChangeDetectionStrategy|null, inputs: string[], outputs: string[], host: {[key: string]: string}, providers: CompileProviderMetadata[], viewProviders: CompileProviderMetadata[], queries: CompileQueryMetadata[], viewQueries: CompileQueryMetadata[], entryComponents: CompileEntryComponentMetadata[], template: CompileTemplateMetadata, componentViewType: StaticSymbol|ProxyClass|null, rendererType: StaticSymbol|object|null, componentFactory: StaticSymbol|object|null, }): CompileDirectiveMetadata { const hostListeners: {[key: string]: string} = {}; const hostProperties: {[key: string]: string} = {}; const hostAttributes: {[key: string]: string} = {}; if (host != null) { Object.keys(host).forEach(key => { const value = host[key]; const matches = key.match(HOST_REG_EXP); if (matches === null) { hostAttributes[key] = value; } else if (matches[1] != null) { hostProperties[matches[1]] = value; } else if (matches[2] != null) { hostListeners[matches[2]] = value; } }); } const inputsMap: {[key: string]: string} = {}; if (inputs != null) { inputs.forEach((bindConfig: string) => { // canonical syntax: `dirProp: elProp` // if there is no `:`, use dirProp = elProp const parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); inputsMap[parts[0]] = parts[1]; }); } const outputsMap: {[key: string]: string} = {}; if (outputs != null) { outputs.forEach((bindConfig: string) => { // canonical syntax: `dirProp: elProp` // if there is no `:`, use dirProp = elProp const parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); outputsMap[parts[0]] = parts[1]; }); } return new CompileDirectiveMetadata({ isHost, type, isComponent: !!isComponent, selector, exportAs, changeDetection, inputs: inputsMap, outputs: outputsMap, hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries, entryComponents, template, componentViewType, rendererType, componentFactory, }); } isHost: boolean; type: CompileTypeMetadata; isComponent: boolean; selector: string|null; exportAs: string|null; changeDetection: ChangeDetectionStrategy|null; inputs: {[key: string]: string}; outputs: {[key: string]: string}; hostListeners: {[key: string]: string}; hostProperties: {[key: string]: string}; hostAttributes: {[key: string]: string}; providers: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[]; queries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[]; entryComponents: CompileEntryComponentMetadata[]; template: CompileTemplateMetadata|null; componentViewType: StaticSymbol|ProxyClass|null; rendererType: StaticSymbol|object|null; componentFactory: StaticSymbol|object|null; constructor({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries, entryComponents, template, componentViewType, rendererType, componentFactory}: { isHost: boolean, type: CompileTypeMetadata, isComponent: boolean, selector: string|null, exportAs: string|null, changeDetection: ChangeDetectionStrategy|null, inputs: {[key: string]: string}, outputs: {[key: string]: string}, hostListeners: {[key: string]: string}, hostProperties: {[key: string]: string}, hostAttributes: {[key: string]: string}, providers: CompileProviderMetadata[], viewProviders: CompileProviderMetadata[], queries: CompileQueryMetadata[], viewQueries: CompileQueryMetadata[], entryComponents: CompileEntryComponentMetadata[], template: CompileTemplateMetadata|null, componentViewType: StaticSymbol|ProxyClass|null, rendererType: StaticSymbol|object|null, componentFactory: StaticSymbol|object|null, }) { this.isHost = !!isHost; this.type = type; this.isComponent = isComponent; this.selector = selector; this.exportAs = exportAs; this.changeDetection = changeDetection; this.inputs = inputs; this.outputs = outputs; this.hostListeners = hostListeners; this.hostProperties = hostProperties; this.hostAttributes = hostAttributes; this.providers = _normalizeArray(providers); this.viewProviders = _normalizeArray(viewProviders); this.queries = _normalizeArray(queries); this.viewQueries = _normalizeArray(viewQueries); this.entryComponents = _normalizeArray(entryComponents); this.template = template; this.componentViewType = componentViewType; this.rendererType = rendererType; this.componentFactory = componentFactory; } toSummary(): CompileDirectiveSummary { return { summaryKind: CompileSummaryKind.Directive, type: this.type, isComponent: this.isComponent, selector: this.selector, exportAs: this.exportAs, inputs: this.inputs, outputs: this.outputs, hostListeners: this.hostListeners, hostProperties: this.hostProperties, hostAttributes: this.hostAttributes, providers: this.providers, viewProviders: this.viewProviders, queries: this.queries, viewQueries: this.viewQueries, entryComponents: this.entryComponents, changeDetection: this.changeDetection, template: this.template && this.template.toSummary(), componentViewType: this.componentViewType, rendererType: this.rendererType, componentFactory: this.componentFactory }; } } export interface CompilePipeSummary extends CompileTypeSummary { type: CompileTypeMetadata; name: string; pure: boolean; } export class CompilePipeMetadata { type: CompileTypeMetadata; name: string; pure: boolean; constructor({type, name, pure}: { type: CompileTypeMetadata, name: string, pure: boolean, }) { this.type = type; this.name = name; this.pure = !!pure; } toSummary(): CompilePipeSummary { return { summaryKind: CompileSummaryKind.Pipe, type: this.type, name: this.name, pure: this.pure }; } } // Note: This should only use interfaces as nested data types // as we need to be able to serialize this from/to JSON! export interface CompileNgModuleSummary extends CompileTypeSummary { type: CompileTypeMetadata; // Note: This is transitive over the exported modules. exportedDirectives: CompileIdentifierMetadata[]; // Note: This is transitive over the exported modules. exportedPipes: CompileIdentifierMetadata[]; // Note: This is transitive. entryComponents: CompileEntryComponentMetadata[]; // Note: This is transitive. providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[]; // Note: This is transitive. modules: CompileTypeMetadata[]; } /** * Metadata regarding compilation of a module. */ export class CompileNgModuleMetadata { type: CompileTypeMetadata; declaredDirectives: CompileIdentifierMetadata[]; exportedDirectives: CompileIdentifierMetadata[]; declaredPipes: CompileIdentifierMetadata[]; exportedPipes: CompileIdentifierMetadata[]; entryComponents: CompileEntryComponentMetadata[]; bootstrapComponents: CompileIdentifierMetadata[]; providers: CompileProviderMetadata[]; importedModules: CompileNgModuleSummary[]; exportedModules: CompileNgModuleSummary[]; schemas: SchemaMetadata[]; id: string|null; transitiveModule: TransitiveCompileNgModuleMetadata; constructor({type, providers, declaredDirectives, exportedDirectives, declaredPipes, exportedPipes, entryComponents, bootstrapComponents, importedModules, exportedModules, schemas, transitiveModule, id}: { type: CompileTypeMetadata, providers: CompileProviderMetadata[], declaredDirectives: CompileIdentifierMetadata[], exportedDirectives: CompileIdentifierMetadata[], declaredPipes: CompileIdentifierMetadata[], exportedPipes: CompileIdentifierMetadata[], entryComponents: CompileEntryComponentMetadata[], bootstrapComponents: CompileIdentifierMetadata[], importedModules: CompileNgModuleSummary[], exportedModules: CompileNgModuleSummary[], transitiveModule: TransitiveCompileNgModuleMetadata, schemas: SchemaMetadata[], id: string|null }) { this.type = type || null; this.declaredDirectives = _normalizeArray(declaredDirectives); this.exportedDirectives = _normalizeArray(exportedDirectives); this.declaredPipes = _normalizeArray(declaredPipes); this.exportedPipes = _normalizeArray(exportedPipes); this.providers = _normalizeArray(providers); this.entryComponents = _normalizeArray(entryComponents); this.bootstrapComponents = _normalizeArray(bootstrapComponents); this.importedModules = _normalizeArray(importedModules); this.exportedModules = _normalizeArray(exportedModules); this.schemas = _normalizeArray(schemas); this.id = id || null; this.transitiveModule = transitiveModule || null; } toSummary(): CompileNgModuleSummary { const module = this.transitiveModule !; return { summaryKind: CompileSummaryKind.NgModule, type: this.type, entryComponents: module.entryComponents, providers: module.providers, modules: module.modules, exportedDirectives: module.exportedDirectives, exportedPipes: module.exportedPipes }; } } export class TransitiveCompileNgModuleMetadata { directivesSet = new Set(); directives: CompileIdentifierMetadata[] = []; exportedDirectivesSet = new Set(); exportedDirectives: CompileIdentifierMetadata[] = []; pipesSet = new Set(); pipes: CompileIdentifierMetadata[] = []; exportedPipesSet = new Set(); exportedPipes: CompileIdentifierMetadata[] = []; modulesSet = new Set(); modules: CompileTypeMetadata[] = []; entryComponentsSet = new Set(); entryComponents: CompileEntryComponentMetadata[] = []; providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = []; addProvider(provider: CompileProviderMetadata, module: CompileIdentifierMetadata) { this.providers.push({provider: provider, module: module}); } addDirective(id: CompileIdentifierMetadata) { if (!this.directivesSet.has(id.reference)) { this.directivesSet.add(id.reference); this.directives.push(id); } } addExportedDirective(id: CompileIdentifierMetadata) { if (!this.exportedDirectivesSet.has(id.reference)) { this.exportedDirectivesSet.add(id.reference); this.exportedDirectives.push(id); } } addPipe(id: CompileIdentifierMetadata) { if (!this.pipesSet.has(id.reference)) { this.pipesSet.add(id.reference); this.pipes.push(id); } } addExportedPipe(id: CompileIdentifierMetadata) { if (!this.exportedPipesSet.has(id.reference)) { this.exportedPipesSet.add(id.reference); this.exportedPipes.push(id); } } addModule(id: CompileTypeMetadata) { if (!this.modulesSet.has(id.reference)) { this.modulesSet.add(id.reference); this.modules.push(id); } } addEntryComponent(ec: CompileEntryComponentMetadata) { if (!this.entryComponentsSet.has(ec.componentType)) { this.entryComponentsSet.add(ec.componentType); this.entryComponents.push(ec); } } } function _normalizeArray(obj: any[] | undefined | null): any[] { return obj || []; } export class ProviderMeta { token: any; useClass: Type|null; useValue: any; useExisting: any; useFactory: Function|null; dependencies: Object[]|null; multi: boolean; constructor(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}: { useClass?: Type, useValue?: any, useExisting?: any, useFactory?: Function|null, deps?: Object[]|null, multi?: boolean }) { this.token = token; this.useClass = useClass || null; this.useValue = useValue; this.useExisting = useExisting; this.useFactory = useFactory || null; this.dependencies = deps || null; this.multi = !!multi; } } export function flatten(list: Array): T[] { return list.reduce((flat: any[], item: T | T[]): T[] => { const flatItem = Array.isArray(item) ? flatten(item) : item; return (flat).concat(flatItem); }, []); } function jitSourceUrl(url: string) { // Note: We need 3 "/" so that ng shows up as a separate domain // in the chrome dev tools. return url.replace(/(\w+:\/\/[\w:-]+)?(\/+)?/, 'ng:///'); } export function templateSourceUrl( ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata}, templateMeta: {isInline: boolean, templateUrl: string | null}) { let url: string; if (templateMeta.isInline) { if (compMeta.type.reference instanceof StaticSymbol) { // Note: a .ts file might contain multiple components with inline templates, // so we need to give them unique urls, as these will be used for sourcemaps. url = `${compMeta.type.reference.filePath}.${compMeta.type.reference.name}.html`; } else { url = `${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`; } } else { url = templateMeta.templateUrl !; } return compMeta.type.reference instanceof StaticSymbol ? url : jitSourceUrl(url); } export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) { const pathParts = meta.moduleUrl !.split(/\/\\/g); const baseName = pathParts[pathParts.length - 1]; return jitSourceUrl(`css/${id}${baseName}.ngstyle.js`); } export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string { return jitSourceUrl(`${identifierName(moduleMeta.type)}/module.ngfactory.js`); } export function templateJitUrl( ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string { return jitSourceUrl( `${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`); }