feat(compiler): integrate compiler with view engine - change detection tests work (#14412)

Included refactoring:
- make ViewData.parentIndex point to component provider index
- split NodeType.Provider into Provider / Directive / Pipe
- make purePipe take the real pipe as argument to detect changes
- order change detection:
  1) directive props
  2) renderer props

Part of #14013

PR Close #14412
This commit is contained in:
Tobias Bosch
2017-02-09 14:59:57 -08:00
committed by Miško Hevery
parent 1dc9be4b7d
commit e4e9dbe33d
39 changed files with 942 additions and 768 deletions

View File

@ -205,14 +205,14 @@ export class AotCompiler {
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const parsedTemplate = this._templateParser.parse(
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type));
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const compiledAnimations =
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
const viewResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
compMeta, parsedTemplate, stylesExpr, usedPipes, compiledAnimations);
if (componentStyles) {
targetStatements.push(
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));

View File

@ -17,58 +17,9 @@ import {createPureProxy} from './identifier_util';
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
export interface NameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression;
}
export class EventHandlerVars { static event = o.variable('$event'); }
export class ConvertPropertyBindingResult {
constructor(
public stmts: o.Statement[], public currValExpr: o.Expression,
public forceUpdate: o.Expression) {}
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression is
* used in a property binding.
*/
export function convertPropertyBinding(
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
expression: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
const currValExpr = createCurrValueExpr(bindingId);
const stmts: o.Statement[] = [];
if (!nameResolver) {
nameResolver = new DefaultNameResolver();
}
const visitor = new _AstToIrVisitor(
builder, nameResolver, implicitReceiver, VAL_UNWRAPPER_VAR, bindingId, false);
const outputExpr: o.Expression = expression.visit(visitor, _Mode.Expression);
if (!outputExpr) {
// e.g. an empty expression was given
return null;
}
if (visitor.temporaryCount) {
for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i));
}
}
if (visitor.needsValueUnwrapper) {
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
stmts.push(initValueUnwrapperStmt);
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
if (visitor.needsValueUnwrapper) {
return new ConvertPropertyBindingResult(
stmts, currValExpr, VAL_UNWRAPPER_VAR.prop('hasWrappedValue'));
} else {
return new ConvertPropertyBindingResult(stmts, currValExpr, null);
}
}
export interface LocalResolver { getLocal(name: string): o.Expression; }
export class ConvertActionBindingResult {
constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {}
@ -79,15 +30,31 @@ export class ConvertActionBindingResult {
* used in an action binding (e.g. an event handler).
*/
export function convertActionBinding(
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
action: cdAst.AST, bindingId: string): ConvertActionBindingResult {
if (!nameResolver) {
nameResolver = new DefaultNameResolver();
localResolver: LocalResolver, implicitReceiver: o.Expression, action: cdAst.AST,
bindingId: string): ConvertActionBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
const visitor =
new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true);
const actionWithoutBuiltins = convertPropertyBindingBuiltins(
{
createLiteralArrayConverter: (argCount: number) => {
// Note: no caching for literal arrays in actions.
return (args: o.Expression[]) => o.literalArr(args);
},
createLiteralMapConverter: (keys: string[]) => {
// Note: no caching for literal maps in actions.
return (args: o.Expression[]) =>
o.literalMap(<[string, o.Expression][]>keys.map((key, i) => [key, args[i]]));
},
createPipeConverter: (name: string) => {
throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
}
},
action);
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
const actionStmts: o.Statement[] = [];
flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts);
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
const lastIndex = actionStmts.length - 1;
let preventDefaultVar: o.ReadVarExpr = null;
@ -106,11 +73,105 @@ export function convertActionBinding(
return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
}
export interface BuiltinConverter { (args: o.Expression[]): o.Expression; }
export interface BuiltinConverterFactory {
createLiteralArrayConverter(argCount: number): BuiltinConverter;
createLiteralMapConverter(keys: string[]): BuiltinConverter;
createPipeConverter(name: string, argCount: number): BuiltinConverter;
}
export function convertPropertyBindingBuiltins(
converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
return convertBuiltins(converterFactory, ast);
}
export class ConvertPropertyBindingResult {
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression
* is used in property binding. The expression has to be preprocessed via
* `convertPropertyBindingBuiltins`.
*/
export function convertPropertyBinding(
localResolver: LocalResolver, implicitReceiver: o.Expression,
expressionWithoutBuiltins: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
const currValExpr = createCurrValueExpr(bindingId);
const stmts: o.Statement[] = [];
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
if (visitor.temporaryCount) {
for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i));
}
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
return new ConvertPropertyBindingResult(stmts, currValExpr);
}
export class LegacyConvertPropertyBindingResult implements ConvertPropertyBindingResult {
constructor(
public stmts: o.Statement[], public currValExpr: o.Expression,
public forceUpdate: o.Expression) {}
}
export interface LegacyNameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression;
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression is
* used in a property binding.
*/
export function legacyConvertPropertyBinding(
builder: ClassBuilder, nameResolver: LegacyNameResolver, implicitReceiver: o.Expression,
expression: cdAst.AST, bindingId: string): LegacyConvertPropertyBindingResult {
if (!nameResolver) {
nameResolver = new LegacyDefaultNameResolver();
}
let needsValueUnwrapper = false;
const expressionWithoutBuiltins = convertBuiltins(
{
createLiteralArrayConverter: (argCount: number) => {
return (args: o.Expression[]) => legacyCreateCachedLiteralArray(builder, args);
},
createLiteralMapConverter: (keys: string[]) => {
return (args: o.Expression[]) => legacyCreateCachedLiteralMap(
builder, <[string, o.Expression][]>keys.map((key, i) => [key, args[i]]));
},
createPipeConverter: (name: string) => {
needsValueUnwrapper = true;
return (args: o.Expression[]) => VAL_UNWRAPPER_VAR.callMethod(
'unwrap', [nameResolver.callPipe(name, args[0], args.slice(1))]);
}
},
expression);
const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, implicitReceiver, expressionWithoutBuiltins, bindingId);
let forceUpdate: o.Expression = null;
if (needsValueUnwrapper) {
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
stmts.unshift(initValueUnwrapperStmt);
forceUpdate = VAL_UNWRAPPER_VAR.prop('hasWrappedValue');
}
return new LegacyConvertPropertyBindingResult(stmts, currValExpr, forceUpdate);
}
/**
* Creates variables that are shared by multiple calls to `convertActionBinding` /
* `convertPropertyBinding`
*/
export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
export function legacyCreateSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
const unwrapperStmts: o.Statement[] = [];
const readVars = o.findReadVarNames(stmts);
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
@ -122,6 +183,11 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St
return unwrapperStmts;
}
function convertBuiltins(converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
const visitor = new _BuiltinAstConverter(converterFactory);
return ast.visit(visitor);
}
function temporaryName(bindingId: string, temporaryNumber: number): string {
return `tmp_${bindingId}_${temporaryNumber}`;
}
@ -162,17 +228,34 @@ function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expressi
}
}
class _BuiltinAstConverter extends cdAst.AstTransformer {
constructor(private _converterFactory: BuiltinConverterFactory) { super(); }
visitPipe(ast: cdAst.BindingPipe, context: any): any {
const args = [ast.exp, ...ast.args].map(ast => ast.visit(this, context));
return new BuiltinFunctionCall(
ast.span, args, this._converterFactory.createPipeConverter(ast.name, args.length));
}
visitLiteralArray(ast: cdAst.LiteralArray, context: any): any {
const args = ast.expressions.map(ast => ast.visit(this, context));
return new BuiltinFunctionCall(
ast.span, args, this._converterFactory.createLiteralArrayConverter(ast.expressions.length));
}
visitLiteralMap(ast: cdAst.LiteralMap, context: any): any {
const args = ast.values.map(ast => ast.visit(this, context));
return new BuiltinFunctionCall(
ast.span, args, this._converterFactory.createLiteralMapConverter(ast.keys));
}
}
class _AstToIrVisitor implements cdAst.AstVisitor {
private _nodeMap = new Map<cdAst.AST, cdAst.AST>();
private _resultMap = new Map<cdAst.AST, o.Expression>();
private _currentTemporary: number = 0;
public needsValueUnwrapper: boolean = false;
public temporaryCount: number = 0;
constructor(
private _builder: ClassBuilder, private _nameResolver: NameResolver,
private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr,
private bindingId: string, private isAction: boolean) {}
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
private bindingId: string) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
let op: o.BinaryOperator;
@ -246,20 +329,19 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
}
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
const input = this.visit(ast.exp, _Mode.Expression);
const args = this.visitAll(ast.args, _Mode.Expression);
const value = this._nameResolver.callPipe(ast.name, input, args);
if (!value) {
throw new Error(`Illegal state: Pipe ${ast.name} is not allowed here!`);
}
this.needsValueUnwrapper = true;
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
throw new Error(
`Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`);
}
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
return convertToStatementIfNeeded(
mode,
this.visit(ast.target, _Mode.Expression).callFn(this.visitAll(ast.args, _Mode.Expression)));
const convertedArgs = this.visitAll(ast.args, _Mode.Expression);
let fnResult: o.Expression;
if (ast instanceof BuiltinFunctionCall) {
fnResult = ast.converter(convertedArgs);
} else {
fnResult = this.visit(ast.target, _Mode.Expression).callFn(convertedArgs);
}
return convertToStatementIfNeeded(mode, fnResult);
}
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
@ -301,32 +383,18 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
}
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
const parts = this.visitAll(ast.expressions, mode);
const literalArr =
this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts);
return convertToStatementIfNeeded(mode, literalArr);
throw new Error(`Illegal State: literal arrays should have been converted into functions`);
}
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
const parts: any[] = [];
for (let i = 0; i < ast.keys.length; i++) {
parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]);
}
const literalMap =
this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts);
return convertToStatementIfNeeded(mode, literalMap);
throw new Error(`Illegal State: literal maps should have been converted into functions`);
}
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value));
}
private _getLocal(name: string): o.Expression {
if (this.isAction && name == EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return this._nameResolver.getLocal(name);
}
private _getLocal(name: string): o.Expression { return this._localResolver.getLocal(name); }
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast);
@ -581,7 +649,8 @@ function flattenStatements(arg: any, output: o.Statement[]) {
}
}
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
function legacyCreateCachedLiteralArray(
builder: ClassBuilder, values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
}
@ -601,7 +670,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
return proxyExpr.callFn(values);
}
function createCachedLiteralMap(
function legacyCreateCachedLiteralMap(
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
if (entries.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
@ -624,10 +693,23 @@ function createCachedLiteralMap(
return proxyExpr.callFn(values);
}
class DefaultLocalResolver implements LocalResolver {
getLocal(name: string): o.Expression {
if (name === EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return null;
}
}
class DefaultNameResolver implements NameResolver {
class LegacyDefaultNameResolver implements LegacyNameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; }
getLocal(name: string): o.Expression { return null; }
getLocal(name: string): o.Expression {
if (name === EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return null;
}
}
function createCurrValueExpr(bindingId: string): o.ReadVarExpr {
@ -646,3 +728,9 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
}
return null;
}
class BuiltinFunctionCall extends cdAst.FunctionCall {
constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) {
super(span, null, args);
}
}

View File

@ -13,12 +13,12 @@ import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
import {isFirstViewCheck} from './binding_util';
import {ConvertPropertyBindingResult} from './expression_converter';
import {LegacyConvertPropertyBindingResult} from './expression_converter';
import {createEnumExpression} from './identifier_util';
export function createCheckRenderBindingStmt(
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult,
oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult,
securityContextExpression?: o.Expression): o.Statement[] {
const checkStmts: o.Statement[] = [...evalResult.stmts];
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
@ -84,7 +84,7 @@ function calcSecurityContext(
export function createCheckAnimationBindingStmts(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) {
oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];

View File

@ -8,7 +8,7 @@
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {EventHandlerVars, convertActionBinding, legacyConvertPropertyBinding} from './compiler_util/expression_converter';
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser';
@ -253,7 +253,7 @@ function addCheckHostMethod(
];
hostProps.forEach((hostProp, hostPropIdx) => {
const field = createCheckBindingField(builder);
const evalResult = convertPropertyBinding(
const evalResult = legacyConvertPropertyBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
if (!evalResult) {
return;
@ -285,8 +285,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
hostListeners.forEach((hostListener, eventIdx) => {
const evalResult = convertActionBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler,
`sub_${eventIdx}`);
null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, `sub_${eventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.allowDefault) {
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());

View File

@ -400,11 +400,36 @@ export class Identifiers {
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.queryDef
};
static pureArrayDef: IdentifierSpec = {
name: 'pureArrayDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.pureArrayDef
};
static pureObjectDef: IdentifierSpec = {
name: 'pureObjectDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.pureObjectDef
};
static purePipeDef: IdentifierSpec = {
name: 'purePipeDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.purePipeDef
};
static pipeDef: IdentifierSpec = {
name: 'pipeDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.pipeDef
};
static nodeValue: IdentifierSpec = {
name: 'nodeValue',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.nodeValue
};
static unwrapValue: IdentifierSpec = {
name: 'unwrapValue',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.unwrapValue
};
}
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {

View File

@ -278,14 +278,14 @@ export class JitCompiler implements Compiler {
template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = template.ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const parsedTemplate = this._templateParser.parse(
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
identifierName(compMeta.type));
const compiledAnimations =
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
pipes, compiledAnimations);
usedPipes, compiledAnimations);
const statements = stylesCompileResult.componentStylesheet.statements
.concat(...compiledAnimations.map(ca => ca.statements))
.concat(compileResult.statements);

View File

@ -51,6 +51,7 @@ export class BoundProperty {
*/
export class BindingParser {
pipesByName: Map<string, CompilePipeSummary> = new Map();
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
constructor(
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
@ -59,6 +60,8 @@ export class BindingParser {
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
BoundElementPropertyAst[] {
if (dirMeta.hostProperties) {
@ -377,11 +380,14 @@ export class BindingParser {
const collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => {
if (!this.pipesByName.has(pipeName)) {
const pipeMeta = this.pipesByName.get(pipeName);
if (!pipeMeta) {
this._reportError(
`The pipe '${pipeName}' could not be found`,
new ParseSourceSpan(
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
} else {
this._usedPipes.set(pipeName, pipeMeta);
}
});
}

View File

@ -7,7 +7,9 @@
*/
import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
@ -25,10 +27,12 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver';
import {syntaxError} from '../util';
import {BindingParser, BoundProperty} from './binding_parser';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
import {PreparsedElementType, preparseElement} from './template_preparser';
// Group 1 = "bind-"
// Group 2 = "let-"
// Group 3 = "ref-/#"
@ -76,7 +80,9 @@ export class TemplateParseError extends ParseError {
}
export class TemplateParseResult {
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {}
constructor(
public templateAst?: TemplateAst[], public usedPipes?: CompilePipeSummary[],
public errors?: ParseError[]) {}
}
@CompilerInjectable()
@ -88,7 +94,8 @@ export class TemplateParser {
parse(
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] {
pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
templateUrl: string): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
@ -102,7 +109,7 @@ export class TemplateParser {
throw syntaxError(`Template parse errors:\n${errorString}`);
}
return result.templateAst;
return {template: result.templateAst, pipes: result.usedPipes};
}
tryParse(
@ -121,6 +128,7 @@ export class TemplateParser {
templateUrl: string): TemplateParseResult {
let result: TemplateAst[];
const errors = htmlAstWithErrors.errors;
const usedPipes: CompilePipeSummary[] = [];
if (htmlAstWithErrors.rootNodes.length > 0) {
const uniqDirectives = removeSummaryDuplicates(directives);
const uniqPipes = removeSummaryDuplicates(pipes);
@ -140,13 +148,14 @@ export class TemplateParser {
errors);
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors.push(...providerViewContext.errors);
usedPipes.push(...bindingParser.getUsedPipes());
} else {
result = [];
}
this._assertNoReferenceDuplicationOnTemplate(result, errors);
if (errors.length > 0) {
return new TemplateParseResult(result, errors);
return new TemplateParseResult(result, usedPipes, errors);
}
if (this.transforms) {
@ -154,7 +163,7 @@ export class TemplateParser {
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
}
return new TemplateParseResult(result, errors);
return new TemplateParseResult(result, usedPipes, errors);
}
expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult {
@ -303,11 +312,12 @@ class TemplateParseVisitor implements html.Visitor {
const {directives: directiveMetas, matchElement} =
this._parseDirectives(this.selectorMatcher, elementCssSelector);
const references: ReferenceAst[] = [];
const boundDirectivePropNames = new Set<string>();
const directiveAsts = this._createDirectiveAsts(
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
elementOrDirectiveRefs, element.sourceSpan, references);
const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
elementOrDirectiveRefs, element.sourceSpan, references, boundDirectivePropNames);
const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, elementOrDirectiveProps, boundDirectivePropNames);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext(
@ -372,11 +382,12 @@ class TemplateParseVisitor implements html.Visitor {
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const {directives: templateDirectiveMetas} =
this._parseDirectives(this.selectorMatcher, templateCssSelector);
const templateBoundDirectivePropNames = new Set<string>();
const templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, []);
element.sourceSpan, [], templateBoundDirectivePropNames);
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
element.name, templateElementOrDirectiveProps, templateBoundDirectivePropNames);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
const templateProviderContext = new ProviderElementContext(
@ -544,7 +555,8 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectiveAsts(
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[],
targetBoundDirectivePropNames: Set<string>): DirectiveAst[] {
const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null;
@ -557,13 +569,14 @@ class TemplateParseVisitor implements html.Visitor {
component = directive;
}
const directiveProperties: BoundDirectivePropertyAst[] = [];
const hostProperties =
let hostProperties =
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
// Note: We need to check the host properties here as well,
// as we don't know the element name in the DirectiveWrapperCompiler yet.
this._checkPropertiesInSchema(elementName, hostProperties);
hostProperties = this._checkPropertiesInSchema(elementName, hostProperties);
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
this._createDirectivePropertyAsts(
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
(directive.exportAs == elOrDirRef.value)) {
@ -596,7 +609,8 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectivePropertyAsts(
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
targetBoundDirectiveProps: BoundDirectivePropertyAst[],
targetBoundDirectivePropNames: Set<string>) {
if (directiveProperties) {
const boundPropsByName = new Map<string, BoundProperty>();
boundProps.forEach(boundProp => {
@ -613,8 +627,11 @@ class TemplateParseVisitor implements html.Visitor {
// Bindings are optional, so this binding only needs to be set up if an expression is given.
if (boundProp) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
targetBoundDirectivePropNames.add(boundProp.name);
if (!isEmptyExpression(boundProp.expression)) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
}
}
});
}
@ -622,23 +639,15 @@ class TemplateParseVisitor implements html.Visitor {
private _createElementPropertyAsts(
elementName: string, props: BoundProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] {
boundDirectivePropNames: Set<string>): BoundElementPropertyAst[] {
const boundElementProps: BoundElementPropertyAst[] = [];
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
directives.forEach((directive: DirectiveAst) => {
directive.inputs.forEach((prop: BoundDirectivePropertyAst) => {
boundDirectivePropsIndex.set(prop.templateName, prop);
});
});
props.forEach((prop: BoundProperty) => {
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) {
if (!prop.isLiteral && !boundDirectivePropNames.has(prop.name)) {
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
}
});
this._checkPropertiesInSchema(elementName, boundElementProps);
return boundElementProps;
return this._checkPropertiesInSchema(elementName, boundElementProps);
}
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
@ -723,8 +732,11 @@ class TemplateParseVisitor implements html.Visitor {
});
}
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) {
boundProps.forEach((boundProp) => {
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]):
BoundElementPropertyAst[] {
// Note: We can't filter out empty expressions before this method,
// as we still want to validate them!
return boundProps.filter((boundProp) => {
if (boundProp.type === PropertyBindingType.Property &&
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
let errorMsg =
@ -741,6 +753,7 @@ class TemplateParseVisitor implements html.Visitor {
}
this._reportError(errorMsg, boundProp.sourceSpan);
}
return !isEmptyExpression(boundProp.value);
});
}
@ -870,3 +883,10 @@ export function removeSummaryDuplicates<T extends{type: CompileTypeMetadata}>(it
return Array.from(map.values());
}
function isEmptyExpression(ast: AST): boolean {
if (ast instanceof ASTWithSource) {
ast = ast.ast;
}
return ast instanceof EmptyExpr;
}

View File

@ -8,7 +8,7 @@
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata';
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter';
import {EventHandlerVars, LegacyNameResolver} from '../compiler_util/expression_converter';
import {CompilerConfig} from '../config';
import {isPresent} from '../facade/lang';
import * as o from '../output/output_ast';
@ -33,7 +33,7 @@ export class CompileViewRootNode {
public ngContentIndex?: number) {}
}
export class CompileView implements NameResolver {
export class CompileView implements LegacyNameResolver {
public viewType: ViewType;
public viewQueries: Map<any, CompileQuery[]>;

View File

@ -102,8 +102,8 @@ function generateHandleEventMethod(
});
boundEvents.forEach((renderEvent, renderEventIdx) => {
const evalResult = convertActionBinding(
compileElement.view, compileElement.view, compileElement.view.componentContext,
renderEvent.handler, `sub_${renderEventIdx}`);
compileElement.view, compileElement.view.componentContext, renderEvent.handler,
`sub_${renderEventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.allowDefault) {
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());

View File

@ -9,7 +9,7 @@
import {SecurityContext} from '@angular/core';
import {createCheckBindingField} from '../compiler_util/binding_util';
import {convertPropertyBinding} from '../compiler_util/expression_converter';
import {legacyConvertPropertyBinding} from '../compiler_util/expression_converter';
import {createEnumExpression} from '../compiler_util/identifier_util';
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
@ -26,7 +26,7 @@ import {getHandleEventMethodName} from './util';
export function bindRenderText(
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
const valueField = createCheckBindingField(view);
const evalResult = convertPropertyBinding(
const evalResult = legacyConvertPropertyBinding(
view, view, view.componentContext, boundText.value, valueField.bindingId);
if (!evalResult) {
return null;
@ -53,7 +53,7 @@ export function bindRenderInputs(
boundProps.forEach((boundProp) => {
const bindingField = createCheckBindingField(view);
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
const evalResult = convertPropertyBinding(
const evalResult = legacyConvertPropertyBinding(
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
if (!evalResult) {
return;
@ -123,7 +123,7 @@ export function bindDirectiveInputs(
const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`;
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
const evalResult =
convertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
legacyConvertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
if (!evalResult) {
return;
}

View File

@ -9,7 +9,7 @@
import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
import {legacyCreateSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
@ -586,7 +586,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts));
}
const varStmts = createSharedBindingVariablesIfNeeded(stmts);
const varStmts = legacyCreateSharedBindingVariablesIfNeeded(stmts);
return varStmts.concat(stmts);
}

View File

@ -10,7 +10,7 @@ import {ChangeDetectionStrategy} from '@angular/core';
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
import {EventHandlerVars, NameResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter';
import {BuiltinConverter, BuiltinConverterFactory, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {CompilerConfig} from '../config';
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers';
@ -25,6 +25,7 @@ import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDep
const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style';
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
@CompilerInjectable()
export class ViewCompilerNext extends ViewCompiler {
@ -35,7 +36,7 @@ export class ViewCompilerNext extends ViewCompiler {
compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
pipes: CompilePipeSummary[],
usedPipes: CompilePipeSummary[],
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
const compName = identifierName(component.type) + (component.isHost ? `_Host` : '');
@ -44,7 +45,7 @@ export class ViewCompilerNext extends ViewCompiler {
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
const embeddedViewIndex = embeddedViewCount++;
const viewName = `view_${compName}_${embeddedViewIndex}`;
return new ViewBuilder(parent, viewName, viewBuilderFactory);
return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory);
};
const visitor = viewBuilderFactory(null);
@ -80,20 +81,31 @@ const NODE_INDEX_VAR = o.variable('nodeIndex');
const EVENT_NAME_VAR = o.variable('eventName');
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
class ViewBuilder implements TemplateAstVisitor, NameResolver {
class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverterFactory {
private nodeDefs: o.Expression[] = [];
private purePipeNodeIndices: {[pipeName: string]: number} = {};
private refNodeIndices: {[refName: string]: number} = {};
private variables: VariableAst[] = [];
private children: ViewBuilder[] = [];
private updateExpressions: UpdateExpression[] = [];
private updateDirectivesExpressions: UpdateExpression[] = [];
private updateRendererExpressions: UpdateExpression[] = [];
private handleEventExpressions: HandleEventExpression[] = [];
constructor(
private parent: ViewBuilder, public viewName: string,
private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[],
private viewBuilderFactory: ViewBuilderFactory) {}
visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) {
this.variables = variables;
// create the pipes for the pure pipes immediately, so that we know their indices.
if (!this.parent) {
this.usedPipes.forEach((pipe) => {
if (pipe.pure) {
this.purePipeNodeIndices[pipe.name] = this._createPipe(pipe);
}
});
}
templateVisitAll(this, astNodes, {elementDepth});
if (astNodes.length === 0 || (this.parent && hasViewContainer(astNodes[astNodes.length - 1]))) {
// if the view is empty, or an embedded view has a view container as last root nde,
@ -108,46 +120,16 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const compType = o.importType(component.type);
this.children.forEach((child) => { child.build(component, targetStatements); });
const updateStmts: o.Statement[] = [];
let updateBindingCount = 0;
this.updateExpressions
.forEach(
({expressions, nodeIndex}) => {
const exprs = expressions.map(({context, value}) => {
const bindingId = `${updateBindingCount++}`;
const {stmts, currValExpr} =
convertPropertyBinding(null, this, context, value, bindingId);
updateStmts.push(...stmts);
return currValExpr;
});
if (exprs.length > 10) {
updateStmts.push(
CHECK_VAR
.callFn([
VIEW_VAR, o.literal(nodeIndex),
o.literal(viewEngine.ArgumentType.Dynamic), o.literalArr(exprs)
])
.toStmt());
} else {
updateStmts.push(
CHECK_VAR.callFn((<o.Expression[]>[VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline)]).concat(exprs)).toStmt());
}
});
let updateFn: o.Expression;
if (updateStmts.length > 0) {
updateFn = o.fn(
[new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)],
[COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]);
} else {
updateFn = o.NULL_EXPR;
}
const updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions, compType);
const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions, compType);
const handleEventStmts: o.Statement[] = [];
let handleEventBindingCount = 0;
this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => {
const bindingId = `${handleEventBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null;
const {stmts, allowDefault} =
convertActionBinding(null, this, context, expression, bindingId);
convertActionBinding(nameResolver, context, expression, bindingId);
const trueStmts = stmts;
if (allowDefault) {
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
@ -181,13 +163,39 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const viewFactory = new o.DeclareFunctionStmt(
this.viewName, [],
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
o.literal(viewFlags), o.literalArr(this.nodeDefs), updateFn, handleEventFn
o.literal(viewFlags), o.literalArr(this.nodeDefs), updateDirectivesFn, updateRendererFn,
handleEventFn
]))]);
targetStatements.push(viewFactory);
return targetStatements;
}
private _createUpdateFn(expressions: UpdateExpression[], compType: o.Type): o.Expression {
const updateStmts: o.Statement[] = [];
let updateBindingCount = 0;
expressions.forEach(({expressions, nodeIndex}) => {
const exprs = expressions.map(({context, value}) => {
const bindingId = `${updateBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null;
const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, context, value, bindingId);
updateStmts.push(...stmts);
return currValExpr;
});
updateStmts.push(callCheckStmt(nodeIndex, exprs).toStmt());
});
let updateFn: o.Expression;
if (updateStmts.length > 0) {
updateFn = o.fn(
[new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)],
[COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]);
} else {
updateFn = o.NULL_EXPR;
}
return updateFn;
}
visitNgContent(ast: NgContentAst, context: any): any {}
visitText(ast: TextAst, context: any): any {
@ -199,17 +207,20 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
visitBoundText(ast: BoundTextAst, context: any): any {
const nodeIndex = this.nodeDefs.length;
// reserve the space in the nodeDefs array
this.nodeDefs.push(null);
const astWithSource = <ASTWithSource>ast.value;
const inter = <Interpolation>astWithSource.ast;
this.updateExpressions.push({
nodeIndex,
expressions: inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; })
});
this._addUpdateExpressions(
nodeIndex, inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }),
this.updateRendererExpressions);
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s)))
]));
]);
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any {
@ -219,11 +230,12 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context);
const childCount = this.nodeDefs.length - nodeIndex - 1;
const childVisitor = this.viewBuilderFactory(this);
this.children.push(childVisitor);
childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1);
const childCount = this.nodeDefs.length - nodeIndex - 1;
// anchorDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef;
@ -243,12 +255,9 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1});
const childCount = this.nodeDefs.length - nodeIndex - 1;
ast.inputs.forEach((inputAst) => {
hostBindings.push({context: COMP_VAR, value: (<ASTWithSource>inputAst.value).ast});
});
this.updateExpressions.push({nodeIndex, expressions: hostBindings});
ast.inputs.forEach(
(inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); });
this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions);
const inputDefs = elementBindingDefs(ast.inputs);
ast.directives.forEach(
@ -258,6 +267,8 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
o.literal(eventName);
});
const childCount = this.nodeDefs.length - nodeIndex - 1;
// elementDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
@ -355,13 +366,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
ast.outputs.forEach(
(outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); });
hostEvents.forEach((hostEvent) => {
this.handleEventExpressions.push({
nodeIndex,
context: hostEvent.context,
eventName:
viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name),
expression: (<ASTWithSource>hostEvent.eventAst.handler).ast
});
this._addHandleEventExpression(
nodeIndex, hostEvent.context,
viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name),
hostEvent.eventAst.handler);
});
return {
@ -383,6 +391,22 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
// reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null);
directiveAst.directive.queries.forEach((query, queryIndex) => {
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
const bindingType =
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
});
// Note: the operation below might also create new nodeDefs,
// but we don't want them to be a child of a directive,
// as they might be a provider/pipe on their own.
// I.e. we only allow queries as children of directives nodes.
const childCount = this.nodeDefs.length - nodeIndex - 1;
const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} =
this._visitProviderOrDirective(providerAst, queryMatches);
@ -415,11 +439,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
}
});
if (directiveAst.inputs.length) {
this.updateExpressions.push({
nodeIndex,
expressions: directiveAst.inputs.map(
input => { return {context: COMP_VAR, value: (<ASTWithSource>input.value).ast}; })
});
this._addUpdateExpressions(
nodeIndex,
directiveAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }),
this.updateDirectivesExpressions);
}
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
@ -434,19 +457,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const hostEvents = directiveAst.hostEvents.map(
(hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; });
const childCount = directiveAst.directive.queries.length;
directiveAst.directive.queries.forEach((query, queryIndex) => {
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
const bindingType =
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
// queryDef(
// flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef
// {
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
});
// directiveDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor:
@ -514,10 +524,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
return {flags, queryMatchExprs, providerExpr, providerType, depsExpr};
}
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression {
throw new Error('Pipes are not yet supported!');
}
getLocal(name: string): o.Expression {
if (name == EventHandlerVars.event.name) {
return EventHandlerVars.event;
@ -536,12 +542,123 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
// check variables
const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
if (varAst) {
return currViewExpr.prop('context').prop(varAst.value);
const varValue = varAst.value || IMPLICIT_TEMPLATE_VAR;
return currViewExpr.prop('context').prop(varValue);
}
}
return null;
}
createLiteralArrayConverter(argCount: number): BuiltinConverter {
if (argCount === 0) {
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
return () => valueExpr;
}
const nodeIndex = this.nodeDefs.length;
// pureArrayDef(argCount: number): NodeDef;
const nodeDef =
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)]);
this.nodeDefs.push(nodeDef);
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
}
createLiteralMapConverter(keys: string[]): BuiltinConverter {
if (keys.length === 0) {
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
return () => valueExpr;
}
const nodeIndex = this.nodeDefs.length;
// function pureObjectDef(propertyNames: string[]): NodeDef
const nodeDef = o.importExpr(createIdentifier(Identifiers.pureObjectDef)).callFn([o.literalArr(
keys.map(key => o.literal(key)))]);
this.nodeDefs.push(nodeDef);
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
}
createPipeConverter(name: string, argCount: number): BuiltinConverter {
const pipe = this._findPipe(name);
if (pipe.pure) {
const nodeIndex = this.nodeDefs.length;
// function purePipeDef(argCount: number): NodeDef;
const nodeDef =
o.importExpr(createIdentifier(Identifiers.purePipeDef)).callFn([o.literal(argCount)]);
this.nodeDefs.push(nodeDef);
// find underlying pipe in the component view
let compViewExpr: o.Expression = VIEW_VAR;
let compBuilder: ViewBuilder = this;
while (compBuilder.parent) {
compBuilder = compBuilder.parent;
compViewExpr = compViewExpr.prop('parent');
}
const pipeNodeIndex = compBuilder.purePipeNodeIndices[name];
const pipeValueExpr: o.Expression =
o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
compViewExpr, o.literal(pipeNodeIndex)
]);
return (args: o.Expression[]) =>
callUnwrapValue(callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
} else {
const nodeIndex = this._createPipe(pipe);
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
VIEW_VAR, o.literal(nodeIndex)
]);
return (args: o.Expression[]) => callUnwrapValue(nodeValueExpr.callMethod('transform', args));
}
}
private _findPipe(name: string): CompilePipeSummary {
return this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
}
private _createPipe(pipe: CompilePipeSummary): number {
const nodeIndex = this.nodeDefs.length;
let flags = viewEngine.NodeFlags.None;
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
// for pipes, we only support ngOnDestroy
if (lifecycleHook === LifecycleHooks.OnDestroy) {
flags |= lifecycleHookToNodeFlag(lifecycleHook);
}
});
const depExprs = pipe.type.diDeps.map(depDef);
// function pipeDef(
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
const nodeDef = o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
]);
this.nodeDefs.push(nodeDef);
return nodeIndex;
}
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
private _addUpdateExpressions(
nodeIndex: number, expressions: {context: o.Expression, value: AST}[],
target: UpdateExpression[]) {
if (expressions.length === 0) {
return;
}
const transformedExpressions = expressions.map(({context, value}) => {
if (value instanceof ASTWithSource) {
value = value.ast;
}
return {context, value: convertPropertyBindingBuiltins(this, value)};
});
target.push({nodeIndex, expressions: transformedExpressions});
}
private _addHandleEventExpression(
nodeIndex: number, context: o.Expression, eventName: string, expression: AST) {
if (expression instanceof ASTWithSource) {
expression = expression.ast;
}
this.handleEventExpressions.push({nodeIndex, context, eventName, expression});
}
visitDirective(ast: DirectiveAst, context: {usedEvents: Set<string>}): any {}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
visitReference(ast: ReferenceAst, context: any): any {}
@ -736,3 +853,19 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
return attrValue2;
}
}
function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression {
if (exprs.length > 10) {
return CHECK_VAR.callFn([
VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Dynamic),
o.literalArr(exprs)
]);
} else {
return CHECK_VAR.callFn(
[VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline), ...exprs]);
}
}
function callUnwrapValue(expr: o.Expression): o.Expression {
return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]);
}