feat(ivy): generate pipe references and definitions (#22034)

PR Close #22034
This commit is contained in:
Chuck Jazdzewski 2018-02-05 17:31:12 -08:00 committed by Victor Berchet
parent ee60bb5b36
commit 99909bbf2c
9 changed files with 1108 additions and 674 deletions

View File

@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {ParseError} from '../parse_util';
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
@ -356,14 +357,22 @@ export class AotCompiler {
error(
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
const {template: parsedTemplate} =
const {template: parsedTemplate, pipes: parsedPipes} =
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
compileIvyComponent(
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
} else {
compileIvyDirective(context, directiveMetadata, this._reflector);
}
});
pipes.forEach(pipeType => {
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
if (pipeMetadata) {
compileIvyPipe(context, pipeMetadata, this._reflector);
}
});
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
if (context.statements && context.statements.length > 0) {

View File

@ -11,7 +11,7 @@ import {OutputContext, error} from './util';
const CONSTANT_PREFIX = '_c';
export const enum DefinitionKind {Injector, Directive, Component}
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
/**
* A node that is a place-holder that allows the node to be replaced when the actual
@ -51,6 +51,7 @@ export class ConstantPool {
private injectorDefinitions = new Map<any, FixupExpression>();
private directiveDefinitions = new Map<any, FixupExpression>();
private componentDefinitions = new Map<any, FixupExpression>();
private pipeDefinitions = new Map<any, FixupExpression>();
private nextNameIndex = 0;
@ -75,18 +76,19 @@ export class ConstantPool {
return fixup;
}
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
const declarations = kind == DefinitionKind.Component ?
this.componentDefinitions :
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
let fixup = declarations.get(type);
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
o.Expression {
const definitions = this.definitionsOf(kind);
let fixup = definitions.get(type);
let newValue = false;
if (!fixup) {
const property = kind == DefinitionKind.Component ?
'ngComponentDef' :
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
const property = this.propertyNameOf(kind);
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
declarations.set(type, fixup);
} else if (!fixup.shared) {
definitions.set(type, fixup);
newValue = true;
}
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
const name = this.freshName();
this.statements.push(
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
@ -104,6 +106,36 @@ export class ConstantPool {
*/
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
private definitionsOf(kind: DefinitionKind): Map<any, FixupExpression> {
switch (kind) {
case DefinitionKind.Component:
return this.componentDefinitions;
case DefinitionKind.Directive:
return this.directiveDefinitions;
case DefinitionKind.Injector:
return this.injectorDefinitions;
case DefinitionKind.Pipe:
return this.pipeDefinitions;
}
error(`Unknown definition kind ${kind}`);
return this.componentDefinitions;
}
public propertyNameOf(kind: DefinitionKind): string {
switch (kind) {
case DefinitionKind.Component:
return 'ngComponentDef';
case DefinitionKind.Directive:
return 'ngDirectiveDef';
case DefinitionKind.Injector:
return 'ngInjectorDef';
case DefinitionKind.Pipe:
return 'ngPipeDef';
}
error(`Unknown definition kind ${kind}`);
return '<unknown>';
}
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
private keyOf(expression: o.Expression) {

View File

@ -435,6 +435,173 @@ export class AstTransformer implements AstVisitor {
}
}
// A transformer that only creates new nodes if the transformer makes a change or
// a change is made a child node.
export class AstMemoryEfficientTransformer implements AstVisitor {
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
visitInterpolation(ast: Interpolation, context: any): Interpolation {
const expressions = this.visitAll(ast.expressions);
if (expressions !== ast.expressions)
return new Interpolation(ast.span, ast.strings, expressions);
return ast;
}
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST { return ast; }
visitPropertyRead(ast: PropertyRead, context: any): AST {
const receiver = ast.receiver.visit(this);
if (receiver !== ast.receiver) {
return new PropertyRead(ast.span, receiver, ast.name);
}
return ast;
}
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
const receiver = ast.receiver.visit(this);
const value = ast.value.visit(this);
if (receiver !== ast.receiver || value !== ast.value) {
return new PropertyWrite(ast.span, receiver, ast.name, value);
}
return ast;
}
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
const receiver = ast.receiver.visit(this);
if (receiver !== ast.receiver) {
return new SafePropertyRead(ast.span, receiver, ast.name);
}
return ast;
}
visitMethodCall(ast: MethodCall, context: any): AST {
const receiver = ast.receiver.visit(this);
if (receiver !== ast.receiver) {
return new MethodCall(ast.span, receiver, ast.name, this.visitAll(ast.args));
}
return ast;
}
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
const receiver = ast.receiver.visit(this);
const args = this.visitAll(ast.args);
if (receiver !== ast.receiver || args !== ast.args) {
return new SafeMethodCall(ast.span, receiver, ast.name, args);
}
return ast;
}
visitFunctionCall(ast: FunctionCall, context: any): AST {
const target = ast.target && ast.target.visit(this);
const args = this.visitAll(ast.args);
if (target !== ast.target || args !== ast.args) {
return new FunctionCall(ast.span, target, args);
}
return ast;
}
visitLiteralArray(ast: LiteralArray, context: any): AST {
const expressions = this.visitAll(ast.expressions);
if (expressions !== ast.expressions) {
return new LiteralArray(ast.span, expressions);
}
return ast;
}
visitLiteralMap(ast: LiteralMap, context: any): AST {
const values = this.visitAll(ast.values);
if (values !== ast.values) {
return new LiteralMap(ast.span, ast.keys, values);
}
return ast;
}
visitBinary(ast: Binary, context: any): AST {
const left = ast.left.visit(this);
const right = ast.right.visit(this);
if (left !== ast.left || right !== ast.right) {
return new Binary(ast.span, ast.operation, left, right);
}
return ast;
}
visitPrefixNot(ast: PrefixNot, context: any): AST {
const expression = ast.expression.visit(this);
if (expression !== ast.expression) {
return new PrefixNot(ast.span, expression);
}
return ast;
}
visitNonNullAssert(ast: NonNullAssert, context: any): AST {
const expression = ast.expression.visit(this);
if (expression !== ast.expression) {
return new NonNullAssert(ast.span, expression);
}
return ast;
}
visitConditional(ast: Conditional, context: any): AST {
const condition = ast.condition.visit(this);
const trueExp = ast.trueExp.visit(this);
const falseExp = ast.falseExp.visit(this);
if (condition !== ast.condition || trueExp !== ast.trueExp || falseExp !== falseExp) {
return new Conditional(ast.span, condition, trueExp, falseExp);
}
return ast;
}
visitPipe(ast: BindingPipe, context: any): AST {
const exp = ast.exp.visit(this);
const args = this.visitAll(ast.args);
if (exp !== ast.exp || args !== ast.args) {
return new BindingPipe(ast.span, exp, ast.name, args);
}
return ast;
}
visitKeyedRead(ast: KeyedRead, context: any): AST {
const obj = ast.obj.visit(this);
const key = ast.key.visit(this);
if (obj !== ast.obj || key !== ast.key) {
return new KeyedRead(ast.span, obj, key);
}
return ast;
}
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
const obj = ast.obj.visit(this);
const key = ast.key.visit(this);
const value = ast.value.visit(this);
if (obj !== ast.obj || key !== ast.key || value !== ast.value) {
return new KeyedWrite(ast.span, obj, key, value);
}
return ast;
}
visitAll(asts: any[]): any[] {
const res = new Array(asts.length);
let modified = false;
for (let i = 0; i < asts.length; ++i) {
const original = asts[i];
const value = original.visit(this);
res[i] = value;
modified = modified || value !== original;
}
return modified ? res : asts;
}
visitChain(ast: Chain, context: any): AST {
const expressions = this.visitAll(ast.expressions);
if (expressions !== ast.expressions) {
return new Chain(ast.span, expressions);
}
return ast;
}
visitQuote(ast: Quote, context: any): AST { return ast; }
}
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
function visit(ast: AST) {
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);

