From 4cac6506753dd110bd38515c46539775b090fd67 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Wed, 19 Oct 2016 13:18:33 -0700 Subject: [PATCH] refactor(compiler): extract expression evaluation and writing to renderer from view_compiler This is needed to that `DirectiveWrapper`s can also use them later on. Part of #11683 --- .../expression_converter.ts | 235 ++++++++++++++---- .../src/compiler_util/identifier_util.ts | 14 ++ .../compiler/src/compiler_util/render_util.ts | 96 +++++++ .../compiler/src/output/class_builder.ts | 8 +- .../src/view_compiler/compile_method.ts | 2 + .../src/view_compiler/compile_pipe.ts | 6 +- .../src/view_compiler/compile_view.ts | 51 +--- .../compiler/src/view_compiler/constants.ts | 3 - .../src/view_compiler/event_binder.ts | 28 +-- .../src/view_compiler/property_binder.ts | 215 ++++------------ .../compiler/src/view_compiler/util.ts | 13 - .../src/view_compiler/view_builder.ts | 8 +- 12 files changed, 374 insertions(+), 305 deletions(-) rename modules/@angular/compiler/src/{view_compiler => compiler_util}/expression_converter.ts (68%) create mode 100644 modules/@angular/compiler/src/compiler_util/render_util.ts diff --git a/modules/@angular/compiler/src/view_compiler/expression_converter.ts b/modules/@angular/compiler/src/compiler_util/expression_converter.ts similarity index 68% rename from modules/@angular/compiler/src/view_compiler/expression_converter.ts rename to modules/@angular/compiler/src/compiler_util/expression_converter.ts index 9e3fbc106a..14c8af7a75 100644 --- a/modules/@angular/compiler/src/view_compiler/expression_converter.ts +++ b/modules/@angular/compiler/src/compiler_util/expression_converter.ts @@ -10,73 +10,130 @@ import * as cdAst from '../expression_parser/ast'; import {isBlank, isPresent} from '../facade/lang'; import {Identifiers, resolveIdentifier} from '../identifiers'; +import {ClassBuilder} from '../output/class_builder'; import * as o from '../output/output_ast'; -import {EventHandlerVars} from './constants'; + +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; - createLiteralArray(values: o.Expression[]): o.Expression; - createLiteralMap(values: Array>): 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) {} } /** - * A wrapper around another NameResolver that removes all locals and pipes. + * Converts the given expression AST into an executable output AST, assuming the expression is + * used in a property binding. */ -export class NoLocalsNameResolver implements NameResolver { - constructor(private _delegate: NameResolver) {} - callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; } - getLocal(name: string): o.Expression { - if (name == EventHandlerVars.event.name) { - return EventHandlerVars.event; - } +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; } - createLiteralArray(values: o.Expression[]): o.Expression { - return this._delegate.createLiteralArray(values); + + if (visitor.temporaryCount) { + for (let i = 0; i < visitor.temporaryCount; i++) { + stmts.push(temporaryDeclaration(bindingId, i)); + } } - createLiteralMap(values: Array>): o.Expression { - return this._delegate.createLiteralMap(values); + + if (visitor.needsValueUnwrapper) { + var 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 class ExpressionWithWrappedValueInfo { - constructor( - public expression: o.Expression, public needsValueUnwrapper: boolean, - public temporaryCount: number) {} +export class ConvertActionBindingResult { + constructor(public stmts: o.Statement[], public preventDefault: o.Expression) {} } -export function convertCdExpressionToIr( - nameResolver: NameResolver, implicitReceiver: o.Expression, expression: cdAst.AST, - valueUnwrapper: o.ReadVarExpr, bindingIndex: number): ExpressionWithWrappedValueInfo { - const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, valueUnwrapper, bindingIndex); - const irAst: o.Expression = expression.visit(visitor, _Mode.Expression); - return new ExpressionWithWrappedValueInfo( - irAst, visitor.needsValueUnwrapper, visitor.temporaryCount); +/** + * Converts the given expression AST into an executable output AST, assuming the expression is + * 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(); + } + const visitor = + new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true); + let actionStmts: o.Statement[] = []; + flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts); + prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts); + var lastIndex = actionStmts.length - 1; + var preventDefaultVar: o.ReadVarExpr = null; + if (lastIndex >= 0) { + var lastStatement = actionStmts[lastIndex]; + var returnExpr = convertStmtIntoExpression(lastStatement); + if (returnExpr) { + // Note: We need to cast the result of the method call to dynamic, + // as it might be a void method! + preventDefaultVar = createPreventDefaultVar(bindingId); + actionStmts[lastIndex] = + preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false))) + .toDeclStmt(null, [o.StmtModifier.Final]); + } + } + return new ConvertActionBindingResult(actionStmts, preventDefaultVar); } -export function convertCdStatementToIr( - nameResolver: NameResolver, implicitReceiver: o.Expression, stmt: cdAst.AST, - bindingIndex: number): o.Statement[] { - const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, null, bindingIndex); - let statements: o.Statement[] = []; - flattenStatements(stmt.visit(visitor, _Mode.Statement), statements); - prependTemporaryDecls(visitor.temporaryCount, bindingIndex, statements); - return statements; +/** + * Creates variables that are shared by multiple calls to `convertActionBinding` / + * `convertPropertyBinding` + */ +export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] { + const unwrapperStmts: o.Statement[] = []; + var readVars = o.findReadVarNames(stmts); + if (readVars.has(VAL_UNWRAPPER_VAR.name)) { + unwrapperStmts.push( + VAL_UNWRAPPER_VAR + .set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([])) + .toDeclStmt(null, [o.StmtModifier.Final])); + } + return unwrapperStmts; } -function temporaryName(bindingIndex: number, temporaryNumber: number): string { - return `tmp_${bindingIndex}_${temporaryNumber}`; +function temporaryName(bindingId: string, temporaryNumber: number): string { + return `tmp_${bindingId}_${temporaryNumber}`; } -export function temporaryDeclaration(bindingIndex: number, temporaryNumber: number): o.Statement { - return new o.DeclareVarStmt(temporaryName(bindingIndex, temporaryNumber), o.NULL_EXPR); +export function temporaryDeclaration(bindingId: string, temporaryNumber: number): o.Statement { + return new o.DeclareVarStmt(temporaryName(bindingId, temporaryNumber), o.NULL_EXPR); } function prependTemporaryDecls( - temporaryCount: number, bindingIndex: number, statements: o.Statement[]) { + temporaryCount: number, bindingId: string, statements: o.Statement[]) { for (let i = temporaryCount - 1; i >= 0; i--) { - statements.unshift(temporaryDeclaration(bindingIndex, i)); + statements.unshift(temporaryDeclaration(bindingId, i)); } } @@ -113,8 +170,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor { public temporaryCount: number = 0; constructor( - private _nameResolver: NameResolver, private _implicitReceiver: o.Expression, - private _valueUnwrapper: o.ReadVarExpr, private bindingIndex: number) {} + private _builder: ClassBuilder, private _nameResolver: NameResolver, + private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr, + private bindingId: string, private isAction: boolean) {} visitBinary(ast: cdAst.Binary, mode: _Mode): any { var op: o.BinaryOperator; @@ -233,8 +291,10 @@ class _AstToIrVisitor implements cdAst.AstVisitor { } visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { - return convertToStatementIfNeeded( - mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode))); + const parts = this.visitAll(ast.expressions, mode); + const literalArr = + this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts); + return convertToStatementIfNeeded(mode, literalArr); } visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { @@ -242,13 +302,22 @@ class _AstToIrVisitor implements cdAst.AstVisitor { for (let i = 0; i < ast.keys.length; i++) { parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]); } - return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts)); + const literalMap = + this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts); + return convertToStatementIfNeeded(mode, literalMap); } 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); + } + visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { const leftMostSafe = this.leftMostSafeNode(ast); if (leftMostSafe) { @@ -258,7 +327,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor { let result: any = null; let receiver = this.visit(ast.receiver, _Mode.Expression); if (receiver === this._implicitReceiver) { - var varExpr = this._nameResolver.getLocal(ast.name); + var varExpr = this._getLocal(ast.name); if (isPresent(varExpr)) { result = varExpr.callFn(args); } @@ -282,7 +351,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor { let result: any = null; var receiver = this.visit(ast.receiver, _Mode.Expression); if (receiver === this._implicitReceiver) { - result = this._nameResolver.getLocal(ast.name); + result = this._getLocal(ast.name); } if (isBlank(result)) { result = receiver.prop(ast.name); @@ -294,7 +363,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor { visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any { let receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression); if (receiver === this._implicitReceiver) { - var varExpr = this._nameResolver.getLocal(ast.name); + var varExpr = this._getLocal(ast.name); if (isPresent(varExpr)) { throw new Error('Cannot assign to a reference or variable!'); } @@ -483,12 +552,12 @@ class _AstToIrVisitor implements cdAst.AstVisitor { private allocateTemporary(): o.ReadVarExpr { const tempNumber = this._currentTemporary++; this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount); - return new o.ReadVarExpr(temporaryName(this.bindingIndex, tempNumber)); + return new o.ReadVarExpr(temporaryName(this.bindingId, tempNumber)); } private releaseTemporary(temporary: o.ReadVarExpr) { this._currentTemporary--; - if (temporary.name != temporaryName(this.bindingIndex, this._currentTemporary)) { + if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) { throw new Error(`Temporary ${temporary.name} released out of order`); } } @@ -501,3 +570,69 @@ function flattenStatements(arg: any, output: o.Statement[]) { output.push(arg); } } + +function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression { + if (values.length === 0) { + return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY)); + } + var proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`); + var proxyParams: o.FnParam[] = []; + var proxyReturnEntries: o.Expression[] = []; + for (var i = 0; i < values.length; i++) { + var paramName = `p${i}`; + proxyParams.push(new o.FnParam(paramName)); + proxyReturnEntries.push(o.variable(paramName)); + } + createPureProxy( + o.fn( + proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))], + new o.ArrayType(o.DYNAMIC_TYPE)), + values.length, proxyExpr, builder); + return proxyExpr.callFn(values); +} + +function createCachedLiteralMap( + builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression { + if (entries.length === 0) { + return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP)); + } + const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`); + const proxyParams: o.FnParam[] = []; + const proxyReturnEntries: [string, o.Expression][] = []; + const values: o.Expression[] = []; + for (var i = 0; i < entries.length; i++) { + const paramName = `p${i}`; + proxyParams.push(new o.FnParam(paramName)); + proxyReturnEntries.push([entries[i][0], o.variable(paramName)]); + values.push(entries[i][1]); + } + createPureProxy( + o.fn( + proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))], + new o.MapType(o.DYNAMIC_TYPE)), + entries.length, proxyExpr, builder); + return proxyExpr.callFn(values); +} + + +class DefaultNameResolver implements NameResolver { + callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; } + getLocal(name: string): o.Expression { return null; } +} + +function createCurrValueExpr(bindingId: string): o.ReadVarExpr { + return o.variable(`currVal_${bindingId}`); // fix syntax highlighting: ` +} + +function createPreventDefaultVar(bindingId: string): o.ReadVarExpr { + return o.variable(`pd_${bindingId}`); +} + +function convertStmtIntoExpression(stmt: o.Statement): o.Expression { + if (stmt instanceof o.ExpressionStatement) { + return stmt.expr; + } else if (stmt instanceof o.ReturnStatement) { + return stmt.value; + } + return null; +} diff --git a/modules/@angular/compiler/src/compiler_util/identifier_util.ts b/modules/@angular/compiler/src/compiler_util/identifier_util.ts index 6f5e4fe70a..8e83885c2e 100644 --- a/modules/@angular/compiler/src/compiler_util/identifier_util.ts +++ b/modules/@angular/compiler/src/compiler_util/identifier_util.ts @@ -34,3 +34,17 @@ export function createFastArray(values: o.Expression[]): o.Expression { o.literal(values.length) ].concat(values)); } + +export function createPureProxy( + fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr, + builder: {fields: o.ClassField[], ctorStmts: {push: (stmt: o.Statement) => void}}) { + builder.fields.push(new o.ClassField(pureProxyProp.name, null)); + var pureProxyId = + argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null; + if (!pureProxyId) { + throw new Error(`Unsupported number of argument for pure functions: ${argCount}`); + } + builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name) + .set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn])) + .toStmt()); +} diff --git a/modules/@angular/compiler/src/compiler_util/render_util.ts b/modules/@angular/compiler/src/compiler_util/render_util.ts new file mode 100644 index 0000000000..4c76e17a4d --- /dev/null +++ b/modules/@angular/compiler/src/compiler_util/render_util.ts @@ -0,0 +1,96 @@ +/** + * @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 {SecurityContext} from '@angular/core'; + +import {isPresent} from '../facade/lang'; +import {Identifiers, resolveIdentifier} from '../identifiers'; +import * as o from '../output/output_ast'; +import {BoundElementPropertyAst, PropertyBindingType} from '../template_parser/template_ast'; + +export function writeToRenderer( + view: o.Expression, boundProp: BoundElementPropertyAst, renderNode: o.Expression, + renderValue: o.Expression, logBindingUpdate: boolean): o.Statement[] { + const updateStmts: o.Statement[] = []; + const renderer = view.prop('renderer'); + renderValue = sanitizedValue(view, boundProp, renderValue); + switch (boundProp.type) { + case PropertyBindingType.Property: + if (logBindingUpdate) { + updateStmts.push(o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo)) + .callFn([renderer, renderNode, o.literal(boundProp.name), renderValue]) + .toStmt()); + } + updateStmts.push( + renderer + .callMethod( + 'setElementProperty', [renderNode, o.literal(boundProp.name), renderValue]) + .toStmt()); + break; + case PropertyBindingType.Attribute: + renderValue = + renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', [])); + updateStmts.push( + renderer + .callMethod( + 'setElementAttribute', [renderNode, o.literal(boundProp.name), renderValue]) + .toStmt()); + break; + case PropertyBindingType.Class: + updateStmts.push( + renderer + .callMethod('setElementClass', [renderNode, o.literal(boundProp.name), renderValue]) + .toStmt()); + break; + case PropertyBindingType.Style: + var strValue: o.Expression = renderValue.callMethod('toString', []); + if (isPresent(boundProp.unit)) { + strValue = strValue.plus(o.literal(boundProp.unit)); + } + + renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue); + updateStmts.push( + renderer + .callMethod('setElementStyle', [renderNode, o.literal(boundProp.name), renderValue]) + .toStmt()); + break; + case PropertyBindingType.Animation: + throw new Error('Illegal state: Should not come here!'); + } + return updateStmts; +} + +function sanitizedValue( + view: o.Expression, boundProp: BoundElementPropertyAst, + renderValue: o.Expression): o.Expression { + let enumValue: string; + switch (boundProp.securityContext) { + case SecurityContext.NONE: + return renderValue; // No sanitization needed. + case SecurityContext.HTML: + enumValue = 'HTML'; + break; + case SecurityContext.STYLE: + enumValue = 'STYLE'; + break; + case SecurityContext.SCRIPT: + enumValue = 'SCRIPT'; + break; + case SecurityContext.URL: + enumValue = 'URL'; + break; + case SecurityContext.RESOURCE_URL: + enumValue = 'RESOURCE_URL'; + break; + default: + throw new Error(`internal error, unexpected SecurityContext ${boundProp.securityContext}.`); + } + let ctx = view.prop('viewUtils').prop('sanitizer'); + let args = + [o.importExpr(resolveIdentifier(Identifiers.SecurityContext)).prop(enumValue), renderValue]; + return ctx.callMethod('sanitize', args); +} diff --git a/modules/@angular/compiler/src/output/class_builder.ts b/modules/@angular/compiler/src/output/class_builder.ts index 8524e4b184..869cf89049 100644 --- a/modules/@angular/compiler/src/output/class_builder.ts +++ b/modules/@angular/compiler/src/output/class_builder.ts @@ -27,10 +27,10 @@ export function createClassStmt(config: { return new o.ClassStmt( config.name, config.parent, builder.fields, builder.getters, ctor, builder.methods, - config.modifiers || []) + config.modifiers || []); } -function concatClassBuilderParts(builders: ClassBuilderPart[]): ClassBuilder { +function concatClassBuilderParts(builders: ClassBuilderPart[]) { return { fields: [].concat(...builders.map(builder => builder.fields || [])), methods: [].concat(...builders.map(builder => builder.methods || [])), @@ -52,9 +52,9 @@ export interface ClassBuilderPart { /** * Collects data for a generated class. */ -export interface ClassBuilder extends ClassBuilderPart { +export interface ClassBuilder { fields: o.ClassField[]; methods: o.ClassMethod[]; getters: o.ClassGetter[]; ctorStmts: o.Statement[]; -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/src/view_compiler/compile_method.ts b/modules/@angular/compiler/src/view_compiler/compile_method.ts index db06c8152b..d4871cc87f 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_method.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_method.ts @@ -65,6 +65,8 @@ export class CompileMethod { this._newState = new _DebugState(nodeIndex, templateAst); } + push(...stmts: o.Statement[]) { this.addStmts(stmts); } + addStmt(stmt: o.Statement) { this._updateDebugContextIfNeeded(); this._bodyStatements.push(stmt); diff --git a/modules/@angular/compiler/src/view_compiler/compile_pipe.ts b/modules/@angular/compiler/src/view_compiler/compile_pipe.ts index ff5a02983f..e80d4ff4f5 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_pipe.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_pipe.ts @@ -8,11 +8,12 @@ import {CompilePipeMetadata} from '../compile_metadata'; +import {createPureProxy} from '../compiler_util/identifier_util'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import * as o from '../output/output_ast'; import {CompileView} from './compile_view'; -import {createPureProxy, getPropertyInView, injectFromViewParentInjector} from './util'; +import {getPropertyInView, injectFromViewParentInjector} from './util'; export class CompilePipe { static call(view: CompileView, name: string, args: o.Expression[]): o.Expression { @@ -65,7 +66,8 @@ export class CompilePipe { createPureProxy( pipeInstanceSeenFromPureProxy.prop('transform') .callMethod(o.BuiltinMethod.Bind, [pipeInstanceSeenFromPureProxy]), - args.length, purePipeProxyInstance, callingView); + args.length, purePipeProxyInstance, + {fields: callingView.fields, ctorStmts: callingView.createMethod}); return o.importExpr(resolveIdentifier(Identifiers.castByValue)) .callFn([purePipeProxyInstance, pipeInstanceSeenFromPureProxy.prop('transform')]) .callFn(args); diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index de64112ccb..a3bcb1e007 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -8,11 +8,12 @@ import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata'; +import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter'; +import {createPureProxy} from '../compiler_util/identifier_util'; import {CompilerConfig} from '../config'; import {MapWrapper} from '../facade/collection'; import {isPresent} from '../facade/lang'; import {Identifiers, resolveIdentifier} from '../identifiers'; -import {ClassBuilder} from '../output/class_builder'; import * as o from '../output/output_ast'; import {ViewType} from '../private_import_core'; @@ -21,11 +22,9 @@ import {CompileElement, CompileNode} from './compile_element'; import {CompileMethod} from './compile_method'; import {CompilePipe} from './compile_pipe'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; -import {EventHandlerVars} from './constants'; -import {NameResolver} from './expression_converter'; -import {createPureProxy, getPropertyInView, getViewFactoryName} from './util'; +import {getPropertyInView, getViewFactoryName} from './util'; -export class CompileView implements NameResolver, ClassBuilder { +export class CompileView implements NameResolver { public viewType: ViewType; public viewQueries: Map; @@ -150,48 +149,6 @@ export class CompileView implements NameResolver, ClassBuilder { } } - createLiteralArray(values: o.Expression[]): o.Expression { - if (values.length === 0) { - return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY)); - } - var proxyExpr = o.THIS_EXPR.prop(`_arr_${this.literalArrayCount++}`); - var proxyParams: o.FnParam[] = []; - var proxyReturnEntries: o.Expression[] = []; - for (var i = 0; i < values.length; i++) { - var paramName = `p${i}`; - proxyParams.push(new o.FnParam(paramName)); - proxyReturnEntries.push(o.variable(paramName)); - } - createPureProxy( - o.fn( - proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))], - new o.ArrayType(o.DYNAMIC_TYPE)), - values.length, proxyExpr, this); - return proxyExpr.callFn(values); - } - - createLiteralMap(entries: [string, o.Expression][]): o.Expression { - if (entries.length === 0) { - return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP)); - } - const proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`); - const proxyParams: o.FnParam[] = []; - const proxyReturnEntries: [string, o.Expression][] = []; - const values: o.Expression[] = []; - for (var i = 0; i < entries.length; i++) { - const paramName = `p${i}`; - proxyParams.push(new o.FnParam(paramName)); - proxyReturnEntries.push([entries[i][0], o.variable(paramName)]); - values.push(entries[i][1]); - } - createPureProxy( - o.fn( - proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))], - new o.MapType(o.DYNAMIC_TYPE)), - entries.length, proxyExpr, this); - return proxyExpr.callFn(values); - } - afterNodes() { MapWrapper.values(this.viewQueries) .forEach( diff --git a/modules/@angular/compiler/src/view_compiler/constants.ts b/modules/@angular/compiler/src/view_compiler/constants.ts index f97a4ff565..72cea01dee 100644 --- a/modules/@angular/compiler/src/view_compiler/constants.ts +++ b/modules/@angular/compiler/src/view_compiler/constants.ts @@ -98,8 +98,6 @@ export class ViewProperties { static viewUtils = o.THIS_EXPR.prop('viewUtils'); } -export class EventHandlerVars { static event = o.variable('$event'); } - export class InjectMethodVars { static token = o.variable('token'); static requestNodeIndex = o.variable('requestNodeIndex'); @@ -110,5 +108,4 @@ export class DetectChangesVars { static throwOnChange = o.variable(`throwOnChange`); static changes = o.variable(`changes`); static changed = o.variable(`changed`); - static valUnwrapper = o.variable(`valUnwrapper`); } diff --git a/modules/@angular/compiler/src/view_compiler/event_binder.ts b/modules/@angular/compiler/src/view_compiler/event_binder.ts index 78491b745f..d3bfcf2a70 100644 --- a/modules/@angular/compiler/src/view_compiler/event_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/event_binder.ts @@ -7,6 +7,7 @@ */ import {CompileDirectiveMetadata} from '../compile_metadata'; +import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter'; import {isPresent} from '../facade/lang'; import {identifierToken} from '../identifiers'; import * as o from '../output/output_ast'; @@ -15,8 +16,7 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {CompileBinding} from './compile_binding'; import {CompileElement} from './compile_element'; import {CompileMethod} from './compile_method'; -import {EventHandlerVars, ViewProperties} from './constants'; -import {NoLocalsNameResolver, convertCdStatementToIr} from './expression_converter'; +import {ViewProperties} from './constants'; export class CompileEventListener { private _method: CompileMethod; @@ -61,24 +61,14 @@ export class CompileEventListener { } this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent); var context = directiveInstance || this.compileElement.view.componentContext; - var actionStmts = convertCdStatementToIr( - directive ? new NoLocalsNameResolver(this.compileElement.view) : this.compileElement.view, - context, hostEvent.handler, this.compileElement.nodeIndex); - var lastIndex = actionStmts.length - 1; - if (lastIndex >= 0) { - var lastStatement = actionStmts[lastIndex]; - var returnExpr = convertStmtIntoExpression(lastStatement); - var preventDefaultVar = o.variable(`pd_${this._actionResultExprs.length}`); - this._actionResultExprs.push(preventDefaultVar); - if (isPresent(returnExpr)) { - // Note: We need to cast the result of the method call to dynamic, - // as it might be a void method! - actionStmts[lastIndex] = - preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false))) - .toDeclStmt(null, [o.StmtModifier.Final]); - } + const view = this.compileElement.view; + const evalResult = convertActionBinding( + view, directive ? null : view, context, hostEvent.handler, + `${this.compileElement.nodeIndex}_${this._actionResultExprs.length}`); + if (evalResult.preventDefault) { + this._actionResultExprs.push(evalResult.preventDefault); } - this._method.addStmts(actionStmts); + this._method.addStmts(evalResult.stmts); } finishMethod() { diff --git a/modules/@angular/compiler/src/view_compiler/property_binder.ts b/modules/@angular/compiler/src/view_compiler/property_binder.ts index 5104428b2b..9b50773e3f 100644 --- a/modules/@angular/compiler/src/view_compiler/property_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/property_binder.ts @@ -8,6 +8,8 @@ import {SecurityContext} from '@angular/core'; +import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter'; +import {writeToRenderer} from '../compiler_util/render_util'; import * as cdAst from '../expression_parser/ast'; import {isPresent} from '../facade/lang'; import {Identifiers, resolveIdentifier} from '../identifiers'; @@ -22,60 +24,14 @@ import {CompileMethod} from './compile_method'; import {CompileView} from './compile_view'; import {DetectChangesVars, ViewProperties} from './constants'; import {CompileEventListener} from './event_binder'; -import {NameResolver, NoLocalsNameResolver, convertCdExpressionToIr, temporaryDeclaration} from './expression_converter'; -function createBindFieldExpr(exprIndex: number): o.ReadPropExpr { - return o.THIS_EXPR.prop(`_expr_${exprIndex}`); +function createBindFieldExpr(bindingId: string): o.ReadPropExpr { + return o.THIS_EXPR.prop(`_expr_${bindingId}`); } -function createCurrValueExpr(exprIndex: number): o.ReadVarExpr { - return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: ` -} - -class EvalResult { - constructor(public forceUpdate: o.Expression) {} -} - -function evalCdAst( - view: CompileView, currValExpr: o.ReadVarExpr, parsedExpression: cdAst.AST, - context: o.Expression, nameResolver: NameResolver, method: CompileMethod, - bindingIndex: number): EvalResult { - var checkExpression = convertCdExpressionToIr( - nameResolver, context, parsedExpression, DetectChangesVars.valUnwrapper, bindingIndex); - if (!checkExpression.expression) { - // e.g. an empty expression was given - return null; - } - - if (checkExpression.temporaryCount) { - for (let i = 0; i < checkExpression.temporaryCount; i++) { - method.addStmt(temporaryDeclaration(bindingIndex, i)); - } - } - - if (checkExpression.needsValueUnwrapper) { - var initValueUnwrapperStmt = DetectChangesVars.valUnwrapper.callMethod('reset', []).toStmt(); - method.addStmt(initValueUnwrapperStmt); - } - method.addStmt( - currValExpr.set(checkExpression.expression).toDeclStmt(null, [o.StmtModifier.Final])); - if (checkExpression.needsValueUnwrapper) { - return new EvalResult(DetectChangesVars.valUnwrapper.prop('hasWrappedValue')); - } else { - return new EvalResult(null); - } -} - -function bind( - view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr, - parsedExpression: cdAst.AST, context: o.Expression, nameResolver: NameResolver, - actions: o.Statement[], method: CompileMethod, bindingIndex: number) { - const evalResult = - evalCdAst(view, currValExpr, parsedExpression, context, nameResolver, method, bindingIndex); - if (!evalResult) { - return; - } - +function createCheckBindingStmt( + view: CompileView, evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr, + actions: o.Statement[], method: CompileMethod) { // private is fine here as no child view will reference the cached value... view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private])); view.createMethod.addStmt(o.THIS_EXPR.prop(fieldExpr.name) @@ -83,30 +39,36 @@ function bind( .toStmt()); var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([ - DetectChangesVars.throwOnChange, fieldExpr, currValExpr + DetectChangesVars.throwOnChange, fieldExpr, evalResult.currValExpr ]); if (evalResult.forceUpdate) { condition = evalResult.forceUpdate.or(condition); } - method.addStmt(new o.IfStmt( - condition, - actions.concat([o.THIS_EXPR.prop(fieldExpr.name).set(currValExpr).toStmt()]))); + method.addStmts(evalResult.stmts); + method.addStmt(new o.IfStmt(condition, actions.concat([ + o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt() + ]))); } export function bindRenderText( - boundText: BoundTextAst, compileNode: CompileNode, view: CompileView) { - var bindingIndex = view.bindings.length; + boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void { + var bindingId = `${view.bindings.length}`; view.bindings.push(new CompileBinding(compileNode, boundText)); - var currValExpr = createCurrValueExpr(bindingIndex); - var valueField = createBindFieldExpr(bindingIndex); + const evalResult = + convertPropertyBinding(view, view, view.componentContext, boundText.value, bindingId); + if (!evalResult) { + return null; + } + + var valueField = createBindFieldExpr(bindingId); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText); - bind( - view, currValExpr, valueField, boundText.value, view.componentContext, view, + createCheckBindingStmt( + view, evalResult, valueField, [o.THIS_EXPR.prop('renderer') - .callMethod('setText', [compileNode.renderNode, currValExpr]) + .callMethod('setText', [compileNode.renderNode, evalResult.currValExpr]) .toStmt()], - view.detectChangesRenderPropertiesMethod, bindingIndex); + view.detectChangesRenderPropertiesMethod); } function bindAndWriteToRenderer( @@ -115,52 +77,22 @@ function bindAndWriteToRenderer( var view = compileElement.view; var renderNode = compileElement.renderNode; boundProps.forEach((boundProp) => { - var bindingIndex = view.bindings.length; + const bindingId = `${view.bindings.length}`; view.bindings.push(new CompileBinding(compileElement, boundProp)); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp); - var fieldExpr = createBindFieldExpr(bindingIndex); - var currValExpr = createCurrValueExpr(bindingIndex); - var oldRenderValue: o.Expression = sanitizedValue(boundProp, fieldExpr); - var renderValue: o.Expression = sanitizedValue(boundProp, currValExpr); + var fieldExpr = createBindFieldExpr(bindingId); + const evalResult = + convertPropertyBinding(view, isHostProp ? null : view, context, boundProp.value, bindingId); var updateStmts: o.Statement[] = []; var compileMethod = view.detectChangesRenderPropertiesMethod; switch (boundProp.type) { case PropertyBindingType.Property: - if (view.genConfig.logBindingUpdate) { - updateStmts.push(logBindingUpdateStmt(renderNode, boundProp.name, renderValue)); - } - updateStmts.push( - o.THIS_EXPR.prop('renderer') - .callMethod( - 'setElementProperty', [renderNode, o.literal(boundProp.name), renderValue]) - .toStmt()); - break; case PropertyBindingType.Attribute: - renderValue = - renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', [])); - updateStmts.push( - o.THIS_EXPR.prop('renderer') - .callMethod( - 'setElementAttribute', [renderNode, o.literal(boundProp.name), renderValue]) - .toStmt()); - break; case PropertyBindingType.Class: - updateStmts.push( - o.THIS_EXPR.prop('renderer') - .callMethod('setElementClass', [renderNode, o.literal(boundProp.name), renderValue]) - .toStmt()); - break; case PropertyBindingType.Style: - var strValue: o.Expression = renderValue.callMethod('toString', []); - if (isPresent(boundProp.unit)) { - strValue = strValue.plus(o.literal(boundProp.unit)); - } - - renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue); - updateStmts.push( - o.THIS_EXPR.prop('renderer') - .callMethod('setElementStyle', [renderNode, o.literal(boundProp.name), renderValue]) - .toStmt()); + updateStmts.push(...writeToRenderer( + o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr, + view.genConfig.logBindingUpdate)); break; case PropertyBindingType.Animation: compileMethod = view.animationBindingsMethod; @@ -182,16 +114,17 @@ function bindAndWriteToRenderer( updateStmts.push( animationTransitionVar .set(animationFnExpr.callFn([ - o.THIS_EXPR, renderNode, oldRenderValue.equals(unitializedValue) - .conditional(emptyStateValue, oldRenderValue), - renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue) + o.THIS_EXPR, renderNode, + fieldExpr.equals(unitializedValue).conditional(emptyStateValue, fieldExpr), + evalResult.currValExpr.equals(unitializedValue) + .conditional(emptyStateValue, evalResult.currValExpr) ])) .toDeclStmt()); - detachStmts.push(animationTransitionVar - .set(animationFnExpr.callFn( - [o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])) - .toDeclStmt()); + detachStmts.push( + animationTransitionVar + .set(animationFnExpr.callFn([o.THIS_EXPR, renderNode, fieldExpr, emptyStateValue])) + .toDeclStmt()); eventListeners.forEach(listener => { if (listener.isAnimation && listener.eventName === animationName) { @@ -206,43 +139,10 @@ function bindAndWriteToRenderer( break; } - bind( - view, currValExpr, fieldExpr, boundProp.value, context, - isHostProp ? new NoLocalsNameResolver(view) : view, updateStmts, compileMethod, - view.bindings.length); + createCheckBindingStmt(view, evalResult, fieldExpr, updateStmts, compileMethod); }); } -function sanitizedValue( - boundProp: BoundElementPropertyAst, renderValue: o.Expression): o.Expression { - let enumValue: string; - switch (boundProp.securityContext) { - case SecurityContext.NONE: - return renderValue; // No sanitization needed. - case SecurityContext.HTML: - enumValue = 'HTML'; - break; - case SecurityContext.STYLE: - enumValue = 'STYLE'; - break; - case SecurityContext.SCRIPT: - enumValue = 'SCRIPT'; - break; - case SecurityContext.URL: - enumValue = 'URL'; - break; - case SecurityContext.RESOURCE_URL: - enumValue = 'RESOURCE_URL'; - break; - default: - throw new Error(`internal error, unexpected SecurityContext ${boundProp.securityContext}.`); - } - let ctx = ViewProperties.viewUtils.prop('sanitizer'); - let args = - [o.importExpr(resolveIdentifier(Identifiers.SecurityContext)).prop(enumValue), renderValue]; - return ctx.callMethod('sanitize', args); -} - export function bindRenderInputs( boundProps: BoundElementPropertyAst[], compileElement: CompileElement, eventListeners: CompileEventListener[]): void { @@ -265,24 +165,24 @@ export function bindDirectiveInputs( detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); directiveAst.inputs.forEach((input) => { - var bindingIndex = view.bindings.length; + const bindingId = `${view.bindings.length}`; view.bindings.push(new CompileBinding(compileElement, input)); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input); - var currValExpr = createCurrValueExpr(bindingIndex); - const evalResult = evalCdAst( - view, currValExpr, input.value, view.componentContext, view, detectChangesInInputsMethod, - bindingIndex); + const evalResult = + convertPropertyBinding(view, view, view.componentContext, input.value, bindingId); if (!evalResult) { return; } - detectChangesInInputsMethod.addStmt(directiveWrapperInstance - .callMethod( - `check_${input.directiveName}`, - [ - currValExpr, DetectChangesVars.throwOnChange, - evalResult.forceUpdate || o.literal(false) - ]) - .toStmt()); + detectChangesInInputsMethod.addStmts(evalResult.stmts); + detectChangesInInputsMethod.addStmt( + directiveWrapperInstance + .callMethod( + `check_${input.directiveName}`, + [ + evalResult.currValExpr, DetectChangesVars.throwOnChange, + evalResult.forceUpdate || o.literal(false) + ]) + .toStmt()); }); var isOnPushComp = directiveAst.directive.isComponent && !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); @@ -296,10 +196,3 @@ export function bindDirectiveInputs( directiveDetectChangesExpr.toStmt(); detectChangesInInputsMethod.addStmt(directiveDetectChangesStmt); } - -function logBindingUpdateStmt( - renderNode: o.Expression, propName: string, value: o.Expression): o.Statement { - return o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo)) - .callFn([o.THIS_EXPR.prop('renderer'), renderNode, o.literal(propName), value]) - .toStmt(); -} diff --git a/modules/@angular/compiler/src/view_compiler/util.ts b/modules/@angular/compiler/src/view_compiler/util.ts index 2649a55c7f..27b3dd51ae 100644 --- a/modules/@angular/compiler/src/view_compiler/util.ts +++ b/modules/@angular/compiler/src/view_compiler/util.ts @@ -91,16 +91,3 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression { } return result; } - -export function createPureProxy( - fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr, view: CompileView) { - view.fields.push(new o.ClassField(pureProxyProp.name, null)); - var pureProxyId = - argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null; - if (!pureProxyId) { - throw new Error(`Unsupported number of argument for pure functions: ${argCount}`); - } - view.createMethod.addStmt(o.THIS_EXPR.prop(pureProxyProp.name) - .set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn])) - .toStmt()); -} diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 1ef321597e..c5fefa7e6a 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -9,6 +9,7 @@ import {ViewEncapsulation} from '@angular/core'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; +import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter'; import {createDiTokenExpression, createFastArray} from '../compiler_util/identifier_util'; import {isPresent} from '../facade/lang'; import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers'; @@ -586,12 +587,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] { DetectChangesVars.changes.set(o.NULL_EXPR) .toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange))))); } - if (readVars.has(DetectChangesVars.valUnwrapper.name)) { - varStmts.push( - DetectChangesVars.valUnwrapper - .set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([])) - .toDeclStmt(null, [o.StmtModifier.Final])); - } + varStmts.push(...createSharedBindingVariablesIfNeeded(stmts)); return varStmts.concat(stmts); }