/** * @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 {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core'; import {ListWrapper, MapWrapper} from './facade/collection'; import {isPresent, isStringMap, normalizeBlank, normalizeBool} from './facade/lang'; import {LifecycleHooks} from './private_import_core'; import {CssSelector} from './selector'; import {sanitizeIdentifier, splitAtColon} from './util'; function unimplemented(): any { throw new Error('unimplemented'); } // 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]+)$/; const UNDEFINED = new Object(); export abstract class CompileMetadataWithIdentifier { get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); } } export class CompileAnimationEntryMetadata { constructor( public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {} } export abstract class CompileAnimationStateMetadata {} export class CompileAnimationStateDeclarationMetadata extends CompileAnimationStateMetadata { constructor(public stateNameExpr: string, public styles: CompileAnimationStyleMetadata) { super(); } } export class CompileAnimationStateTransitionMetadata extends CompileAnimationStateMetadata { constructor(public stateChangeExpr: string, public steps: CompileAnimationMetadata) { super(); } } export abstract class CompileAnimationMetadata {} export class CompileAnimationKeyframesSequenceMetadata extends CompileAnimationMetadata { constructor(public steps: CompileAnimationStyleMetadata[] = []) { super(); } } export class CompileAnimationStyleMetadata extends CompileAnimationMetadata { constructor( public offset: number, public styles: Array<string|{[key: string]: string | number}> = null) { super(); } } export class CompileAnimationAnimateMetadata extends CompileAnimationMetadata { constructor( public timings: string|number = 0, public styles: CompileAnimationStyleMetadata| CompileAnimationKeyframesSequenceMetadata = null) { super(); } } export abstract class CompileAnimationWithStepsMetadata extends CompileAnimationMetadata { constructor(public steps: CompileAnimationMetadata[] = null) { super(); } } export class CompileAnimationSequenceMetadata extends CompileAnimationWithStepsMetadata { constructor(steps: CompileAnimationMetadata[] = null) { super(steps); } } export class CompileAnimationGroupMetadata extends CompileAnimationWithStepsMetadata { constructor(steps: CompileAnimationMetadata[] = null) { super(steps); } } export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier { reference: any; name: string; prefix: string; moduleUrl: string; value: any; constructor( {reference, name, moduleUrl, prefix, value}: {reference?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) { this.reference = reference; this.name = name; this.prefix = prefix; this.moduleUrl = moduleUrl; this.value = value; } get identifier(): CompileIdentifierMetadata { return this; } } export class CompileDiDependencyMetadata { isAttribute: boolean; isSelf: boolean; isHost: boolean; isSkipSelf: boolean; isOptional: boolean; isValue: boolean; query: CompileQueryMetadata; viewQuery: CompileQueryMetadata; token: CompileTokenMetadata; value: any; constructor( {isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, query, viewQuery, token, value}: { isAttribute?: boolean, isSelf?: boolean, isHost?: boolean, isSkipSelf?: boolean, isOptional?: boolean, isValue?: boolean, query?: CompileQueryMetadata, viewQuery?: CompileQueryMetadata, token?: CompileTokenMetadata, value?: any } = {}) { this.isAttribute = normalizeBool(isAttribute); this.isSelf = normalizeBool(isSelf); this.isHost = normalizeBool(isHost); this.isSkipSelf = normalizeBool(isSkipSelf); this.isOptional = normalizeBool(isOptional); this.isValue = normalizeBool(isValue); this.query = query; this.viewQuery = viewQuery; this.token = token; this.value = value; } } export class CompileProviderMetadata { token: CompileTokenMetadata; useClass: CompileTypeMetadata; useValue: any; useExisting: CompileTokenMetadata; useFactory: CompileFactoryMetadata; deps: CompileDiDependencyMetadata[]; multi: boolean; constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: { token?: CompileTokenMetadata, useClass?: CompileTypeMetadata, useValue?: any, useExisting?: CompileTokenMetadata, useFactory?: CompileFactoryMetadata, deps?: CompileDiDependencyMetadata[], multi?: boolean }) { this.token = token; this.useClass = useClass; this.useValue = useValue; this.useExisting = useExisting; this.useFactory = useFactory; this.deps = normalizeBlank(deps); this.multi = normalizeBool(multi); } } export class CompileFactoryMetadata extends CompileIdentifierMetadata { diDeps: CompileDiDependencyMetadata[]; constructor({reference, name, moduleUrl, prefix, diDeps, value}: { reference?: Function, name?: string, prefix?: string, moduleUrl?: string, value?: boolean, diDeps?: CompileDiDependencyMetadata[] }) { super({reference: reference, name: name, prefix: prefix, moduleUrl: moduleUrl, value: value}); this.diDeps = _normalizeArray(diDeps); } } export class CompileTokenMetadata implements CompileMetadataWithIdentifier { value: any; identifier: CompileIdentifierMetadata; identifierIsInstance: boolean; constructor( {value, identifier, identifierIsInstance}: {value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) { this.value = value; this.identifier = identifier; this.identifierIsInstance = normalizeBool(identifierIsInstance); } get reference(): any { if (isPresent(this.identifier)) { return this.identifier.reference; } else { return this.value; } } get name(): string { return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name; } } /** * Metadata regarding compilation of a type. */ export class CompileTypeMetadata extends CompileIdentifierMetadata { isHost: boolean; diDeps: CompileDiDependencyMetadata[]; lifecycleHooks: LifecycleHooks[]; constructor({reference, name, moduleUrl, prefix, isHost, value, diDeps, lifecycleHooks}: { reference?: Type<any>, name?: string, moduleUrl?: string, prefix?: string, isHost?: boolean, value?: any, diDeps?: CompileDiDependencyMetadata[], lifecycleHooks?: LifecycleHooks[]; } = {}) { super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value}); this.isHost = normalizeBool(isHost); this.diDeps = _normalizeArray(diDeps); this.lifecycleHooks = _normalizeArray(lifecycleHooks); } } export class CompileQueryMetadata { selectors: Array<CompileTokenMetadata>; descendants: boolean; first: boolean; propertyName: string; read: CompileTokenMetadata; constructor({selectors, descendants, first, propertyName, read}: { selectors?: Array<CompileTokenMetadata>, descendants?: boolean, first?: boolean, propertyName?: string, read?: CompileTokenMetadata } = {}) { this.selectors = selectors; this.descendants = normalizeBool(descendants); this.first = normalizeBool(first); this.propertyName = propertyName; this.read = read; } } /** * Metadata about a stylesheet */ export class CompileStylesheetMetadata { moduleUrl: string; styles: string[]; styleUrls: string[]; constructor( {moduleUrl, styles, styleUrls}: {moduleUrl?: string, styles?: string[], styleUrls?: string[]} = {}) { this.moduleUrl = moduleUrl; this.styles = _normalizeArray(styles); this.styleUrls = _normalizeArray(styleUrls); } } /** * Metadata regarding compilation of a template. */ export class CompileTemplateMetadata { encapsulation: ViewEncapsulation; template: string; templateUrl: string; styles: string[]; styleUrls: string[]; externalStylesheets: CompileStylesheetMetadata[]; animations: CompileAnimationEntryMetadata[]; ngContentSelectors: string[]; interpolation: [string, string]; constructor( {encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations, ngContentSelectors, interpolation}: { encapsulation?: ViewEncapsulation, template?: string, templateUrl?: string, styles?: string[], styleUrls?: string[], externalStylesheets?: CompileStylesheetMetadata[], ngContentSelectors?: string[], animations?: CompileAnimationEntryMetadata[], interpolation?: [string, string] } = {}) { this.encapsulation = encapsulation; this.template = template; this.templateUrl = templateUrl; this.styles = _normalizeArray(styles); this.styleUrls = _normalizeArray(styleUrls); this.externalStylesheets = _normalizeArray(externalStylesheets); this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : []; this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : []; if (isPresent(interpolation) && interpolation.length != 2) { throw new Error(`'interpolation' should have a start and an end symbol.`); } this.interpolation = interpolation; } } /** * Metadata regarding compilation of a directive. */ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier { static create( {type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, providers, viewProviders, queries, viewQueries, entryComponents, template}: { type?: CompileTypeMetadata, isComponent?: boolean, selector?: string, exportAs?: string, changeDetection?: ChangeDetectionStrategy, inputs?: string[], outputs?: string[], host?: {[key: string]: string}, providers?: Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, viewProviders?: Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, queries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[], entryComponents?: CompileTypeMetadata[], viewDirectives?: CompileTypeMetadata[], viewPipes?: CompileTypeMetadata[], template?: CompileTemplateMetadata } = {}): CompileDirectiveMetadata { var hostListeners: {[key: string]: string} = {}; var hostProperties: {[key: string]: string} = {}; var hostAttributes: {[key: string]: string} = {}; if (isPresent(host)) { Object.keys(host).forEach(key => { const value = host[key]; const matches = key.match(HOST_REG_EXP); if (matches === null) { hostAttributes[key] = value; } else if (isPresent(matches[1])) { hostProperties[matches[1]] = value; } else if (isPresent(matches[2])) { hostListeners[matches[2]] = value; } }); } var inputsMap: {[key: string]: string} = {}; if (isPresent(inputs)) { inputs.forEach((bindConfig: string) => { // canonical syntax: `dirProp: elProp` // if there is no `:`, use dirProp = elProp var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); inputsMap[parts[0]] = parts[1]; }); } var outputsMap: {[key: string]: string} = {}; if (isPresent(outputs)) { outputs.forEach((bindConfig: string) => { // canonical syntax: `dirProp: elProp` // if there is no `:`, use dirProp = elProp var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); outputsMap[parts[0]] = parts[1]; }); } return new CompileDirectiveMetadata({ type, isComponent: normalizeBool(isComponent), selector, exportAs, changeDetection, inputs: inputsMap, outputs: outputsMap, hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries, entryComponents, template, }); } type: CompileTypeMetadata; isComponent: boolean; selector: string; exportAs: string; changeDetection: ChangeDetectionStrategy; 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[]; // Note: Need to keep types here to prevent cycles! entryComponents: CompileTypeMetadata[]; template: CompileTemplateMetadata; constructor( {type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries, entryComponents, template}: { type?: CompileTypeMetadata, isComponent?: boolean, selector?: string, exportAs?: string, changeDetection?: ChangeDetectionStrategy, inputs?: {[key: string]: string}, outputs?: {[key: string]: string}, hostListeners?: {[key: string]: string}, hostProperties?: {[key: string]: string}, hostAttributes?: {[key: string]: string}, providers?: Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, viewProviders?: Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, queries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[], entryComponents?: CompileTypeMetadata[], viewDirectives?: CompileTypeMetadata[], viewPipes?: CompileTypeMetadata[], template?: CompileTemplateMetadata, } = {}) { 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; } get identifier(): CompileIdentifierMetadata { return this.type; } } /** * Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector. */ export function createHostComponentMeta(compMeta: CompileDirectiveMetadata): CompileDirectiveMetadata { var template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate(); return CompileDirectiveMetadata.create({ type: new CompileTypeMetadata({ reference: Object, name: `${compMeta.type.name}_Host`, moduleUrl: compMeta.type.moduleUrl, isHost: true }), template: new CompileTemplateMetadata({ encapsulation: ViewEncapsulation.None, template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: [], animations: [] }), changeDetection: ChangeDetectionStrategy.Default, inputs: [], outputs: [], host: {}, isComponent: true, selector: '*', providers: [], viewProviders: [], queries: [], viewQueries: [] }); } export class CompilePipeMetadata implements CompileMetadataWithIdentifier { type: CompileTypeMetadata; name: string; pure: boolean; constructor({type, name, pure}: { type?: CompileTypeMetadata, name?: string, pure?: boolean, } = {}) { this.type = type; this.name = name; this.pure = normalizeBool(pure); } get identifier(): CompileIdentifierMetadata { return this.type; } } /** * Metadata regarding compilation of a directive. */ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { type: CompileTypeMetadata; declaredDirectives: CompileDirectiveMetadata[]; exportedDirectives: CompileDirectiveMetadata[]; declaredPipes: CompilePipeMetadata[]; exportedPipes: CompilePipeMetadata[]; // Note: See CompileDirectiveMetadata.entryComponents why this has to be a type. entryComponents: CompileTypeMetadata[]; bootstrapComponents: CompileTypeMetadata[]; providers: CompileProviderMetadata[]; importedModules: CompileNgModuleMetadata[]; exportedModules: CompileNgModuleMetadata[]; schemas: SchemaMetadata[]; id: string; transitiveModule: TransitiveCompileNgModuleMetadata; constructor( {type, providers, declaredDirectives, exportedDirectives, declaredPipes, exportedPipes, entryComponents, bootstrapComponents, importedModules, exportedModules, schemas, transitiveModule, id}: { type?: CompileTypeMetadata, providers?: Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, declaredDirectives?: CompileDirectiveMetadata[], exportedDirectives?: CompileDirectiveMetadata[], declaredPipes?: CompilePipeMetadata[], exportedPipes?: CompilePipeMetadata[], entryComponents?: CompileTypeMetadata[], bootstrapComponents?: CompileTypeMetadata[], importedModules?: CompileNgModuleMetadata[], exportedModules?: CompileNgModuleMetadata[], transitiveModule?: TransitiveCompileNgModuleMetadata, schemas?: SchemaMetadata[], id?: string } = {}) { this.type = type; 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; this.transitiveModule = transitiveModule; } get identifier(): CompileIdentifierMetadata { return this.type; } } export class TransitiveCompileNgModuleMetadata { directivesSet = new Set<Type<any>>(); pipesSet = new Set<Type<any>>(); constructor( public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[], public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) { directives.forEach(dir => this.directivesSet.add(dir.type.reference)); pipes.forEach(pipe => this.pipesSet.add(pipe.type.reference)); } } export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]): T[] { const map = new Map<any, T>(); items.forEach((item) => { if (!map.get(item.identifier.reference)) { map.set(item.identifier.reference, item); } }); return MapWrapper.values(map); } function _normalizeArray(obj: any[]): any[] { return isPresent(obj) ? obj : []; } export function isStaticSymbol(value: any): value is StaticSymbol { return isStringMap(value) && isPresent(value['name']) && isPresent(value['filePath']); } export interface StaticSymbol { name: string; filePath: string; } export class ProviderMeta { token: any; useClass: Type<any>; useValue: any; useExisting: any; useFactory: Function; dependencies: Object[]; multi: boolean; constructor(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}: { useClass?: Type<any>, useValue?: any, useExisting?: any, useFactory?: Function, deps?: Object[], multi?: boolean }) { this.token = token; this.useClass = useClass; this.useValue = useValue; this.useExisting = useExisting; this.useFactory = useFactory; this.dependencies = deps; this.multi = !!multi; } }