View File

@ -21,6 +21,7 @@ export class Identifiers {
/* Methods */
static NEW_METHOD = 'n';
static HOST_BINDING_METHOD = 'h';
static TRANSFORM_METHOD = 'transform';
/* Instructions */
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
@ -63,8 +64,16 @@ export class Identifiers {
static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE};
static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE};
static pipeBind1: o.ExternalReference = {name: 'ɵpb1', moduleName: CORE};
static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE};
static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE};
static pipeBind4: o.ExternalReference = {name: 'ɵpb4', moduleName: CORE};
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
@ -88,5 +97,7 @@ export class Identifiers {
moduleName: CORE,
};
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
}

View File

@ -0,0 +1,50 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {DefinitionKind} from '../constant_pool';
import * as o from '../output/output_ast';
import {OutputContext, error} from '../util';
import {Identifiers as R3} from './r3_identifiers';
import {createFactory} from './r3_view_compiler';
export function compilePipe(
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. 'type: MyPipe`
definitionMapValues.push(
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
const templateFactory = createFactory(pipe.type, outputCtx, reflector);
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
// e.g. pure: true
if (pipe.pure) {
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
}
const className = identifierName(pipe.type) !;
className || error(`Cannot resolve the name of ${pipe.type}`);
outputCtx.statements.push(new o.ClassStmt(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe),
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap(
definitionMapValues)]))],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
}

