angular-cn/packages/compiler/src/compiler_util/expression_converter.ts

819 lines
32 KiB
TypeScript
Raw Normal View History

/**
* @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 * as cdAst from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast';
import {ParseSourceSpan} from '../parse_util';
export class EventHandlerVars { static event = o.variable('$event'); }
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
export interface LocalResolver {
getLocal(name: string): o.Expression|null;
notifyImplicitReceiverUse(): void;
}
export class ConvertActionBindingResult {
/**
* Store statements which are render3 compatible.
*/
render3Stmts: o.Statement[];
constructor(
/**
* Render2 compatible statements,
*/
public stmts: o.Statement[],
/**
* Variable name used with render2 compatible statements.
*/
public allowDefault: o.ReadVarExpr) {
/**
* This is bit of a hack. It converts statements which render2 expects to statements which are
* expected by render3.
*
* Example: `<div click="doSomething($event)">` will generate:
*
* Render3:
* ```
* const pd_b:any = ((<any>ctx.doSomething($event)) !== false);
* return pd_b;
* ```
*
* but render2 expects:
* ```
* return ctx.doSomething($event);
* ```
*/
// TODO(misko): remove this hack once we no longer support ViewEngine.
this.render3Stmts = stmts.map((statement: o.Statement) => {
if (statement instanceof o.DeclareVarStmt && statement.name == allowDefault.name &&
statement.value instanceof o.BinaryOperatorExpr) {
const lhs = statement.value.lhs as o.CastExpr;
return new o.ReturnStatement(lhs.value);
}
return statement;
});
}
}
export type InterpolationFunction = (args: o.Expression[]) => o.Expression;
/**
* 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(
localResolver: LocalResolver | null, implicitReceiver: o.Expression, action: cdAst.AST,
bindingId: string, interpolationFunction?: InterpolationFunction,
baseSourceSpan?: ParseSourceSpan): ConvertActionBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
const actionWithoutBuiltins = convertPropertyBindingBuiltins(
{
createLiteralArrayConverter: (argCount: number) => {
// Note: no caching for literal arrays in actions.
return (args: o.Expression[]) => o.literalArr(args);
},
createLiteralMapConverter: (keys: {key: string, quoted: boolean}[]) => {
// Note: no caching for literal maps in actions.
return (values: o.Expression[]) => {
const entries = keys.map((k, i) => ({
key: k.key,
value: values[i],
quoted: k.quoted,
}));
return o.literalMap(entries);
};
},
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, interpolationFunction, baseSourceSpan);
const actionStmts: o.Statement[] = [];
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
if (visitor.usesImplicitReceiver) {
localResolver.notifyImplicitReceiverUse();
}
const lastIndex = actionStmts.length - 1;
let preventDefaultVar: o.ReadVarExpr = null !;
if (lastIndex >= 0) {
const lastStatement = actionStmts[lastIndex];
const 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 interface BuiltinConverter { (args: o.Expression[]): o.Expression; }
export interface BuiltinConverterFactory {
createLiteralArrayConverter(argCount: number): BuiltinConverter;
createLiteralMapConverter(keys: {key: string, quoted: boolean}[]): 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) {}
}
export enum BindingForm {
// The general form of binding expression, supports all expressions.
General,
// Try to generate a simple binding (no temporaries or statements)
// otherwise generate a general binding
TrySimple,
}
/**
* 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 | null, implicitReceiver: o.Expression,
expressionWithoutBuiltins: cdAst.AST, bindingId: string, form: BindingForm,
interpolationFunction?: InterpolationFunction): ConvertPropertyBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
const currValExpr = createCurrValueExpr(bindingId);
const visitor =
new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
const stmts: o.Statement[] = getStatementsFromVisitor(visitor, bindingId);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
if (visitor.usesImplicitReceiver) {
localResolver.notifyImplicitReceiverUse();
}
if (visitor.temporaryCount === 0 && form == BindingForm.TrySimple) {
return new ConvertPropertyBindingResult([], outputExpr);
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Final]));
return new ConvertPropertyBindingResult(stmts, currValExpr);
}
/**
* Given some expression, such as a binding or interpolation expression, and a context expression to
* look values up on, visit each facet of the given expression resolving values from the context
* expression such that a list of arguments can be derived from the found values that can be used as
* arguments to an external update instruction.
*
* @param localResolver The resolver to use to look up expressions by name appropriately
* @param contextVariableExpression The expression representing the context variable used to create
* the final argument expressions
* @param expressionWithArgumentsToExtract The expression to visit to figure out what values need to
* be resolved and what arguments list to build.
* @param bindingId A name prefix used to create temporary variable names if they're needed for the
* arguments generated
* @returns An array of expressions that can be passed as arguments to instruction expressions like
* `o.importExpr(R3.propertyInterpolate).callFn(result)`
*/
export function convertUpdateArguments(
localResolver: LocalResolver, contextVariableExpression: o.Expression,
expressionWithArgumentsToExtract: cdAst.AST, bindingId: string) {
const visitor =
new _AstToIrVisitor(localResolver, contextVariableExpression, bindingId, undefined);
const outputExpr: o.InvokeFunctionExpr =
expressionWithArgumentsToExtract.visit(visitor, _Mode.Expression);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
if (visitor.usesImplicitReceiver) {
localResolver.notifyImplicitReceiverUse();
}
const stmts = getStatementsFromVisitor(visitor, bindingId);
// Removing the first argument, because it was a length for ViewEngine, not Ivy.
let args = outputExpr.args.slice(1);
if (expressionWithArgumentsToExtract instanceof cdAst.Interpolation) {
// If we're dealing with an interpolation of 1 value with an empty prefix and suffix, reduce the
// args returned to just the value, because we're going to pass it to a special instruction.
const strings = expressionWithArgumentsToExtract.strings;
if (args.length === 3 && strings[0] === '' && strings[1] === '') {
// Single argument interpolate instructions.
args = [args[1]];
} else if (args.length >= 19) {
// 19 or more arguments must be passed to the `interpolateV`-style instructions, which accept
// an array of arguments
args = [o.literalArr(args)];
}
}
return {stmts, args};
}
function getStatementsFromVisitor(visitor: _AstToIrVisitor, bindingId: string) {
const stmts: o.Statement[] = [];
for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i));
}
return stmts;
}
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}`;
}
export function temporaryDeclaration(bindingId: string, temporaryNumber: number): o.Statement {
return new o.DeclareVarStmt(temporaryName(bindingId, temporaryNumber), o.NULL_EXPR);
}
function prependTemporaryDecls(
temporaryCount: number, bindingId: string, statements: o.Statement[]) {
for (let i = temporaryCount - 1; i >= 0; i--) {
statements.unshift(temporaryDeclaration(bindingId, i));
}
}
enum _Mode {
Statement,
Expression
}
function ensureStatementMode(mode: _Mode, ast: cdAst.AST) {
if (mode !== _Mode.Statement) {
throw new Error(`Expected a statement, but saw ${ast}`);
}
}
function ensureExpressionMode(mode: _Mode, ast: cdAst.AST) {
if (mode !== _Mode.Expression) {
throw new Error(`Expected an expression, but saw ${ast}`);
}
}
function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expression|o.Statement {
if (mode === _Mode.Statement) {
return expr.toStmt();
} else {
return expr;
}
}
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 temporaryCount: number = 0;
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
public usesImplicitReceiver: boolean = false;
constructor(
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
private bindingId: string, private interpolationFunction: InterpolationFunction|undefined,
private baseSourceSpan?: ParseSourceSpan) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
let op: o.BinaryOperator;
switch (ast.operation) {
case '+':
op = o.BinaryOperator.Plus;
break;
case '-':
op = o.BinaryOperator.Minus;
break;
case '*':
op = o.BinaryOperator.Multiply;
break;
case '/':
op = o.BinaryOperator.Divide;
break;
case '%':
op = o.BinaryOperator.Modulo;
break;
case '&&':
op = o.BinaryOperator.And;
break;
case '||':
op = o.BinaryOperator.Or;
break;
case '==':
op = o.BinaryOperator.Equals;
break;
case '!=':
op = o.BinaryOperator.NotEquals;
break;
case '===':
op = o.BinaryOperator.Identical;
break;
case '!==':
op = o.BinaryOperator.NotIdentical;
break;
case '<':
op = o.BinaryOperator.Lower;
break;
case '>':
op = o.BinaryOperator.Bigger;
break;
case '<=':
op = o.BinaryOperator.LowerEquals;
break;
case '>=':
op = o.BinaryOperator.BiggerEquals;
break;
default:
throw new Error(`Unsupported operation ${ast.operation}`);
}
return convertToStatementIfNeeded(
mode,
new o.BinaryOperatorExpr(
op, this._visit(ast.left, _Mode.Expression), this._visit(ast.right, _Mode.Expression),
undefined, this.convertSourceSpan(ast.span)));
}
visitChain(ast: cdAst.Chain, mode: _Mode): any {
ensureStatementMode(mode, ast);
return this.visitAll(ast.expressions, mode);
}
visitConditional(ast: cdAst.Conditional, mode: _Mode): any {
const value: o.Expression = this._visit(ast.condition, _Mode.Expression);
return convertToStatementIfNeeded(
mode, value.conditional(
this._visit(ast.trueExp, _Mode.Expression),
this._visit(ast.falseExp, _Mode.Expression), this.convertSourceSpan(ast.span)));
}
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
throw new Error(
`Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`);
}
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
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, this.convertSourceSpan(ast.span));
}
return convertToStatementIfNeeded(mode, fnResult);
}
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
ensureExpressionMode(mode, ast);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
this.usesImplicitReceiver = true;
return this._implicitReceiver;
}
visitInterpolation(ast: cdAst.Interpolation, mode: _Mode): any {
ensureExpressionMode(mode, ast);
const args = [o.literal(ast.expressions.length)];
for (let i = 0; i < ast.strings.length - 1; i++) {
args.push(o.literal(ast.strings[i]));
args.push(this._visit(ast.expressions[i], _Mode.Expression));
}
args.push(o.literal(ast.strings[ast.strings.length - 1]));
if (this.interpolationFunction) {
return this.interpolationFunction(args);
}
return ast.expressions.length <= 9 ?
o.importExpr(Identifiers.inlineInterpolate).callFn(args) :
o.importExpr(Identifiers.interpolate).callFn([
args[0], o.literalArr(args.slice(1), undefined, this.convertSourceSpan(ast.span))
]);
}
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) {
return this.convertSafeAccess(ast, leftMostSafe, mode);
} else {
return convertToStatementIfNeeded(
mode, this._visit(ast.obj, _Mode.Expression).key(this._visit(ast.key, _Mode.Expression)));
}
}
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
const obj: o.Expression = this._visit(ast.obj, _Mode.Expression);
const key: o.Expression = this._visit(ast.key, _Mode.Expression);
const value: o.Expression = this._visit(ast.value, _Mode.Expression);
return convertToStatementIfNeeded(mode, obj.key(key).set(value));
}
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
throw new Error(`Illegal State: literal arrays should have been converted into functions`);
}
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
throw new Error(`Illegal State: literal maps should have been converted into functions`);
}
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
// For literal values of null, undefined, true, or false allow type interference
// to infer the type.
const type =
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
o.INFERRED_TYPE :
undefined;
return convertToStatementIfNeeded(
mode, o.literal(ast.value, type, this.convertSourceSpan(ast.span)));
}
private _getLocal(name: string): o.Expression|null { return this._localResolver.getLocal(name); }
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
if (ast.receiver instanceof cdAst.ImplicitReceiver && ast.name == '$any') {
const args = this.visitAll(ast.args, _Mode.Expression) as any[];
if (args.length != 1) {
throw new Error(
`Invalid call to $any, expected 1 argument but received ${args.length || 'none'}`);
}
return (args[0] as o.Expression).cast(o.DYNAMIC_TYPE, this.convertSourceSpan(ast.span));
}
const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) {
return this.convertSafeAccess(ast, leftMostSafe, mode);
} else {
const args = this.visitAll(ast.args, _Mode.Expression);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
const prevUsesImplicitReceiver = this.usesImplicitReceiver;
let result: any = null;
const receiver = this._visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name);
2017-01-04 13:59:43 -08:00
if (varExpr) {
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
// Restore the previous "usesImplicitReceiver" state since the implicit
// receiver has been replaced with a resolved local expression.
this.usesImplicitReceiver = prevUsesImplicitReceiver;
result = varExpr.callFn(args);
}
}
2017-03-02 09:37:01 -08:00
if (result == null) {
result = receiver.callMethod(ast.name, args, this.convertSourceSpan(ast.span));
}
return convertToStatementIfNeeded(mode, result);
}
}
visitPrefixNot(ast: cdAst.PrefixNot, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.not(this._visit(ast.expression, _Mode.Expression)));
}
visitNonNullAssert(ast: cdAst.NonNullAssert, mode: _Mode): any {
return convertToStatementIfNeeded(
mode, o.assertNotNull(this._visit(ast.expression, _Mode.Expression)));
}
visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) {
return this.convertSafeAccess(ast, leftMostSafe, mode);
} else {
let result: any = null;
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
const prevUsesImplicitReceiver = this.usesImplicitReceiver;
const receiver = this._visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) {
result = this._getLocal(ast.name);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
if (result) {
// Restore the previous "usesImplicitReceiver" state since the implicit
// receiver has been replaced with a resolved local expression.
this.usesImplicitReceiver = prevUsesImplicitReceiver;
}
}
2017-03-02 09:37:01 -08:00
if (result == null) {
result = receiver.prop(ast.name);
}
return convertToStatementIfNeeded(mode, result);
}
}
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
const receiver: o.Expression = this._visit(ast.receiver, _Mode.Expression);
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
const prevUsesImplicitReceiver = this.usesImplicitReceiver;
let varExpr: o.ReadPropExpr|null = null;
if (receiver === this._implicitReceiver) {
const localExpr = this._getLocal(ast.name);
if (localExpr) {
if (localExpr instanceof o.ReadPropExpr) {
// If the local variable is a property read expression, it's a reference
// to a 'context.property' value and will be used as the target of the
// write expression.
varExpr = localExpr;
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
// Restore the previous "usesImplicitReceiver" state since the implicit
// receiver has been replaced with a resolved local expression.
this.usesImplicitReceiver = prevUsesImplicitReceiver;
} else {
// Otherwise it's an error.
throw new Error('Cannot assign to a reference or variable!');
}
}
}
// If no local expression could be produced, use the original receiver's
// property as the target.
if (varExpr === null) {
varExpr = receiver.prop(ast.name);
}
return convertToStatementIfNeeded(mode, varExpr.set(this._visit(ast.value, _Mode.Expression)));
}
visitSafePropertyRead(ast: cdAst.SafePropertyRead, mode: _Mode): any {
return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
}
visitSafeMethodCall(ast: cdAst.SafeMethodCall, mode: _Mode): any {
return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
}
visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => this._visit(ast, mode)); }
visitQuote(ast: cdAst.Quote, mode: _Mode): any {
throw new Error(`Quotes are not supported for evaluation!
Statement: ${ast.uninterpretedExpression} located at ${ast.location}`);
}
private _visit(ast: cdAst.AST, mode: _Mode): any {
const result = this._resultMap.get(ast);
if (result) return result;
return (this._nodeMap.get(ast) || ast).visit(this, mode);
}
private convertSafeAccess(
ast: cdAst.AST, leftMostSafe: cdAst.SafeMethodCall|cdAst.SafePropertyRead, mode: _Mode): any {
// If the expression contains a safe access node on the left it needs to be converted to
// an expression that guards the access to the member by checking the receiver for blank. As
// execution proceeds from left to right, the left most part of the expression must be guarded
// first but, because member access is left associative, the right side of the expression is at
// the top of the AST. The desired result requires lifting a copy of the the left part of the
// expression up to test it for blank before generating the unguarded version.
// Consider, for example the following expression: a?.b.c?.d.e
// This results in the ast:
// .
// / \
// ?. e
// / \
// . d
// / \
// ?. c
// / \
// a b
// The following tree should be generated:
//
// /---- ? ----\
// / | \
// a /--- ? ---\ null
// / | \
// . . null
// / \ / \
// . c . e
// / \ / \
// a b . d
// / \
// . c
// / \
// a b
//
// Notice that the first guard condition is the left hand of the left most safe access node
// which comes in as leftMostSafe to this routine.
let guardedExpression = this._visit(leftMostSafe.receiver, _Mode.Expression);
let temporary: o.ReadVarExpr = undefined !;
if (this.needsTemporary(leftMostSafe.receiver)) {
// If the expression has method calls or pipes then we need to save the result into a
// temporary variable to avoid calling stateful or impure code more than once.
temporary = this.allocateTemporary();
// Preserve the result in the temporary variable
guardedExpression = temporary.set(guardedExpression);
// Ensure all further references to the guarded expression refer to the temporary instead.
this._resultMap.set(leftMostSafe.receiver, temporary);
}
const condition = guardedExpression.isBlank();
// Convert the ast to an unguarded access to the receiver's member. The map will substitute
// leftMostNode with its unguarded version in the call to `this.visit()`.
if (leftMostSafe instanceof cdAst.SafeMethodCall) {
this._nodeMap.set(
leftMostSafe,
new cdAst.MethodCall(
leftMostSafe.span, leftMostSafe.receiver, leftMostSafe.name, leftMostSafe.args));
} else {
this._nodeMap.set(
leftMostSafe,
new cdAst.PropertyRead(leftMostSafe.span, leftMostSafe.receiver, leftMostSafe.name));
}
// Recursively convert the node now without the guarded member access.
const access = this._visit(ast, _Mode.Expression);
// Remove the mapping. This is not strictly required as the converter only traverses each node
// once but is safer if the conversion is changed to traverse the nodes more than once.
this._nodeMap.delete(leftMostSafe);
2017-03-23 11:14:56 +11:00
// If we allocated a temporary, release it.
if (temporary) {
this.releaseTemporary(temporary);
}
// Produce the conditional
return convertToStatementIfNeeded(mode, condition.conditional(o.literal(null), access));
}
// Given a expression of the form a?.b.c?.d.e the the left most safe node is
// the (a?.b). The . and ?. are left associative thus can be rewritten as:
// ((((a?.c).b).c)?.d).e. This returns the most deeply nested safe read or
// safe method call as this needs be transform initially to:
// a == null ? null : a.c.b.c?.d.e
// then to:
// a == null ? null : a.b.c == null ? null : a.b.c.d.e
private leftMostSafeNode(ast: cdAst.AST): cdAst.SafePropertyRead|cdAst.SafeMethodCall {
const visit = (visitor: cdAst.AstVisitor, ast: cdAst.AST): any => {
return (this._nodeMap.get(ast) || ast).visit(visitor);
};
return ast.visit({
visitBinary(ast: cdAst.Binary) { return null; },
visitChain(ast: cdAst.Chain) { return null; },
visitConditional(ast: cdAst.Conditional) { return null; },
visitFunctionCall(ast: cdAst.FunctionCall) { return null; },
visitImplicitReceiver(ast: cdAst.ImplicitReceiver) { return null; },
visitInterpolation(ast: cdAst.Interpolation) { return null; },
visitKeyedRead(ast: cdAst.KeyedRead) { return visit(this, ast.obj); },
visitKeyedWrite(ast: cdAst.KeyedWrite) { return null; },
visitLiteralArray(ast: cdAst.LiteralArray) { return null; },
visitLiteralMap(ast: cdAst.LiteralMap) { return null; },
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive) { return null; },
visitMethodCall(ast: cdAst.MethodCall) { return visit(this, ast.receiver); },
visitPipe(ast: cdAst.BindingPipe) { return null; },
visitPrefixNot(ast: cdAst.PrefixNot) { return null; },
visitNonNullAssert(ast: cdAst.NonNullAssert) { return null; },
visitPropertyRead(ast: cdAst.PropertyRead) { return visit(this, ast.receiver); },
visitPropertyWrite(ast: cdAst.PropertyWrite) { return null; },
visitQuote(ast: cdAst.Quote) { return null; },
visitSafeMethodCall(ast: cdAst.SafeMethodCall) { return visit(this, ast.receiver) || ast; },
visitSafePropertyRead(ast: cdAst.SafePropertyRead) {
return visit(this, ast.receiver) || ast;
}
});
}
// Returns true of the AST includes a method or a pipe indicating that, if the
// expression is used as the target of a safe property or method access then
// the expression should be stored into a temporary variable.
private needsTemporary(ast: cdAst.AST): boolean {
const visit = (visitor: cdAst.AstVisitor, ast: cdAst.AST): boolean => {
return ast && (this._nodeMap.get(ast) || ast).visit(visitor);
};
const visitSome = (visitor: cdAst.AstVisitor, ast: cdAst.AST[]): boolean => {
return ast.some(ast => visit(visitor, ast));
};
return ast.visit({
visitBinary(ast: cdAst.Binary):
boolean{return visit(this, ast.left) || visit(this, ast.right);},
visitChain(ast: cdAst.Chain) { return false; },
visitConditional(ast: cdAst.Conditional):
boolean{return visit(this, ast.condition) || visit(this, ast.trueExp) ||
visit(this, ast.falseExp);},
visitFunctionCall(ast: cdAst.FunctionCall) { return true; },
visitImplicitReceiver(ast: cdAst.ImplicitReceiver) { return false; },
visitInterpolation(ast: cdAst.Interpolation) { return visitSome(this, ast.expressions); },
visitKeyedRead(ast: cdAst.KeyedRead) { return false; },
visitKeyedWrite(ast: cdAst.KeyedWrite) { return false; },
visitLiteralArray(ast: cdAst.LiteralArray) { return true; },
visitLiteralMap(ast: cdAst.LiteralMap) { return true; },
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive) { return false; },
visitMethodCall(ast: cdAst.MethodCall) { return true; },
visitPipe(ast: cdAst.BindingPipe) { return true; },
visitPrefixNot(ast: cdAst.PrefixNot) { return visit(this, ast.expression); },
visitNonNullAssert(ast: cdAst.PrefixNot) { return visit(this, ast.expression); },
visitPropertyRead(ast: cdAst.PropertyRead) { return false; },
visitPropertyWrite(ast: cdAst.PropertyWrite) { return false; },
visitQuote(ast: cdAst.Quote) { return false; },
visitSafeMethodCall(ast: cdAst.SafeMethodCall) { return true; },
visitSafePropertyRead(ast: cdAst.SafePropertyRead) { return false; }
});
}
private allocateTemporary(): o.ReadVarExpr {
const tempNumber = this._currentTemporary++;
this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount);
return new o.ReadVarExpr(temporaryName(this.bindingId, tempNumber));
}
private releaseTemporary(temporary: o.ReadVarExpr) {
this._currentTemporary--;
if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) {
throw new Error(`Temporary ${temporary.name} released out of order`);
}
}
/**
* Creates an absolute `ParseSourceSpan` from the relative `ParseSpan`.
*
* `ParseSpan` objects are relative to the start of the expression.
* This method converts these to full `ParseSourceSpan` objects that
* show where the span is within the overall source file.
*
* @param span the relative span to convert.
* @returns a `ParseSourceSpan` for the the given span or null if no
* `baseSourceSpan` was provided to this class.
*/
private convertSourceSpan(span: cdAst.ParseSpan) {
if (this.baseSourceSpan) {
const start = this.baseSourceSpan.start.moveBy(span.start);
const end = this.baseSourceSpan.start.moveBy(span.end);
return new ParseSourceSpan(start, end);
} else {
return null;
}
}
}
function flattenStatements(arg: any, output: o.Statement[]) {
2016-10-19 13:42:39 -07:00
if (Array.isArray(arg)) {
(<any[]>arg).forEach((entry) => flattenStatements(entry, output));
} else {
output.push(arg);
}
}
class DefaultLocalResolver implements LocalResolver {
fix(ivy): unable to bind to implicit receiver in embedded views (#30897) To provide some context: The implicit receiver is part of the parsed Angular template AST. Any property reads in bindings, interpolations etc. read from a given object (usually the component instance). In that case there is an _implicit_ receiver which can also be specified explicitly by just using `this`. e.g. ```html <ng-template>{{this.myProperty}}</ng-template> ``` This works as expected in Ivy and View Engine, but breaks in case the implicit receiver is not used for property reads. For example: ```html <my-dir [myFn]="greetFn.bind(this)"></my-dir> ``` In that case the `this` will not be properly translated into the generated template function code because the Ivy compiler currently always treats the `ctx` variable as the implicit receiver. This is **not correct** and breaks compatibility with View Engine. Rather we need to ensure that we retrieve the root context for the standalone implicit receiver similar to how it works for property reads (as seen in the example above with `this.myProperty`) Note that this requires some small changes to the `expression_converter` because we only want to generate the `eenextContent()` instruction if the implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication since templates are isolated in different functions and it another pass through the output AST for every template expression. [ve_check]: https://github.com/angular/angular/blob/0d6c9d36a174f7dc6eb1029e459beecc2dfb0026/packages/compiler/src/view_compiler/view_compiler.ts#L206-L208 Resolves FW-1366. PR Close #30897
2019-06-06 20:01:51 +02:00
notifyImplicitReceiverUse(): void {}
getLocal(name: string): o.Expression|null {
if (name === EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
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|null {
if (stmt instanceof o.ExpressionStatement) {
return stmt.expr;
} else if (stmt instanceof o.ReturnStatement) {
return stmt.value;
}
return null;
}
export class BuiltinFunctionCall extends cdAst.FunctionCall {
constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) {
super(span, null, args);
}
2017-03-02 09:37:01 -08:00
}