refactor(compiler): introduce summaries for metadata (#12799)

This does not yet introduce loading / serialization of summaries.

Part of #12787
This commit is contained in:
Tobias Bosch 2016-11-10 16:27:53 -08:00 committed by Victor Berchet
parent ef881475e9
commit fcb4e66493
17 changed files with 727 additions and 396 deletions

View File

@ -98,6 +98,15 @@ export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier
get identifier(): CompileIdentifierMetadata { return this; } get identifier(): CompileIdentifierMetadata { return this; }
} }
/**
* 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 CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
}
export class CompileDiDependencyMetadata { export class CompileDiDependencyMetadata {
isAttribute: boolean; isAttribute: boolean;
isSelf: boolean; isSelf: boolean;
@ -264,6 +273,16 @@ export class CompileStylesheetMetadata {
} }
} }
/**
* Summary Metadata regarding compilation of a template.
*/
export interface CompileTemplateSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
animations: string[];
ngContentSelectors: string[];
encapsulation: ViewEncapsulation;
}
/** /**
* Metadata regarding compilation of a template. * Metadata regarding compilation of a template.
*/ */
@ -303,6 +322,34 @@ export class CompileTemplateMetadata {
} }
this.interpolation = interpolation; this.interpolation = interpolation;
} }
toSummary(): CompileTemplateSummary {
return {
isSummary: true,
animations: this.animations.map(anim => anim.name),
ngContentSelectors: this.ngContentSelectors,
encapsulation: this.encapsulation
};
}
}
export interface CompileDirectiveSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
isComponent: boolean;
selector: string;
exportAs: string;
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[];
entryComponents: CompileIdentifierMetadata[];
changeDetection: ChangeDetectionStrategy;
template: CompileTemplateSummary;
} }
/** /**
@ -394,7 +441,6 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
viewProviders: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[];
// Note: Need to keep types here to prevent cycles!
entryComponents: CompileIdentifierMetadata[]; entryComponents: CompileIdentifierMetadata[];
template: CompileTemplateMetadata; template: CompileTemplateMetadata;
@ -442,6 +488,27 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompileDirectiveSummary {
return {
isSummary: true,
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,
entryComponents: this.entryComponents,
changeDetection: this.changeDetection,
template: this.template && this.template.toSummary()
};
}
} }
/** /**
@ -479,6 +546,12 @@ export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
}); });
} }
export interface CompilePipeSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
name: string;
pure: boolean;
}
export class CompilePipeMetadata implements CompileMetadataWithIdentifier { export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata; type: CompileTypeMetadata;
@ -495,8 +568,33 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
this.pure = !!pure; this.pure = !!pure;
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompilePipeSummary {
return {isSummary: true, type: this.type, name: this.name, pure: this.pure};
}
} }
export interface CompileNgModuleInjectorSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
entryComponents: CompileIdentifierMetadata[];
providers: CompileProviderMetadata[];
importedModules: CompileNgModuleInjectorSummary[];
exportedModules: CompileNgModuleInjectorSummary[];
}
export interface CompileNgModuleDirectiveSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
exportedDirectives: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
exportedModules: CompileNgModuleDirectiveSummary[];
loadingPromises: Promise<any>[];
}
export type CompileNgModuleSummary =
CompileNgModuleInjectorSummary & CompileNgModuleDirectiveSummary;
/** /**
* Metadata regarding compilation of a module. * Metadata regarding compilation of a module.
*/ */
@ -506,13 +604,12 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedDirectives: CompileIdentifierMetadata[]; exportedDirectives: CompileIdentifierMetadata[];
declaredPipes: CompileIdentifierMetadata[]; declaredPipes: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[]; exportedPipes: CompileIdentifierMetadata[];
// Note: See CompileDirectiveMetadata.entryComponents why this has to be a type.
entryComponents: CompileIdentifierMetadata[]; entryComponents: CompileIdentifierMetadata[];
bootstrapComponents: CompileIdentifierMetadata[]; bootstrapComponents: CompileIdentifierMetadata[];
providers: CompileProviderMetadata[]; providers: CompileProviderMetadata[];
importedModules: CompileNgModuleMetadata[]; importedModules: CompileNgModuleSummary[];
exportedModules: CompileNgModuleMetadata[]; exportedModules: CompileNgModuleSummary[];
schemas: SchemaMetadata[]; schemas: SchemaMetadata[];
id: string; id: string;
@ -531,8 +628,8 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedPipes?: CompileIdentifierMetadata[], exportedPipes?: CompileIdentifierMetadata[],
entryComponents?: CompileIdentifierMetadata[], entryComponents?: CompileIdentifierMetadata[],
bootstrapComponents?: CompileIdentifierMetadata[], bootstrapComponents?: CompileIdentifierMetadata[],
importedModules?: CompileNgModuleMetadata[], importedModules?: CompileNgModuleSummary[],
exportedModules?: CompileNgModuleMetadata[], exportedModules?: CompileNgModuleSummary[],
transitiveModule?: TransitiveCompileNgModuleMetadata, transitiveModule?: TransitiveCompileNgModuleMetadata,
schemas?: SchemaMetadata[], schemas?: SchemaMetadata[],
id?: string id?: string
@ -553,6 +650,41 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompileNgModuleSummary {
return {
isSummary: true,
type: this.type,
entryComponents: this.entryComponents,
providers: this.providers,
importedModules: this.importedModules,
exportedModules: this.exportedModules,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
loadingPromises: this.transitiveModule.loadingPromises
};
}
toInjectorSummary(): CompileNgModuleInjectorSummary {
return {
isSummary: true,
type: this.type,
entryComponents: this.entryComponents,
providers: this.providers,
importedModules: this.importedModules,
exportedModules: this.exportedModules
};
}
toDirectiveSummary(): CompileNgModuleDirectiveSummary {
return {
isSummary: true,
type: this.type,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
exportedModules: this.exportedModules,
loadingPromises: this.transitiveModule.loadingPromises
};
}
} }
export class TransitiveCompileNgModuleMetadata { export class TransitiveCompileNgModuleMetadata {
@ -560,7 +692,7 @@ export class TransitiveCompileNgModuleMetadata {
pipesSet = new Set<any>(); pipesSet = new Set<any>();
constructor( constructor(
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
public entryComponents: CompileIdentifierMetadata[], public entryComponents: CompileIdentifierMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[], public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
public loadingPromises: Promise<any>[]) { public loadingPromises: Promise<any>[]) {

View File

@ -8,7 +8,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util'; import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter'; import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util'; import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
@ -355,8 +355,8 @@ function parseHostBindings(
const sourceSpan = new ParseSourceSpan( const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null), new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null)); new ParseLocation(sourceFile, null, null, null));
const parsedHostProps = parser.createDirectiveHostPropertyAsts(dirMeta, sourceSpan); const parsedHostProps = parser.createDirectiveHostPropertyAsts(dirMeta.toSummary(), sourceSpan);
const parsedHostListeners = parser.createDirectiveHostEventAsts(dirMeta, sourceSpan); const parsedHostListeners = parser.createDirectiveHostEventAsts(dirMeta.toSummary(), sourceSpan);
return new ParseResult(parsedHostProps, parsedHostListeners, errors); return new ParseResult(parsedHostProps, parsedHostListeners, errors);
} }
@ -418,7 +418,7 @@ export class DirectiveWrapperExpressions {
return []; return [];
} }
} }
static ngOnDestroy(dir: CompileDirectiveMetadata, dirWrapper: o.Expression): o.Statement[] { static ngOnDestroy(dir: CompileDirectiveSummary, dirWrapper: o.Expression): o.Statement[] {
if (dir.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1 || if (dir.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1 ||
Object.keys(dir.outputs).length > 0) { Object.keys(dir.outputs).length > 0) {
return [dirWrapper.callMethod('ngOnDestroy', []).toStmt()]; return [dirWrapper.callMethod('ngOnDestroy', []).toStmt()];
@ -427,7 +427,7 @@ export class DirectiveWrapperExpressions {
} }
} }
static subscribe( static subscribe(
dirMeta: CompileDirectiveMetadata, hostProps: BoundElementPropertyAst[], usedEvents: string[], dirMeta: CompileDirectiveSummary, hostProps: BoundElementPropertyAst[], usedEvents: string[],
dirWrapper: o.Expression, view: o.Expression, eventListener: o.Expression): o.Statement[] { dirWrapper: o.Expression, view: o.Expression, eventListener: o.Expression): o.Statement[] {
let needsSubscribe = false; let needsSubscribe = false;
let eventFlags: o.Expression[] = []; let eventFlags: o.Expression[] = [];

View File

@ -12,7 +12,7 @@ import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata'; import * as cpl from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver'; import {DirectiveResolver} from './directive_resolver';
import {ListWrapper} from './facade/collection'; import {ListWrapper, StringMapWrapper} from './facade/collection';
import {isBlank, isPresent, stringify} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifierToken} from './identifiers';
import {hasLifecycleHook} from './lifecycle_reflector'; import {hasLifecycleHook} from './lifecycle_reflector';
@ -24,6 +24,7 @@ import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, visitValue} from './util'; import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
// Design notes: // Design notes:
// - don't lazily create metadata: // - don't lazily create metadata:
// For some metadata, we need to do async work sometimes, // For some metadata, we need to do async work sometimes,
@ -34,7 +35,9 @@ import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, vi
@Injectable() @Injectable()
export class CompileMetadataResolver { export class CompileMetadataResolver {
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>(); private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
private _directiveSummaryCache = new Map<Type<any>, cpl.CompileDirectiveSummary>();
private _pipeCache = new Map<Type<any>, cpl.CompilePipeMetadata>(); private _pipeCache = new Map<Type<any>, cpl.CompilePipeMetadata>();
private _pipeSummaryCache = new Map<Type<any>, cpl.CompilePipeSummary>();
private _ngModuleCache = new Map<Type<any>, cpl.CompileNgModuleMetadata>(); private _ngModuleCache = new Map<Type<any>, cpl.CompileNgModuleMetadata>();
private _ngModuleOfTypes = new Map<Type<any>, Type<any>>(); private _ngModuleOfTypes = new Map<Type<any>, Type<any>>();
private _anonymousTypes = new Map<Object, number>(); private _anonymousTypes = new Map<Object, number>();
@ -63,7 +66,9 @@ export class CompileMetadataResolver {
clearCacheFor(type: Type<any>) { clearCacheFor(type: Type<any>) {
const dirMeta = this._directiveCache.get(type); const dirMeta = this._directiveCache.get(type);
this._directiveCache.delete(type); this._directiveCache.delete(type);
this._directiveSummaryCache.delete(type);
this._pipeCache.delete(type); this._pipeCache.delete(type);
this._pipeSummaryCache.delete(type);
this._ngModuleOfTypes.delete(type); this._ngModuleOfTypes.delete(type);
// Clear all of the NgModule as they contain transitive information! // Clear all of the NgModule as they contain transitive information!
this._ngModuleCache.clear(); this._ngModuleCache.clear();
@ -74,7 +79,9 @@ export class CompileMetadataResolver {
clearCache() { clearCache() {
this._directiveCache.clear(); this._directiveCache.clear();
this._directiveSummaryCache.clear();
this._pipeCache.clear(); this._pipeCache.clear();
this._pipeSummaryCache.clear();
this._ngModuleCache.clear(); this._ngModuleCache.clear();
this._ngModuleOfTypes.clear(); this._ngModuleOfTypes.clear();
this._directiveNormalizer.clearCache(); this._directiveNormalizer.clearCache();
@ -205,6 +212,7 @@ export class CompileMetadataResolver {
entryComponents: entryComponentMetadata entryComponents: entryComponentMetadata
}); });
this._directiveCache.set(directiveType, meta); this._directiveCache.set(directiveType, meta);
this._directiveSummaryCache.set(directiveType, meta.toSummary());
return meta; return meta;
}; };
@ -259,6 +267,15 @@ export class CompileMetadataResolver {
return dirMeta; return dirMeta;
} }
getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary {
const dirSummary = this._directiveSummaryCache.get(dirType);
if (!dirSummary) {
throw new Error(
`Illegal state: getDirectiveSummary can only be called after loadNgModuleMetadata for a module that imports it. Directive ${stringify(dirType)}.`);
}
return dirSummary;
}
isDirective(type: any) { return this._directiveResolver.isDirective(type); } isDirective(type: any) { return this._directiveResolver.isDirective(type); }
isPipe(type: any) { return this._pipeResolver.isPipe(type); } isPipe(type: any) { return this._pipeResolver.isPipe(type); }
@ -276,6 +293,14 @@ export class CompileMetadataResolver {
return modMeta; return modMeta;
} }
private _loadNgModuleSummary(moduleType: any, isSync: boolean): cpl.CompileNgModuleSummary {
// TODO(tbosch): add logic to read summary files!
// - needs to add directive / pipe summaries to this._directiveSummaryCache /
// this._pipeSummaryCache as well!
const moduleMeta = this._loadNgModuleMetadata(moduleType, isSync, false);
return moduleMeta ? moduleMeta.toSummary() : null;
}
/** /**
* Loads an NgModule and all of its directives. This includes loading the exported directives of * Loads an NgModule and all of its directives. This includes loading the exported directives of
* imported modules, * imported modules,
@ -301,11 +326,10 @@ export class CompileMetadataResolver {
return null; return null;
} }
const declaredDirectives: cpl.CompileIdentifierMetadata[] = []; const declaredDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedDirectives: cpl.CompileIdentifierMetadata[] = []; const exportedNonModuleIdentifiers: cpl.CompileIdentifierMetadata[] = [];
const declaredPipes: cpl.CompileIdentifierMetadata[] = []; const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
const exportedPipes: cpl.CompileIdentifierMetadata[] = []; const importedModules: cpl.CompileNgModuleSummary[] = [];
const importedModules: cpl.CompileNgModuleMetadata[] = []; const exportedModules: cpl.CompileNgModuleSummary[] = [];
const exportedModules: cpl.CompileNgModuleMetadata[] = [];
const providers: any[] = []; const providers: any[] = [];
const entryComponents: cpl.CompileIdentifierMetadata[] = []; const entryComponents: cpl.CompileIdentifierMetadata[] = [];
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = []; const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
@ -327,12 +351,12 @@ export class CompileMetadataResolver {
} }
if (importedModuleType) { if (importedModuleType) {
const importedMeta = this._loadNgModuleMetadata(importedModuleType, isSync, false); const importedModuleSummary = this._loadNgModuleSummary(importedModuleType, isSync);
if (importedMeta === null) { if (!importedModuleSummary) {
throw new Error( throw new Error(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`); `Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
} }
importedModules.push(importedMeta); importedModules.push(importedModuleSummary);
} else { } else {
throw new Error( throw new Error(
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
@ -346,18 +370,12 @@ export class CompileMetadataResolver {
throw new Error( throw new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
} }
let identifier = const exportedModuleSummary = this._loadNgModuleSummary(exportedType, isSync);
this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType)); if (exportedModuleSummary) {
let exportedModuleMeta: cpl.CompileNgModuleMetadata; exportedModules.push(exportedModuleSummary);
if (this._directiveResolver.isDirective(exportedType)) {
exportedDirectives.push(identifier);
} else if (this._pipeResolver.isPipe(exportedType)) {
exportedPipes.push(identifier);
} else if (exportedModuleMeta = this._loadNgModuleMetadata(exportedType, isSync, false)) {
exportedModules.push(exportedModuleMeta);
} else { } else {
throw new Error( exportedNonModuleIdentifiers.push(
`Unexpected ${this._getTypeDescriptor(exportedType)} '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`); this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType)));
} }
}); });
} }
@ -395,6 +413,19 @@ export class CompileMetadataResolver {
}); });
} }
const exportedDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedPipes: cpl.CompileIdentifierMetadata[] = [];
exportedNonModuleIdentifiers.forEach((exportedId) => {
if (transitiveModule.directivesSet.has(exportedId.reference)) {
exportedDirectives.push(exportedId);
} else if (transitiveModule.pipesSet.has(exportedId.reference)) {
exportedPipes.push(exportedId);
} else {
throw new Error(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`);
}
});
// The providers of the module have to go last // The providers of the module have to go last
// so that they overwrite any other provider we already added. // so that they overwrite any other provider we already added.
if (meta.providers) { if (meta.providers) {
@ -444,29 +475,11 @@ export class CompileMetadataResolver {
id: meta.id, id: meta.id,
}); });
transitiveModule.modules.push(compileMeta); transitiveModule.modules.push(compileMeta.toInjectorSummary());
this._verifyModule(compileMeta);
this._ngModuleCache.set(moduleType, compileMeta); this._ngModuleCache.set(moduleType, compileMeta);
return compileMeta; return compileMeta;
} }
private _verifyModule(moduleMeta: cpl.CompileNgModuleMetadata) {
moduleMeta.exportedDirectives.forEach((dirIdentifier) => {
if (!moduleMeta.transitiveModule.directivesSet.has(dirIdentifier.reference)) {
throw new Error(
`Can't export directive ${stringify(dirIdentifier.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
}
});
moduleMeta.exportedPipes.forEach((pipeIdentifier) => {
if (!moduleMeta.transitiveModule.pipesSet.has(pipeIdentifier.reference)) {
throw new Error(
`Can't export pipe ${stringify(pipeIdentifier.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
}
});
}
private _getTypeDescriptor(type: Type<any>): string { private _getTypeDescriptor(type: Type<any>): string {
if (this._directiveResolver.isDirective(type)) { if (this._directiveResolver.isDirective(type)) {
return 'directive'; return 'directive';
@ -500,20 +513,20 @@ export class CompileMetadataResolver {
} }
private _getTransitiveNgModuleMetadata( private _getTransitiveNgModuleMetadata(
importedModules: cpl.CompileNgModuleMetadata[], importedModules: cpl.CompileNgModuleSummary[],
exportedModules: cpl.CompileNgModuleMetadata[]): cpl.TransitiveCompileNgModuleMetadata { exportedModules: cpl.CompileNgModuleSummary[]): cpl.TransitiveCompileNgModuleMetadata {
// collect `providers` / `entryComponents` from all imported and all exported modules // collect `providers` / `entryComponents` from all imported and all exported modules
const transitiveModules = getTransitiveModules(importedModules.concat(exportedModules), true); const transitiveModules = getTransitiveImportedModules(importedModules.concat(exportedModules));
const providers = flattenArray(transitiveModules.map((ngModule) => ngModule.providers)); const providers = flattenArray(transitiveModules.map((ngModule) => ngModule.providers));
const entryComponents = const entryComponents =
flattenArray(transitiveModules.map((ngModule) => ngModule.entryComponents)); flattenArray(transitiveModules.map((ngModule) => ngModule.entryComponents));
const transitiveExportedModules = getTransitiveModules(importedModules, false); const transitiveExportedModules = getTransitiveExportedModules(importedModules);
const directives = const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives)); flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes)); const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const loadingPromises = ListWrapper.flatten( const loadingPromises =
transitiveExportedModules.map(ngModule => ngModule.transitiveModule.loadingPromises)); ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises));
return new cpl.TransitiveCompileNgModuleMetadata( return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises); transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
} }
@ -562,6 +575,15 @@ export class CompileMetadataResolver {
return pipeMeta; return pipeMeta;
} }
getPipeSummary(pipeType: any): cpl.CompilePipeSummary {
const pipeSummary = this._pipeSummaryCache.get(pipeType);
if (!pipeSummary) {
throw new Error(
`Illegal state: getPipeSummary can only be called after loadNgModuleMetadata for a module that imports it. Pipe ${stringify(pipeType)}.`);
}
return pipeSummary;
}
private _loadPipeMetadata(pipeType: Type<any>): void { private _loadPipeMetadata(pipeType: Type<any>): void {
pipeType = resolveForwardRef(pipeType); pipeType = resolveForwardRef(pipeType);
const pipeMeta = this._pipeResolver.resolve(pipeType); const pipeMeta = this._pipeResolver.resolve(pipeType);
@ -575,6 +597,7 @@ export class CompileMetadataResolver {
pure: pipeMeta.pure pure: pipeMeta.pure
}); });
this._pipeCache.set(pipeType, meta); this._pipeCache.set(pipeType, meta);
this._pipeSummaryCache.set(pipeType, meta.toSummary());
} }
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]): private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
@ -790,17 +813,31 @@ export class CompileMetadataResolver {
} }
} }
function getTransitiveModules( function getTransitiveExportedModules(
modules: cpl.CompileNgModuleMetadata[], includeImports: boolean, modules: cpl.CompileNgModuleDirectiveSummary[],
targetModules: cpl.CompileNgModuleMetadata[] = [], targetModules: cpl.CompileNgModuleDirectiveSummary[] = [],
visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleMetadata[] { visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleDirectiveSummary[] {
modules.forEach((ngModule) => { modules.forEach((ngModule) => {
if (!visitedModules.has(ngModule.type.reference)) { if (!visitedModules.has(ngModule.type.reference)) {
visitedModules.add(ngModule.type.reference); visitedModules.add(ngModule.type.reference);
const nestedModules = includeImports ? getTransitiveExportedModules(ngModule.exportedModules, targetModules, visitedModules);
ngModule.importedModules.concat(ngModule.exportedModules) : // Add after recursing so imported/exported modules are before the module itself.
ngModule.exportedModules; // This is important for overwriting providers of imported modules!
getTransitiveModules(nestedModules, includeImports, targetModules, visitedModules); targetModules.push(ngModule);
}
});
return targetModules;
}
function getTransitiveImportedModules(
modules: cpl.CompileNgModuleInjectorSummary[],
targetModules: cpl.CompileNgModuleInjectorSummary[] = [],
visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleInjectorSummary[] {
modules.forEach((ngModule) => {
if (!visitedModules.has(ngModule.type.reference)) {
visitedModules.add(ngModule.type.reference);
const nestedModules = ngModule.importedModules.concat(ngModule.exportedModules);
getTransitiveImportedModules(nestedModules, targetModules, visitedModules);
// Add after recursing so imported/exported modules are before the module itself. // Add after recursing so imported/exported modules are before the module itself.
// This is important for overwriting providers of imported modules! // This is important for overwriting providers of imported modules!
targetModules.push(ngModule); targetModules.push(ngModule);

View File

@ -228,9 +228,9 @@ export class OfflineCompiler {
fileSuffix: string, targetStatements: o.Statement[]): string { fileSuffix: string, targetStatements: o.Statement[]): string {
const parsedAnimations = this._animationParser.parseComponent(compMeta); const parsedAnimations = this._animationParser.parseComponent(compMeta);
const directives = const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveMetadata(dir.reference)); directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map( const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeMetadata(pipe.reference)); pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const parsedTemplate = this._templateParser.parse( const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas, compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,

View File

@ -7,7 +7,7 @@
*/ */
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifierToken} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util'; import {ParseError, ParseSourceSpan} from './parse_util';
@ -440,7 +440,7 @@ function _normalizeProviders(
function _resolveProvidersFromDirectives( function _resolveProvidersFromDirectives(
directives: CompileDirectiveMetadata[], sourceSpan: ParseSourceSpan, directives: CompileDirectiveSummary[], sourceSpan: ParseSourceSpan,
targetErrors: ParseError[]): Map<any, ProviderAst> { targetErrors: ParseError[]): Map<any, ProviderAst> {
var providersByToken = new Map<any, ProviderAst>(); var providersByToken = new Map<any, ProviderAst>();
directives.forEach((directive) => { directives.forEach((directive) => {
@ -504,7 +504,7 @@ function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQ
return viewQueries; return viewQueries;
} }
function _getContentQueries(directives: CompileDirectiveMetadata[]): function _getContentQueries(directives: CompileDirectiveSummary[]):
Map<any, CompileQueryMetadata[]> { Map<any, CompileQueryMetadata[]> {
var contentQueries = new Map<any, CompileQueryMetadata[]>(); var contentQueries = new Map<any, CompileQueryMetadata[]>();
directives.forEach(directive => { directives.forEach(directive => {

View File

@ -149,7 +149,9 @@ export class RuntimeCompiler implements Compiler {
const moduleByDirective = new Map<any, CompileNgModuleMetadata>(); const moduleByDirective = new Map<any, CompileNgModuleMetadata>();
const templates = new Set<CompiledTemplate>(); const templates = new Set<CompiledTemplate>();
ngModule.transitiveModule.modules.forEach((localModuleMeta) => { ngModule.transitiveModule.modules.forEach((localModuleSummary) => {
const localModuleMeta =
this._metadataResolver.getNgModuleMetadata(localModuleSummary.type.reference);
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => { localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
moduleByDirective.set(dirIdentifier.reference, localModuleMeta); moduleByDirective.set(dirIdentifier.reference, localModuleMeta);
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference); const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
@ -165,7 +167,9 @@ export class RuntimeCompiler implements Compiler {
} }
}); });
}); });
ngModule.transitiveModule.modules.forEach((localModuleMeta) => { ngModule.transitiveModule.modules.forEach((localModuleSummary) => {
const localModuleMeta =
this._metadataResolver.getNgModuleMetadata(localModuleSummary.type.reference);
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => { localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference); const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
if (dirMeta.isComponent) { if (dirMeta.isComponent) {
@ -279,9 +283,9 @@ export class RuntimeCompiler implements Compiler {
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl); stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
const parsedAnimations = this._animationParser.parseComponent(compMeta); const parsedAnimations = this._animationParser.parseComponent(compMeta);
const directives = const directives =
template.directives.map(dir => this._metadataResolver.getDirectiveMetadata(dir.reference)); template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = template.ngModule.transitiveModule.pipes.map( const pipes = template.ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeMetadata(pipe.reference)); pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const parsedTemplate = this._templateParser.parse( const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas, compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
compMeta.type.name); compMeta.type.name);

View File

@ -8,7 +8,7 @@
import {SecurityContext} from '@angular/core'; import {SecurityContext} from '@angular/core';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, LiteralPrimitive, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, LiteralPrimitive, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
@ -52,16 +52,16 @@ export class BoundProperty {
* Parses bindings in templates and in the directive host area. * Parses bindings in templates and in the directive host area.
*/ */
export class BindingParser { export class BindingParser {
pipesByName: Map<string, CompilePipeMetadata> = new Map(); pipesByName: Map<string, CompilePipeSummary> = new Map();
constructor( constructor(
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig, private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeMetadata[], private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[],
private _targetErrors: ParseError[]) { private _targetErrors: ParseError[]) {
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe)); pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
} }
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveMetadata, sourceSpan: ParseSourceSpan): createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
BoundElementPropertyAst[] { BoundElementPropertyAst[] {
if (dirMeta.hostProperties) { if (dirMeta.hostProperties) {
const boundProps: BoundProperty[] = []; const boundProps: BoundProperty[] = [];
@ -79,7 +79,7 @@ export class BindingParser {
} }
} }
createDirectiveHostEventAsts(dirMeta: CompileDirectiveMetadata, sourceSpan: ParseSourceSpan): createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
BoundEventAst[] { BoundEventAst[] {
if (dirMeta.hostListeners) { if (dirMeta.hostListeners) {
const targetEventAsts: BoundEventAst[] = []; const targetEventAsts: BoundEventAst[] = [];

View File

@ -8,7 +8,7 @@
import {SecurityContext} from '@angular/core'; import {SecurityContext} from '@angular/core';
import {CompileDirectiveMetadata, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
import {AST} from '../expression_parser/ast'; import {AST} from '../expression_parser/ast';
import {ParseSourceSpan} from '../parse_util'; import {ParseSourceSpan} from '../parse_util';
import {LifecycleHooks} from '../private_import_core'; import {LifecycleHooks} from '../private_import_core';
@ -168,7 +168,7 @@ export class BoundDirectivePropertyAst implements TemplateAst {
*/ */
export class DirectiveAst implements TemplateAst { export class DirectiveAst implements TemplateAst {
constructor( constructor(
public directive: CompileDirectiveMetadata, public inputs: BoundDirectivePropertyAst[], public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[],
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
public sourceSpan: ParseSourceSpan) {} public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {

View File

@ -8,7 +8,7 @@
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core'; import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core';
import {CompileDirectiveMetadata, CompilePipeMetadata, CompileTemplateMetadata, CompileTokenMetadata, removeIdentifierDuplicates} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateMetadata, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, removeIdentifierDuplicates} from '../compile_metadata';
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
@ -90,8 +90,8 @@ export class TemplateParser {
@Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {} @Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {}
parse( parse(
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[], component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] { pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] {
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl); const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING); const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL); const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
@ -109,8 +109,8 @@ export class TemplateParser {
} }
tryParse( tryParse(
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[], component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
templateUrl: string): TemplateParseResult { templateUrl: string): TemplateParseResult {
return this.tryParseHtml( return this.tryParseHtml(
this.expandHtml(this._htmlParser.parse( this.expandHtml(this._htmlParser.parse(
@ -120,13 +120,13 @@ export class TemplateParser {
tryParseHtml( tryParseHtml(
htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, template: string, htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, template: string,
directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
schemas: SchemaMetadata[], templateUrl: string): TemplateParseResult { templateUrl: string): TemplateParseResult {
var result: TemplateAst[]; var result: TemplateAst[];
var errors = htmlAstWithErrors.errors; var errors = htmlAstWithErrors.errors;
if (htmlAstWithErrors.rootNodes.length > 0) { if (htmlAstWithErrors.rootNodes.length > 0) {
const uniqDirectives = removeIdentifierDuplicates(directives); const uniqDirectives = removeSummaryDuplicates(directives);
const uniqPipes = removeIdentifierDuplicates(pipes); const uniqPipes = removeSummaryDuplicates(pipes);
const providerViewContext = const providerViewContext =
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan); new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
let interpolationConfig: InterpolationConfig; let interpolationConfig: InterpolationConfig;
@ -200,14 +200,14 @@ export class TemplateParser {
class TemplateParseVisitor implements html.Visitor { class TemplateParseVisitor implements html.Visitor {
selectorMatcher = new SelectorMatcher(); selectorMatcher = new SelectorMatcher();
directivesIndex = new Map<CompileDirectiveMetadata, number>(); directivesIndex = new Map<CompileDirectiveSummary, number>();
ngContentCount: number = 0; ngContentCount: number = 0;
constructor( constructor(
public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[], public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[],
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry, private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) { private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) {
directives.forEach((directive: CompileDirectiveMetadata, index: number) => { directives.forEach((directive, index) => {
const selector = CssSelector.parse(directive.selector); const selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive); this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index); this.directivesIndex.set(directive, index);
@ -360,7 +360,8 @@ class TemplateParseVisitor implements html.Visitor {
componentDirectiveAst.directive.template)); componentDirectiveAst.directive.template));
const componentTemplate = providerContext.viewContext.component.template; const componentTemplate = providerContext.viewContext.component.template;
this._validateElementAnimationInputOutputs(elementProps, events, componentTemplate); this._validateElementAnimationInputOutputs(
elementProps, events, componentTemplate.toSummary());
} }
if (hasInlineTemplates) { if (hasInlineTemplates) {
@ -392,9 +393,9 @@ class TemplateParseVisitor implements html.Visitor {
private _validateElementAnimationInputOutputs( private _validateElementAnimationInputOutputs(
inputs: BoundElementPropertyAst[], outputs: BoundEventAst[], inputs: BoundElementPropertyAst[], outputs: BoundEventAst[],
template: CompileTemplateMetadata) { template: CompileTemplateSummary) {
const triggerLookup = new Set<string>(); const triggerLookup = new Set<string>();
template.animations.forEach(entry => { triggerLookup.add(entry.name); }); template.animations.forEach(entry => { triggerLookup.add(entry); });
const animationInputs = inputs.filter(input => input.isAnimation); const animationInputs = inputs.filter(input => input.isAnimation);
animationInputs.forEach(input => { animationInputs.forEach(input => {
@ -518,7 +519,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector): private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector):
{directives: CompileDirectiveMetadata[], matchElement: boolean} { {directives: CompileDirectiveSummary[], matchElement: boolean} {
// Need to sort the directives so that we get consistent results throughout, // Need to sort the directives so that we get consistent results throughout,
// as selectorMatcher uses Maps inside. // as selectorMatcher uses Maps inside.
// Also deduplicate directives as they might match more than one time! // Also deduplicate directives as they might match more than one time!
@ -538,12 +539,12 @@ class TemplateParseVisitor implements html.Visitor {
} }
private _createDirectiveAsts( private _createDirectiveAsts(
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveMetadata[], isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[], props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] { elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>(); const matchedReferences = new Set<string>();
let component: CompileDirectiveMetadata = null; let component: CompileDirectiveSummary = null;
const directiveAsts = directives.map((directive: CompileDirectiveMetadata) => { const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan( const sourceSpan = new ParseSourceSpan(
elementSourceSpan.start, elementSourceSpan.end, `Directive ${directive.type.name}`); elementSourceSpan.start, elementSourceSpan.end, `Directive ${directive.type.name}`);
if (directive.isComponent) { if (directive.isComponent) {
@ -837,3 +838,15 @@ const NON_BINDABLE_VISITOR = new NonBindableVisitor();
function _isEmptyTextNode(node: html.Node): boolean { function _isEmptyTextNode(node: html.Node): boolean {
return node instanceof html.Text && node.value.trim().length == 0; return node instanceof html.Text && node.value.trim().length == 0;
} }
export function removeSummaryDuplicates<T extends{type: CompileTypeMetadata}>(items: T[]): T[] {
const map = new Map<any, T>();
items.forEach((item) => {
if (!map.get(item.type.reference)) {
map.set(item.type.reference, item);
}
});
return Array.from(map.values());
}

View File

@ -7,7 +7,7 @@
*/ */
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util'; import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {DirectiveWrapperCompiler, DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import {DirectiveWrapperCompiler, DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
@ -54,8 +54,8 @@ export class CompileElement extends CompileNode {
constructor( constructor(
parent: CompileElement, view: CompileView, nodeIndex: number, renderNode: o.Expression, parent: CompileElement, view: CompileView, nodeIndex: number, renderNode: o.Expression,
sourceAst: TemplateAst, public component: CompileDirectiveMetadata, sourceAst: TemplateAst, public component: CompileDirectiveSummary,
private _directives: CompileDirectiveMetadata[], private _directives: CompileDirectiveSummary[],
private _resolvedProvidersArray: ProviderAst[], public hasViewContainer: boolean, private _resolvedProvidersArray: ProviderAst[], public hasViewContainer: boolean,
public hasEmbeddedView: boolean, references: ReferenceAst[], public hasEmbeddedView: boolean, references: ReferenceAst[],
private _targetDependencies: private _targetDependencies:

View File

@ -7,7 +7,7 @@
*/ */
import {CompilePipeMetadata} from '../compile_metadata'; import {CompilePipeSummary} from '../compile_metadata';
import {createPureProxy} from '../compiler_util/identifier_util'; import {createPureProxy} from '../compiler_util/identifier_util';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -39,7 +39,7 @@ export class CompilePipe {
instance: o.ReadPropExpr; instance: o.ReadPropExpr;
private _purePipeProxyCount = 0; private _purePipeProxyCount = 0;
constructor(public view: CompileView, public meta: CompilePipeMetadata) { constructor(public view: CompileView, public meta: CompilePipeSummary) {
this.instance = o.THIS_EXPR.prop(`_pipe_${meta.name}_${view.pipeCount++}`); this.instance = o.THIS_EXPR.prop(`_pipe_${meta.name}_${view.pipeCount++}`);
var deps = this.meta.type.diDeps.map((diDep) => { var deps = this.meta.type.diDeps.map((diDep) => {
if (diDep.token.reference === if (diDep.token.reference ===
@ -77,8 +77,8 @@ export class CompilePipe {
} }
} }
function _findPipeMeta(view: CompileView, name: string): CompilePipeMetadata { function _findPipeMeta(view: CompileView, name: string): CompilePipeSummary {
var pipeMeta: CompilePipeMetadata = null; var pipeMeta: CompilePipeSummary = null;
for (var i = view.pipeMetas.length - 1; i >= 0; i--) { for (var i = view.pipeMetas.length - 1; i >= 0; i--) {
var localPipeMeta = view.pipeMetas[i]; var localPipeMeta = view.pipeMetas[i];
if (localPipeMeta.name == name) { if (localPipeMeta.name == name) {

View File

@ -7,7 +7,7 @@
*/ */
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeSummary} from '../compile_metadata';
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter'; import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter';
import {createPureProxy} from '../compiler_util/identifier_util'; import {createPureProxy} from '../compiler_util/identifier_util';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
@ -82,7 +82,7 @@ export class CompileView implements NameResolver {
constructor( constructor(
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig, public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression, public pipeMetas: CompilePipeSummary[], public styles: o.Expression,
public animations: AnimationEntryCompileResult[], public viewIndex: number, public animations: AnimationEntryCompileResult[], public viewIndex: number,
public declarationElement: CompileElement, public templateVariableBindings: string[][]) { public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
this.createMethod = new CompileMethod(this); this.createMethod = new CompileMethod(this);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {LifecycleHooks} from '../private_import_core'; import {LifecycleHooks} from '../private_import_core';
@ -20,7 +20,7 @@ var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.
var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange); var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange);
export function bindDirectiveAfterContentLifecycleCallbacks( export function bindDirectiveAfterContentLifecycleCallbacks(
directiveMeta: CompileDirectiveMetadata, directiveInstance: o.Expression, directiveMeta: CompileDirectiveSummary, directiveInstance: o.Expression,
compileElement: CompileElement) { compileElement: CompileElement) {
var view = compileElement.view; var view = compileElement.view;
var lifecycleHooks = directiveMeta.type.lifecycleHooks; var lifecycleHooks = directiveMeta.type.lifecycleHooks;
@ -38,7 +38,7 @@ export function bindDirectiveAfterContentLifecycleCallbacks(
} }
export function bindDirectiveAfterViewLifecycleCallbacks( export function bindDirectiveAfterViewLifecycleCallbacks(
directiveMeta: CompileDirectiveMetadata, directiveInstance: o.Expression, directiveMeta: CompileDirectiveSummary, directiveInstance: o.Expression,
compileElement: CompileElement) { compileElement: CompileElement) {
var view = compileElement.view; var view = compileElement.view;
var lifecycleHooks = directiveMeta.type.lifecycleHooks; var lifecycleHooks = directiveMeta.type.lifecycleHooks;
@ -77,7 +77,7 @@ export function bindInjectableDestroyLifecycleCallbacks(
} }
export function bindPipeDestroyLifecycleCallbacks( export function bindPipeDestroyLifecycleCallbacks(
pipeMeta: CompilePipeMetadata, pipeInstance: o.Expression, view: CompileView) { pipeMeta: CompilePipeSummary, pipeInstance: o.Expression, view: CompileView) {
var onDestroyMethod = view.destroyMethod; var onDestroyMethod = view.destroyMethod;
if (pipeMeta.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) { if (pipeMeta.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(pipeInstance.callMethod('ngOnDestroy', []).toStmt()); onDestroyMethod.addStmt(pipeInstance.callMethod('ngOnDestroy', []).toStmt());

View File

@ -7,7 +7,7 @@
*/ */
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util'; import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
@ -72,7 +72,8 @@ export function injectFromViewParentInjector(
} }
export function getViewClassName( export function getViewClassName(
component: CompileDirectiveMetadata, embeddedTemplateIndex: number): string { component: CompileDirectiveSummary | CompileDirectiveMetadata,
embeddedTemplateIndex: number): string {
return `View_${component.type.name}${embeddedTemplateIndex}`; return `View_${component.type.name}${embeddedTemplateIndex}`;
} }

View File

@ -8,7 +8,7 @@
import {ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter'; import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util'; import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
@ -336,7 +336,7 @@ function _isNgContainer(node: CompileNode, view: CompileView): boolean {
function _mergeHtmlAndDirectiveAttrs( function _mergeHtmlAndDirectiveAttrs(
declaredHtmlAttrs: {[key: string]: string}, directives: CompileDirectiveMetadata[]): string[] { declaredHtmlAttrs: {[key: string]: string}, directives: CompileDirectiveSummary[]): string[] {
const mapResult: {[key: string]: string} = {}; const mapResult: {[key: string]: string} = {};
Object.keys(declaredHtmlAttrs).forEach(key => { mapResult[key] = declaredHtmlAttrs[key]; }); Object.keys(declaredHtmlAttrs).forEach(key => { mapResult[key] = declaredHtmlAttrs[key]; });
directives.forEach(directiveMeta => { directives.forEach(directiveMeta => {

View File

@ -9,7 +9,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
@ -36,7 +36,7 @@ export class ViewCompiler {
compileComponent( compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
pipes: CompilePipeMetadata[], pipes: CompilePipeSummary[],
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult { compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
const dependencies: const dependencies:
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency> = []; Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency> = [];

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata';
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry'; import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast';
@ -16,6 +16,7 @@ import {SchemaMetadata, SecurityContext, Type} from '@angular/core';
import {Console} from '@angular/core/src/console'; import {Console} from '@angular/core/src/console';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {Identifiers, identifierToken, resolveIdentifierToken} from '../../src/identifiers'; import {Identifiers, identifierToken, resolveIdentifierToken} from '../../src/identifiers';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
import {MockSchemaRegistry} from '../../testing/index'; import {MockSchemaRegistry} from '../../testing/index';
@ -31,9 +32,9 @@ const MOCK_SCHEMA_REGISTRY = [{
}]; }];
export function main() { export function main() {
var ngIf: CompileDirectiveMetadata; var ngIf: CompileDirectiveSummary;
var parse: ( var parse: (
template: string, directives: CompileDirectiveMetadata[], pipes?: CompilePipeMetadata[], template: string, directives: CompileDirectiveSummary[], pipes?: CompilePipeSummary[],
schemas?: SchemaMetadata[]) => TemplateAst[]; schemas?: SchemaMetadata[]) => TemplateAst[];
var console: ArrayConsole; var console: ArrayConsole;
@ -52,17 +53,19 @@ export function main() {
{moduleUrl: someModuleUrl, name: 'Root', reference: {} as Type<any>}), {moduleUrl: someModuleUrl, name: 'Root', reference: {} as Type<any>}),
isComponent: true isComponent: true
}); });
ngIf = CompileDirectiveMetadata.create({ ngIf = CompileDirectiveMetadata
selector: '[ngIf]', .create({
template: someTemplate, selector: '[ngIf]',
type: new CompileTypeMetadata( template: someTemplate,
{moduleUrl: someModuleUrl, name: 'NgIf', reference: {} as Type<any>}), type: new CompileTypeMetadata(
inputs: ['ngIf'] {moduleUrl: someModuleUrl, name: 'NgIf', reference: {} as Type<any>}),
}); inputs: ['ngIf']
})
.toSummary();
parse = parse =
(template: string, directives: CompileDirectiveMetadata[], (template: string, directives: CompileDirectiveSummary[],
pipes: CompilePipeMetadata[] = null, schemas: SchemaMetadata[] = []): TemplateAst[] => { pipes: CompilePipeSummary[] = null, schemas: SchemaMetadata[] = []): TemplateAst[] => {
if (pipes === null) { if (pipes === null) {
pipes = []; pipes = [];
} }
@ -462,25 +465,34 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
it('should not issue a warning when host attributes contain a valid property-bound animation trigger', it('should not issue a warning when host attributes contain a valid property-bound animation trigger',
() => { () => {
const animationEntries = [new CompileAnimationEntryMetadata('prop', [])]; const animationEntries = [new CompileAnimationEntryMetadata('prop', [])];
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
template: new CompileTemplateMetadata({animations: animationEntries}), .create({
type: new CompileTypeMetadata( selector: 'div',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), template: new CompileTemplateMetadata({animations: animationEntries}),
host: {'[@prop]': 'expr'} type: new CompileTypeMetadata({
}); moduleUrl: someModuleUrl,
name: 'DirA',
reference: {} as Type<any>
}),
host: {'[@prop]': 'expr'}
})
.toSummary();
humanizeTplAst(parse('<div></div>', [dirA])); humanizeTplAst(parse('<div></div>', [dirA]));
expect(console.warnings.length).toEqual(0); expect(console.warnings.length).toEqual(0);
}); });
it('should throw descriptive error when a host binding is not a string expression', () => { it('should throw descriptive error when a host binding is not a string expression', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'broken', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'broken',
host: {'[class.foo]': null} type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
host: {'[class.foo]': null}
})
.toSummary();
expect(() => { parse('<broken></broken>', [dirA]); }) expect(() => { parse('<broken></broken>', [dirA]); })
.toThrowError( .toThrowError(
@ -488,12 +500,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should throw descriptive error when a host event is not a string expression', () => { it('should throw descriptive error when a host event is not a string expression', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'broken', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'broken',
host: {'(click)': null} type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
host: {'(click)': null}
})
.toSummary();
expect(() => { parse('<broken></broken>', [dirA]); }) expect(() => { parse('<broken></broken>', [dirA]); })
.toThrowError( .toThrowError(
@ -553,12 +568,17 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
it('should allow events on explicit embedded templates that are emitted by a directive', it('should allow events on explicit embedded templates that are emitted by a directive',
() => { () => {
var dirA = CompileDirectiveMetadata.create({ var dirA = CompileDirectiveMetadata
selector: 'template', .create({
outputs: ['e'], selector: 'template',
type: new CompileTypeMetadata( outputs: ['e'],
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) type: new CompileTypeMetadata({
}); moduleUrl: someModuleUrl,
name: 'DirA',
reference: {} as Type<any>
})
})
.toSummary();
expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA]))).toEqual([
[EmbeddedTemplateAst], [EmbeddedTemplateAst],
[BoundEventAst, 'e', null, 'f'], [BoundEventAst, 'e', null, 'f'],
@ -591,21 +611,36 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
describe('directives', () => { describe('directives', () => {
it('should order directives by the directives array in the View and match them only once', it('should order directives by the directives array in the View and match them only once',
() => { () => {
var dirA = CompileDirectiveMetadata.create({ var dirA = CompileDirectiveMetadata
selector: '[a]', .create({
type: new CompileTypeMetadata( selector: '[a]',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) type: new CompileTypeMetadata({
}); moduleUrl: someModuleUrl,
var dirB = CompileDirectiveMetadata.create({ name: 'DirA',
selector: '[b]', reference: {} as Type<any>
type: new CompileTypeMetadata( })
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}) })
}); .toSummary();
var dirC = CompileDirectiveMetadata.create({ var dirB = CompileDirectiveMetadata
selector: '[c]', .create({
type: new CompileTypeMetadata( selector: '[b]',
{moduleUrl: someModuleUrl, name: 'DirC', reference: {} as Type<any>}) type: new CompileTypeMetadata({
}); moduleUrl: someModuleUrl,
name: 'DirB',
reference: {} as Type<any>
})
})
.toSummary();
var dirC = CompileDirectiveMetadata
.create({
selector: '[c]',
type: new CompileTypeMetadata({
moduleUrl: someModuleUrl,
name: 'DirC',
reference: {} as Type<any>
})
})
.toSummary();
expect(humanizeTplAst(parse('<div a c b a b>', [dirA, dirB, dirC]))).toEqual([ expect(humanizeTplAst(parse('<div a c b a b>', [dirA, dirB, dirC]))).toEqual([
[ElementAst, 'div'], [AttrAst, 'a', ''], [AttrAst, 'c', ''], [AttrAst, 'b', ''], [ElementAst, 'div'], [AttrAst, 'a', ''], [AttrAst, 'c', ''], [AttrAst, 'b', ''],
[AttrAst, 'a', ''], [AttrAst, 'b', ''], [DirectiveAst, dirA], [AttrAst, 'a', ''], [AttrAst, 'b', ''], [DirectiveAst, dirA],
@ -614,16 +649,22 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should locate directives in property bindings', () => { it('should locate directives in property bindings', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a=b]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) selector: '[a=b]',
}); type: new CompileTypeMetadata(
var dirB = CompileDirectiveMetadata.create({ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>})
selector: '[b]', })
type: new CompileTypeMetadata( .toSummary();
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}) var dirB =
}); CompileDirectiveMetadata
.create({
selector: '[b]',
type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>})
})
.toSummary();
expect(humanizeTplAst(parse('<div [a]="b">', [dirA, dirB]))).toEqual([ expect(humanizeTplAst(parse('<div [a]="b">', [dirA, dirB]))).toEqual([
[ElementAst, 'div'], [ElementAst, 'div'],
[BoundElementPropertyAst, PropertyBindingType.Property, 'a', 'b', null], [BoundElementPropertyAst, PropertyBindingType.Property, 'a', 'b', null],
@ -632,11 +673,14 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should locate directives in event bindings', () => { it('should locate directives in event bindings', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}) selector: '[a]',
}); type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>})
})
.toSummary();
expect(humanizeTplAst(parse('<div (a)="b">', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div (a)="b">', [dirA]))).toEqual([
[ElementAst, 'div'], [BoundEventAst, 'a', null, 'b'], [DirectiveAst, dirA] [ElementAst, 'div'], [BoundEventAst, 'a', null, 'b'], [DirectiveAst, dirA]
@ -644,12 +688,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should parse directive host properties', () => { it('should parse directive host properties', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
host: {'[a]': 'expr'} type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
host: {'[a]': 'expr'}
})
.toSummary();
expect(humanizeTplAst(parse('<div></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [DirectiveAst, dirA], [ElementAst, 'div'], [DirectiveAst, dirA],
[BoundElementPropertyAst, PropertyBindingType.Property, 'a', 'expr', null] [BoundElementPropertyAst, PropertyBindingType.Property, 'a', 'expr', null]
@ -657,24 +704,30 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should parse directive host listeners', () => { it('should parse directive host listeners', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
host: {'(a)': 'expr'} type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
host: {'(a)': 'expr'}
})
.toSummary();
expect(humanizeTplAst(parse('<div></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [DirectiveAst, dirA], [BoundEventAst, 'a', null, 'expr'] [ElementAst, 'div'], [DirectiveAst, dirA], [BoundEventAst, 'a', null, 'expr']
]); ]);
}); });
it('should parse directive properties', () => { it('should parse directive properties', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['aProp'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['aProp']
})
.toSummary();
expect(humanizeTplAst(parse('<div [aProp]="expr"></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div [aProp]="expr"></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [DirectiveAst, dirA], [ElementAst, 'div'], [DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'aProp', 'expr'] [BoundDirectivePropertyAst, 'aProp', 'expr']
@ -682,12 +735,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should parse renamed directive properties', () => { it('should parse renamed directive properties', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['b:a'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['b:a']
})
.toSummary();
expect(humanizeTplAst(parse('<div [a]="expr"></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div [a]="expr"></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [DirectiveAst, dirA], [ElementAst, 'div'], [DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'b', 'expr'] [BoundDirectivePropertyAst, 'b', 'expr']
@ -695,12 +751,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should parse literal directive properties', () => { it('should parse literal directive properties', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['a'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['a']
})
.toSummary();
expect(humanizeTplAst(parse('<div a="literal"></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div a="literal"></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [AttrAst, 'a', 'literal'], [DirectiveAst, dirA], [ElementAst, 'div'], [AttrAst, 'a', 'literal'], [DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'a', '"literal"'] [BoundDirectivePropertyAst, 'a', '"literal"']
@ -708,12 +767,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should favor explicit bound properties over literal properties', () => { it('should favor explicit bound properties over literal properties', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['a'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['a']
})
.toSummary();
expect(humanizeTplAst(parse('<div a="literal" [a]="\'literal2\'"></div>', [dirA]))) expect(humanizeTplAst(parse('<div a="literal" [a]="\'literal2\'"></div>', [dirA])))
.toEqual([ .toEqual([
[ElementAst, 'div'], [AttrAst, 'a', 'literal'], [DirectiveAst, dirA], [ElementAst, 'div'], [AttrAst, 'a', 'literal'], [DirectiveAst, dirA],
@ -722,12 +784,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should support optional directive properties', () => { it('should support optional directive properties', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['a'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['a']
})
.toSummary();
expect(humanizeTplAst(parse('<div></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [DirectiveAst, dirA] [ElementAst, 'div'], [DirectiveAst, dirA]
]); ]);
@ -795,23 +860,25 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
viewProviders?: CompileProviderMetadata[], viewProviders?: CompileProviderMetadata[],
deps?: string[], deps?: string[],
queries?: string[] queries?: string[]
} = {}): CompileDirectiveMetadata { } = {}): CompileDirectiveSummary {
var isComponent = !selector.startsWith('['); var isComponent = !selector.startsWith('[');
return CompileDirectiveMetadata.create({ return CompileDirectiveMetadata
selector: selector, .create({
type: new CompileTypeMetadata({ selector: selector,
moduleUrl: someModuleUrl, type: new CompileTypeMetadata({
name: selector, moduleUrl: someModuleUrl,
diDeps: deps.map(createDep), name: selector,
reference: selector as any as Type<any> diDeps: deps.map(createDep),
}), reference: selector as any as Type<any>
isComponent: isComponent, }),
template: new CompileTemplateMetadata({ngContentSelectors: []}), isComponent: isComponent,
providers: providers, template: new CompileTemplateMetadata({ngContentSelectors: []}),
viewProviders: viewProviders, providers: providers,
queries: queries.map( viewProviders: viewProviders,
(value) => new CompileQueryMetadata({selectors: [createToken(value)]})) queries: queries.map(
}); (value) => new CompileQueryMetadata({selectors: [createToken(value)]}))
})
.toSummary();
} }
beforeEach(() => { nextProviderId = 0; }); beforeEach(() => { nextProviderId = 0; });
@ -1051,12 +1118,15 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should assign references to directives via exportAs', () => { it('should assign references to directives via exportAs', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: '[a]',
exportAs: 'dirA' type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
exportAs: 'dirA'
})
.toSummary();
expect(humanizeTplAst(parse('<div a #a="dirA"></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div a #a="dirA"></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [ElementAst, 'div'],
[AttrAst, 'a', ''], [AttrAst, 'a', ''],
@ -1095,14 +1165,17 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should assign references with empty value to components', () => { it('should assign references with empty value to components', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
isComponent: true, .create({
type: new CompileTypeMetadata( selector: '[a]',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), isComponent: true,
exportAs: 'dirA', type: new CompileTypeMetadata(
template: new CompileTemplateMetadata({ngContentSelectors: []}) {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
}); exportAs: 'dirA',
template: new CompileTemplateMetadata({ngContentSelectors: []})
})
.toSummary();
expect(humanizeTplAst(parse('<div a #a></div>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div a #a></div>', [dirA]))).toEqual([
[ElementAst, 'div'], [ElementAst, 'div'],
[AttrAst, 'a', ''], [AttrAst, 'a', ''],
@ -1112,11 +1185,14 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should not locate directives in references', () => { it('should not locate directives in references', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) selector: '[a]',
}); type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>})
})
.toSummary();
expect(humanizeTplAst(parse('<div ref-a>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div ref-a>', [dirA]))).toEqual([
[ElementAst, 'div'], [ReferenceAst, 'a', null] [ElementAst, 'div'], [ReferenceAst, 'a', null]
]); ]);
@ -1159,11 +1235,14 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should not locate directives in variables', () => { it('should not locate directives in variables', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) selector: '[a]',
}); type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>})
})
.toSummary();
expect(humanizeTplAst(parse('<template let-a="b"></template>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<template let-a="b"></template>', [dirA]))).toEqual([
[EmbeddedTemplateAst], [VariableAst, 'a', 'b'] [EmbeddedTemplateAst], [VariableAst, 'a', 'b']
]); ]);
@ -1202,17 +1281,25 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
describe('directives', () => { describe('directives', () => {
it('should locate directives in property bindings', () => { it('should locate directives in property bindings', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a=b]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: '[a=b]',
inputs: ['a'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
var dirB = CompileDirectiveMetadata.create({ inputs: ['a']
selector: '[b]', })
type: new CompileTypeMetadata( .toSummary();
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}) var dirB = CompileDirectiveMetadata
}); .create({
selector: '[b]',
type: new CompileTypeMetadata({
moduleUrl: someModuleUrl,
name: 'DirB',
reference: {} as Type<any>
})
})
.toSummary();
expect(humanizeTplAst(parse('<div template="a b" b>', [dirA, dirB]))).toEqual([ expect(humanizeTplAst(parse('<div template="a b" b>', [dirA, dirB]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, dirA], [EmbeddedTemplateAst], [DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'a', 'b'], [ElementAst, 'div'], [AttrAst, 'b', ''], [BoundDirectivePropertyAst, 'a', 'b'], [ElementAst, 'div'], [AttrAst, 'b', ''],
@ -1221,22 +1308,32 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should not locate directives in variables', () => { it('should not locate directives in variables', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA = CompileDirectiveMetadata
selector: '[a]', .create({
type: new CompileTypeMetadata( selector: '[a]',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) type: new CompileTypeMetadata({
}); moduleUrl: someModuleUrl,
name: 'DirA',
reference: {} as Type<any>
})
})
.toSummary();
expect(humanizeTplAst(parse('<div template="let a=b">', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div template="let a=b">', [dirA]))).toEqual([
[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div'] [EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']
]); ]);
}); });
it('should not locate directives in references', () => { it('should not locate directives in references', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA = CompileDirectiveMetadata
selector: '[a]', .create({
type: new CompileTypeMetadata( selector: '[a]',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) type: new CompileTypeMetadata({
}); moduleUrl: someModuleUrl,
name: 'DirA',
reference: {} as Type<any>
})
})
.toSummary();
expect(humanizeTplAst(parse('<div ref-a>', [dirA]))).toEqual([ expect(humanizeTplAst(parse('<div ref-a>', [dirA]))).toEqual([
[ElementAst, 'div'], [ReferenceAst, 'a', null] [ElementAst, 'div'], [ReferenceAst, 'a', null]
]); ]);
@ -1265,28 +1362,32 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
beforeEach(() => { compCounter = 0; }); beforeEach(() => { compCounter = 0; });
function createComp( function createComp(
selector: string, ngContentSelectors: string[]): CompileDirectiveMetadata { selector: string, ngContentSelectors: string[]): CompileDirectiveSummary {
return CompileDirectiveMetadata.create({ return CompileDirectiveMetadata
selector: selector, .create({
isComponent: true, selector: selector,
type: new CompileTypeMetadata({ isComponent: true,
moduleUrl: someModuleUrl, type: new CompileTypeMetadata({
name: `SomeComp${compCounter++}`, moduleUrl: someModuleUrl,
reference: {} as Type<any> name: `SomeComp${compCounter++}`,
}), reference: {} as Type<any>
template: new CompileTemplateMetadata({ngContentSelectors: ngContentSelectors}) }),
}); template: new CompileTemplateMetadata({ngContentSelectors: ngContentSelectors})
})
.toSummary();
} }
function createDir(selector: string): CompileDirectiveMetadata { function createDir(selector: string): CompileDirectiveSummary {
return CompileDirectiveMetadata.create({ return CompileDirectiveMetadata
selector: selector, .create({
type: new CompileTypeMetadata({ selector: selector,
moduleUrl: someModuleUrl, type: new CompileTypeMetadata({
name: `SomeDir${compCounter++}`, moduleUrl: someModuleUrl,
reference: {} as Type<any> name: `SomeDir${compCounter++}`,
}) reference: {} as Type<any>
}); })
})
.toSummary();
} }
describe('project text nodes', () => { describe('project text nodes', () => {
@ -1454,12 +1555,15 @@ Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ER
}); });
it('should report invalid host property names', () => { it('should report invalid host property names', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
host: {'[invalidProp]': 'someProp'} type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
host: {'[invalidProp]': 'someProp'}
})
.toSummary();
expect(() => parse('<div></div>', [dirA])).toThrowError(`Template parse errors: expect(() => parse('<div></div>', [dirA])).toThrowError(`Template parse errors:
Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("[ERROR ->]<div></div>"): TestComp@0:0, Directive DirA`); Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("[ERROR ->]<div></div>"): TestComp@0:0, Directive DirA`);
}); });
@ -1471,30 +1575,39 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
it('should not throw on invalid property names if the property is used by a directive', it('should not throw on invalid property names if the property is used by a directive',
() => { () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['invalidProp'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['invalidProp']
})
.toSummary();
expect(() => parse('<div [invalid-prop]></div>', [dirA])).not.toThrow(); expect(() => parse('<div [invalid-prop]></div>', [dirA])).not.toThrow();
}); });
it('should not allow more than 1 component per element', () => { it('should not allow more than 1 component per element', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
isComponent: true, .create({
type: new CompileTypeMetadata( selector: 'div',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), isComponent: true,
template: new CompileTemplateMetadata({ngContentSelectors: []}) type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
var dirB = CompileDirectiveMetadata.create({ template: new CompileTemplateMetadata({ngContentSelectors: []})
selector: 'div', })
isComponent: true, .toSummary();
type: new CompileTypeMetadata( var dirB =
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}), CompileDirectiveMetadata
template: new CompileTemplateMetadata({ngContentSelectors: []}) .create({
}); selector: 'div',
isComponent: true,
type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
})
.toSummary();
expect(() => parse('<div>', [dirB, dirA])) expect(() => parse('<div>', [dirB, dirA]))
.toThrowError( .toThrowError(
`Template parse errors:\n` + `Template parse errors:\n` +
@ -1505,13 +1618,16 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
it('should not allow components or element bindings nor dom events on explicit embedded templates', it('should not allow components or element bindings nor dom events on explicit embedded templates',
() => { () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
isComponent: true, .create({
type: new CompileTypeMetadata( selector: '[a]',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), isComponent: true,
template: new CompileTemplateMetadata({ngContentSelectors: []}) type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
})
.toSummary();
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA])) expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
.toThrowError(`Template parse errors: .toThrowError(`Template parse errors:
Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "directives" section. ("<template [a]="b" [ERROR ->](e)="f"></template>"): TestComp@0:18 Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "directives" section. ("<template [a]="b" [ERROR ->](e)="f"></template>"): TestComp@0:18
@ -1520,13 +1636,16 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should not allow components or element bindings on inline embedded templates', () => { it('should not allow components or element bindings on inline embedded templates', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
isComponent: true, .create({
type: new CompileTypeMetadata( selector: '[a]',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), isComponent: true,
template: new CompileTemplateMetadata({ngContentSelectors: []}) type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
})
.toSummary();
expect(() => parse('<div *a="b"></div>', [dirA])).toThrowError(`Template parse errors: expect(() => parse('<div *a="b"></div>', [dirA])).toThrowError(`Template parse errors:
Components on an embedded template: DirA ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0 Components on an embedded template: DirA ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section. ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0`); Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section. ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0`);
@ -1678,18 +1797,24 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support directive', () => { it('should support directive', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: '[a]', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) selector: '[a]',
}); type: new CompileTypeMetadata(
var comp = CompileDirectiveMetadata.create({ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>})
selector: 'div', })
isComponent: true, .toSummary();
type: new CompileTypeMetadata( var comp =
{moduleUrl: someModuleUrl, name: 'ZComp', reference: {} as Type<any>}), CompileDirectiveMetadata
template: new CompileTemplateMetadata({ngContentSelectors: []}) .create({
}); selector: 'div',
isComponent: true,
type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'ZComp', reference: {} as Type<any>}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
})
.toSummary();
expect(humanizeTplAstSourceSpans(parse('<div a>', [dirA, comp]))).toEqual([ expect(humanizeTplAstSourceSpans(parse('<div a>', [dirA, comp]))).toEqual([
[ElementAst, 'div', '<div a>'], [AttrAst, 'a', '', 'a'], [ElementAst, 'div', '<div a>'], [AttrAst, 'a', '', 'a'],
[DirectiveAst, dirA, '<div a>'], [DirectiveAst, comp, '<div a>'] [DirectiveAst, dirA, '<div a>'], [DirectiveAst, comp, '<div a>']
@ -1697,16 +1822,22 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support directive in namespace', () => { it('should support directive in namespace', () => {
var tagSel = CompileDirectiveMetadata.create({ var tagSel =
selector: 'circle', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'elDir', reference: {} as Type<any>}) selector: 'circle',
}); type: new CompileTypeMetadata(
var attrSel = CompileDirectiveMetadata.create({ {moduleUrl: someModuleUrl, name: 'elDir', reference: {} as Type<any>})
selector: '[href]', })
type: new CompileTypeMetadata( .toSummary();
{moduleUrl: someModuleUrl, name: 'attrDir', reference: {} as Type<any>}) var attrSel =
}); CompileDirectiveMetadata
.create({
selector: '[href]',
type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'attrDir', reference: {} as Type<any>})
})
.toSummary();
expect(humanizeTplAstSourceSpans( expect(humanizeTplAstSourceSpans(
parse('<svg><circle /><use xlink:href="Port" /></svg>', [tagSel, attrSel]))) parse('<svg><circle /><use xlink:href="Port" /></svg>', [tagSel, attrSel])))
@ -1721,12 +1852,15 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support directive property', () => { it('should support directive property', () => {
var dirA = CompileDirectiveMetadata.create({ var dirA =
selector: 'div', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}), selector: 'div',
inputs: ['aProp'] type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}),
inputs: ['aProp']
})
.toSummary();
expect(humanizeTplAstSourceSpans(parse('<div [aProp]="foo"></div>', [dirA]))).toEqual([ expect(humanizeTplAstSourceSpans(parse('<div [aProp]="foo"></div>', [dirA]))).toEqual([
[ElementAst, 'div', '<div [aProp]="foo">'], [ElementAst, 'div', '<div [aProp]="foo">'],
[DirectiveAst, dirA, '<div [aProp]="foo">'], [DirectiveAst, dirA, '<div [aProp]="foo">'],
@ -1735,11 +1869,14 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should support endSourceSpan for elements', () => { it('should support endSourceSpan for elements', () => {
const tagSel = CompileDirectiveMetadata.create({ const tagSel =
selector: 'circle', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'elDir', reference: {} as Type<any>}) selector: 'circle',
}); type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'elDir', reference: {} as Type<any>})
})
.toSummary();
const result = parse('<circle></circle>', [tagSel]); const result = parse('<circle></circle>', [tagSel]);
const circle = result[0] as ElementAst; const circle = result[0] as ElementAst;
expect(circle.endSourceSpan).toBeDefined(); expect(circle.endSourceSpan).toBeDefined();
@ -1748,16 +1885,22 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should report undefined for endSourceSpan for elements without an end-tag', () => { it('should report undefined for endSourceSpan for elements without an end-tag', () => {
const ulSel = CompileDirectiveMetadata.create({ const ulSel =
selector: 'ul', CompileDirectiveMetadata
type: new CompileTypeMetadata( .create({
{moduleUrl: someModuleUrl, name: 'ulDir', reference: {} as Type<any>}) selector: 'ul',
}); type: new CompileTypeMetadata(
const liSel = CompileDirectiveMetadata.create({ {moduleUrl: someModuleUrl, name: 'ulDir', reference: {} as Type<any>})
selector: 'li', })
type: new CompileTypeMetadata( .toSummary();
{moduleUrl: someModuleUrl, name: 'liDir', reference: {} as Type<any>}) const liSel =
}); CompileDirectiveMetadata
.create({
selector: 'li',
type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'liDir', reference: {} as Type<any>})
})
.toSummary();
const result = parse('<ul><li><li></ul>', [ulSel, liSel]); const result = parse('<ul><li><li></ul>', [ulSel, liSel]);
const ul = result[0] as ElementAst; const ul = result[0] as ElementAst;
const li = ul.children[0] as ElementAst; const li = ul.children[0] as ElementAst;
@ -1767,11 +1910,12 @@ Property binding a not used by any directive on an embedded template. Make sure
describe('pipes', () => { describe('pipes', () => {
it('should allow pipes that have been defined as dependencies', () => { it('should allow pipes that have been defined as dependencies', () => {
var testPipe = new CompilePipeMetadata({ var testPipe =
name: 'test', new CompilePipeMetadata({
type: new CompileTypeMetadata( name: 'test',
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>}) type: new CompileTypeMetadata(
}); {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type<any>})
}).toSummary();
expect(() => parse('{{a | test}}', [], [testPipe])).not.toThrow(); expect(() => parse('{{a | test}}', [], [testPipe])).not.toThrow();
}); });