View File

@ -8,9 +8,9 @@
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {ConstantPool, DefinitionKind} from '../constant_pool';
import {AST} from '../expression_parser/ast';
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import {LifecycleHooks} from '../lifecycle_reflector';
import * as o from '../output/output_ast';
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
import {Identifiers as R3} from './r3_identifiers';
/** Name of the context parameter passed into a template function */
const CONTEXT_NAME = 'ctx';
@ -62,7 +63,7 @@ export function compileDirective(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ 'ngDirectiveDef',
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
@ -73,8 +74,8 @@ export function compileDirective(
}
export function compileComponent(
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
reflector: CompileReflector) {
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
template: TemplateAst[], reflector: CompileReflector) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. `type: MyApp`
@ -112,10 +113,11 @@ export function compileComponent(
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
const templateTypeName = component.type.reference.name;
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
const pipeMap = new Map(pipes.map<[string, CompilePipeSummary]>(pipe => [pipe.name, pipe]));
const templateFunctionExpression =
new TemplateDefinitionBuilder(
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
component.template !.ngContentSelectors, templateTypeName, templateName)
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap)
.buildTemplateFunction(template, []);
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
@ -143,7 +145,7 @@ export function compileComponent(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ 'ngComponentDef',
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
@ -199,6 +201,22 @@ function interpolate(args: o.Expression[]): o.Expression {
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
}
function pipeBinding(args: o.Expression[]): o.ExternalReference {
switch (args.length) {
case 0:
// The first parameter to pipeBind is always the value to be transformed followed
// by arg.length arguments so the total number of arguments to pipeBind are
// arg.length + 1.
return R3.pipeBind1;
case 1:
return R3.pipeBind2;
case 2:
return R3.pipeBind3;
default:
return R3.pipeBindV;
}
}
class BindingScope {
private map = new Map<string, o.Expression>();
private referenceNameIndex = 0;
@ -219,10 +237,10 @@ class BindingScope {
return null;
}
set(name: string, variableName: string): BindingScope {
set(name: string, value: o.Expression): BindingScope {
!this.map.has(name) ||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
this.map.set(name, o.variable(variableName));
this.map.set(name, value);
return this;
}
@ -236,7 +254,7 @@ class BindingScope {
}
}
const ROOT_SCOPE = new BindingScope(null).set('$event', '$event');
const ROOT_SCOPE = new BindingScope(null).set('$event', o.variable('$event'));
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _dataIndex = 0;
@ -251,6 +269,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _postfix: o.Statement[] = [];
private _contentProjections: Map<NgContentAst, NgContentInfo>;
private _projectionDefinitionIndex = 0;
private _pipeConverter: PipeConverter;
private unsupported = unsupported;
private invalid = invalid;
@ -258,7 +277,23 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private outputCtx: OutputContext, private constantPool: ConstantPool,
private reflector: CompileReflector, private contextParameter: string,
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
private contextName: string|null, private templateName: string|null) {}
private contextName: string|null, private templateName: string|null,
private pipes: Map<string, CompilePipeSummary>) {
this._pipeConverter =
new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => {
bindingScope.set(localName, value);
const pipe = pipes.get(name) !;
pipe || error(`Could not find pipe ${name}`);
const pipeDefinition = constantPool.getDefinition(
pipe.type.reference, DefinitionKind.Pipe, outputCtx, /* forceShared */ true);
this._creationMode.push(
o.importExpr(R3.pipe)
.callFn([
o.literal(slot), pipeDefinition, pipeDefinition.callMethod(R3.NEW_METHOD, [])
])
.toStmt());
});
}
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
// Create variable bindings
@ -272,7 +307,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
]);
// Add the reference to the local scope.
this.bindingScope.set(variableName, scopedName);
this.bindingScope.set(variableName, o.variable(scopedName));
// Declare the local variable in binding mode
this._bindingMode.push(declaration);
@ -332,8 +367,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
o.INFERRED_TYPE, null, this.templateName);
}
// LocalResolver
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
// TemplateAstVisitor
visitNgContent(ast: NgContentAst) {
const info = this._contentProjections.get(ast) !;
info || error(`Expected ${ast.sourceSpan} to be included in content projection collection`);
@ -361,6 +398,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
};
}
// TemplateAstVisitor
visitElement(ast: ElementAst) {
let bindingCount = 0;
const elementIndex = this.allocateDataSlot();
@ -410,7 +448,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
this.bindingScope.set(reference.name, variableName);
this.bindingScope.set(reference.name, o.variable(variableName));
return [reference.name, reference.originalValue];
})).map(value => o.literal(value));
parameters.push(
@ -434,18 +472,14 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
if (input.isAnimation) {
this.unsupported('animations');
}
// TODO(chuckj): Built-in transform?
const convertedBinding = convertPropertyBinding(
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
const parameters =
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
const parameters = [o.literal(elementIndex), o.literal(input.name), convertedBinding];
const instruction = BINDING_INSTRUCTION_MAP[input.type];
if (instruction) {
// TODO(chuckj): runtime: security context?
this.instruction(
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), convertedBinding.currValExpr);
o.literal(input.name), convertedBinding);
} else {
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
}
@ -479,13 +513,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
// Bindings
for (const input of directive.inputs) {
const convertedBinding = convertPropertyBinding(
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
this.instruction(
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
o.literal(input.templateName),
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
}
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
@ -501,6 +532,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
}
// TemplateAstVisitor
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
const templateIndex = this.allocateDataSlot();
@ -539,7 +571,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
const templateVisitor = new TemplateDefinitionBuilder(
this.outputCtx, this.constantPool, this.reflector, templateContext,
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
templateName);
templateName, this.pipes);
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
}
@ -551,6 +583,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
readonly visitElementProperty = invalid;
readonly visitAttr = invalid;
// TemplateAstVisitor
visitBoundText(ast: BoundTextAst) {
const nodeIndex = this.allocateDataSlot();
@ -563,6 +596,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
}
// TemplateAstVisitor
visitText(ast: TextAst) {
// Text is defined in creation mode only.
this.instruction(
@ -600,8 +634,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const pipesConvertedValue = value.visit(this._pipeConverter);
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
interpolate);
this._refreshMode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
}
@ -611,7 +647,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
}
function createFactory(
export function createFactory(
type: CompileTypeMetadata, outputCtx: OutputContext,
reflector: CompileReflector): o.FunctionExpr {
let args: o.Expression[] = [];
@ -652,6 +688,35 @@ function createFactory(
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
}
class PipeConverter extends AstMemoryEfficientTransformer {
private pipeSlots = new Map<string, number>();
constructor(
private allocateSlot: () => number,
private definePipe:
(name: string, localName: string, slot: number, value: o.Expression) => void) {
super();
}
// AstMemoryEfficientTransformer
visitPipe(ast: BindingPipe, context: any): AST {
// Allocate a slot to create the pipe
let slot = this.pipeSlots.get(ast.name);
if (slot == null) {
slot = this.allocateSlot();
this.pipeSlots.set(ast.name, slot);
}
const slotPseudoLocal = `PIPE:${slot}`;
const target = new PropertyRead(ast.span, new ImplicitReceiver(ast.span), slotPseudoLocal);
const bindingId = pipeBinding(ast.args);
this.definePipe(ast.name, slotPseudoLocal, slot, o.importExpr(bindingId));
const value = ast.exp.visit(this);
const args = this.visitAll(ast.args);
return new FunctionCall(
ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]);
}
}
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
throw new Error(
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as ts from 'typescript';
import {ConstantPool} from '../../src/constant_pool';
import * as o from '../../src/output/output_ast';
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
import {OutputContext} from '../../src/util';
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
@ -173,7 +174,7 @@ export function compile(
// to generate a template definition.
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
const fakeOutputContext: OutputContext = {
genFilePath: 'fakeFactory.ts',
@ -193,20 +194,20 @@ export function compile(
constantPool: new ConstantPool()
};
// Load All directives
for (const directive of directives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
// Load all directives and pipes
for (const pipeOrDirective of pipesOrDirectives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
}
// Compile the directives.
for (const directive of directives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
for (const pipeOrDirective of pipesOrDirectives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
if (!module || !module.type.reference.filePath.startsWith('/app')) {
continue;
}
if (resolver.isDirective(directive)) {
const metadata = resolver.getDirectiveMetadata(directive);
if (resolver.isDirective(pipeOrDirective)) {
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
if (metadata.isComponent) {
const fakeUrl = 'ng://fake-template-url.html';
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
@ -217,11 +218,16 @@ export function compile(
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
const parsedTemplate = templateParser.parse(
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
compileComponent(
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector);
} else {
compileDirective(fakeOutputContext, metadata, staticReflector);
}
} else if (resolver.isPipe(pipeOrDirective)) {
const metadata = resolver.getPipeMetadata(pipeOrDirective);
if (metadata) {
compilePipe(fakeOutputContext, metadata, staticReflector);
}
}
}

View File

@ -0,0 +1,716 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MockDirectory, setup} from '../aot/test_util';
import {compile, expectEmit} from './mock_compile';
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler compliance', () => {
const angularFiles = setup({
compileAngular: true,
compileAnimations: false,
compileCommon: true,
});
describe('elements', () => {
it('should translate DOM structure', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'div', $c1$);
$r3$.ɵT(1, 'Hello ');
$r3$.ɵE(2, 'b');
$r3$.ɵT(3, 'World');
$r3$.ɵe();
$r3$.ɵT(4, '!');
$r3$.ɵe();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
});
describe('components & directives', () => {
it('should instantiate directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'child', template: 'child-view'})
export class ChildComponent {}
@Directive({selector: '[some-directive]'})
export class SomeDirective {}
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
export class MyComponent {}
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// ChildComponent definition should be:
const ChildComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: ChildComponent,
tag: 'child',
factory: function ChildComponent_Factory() { return new ChildComponent(); },
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵT(0, 'child-view');
}
}
});`;
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
factory: function SomeDirective_Factory() {return new SomeDirective(); }
});
`;
// MyComponent definition should be:
const MyComponentDefinition = `
const $c1$ = ['some-directive', ''];
const $c2$ = [SomeDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
$r3$.ɵe();
$r3$.ɵT(3, '!');
}
ChildComponent.ngComponentDef.h(1, 0);
SomeDirective.ngDirectiveDef.h(2, 0);
$r3$.ɵr(1, 0);
$r3$.ɵr(2, 0);
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
});
it('should support structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({selector: '[if]'})
export class IfDirective {
constructor(template: TemplateRef<any>) { }
}
@Component({
selector: 'my-component',
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
})
export class MyComponent {
salutation = 'Hello';
}
@NgModule({declarations: [IfDirective, MyComponent]})
export class MyModule {}
`
}
};
const IfDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: IfDirective,
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
});`;
const MyComponentDefinition = `
const $c1$ = ['foo', ''];
const $c2$ = [IfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul', null, null, $c1$);
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
$r3$.ɵe();
}
const $foo$ = $r3$.ɵld(1);
IfDirective.ngDirectiveDef.h(3,2);
$r3$.ɵcR(2);
$r3$.ɵr(3,2);
$r3$.ɵcr();
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
$r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
it('should support content projection', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
export class SimpleComponent {}
@Component({
selector: 'complex',
template: \`
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
<div id="second"><ng-content select="span[title=toSecond]"></ng-content></div>\`
})
export class ComplexComponent { }
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
export class MyModule {}
@Component({
selector: 'my-app',
template: '<simple>content</simple> <complex></complex>'
})
export class MyApp {}
`
}
};
const SimpleComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleComponent,
tag: 'simple',
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵe();
}
}
});`;
const ComplexComponentDefinition = `
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
const $c2$ = ['id','first'];
const $c3$ = ['id','second'];
static ngComponentDef = $r3$.ɵdefineComponent({
type: ComplexComponent,
tag: 'complex',
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0, $c1$);
$r3$.ɵE(1, 'div', $c2$);
$r3$.ɵP(2, 0, 1);
$r3$.ɵe();
$r3$.ɵE(3, 'div', $c3$);
$r3$.ɵP(4, 0, 2);
$r3$.ɵe();
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
expectEmit(
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
});
describe('pipes', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
@Pipe({
name: 'myPipe',
pure: false
})
export class MyPipe implements PipeTransform,
OnDestroy {
transform(value: any, ...args: any[]) { return value; }
ngOnDestroy(): void { }
}
@Pipe({
name: 'myPurePipe',
pure: true,
})
export class MyPurePipe implements PipeTransform {
transform(value: any, ...args: any[]) { return value; }
}
@Component({selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}'})
export class MyApp {
name = 'World';
size = 0;
}
@NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
export class MyModule {}
`
}
};
it('should render pipes', () => {
const MyPipeDefinition = `
static ngPipeDef = $r3$.ɵdefinePipe(
{type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }});
`;
const MyPurePipeDefinition = `
static ngPipeDef = $r3$.ɵdefinePipe({
type: MyPurePipe,
factory: function MyPurePipe_Factory() { return new MyPurePipe(); },
pure: true
});`;
const MyAppDefinition = `
const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef;
const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
tag: 'my-app',
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵT(0);
$r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n());
$r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n());
}
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), ''));
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyPipeDefinition, 'Invalid pipe definition');
expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition');
expectEmit(source, MyAppDefinition, 'Invalid MyApp definition');
});
});
it('local reference', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = ['user', ''];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'input', null, null, $c1$);
$r3$.ɵe();
$r3$.ɵT(2);
}
const $user$ = $r3$.ɵld(1);
$r3$.ɵt(2, $r3$.ɵi1('Hello ', $user$.value, '!'));
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
describe('lifecycle hooks', () => {
const files = {
app: {
'spec.ts': `
import {Component, Input, NgModule} from '@angular/core';
let events: string[] = [];
@Component({selector: 'lifecycle-comp', template: ''})
export class LifecycleComp {
@Input('name') nameMin: string;
ngOnChanges() { events.push('changes' + this.nameMin); }
ngOnInit() { events.push('init' + this.nameMin); }
ngDoCheck() { events.push('check' + this.nameMin); }
ngAfterContentInit() { events.push('content init' + this.nameMin); }
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
ngAfterViewInit() { events.push('view init' + this.nameMin); }
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
ngOnDestroy() { events.push(this.nameMin); }
}
@Component({
selector: 'simple-layout',
template: \`
<lifecycle-comp [name]="name1"></lifecycle-comp>
<lifecycle-comp [name]="name2"></lifecycle-comp>
\`
})
export class SimpleLayout {
name1 = '1';
name2 = '2';
}
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
export class LifecycleModule {}
`
}
};
it('should gen hooks with a few simple components', () => {
const LifecycleCompDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: LifecycleComp,
tag: 'lifecycle-comp',
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
inputs: {nameMin: 'name'},
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
});`;
const SimpleLayoutDefinition = `
const $c1$ = LifecycleComp.ngComponentDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleLayout,
tag: 'simple-layout',
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, LifecycleComp);
$r3$.ɵe();
$r3$.ɵE(2, LifecycleComp);
$r3$.ɵe();
}
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
$c1$.h(1, 0);
$c1$.h(3, 2);
$r3$.ɵr(1, 0);
$r3$.ɵr(3, 2);
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
});
});
describe('template variables', () => {
const shared = {
shared: {
'for_of.ts': `
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
export interface ForOfContext {
$implicit: any;
index: number;
even: boolean;
odd: boolean;
}
@Directive({selector: '[forOf]'})
export class ForOfDirective {
private previous: any[];
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
@Input() forOf: any[];
ngOnChanges(simpleChanges: SimpleChanges) {
if ('forOf' in simpleChanges) {
this.update();
}
}
ngDoCheck(): void {
const previous = this.previous;
const current = this.forOf;
if (!previous || previous.length != current.length ||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
this.update();
}
}
private update() {
// TODO(chuckj): Not implemented yet
// this.view.clear();
if (this.forOf) {
const current = this.forOf;
for (let i = 0; i < current.length; i++) {
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
// TODO(chuckj): Not implemented yet
// this.view.createEmbeddedView(this.template, context);
}
this.previous = [...this.forOf];
}
}
}
`
}
};
it('should support a let variable and reference', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
})
export class MyComponent {
items = [{name: 'one'}, {name: 'two'}];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
// TODO(chuckj): Enforce this when the directives are specified
const ForDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: ForOfDirective,
factory: function ForOfDirective_Factory() {
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
},
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
inputs: {forOf: 'forOf'}
});
`;
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
ForOfDirective.ngDirectiveDef.h(2, 1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
// TODO(chuckj): Enforce this when the directives are specified
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
it('should support accessing parent template variables', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`
<ul>
<li *for="let item of items">
<div>{{item.name}}</div>
<ul>
<li *for="let info of item.infos">
{{item.name}}: {{info.description}}
</li>
</ul>
</li>
</ul>\`
})
export class MyComponent {
items = [
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
const $c2$ = ForOfDirective.ngDirectiveDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
$c2$.h(2,1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵE(1, 'div');
$r3$.ɵT(2);
$r3$.ɵe();
$r3$.ɵE(3, 'ul');
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
$r3$.ɵe();
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
$c2$.h(5,4);
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
$r3$.ɵcR(4);
$r3$.ɵr(5, 4);
$r3$.ɵcr();
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
ctx1: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $info$ = ctx1.$implicit;
$r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
}
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
});
});
});

View File

@ -122,626 +122,4 @@ describe('r3_view_compiler', () => {
});
});
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler conformance', () => {
describe('elements', () => {
it('should translate DOM structure', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'div', $c1$);
$r3$.ɵT(1, 'Hello ');
$r3$.ɵE(2, 'b');
$r3$.ɵT(3, 'World');
$r3$.ɵe();
$r3$.ɵT(4, '!');
$r3$.ɵe();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
});
});
describe('components & directives', () => {
it('should instantiate directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'child', template: 'child-view'})
export class ChildComponent {}
@Directive({selector: '[some-directive]'})
export class SomeDirective {}
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
export class MyComponent {}
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// ChildComponent definition should be:
const ChildComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: ChildComponent,
tag: 'child',
factory: function ChildComponent_Factory() { return new ChildComponent(); },
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵT(0, 'child-view');
}
}
});`;
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
factory: function SomeDirective_Factory() {return new SomeDirective(); }
});
`;
// MyComponent definition should be:
const MyComponentDefinition = `
const $c1$ = ['some-directive', ''];
const $c2$ = [SomeDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
$r3$.ɵe();
$r3$.ɵT(3, '!');
}
ChildComponent.ngComponentDef.h(1, 0);
SomeDirective.ngDirectiveDef.h(2, 0);
$r3$.ɵr(1, 0);
$r3$.ɵr(2, 0);
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
});
it('should support structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({selector: '[if]'})
export class IfDirective {
constructor(template: TemplateRef<any>) { }
}
@Component({
selector: 'my-component',
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
})
export class MyComponent {
salutation = 'Hello';
}
@NgModule({declarations: [IfDirective, MyComponent]})
export class MyModule {}
`
}
};
const IfDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: IfDirective,
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
});`;
const MyComponentDefinition = `
const $c1$ = ['foo', ''];
const $c2$ = [IfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul', null, null, $c1$);
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
$r3$.ɵe();
}
const $foo$ = $r3$.ɵld(1);
IfDirective.ngDirectiveDef.h(3,2);
$r3$.ɵcR(2);
$r3$.ɵr(3,2);
$r3$.ɵcr();
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
$r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
it('should support content projection', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
export class SimpleComponent {}
@Component({
selector: 'complex',
template: \`
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
<div id="second"><ng-content select="span[title=toSecond]"></ng-content></div>\`
})
export class ComplexComponent { }
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
export class MyModule {}
@Component({
selector: 'my-app',
template: '<simple>content</simple> <complex></complex>'
})
export class MyApp {}
`
}
};
const SimpleComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleComponent,
tag: 'simple',
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵe();
}
}
});`;
const ComplexComponentDefinition = `
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
const $c2$ = ['id','first'];
const $c3$ = ['id','second'];
static ngComponentDef = $r3$.ɵdefineComponent({
type: ComplexComponent,
tag: 'complex',
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0, $c1$);
$r3$.ɵE(1, 'div', $c2$);
$r3$.ɵP(2, 0, 1);
$r3$.ɵe();
$r3$.ɵE(3, 'div', $c3$);
$r3$.ɵP(4, 0, 2);
$r3$.ɵe();
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
expectEmit(
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
});
it('local reference', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = ['user', ''];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'input', null, null, $c1$);
$r3$.ɵe();
$r3$.ɵT(2);
}
const $user$ = $r3$.ɵld(1);
$r3$.ɵt(2, $r3$.ɵi1('Hello ', $user$.value, '!'));
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
describe('lifecycle hooks', () => {
const files = {
app: {
'spec.ts': `
import {Component, Input, NgModule} from '@angular/core';
let events: string[] = [];
@Component({selector: 'lifecycle-comp', template: ''})
export class LifecycleComp {
@Input('name') nameMin: string;
ngOnChanges() { events.push('changes' + this.nameMin); }
ngOnInit() { events.push('init' + this.nameMin); }
ngDoCheck() { events.push('check' + this.nameMin); }
ngAfterContentInit() { events.push('content init' + this.nameMin); }
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
ngAfterViewInit() { events.push('view init' + this.nameMin); }
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
ngOnDestroy() { events.push(this.nameMin); }
}
@Component({
selector: 'simple-layout',
template: \`
<lifecycle-comp [name]="name1"></lifecycle-comp>
<lifecycle-comp [name]="name2"></lifecycle-comp>
\`
})
export class SimpleLayout {
name1 = '1';
name2 = '2';
}
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
export class LifecycleModule {}
`
}
};
it('should gen hooks with a few simple components', () => {
const LifecycleCompDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: LifecycleComp,
tag: 'lifecycle-comp',
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
inputs: {nameMin: 'name'},
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
});`;
const SimpleLayoutDefinition = `
const $c1$ = LifecycleComp.ngComponentDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleLayout,
tag: 'simple-layout',
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, LifecycleComp);
$r3$.ɵe();
$r3$.ɵE(2, LifecycleComp);
$r3$.ɵe();
}
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
$c1$.h(1, 0);
$c1$.h(3, 2);
$r3$.ɵr(1, 0);
$r3$.ɵr(3, 2);
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
});
});
describe('template variables', () => {
const shared = {
shared: {
'for_of.ts': `
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
export interface ForOfContext {
$implicit: any;
index: number;
even: boolean;
odd: boolean;
}
@Directive({selector: '[forOf]'})
export class ForOfDirective {
private previous: any[];
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
@Input() forOf: any[];
ngOnChanges(simpleChanges: SimpleChanges) {
if ('forOf' in simpleChanges) {
this.update();
}
}
ngDoCheck(): void {
const previous = this.previous;
const current = this.forOf;
if (!previous || previous.length != current.length ||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
this.update();
}
}
private update() {
// TODO(chuckj): Not implemented yet
// this.view.clear();
if (this.forOf) {
const current = this.forOf;
for (let i = 0; i < current.length; i++) {
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
// TODO(chuckj): Not implemented yet
// this.view.createEmbeddedView(this.template, context);
}
this.previous = [...this.forOf];
}
}
}
`
}
};
it('should support a let variable and reference', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
})
export class MyComponent {
items = [{name: 'one'}, {name: 'two'}];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
// TODO(chuckj): Enforce this when the directives are specified
const ForDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: ForOfDirective,
factory: function ForOfDirective_Factory() {
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
},
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
inputs: {forOf: 'forOf'}
});
`;
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
ForOfDirective.ngDirectiveDef.h(2, 1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
// TODO(chuckj): Enforce this when the directives are specified
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
it('should support accessing parent template variables', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`
<ul>
<li *for="let item of items">
<div>{{item.name}}</div>
<ul>
<li *for="let info of item.infos">
{{item.name}}: {{info.description}}
</li>
</ul>
</li>
</ul>\`
})
export class MyComponent {
items = [
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
const $c2$ = ForOfDirective.ngDirectiveDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
$c2$.h(2,1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵE(1, 'div');
$r3$.ɵT(2);
$r3$.ɵe();
$r3$.ɵE(3, 'ul');
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
$r3$.ɵe();
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
$c2$.h(5,4);
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
$r3$.ɵcR(4);
$r3$.ɵr(5, 4);
$r3$.ɵcr();
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
ctx1: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $info$ = ctx1.$implicit;
$r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
}
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
});
});
});