2016-06-23 09:47:54 -07:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2016-06-23 09:47:54 -07:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
import * as cdAst from '../expression_parser/ast';
|
2017-05-18 13:46:51 -07:00
|
|
|
import {Identifiers} from '../identifiers';
|
2016-06-08 16:38:52 -07:00
|
|
|
import * as o from '../output/output_ast';
|
2019-02-08 22:10:20 +00:00
|
|
|
import {ParseSourceSpan} from '../parse_util';
|
2016-10-19 13:18:33 -07:00
|
|
|
|
2020-04-08 10:14:18 -07:00
|
|
|
export class EventHandlerVars {
|
|
|
|
static event = o.variable('$event');
|
|
|
|
}
|
2016-10-19 13:18:33 -07:00
|
|
|
|
2019-06-06 20:01:51 +02:00
|
|
|
export interface LocalResolver {
|
|
|
|
getLocal(name: string): o.Expression|null;
|
|
|
|
notifyImplicitReceiverUse(): void;
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
globals?: Set<string>;
|
2019-06-06 20:01:51 +02:00
|
|
|
}
|
2016-10-20 15:24:58 -07:00
|
|
|
|
2016-10-19 13:18:33 -07:00
|
|
|
export class ConvertActionBindingResult {
|
2018-04-05 11:38:06 -07:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
});
|
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
export type InterpolationFunction = (args: o.Expression[]) => o.Expression;
|
|
|
|
|
2016-10-19 13:18:33 -07:00
|
|
|
/**
|
|
|
|
* 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(
|
2020-04-08 10:14:18 -07:00
|
|
|
localResolver: LocalResolver|null, implicitReceiver: o.Expression, action: cdAst.AST,
|
2019-02-08 22:10:20 +00:00
|
|
|
bindingId: string, interpolationFunction?: InterpolationFunction,
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
baseSourceSpan?: ParseSourceSpan, implicitReceiverAccesses?: Set<string>,
|
|
|
|
globals?: Set<string>): ConvertActionBindingResult {
|
2017-02-09 14:59:57 -08:00
|
|
|
if (!localResolver) {
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
localResolver = new DefaultLocalResolver(globals);
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
|
|
|
const actionWithoutBuiltins = convertPropertyBindingBuiltins(
|
|
|
|
{
|
|
|
|
createLiteralArrayConverter: (argCount: number) => {
|
|
|
|
// Note: no caching for literal arrays in actions.
|
|
|
|
return (args: o.Expression[]) => o.literalArr(args);
|
|
|
|
},
|
2017-07-05 14:51:39 -07:00
|
|
|
createLiteralMapConverter: (keys: {key: string, quoted: boolean}[]) => {
|
2017-02-09 14:59:57 -08:00
|
|
|
// Note: no caching for literal maps in actions.
|
2017-07-05 14:51:39 -07:00
|
|
|
return (values: o.Expression[]) => {
|
|
|
|
const entries = keys.map((k, i) => ({
|
|
|
|
key: k.key,
|
|
|
|
value: values[i],
|
|
|
|
quoted: k.quoted,
|
|
|
|
}));
|
|
|
|
return o.literalMap(entries);
|
|
|
|
};
|
2017-02-09 14:59:57 -08:00
|
|
|
},
|
|
|
|
createPipeConverter: (name: string) => {
|
|
|
|
throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
action);
|
|
|
|
|
2019-02-08 22:10:20 +00:00
|
|
|
const visitor = new _AstToIrVisitor(
|
2020-02-01 13:19:31 +01:00
|
|
|
localResolver, implicitReceiver, bindingId, interpolationFunction, baseSourceSpan,
|
|
|
|
implicitReceiverAccesses);
|
2016-11-12 14:08:58 +01:00
|
|
|
const actionStmts: o.Statement[] = [];
|
2017-02-09 14:59:57 -08:00
|
|
|
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
|
2016-10-19 13:18:33 -07:00
|
|
|
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
|
2019-06-06 20:01:51 +02:00
|
|
|
|
|
|
|
if (visitor.usesImplicitReceiver) {
|
|
|
|
localResolver.notifyImplicitReceiverUse();
|
|
|
|
}
|
|
|
|
|
2016-11-12 14:08:58 +01:00
|
|
|
const lastIndex = actionStmts.length - 1;
|
2020-04-08 10:14:18 -07:00
|
|
|
let preventDefaultVar: o.ReadVarExpr = null!;
|
2016-10-19 13:18:33 -07:00
|
|
|
if (lastIndex >= 0) {
|
2016-11-12 14:08:58 +01:00
|
|
|
const lastStatement = actionStmts[lastIndex];
|
|
|
|
const returnExpr = convertStmtIntoExpression(lastStatement);
|
2016-10-19 13:18:33 -07:00
|
|
|
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);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
|
2020-04-08 10:14:18 -07:00
|
|
|
export interface BuiltinConverter {
|
|
|
|
(args: o.Expression[]): o.Expression;
|
|
|
|
}
|
2017-02-09 14:59:57 -08:00
|
|
|
|
|
|
|
export interface BuiltinConverterFactory {
|
|
|
|
createLiteralArrayConverter(argCount: number): BuiltinConverter;
|
2017-07-05 14:51:39 -07:00
|
|
|
createLiteralMapConverter(keys: {key: string, quoted: boolean}[]): BuiltinConverter;
|
2017-02-09 14:59:57 -08:00
|
|
|
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) {}
|
|
|
|
}
|
|
|
|
|
2017-11-29 16:29:05 -08:00
|
|
|
export enum BindingForm {
|
|
|
|
// The general form of binding expression, supports all expressions.
|
|
|
|
General,
|
|
|
|
|
|
|
|
// Try to generate a simple binding (no temporaries or statements)
|
2018-02-14 17:12:05 -08:00
|
|
|
// otherwise generate a general binding
|
2017-11-29 16:29:05 -08:00
|
|
|
TrySimple,
|
fix(compiler): evaluate safe navigation expressions in correct binding order (#37911)
When using the safe navigation operator in a binding expression, a temporary
variable may be used for storing the result of a side-effectful call.
For example, the following template uses a pipe and a safe property access:
```html
<app-person-view [enabled]="enabled" [firstName]="(person$ | async)?.name"></app-person-view>
```
The result of the pipe evaluation is stored in a temporary to be able to check
whether it is present. The temporary variable needs to be declared in a separate
statement and this would also cause the full expression itself to be pulled out
into a separate statement. This would compile into the following
pseudo-code instructions:
```js
var temp = null;
var firstName = (temp = pipe('async', ctx.person$)) == null ? null : temp.name;
property('enabled', ctx.enabled)('firstName', firstName);
```
Notice that the pipe evaluation happens before evaluating the `enabled` binding,
such that the runtime's internal binding index would correspond with `enabled`,
not `firstName`. This introduces a problem when the pipe uses `WrappedValue` to
force a change to be detected, as the runtime would then mark the binding slot
corresponding with `enabled` as dirty, instead of `firstName`. This results
in the `enabled` binding to be updated, triggering setters and affecting how
`OnChanges` is called.
In the pseudo-code above, the intermediate `firstName` variable is not strictly
necessary---it only improved readability a bit---and emitting it inline with
the binding itself avoids the out-of-order execution of the pipe:
```js
var temp = null;
property('enabled', ctx.enabled)
('firstName', (temp = pipe('async', ctx.person$)) == null ? null : temp.name);
```
This commit introduces a new `BindingForm` that results in the above code to be
generated and adds compiler and acceptance tests to verify the proper behavior.
Fixes #37194
PR Close #37911
2020-07-03 19:35:44 +02:00
|
|
|
|
|
|
|
// Inlines assignment of temporaries into the generated expression. The result may still
|
|
|
|
// have statements attached for declarations of temporary variables.
|
|
|
|
// This is the only relevant form for Ivy, the other forms are only used in ViewEngine.
|
|
|
|
Expression,
|
2017-11-29 16:29:05 -08:00
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2017-02-09 14:59:57 -08:00
|
|
|
/**
|
|
|
|
* 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(
|
2020-04-08 10:14:18 -07:00
|
|
|
localResolver: LocalResolver|null, implicitReceiver: o.Expression,
|
2017-11-20 10:21:17 -08:00
|
|
|
expressionWithoutBuiltins: cdAst.AST, bindingId: string, form: BindingForm,
|
|
|
|
interpolationFunction?: InterpolationFunction): ConvertPropertyBindingResult {
|
2017-02-09 14:59:57 -08:00
|
|
|
if (!localResolver) {
|
|
|
|
localResolver = new DefaultLocalResolver();
|
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
const visitor =
|
|
|
|
new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
|
2017-02-09 14:59:57 -08:00
|
|
|
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
|
2019-04-25 14:27:57 -07:00
|
|
|
const stmts: o.Statement[] = getStatementsFromVisitor(visitor, bindingId);
|
2017-02-09 14:59:57 -08:00
|
|
|
|
2019-06-06 20:01:51 +02:00
|
|
|
if (visitor.usesImplicitReceiver) {
|
|
|
|
localResolver.notifyImplicitReceiverUse();
|
|
|
|
}
|
|
|
|
|
2019-04-25 14:27:57 -07:00
|
|
|
if (visitor.temporaryCount === 0 && form == BindingForm.TrySimple) {
|
2017-11-29 16:29:05 -08:00
|
|
|
return new ConvertPropertyBindingResult([], outputExpr);
|
fix(compiler): evaluate safe navigation expressions in correct binding order (#37911)
When using the safe navigation operator in a binding expression, a temporary
variable may be used for storing the result of a side-effectful call.
For example, the following template uses a pipe and a safe property access:
```html
<app-person-view [enabled]="enabled" [firstName]="(person$ | async)?.name"></app-person-view>
```
The result of the pipe evaluation is stored in a temporary to be able to check
whether it is present. The temporary variable needs to be declared in a separate
statement and this would also cause the full expression itself to be pulled out
into a separate statement. This would compile into the following
pseudo-code instructions:
```js
var temp = null;
var firstName = (temp = pipe('async', ctx.person$)) == null ? null : temp.name;
property('enabled', ctx.enabled)('firstName', firstName);
```
Notice that the pipe evaluation happens before evaluating the `enabled` binding,
such that the runtime's internal binding index would correspond with `enabled`,
not `firstName`. This introduces a problem when the pipe uses `WrappedValue` to
force a change to be detected, as the runtime would then mark the binding slot
corresponding with `enabled` as dirty, instead of `firstName`. This results
in the `enabled` binding to be updated, triggering setters and affecting how
`OnChanges` is called.
In the pseudo-code above, the intermediate `firstName` variable is not strictly
necessary---it only improved readability a bit---and emitting it inline with
the binding itself avoids the out-of-order execution of the pipe:
```js
var temp = null;
property('enabled', ctx.enabled)
('firstName', (temp = pipe('async', ctx.person$)) == null ? null : temp.name);
```
This commit introduces a new `BindingForm` that results in the above code to be
generated and adds compiler and acceptance tests to verify the proper behavior.
Fixes #37194
PR Close #37911
2020-07-03 19:35:44 +02:00
|
|
|
} else if (form === BindingForm.Expression) {
|
|
|
|
return new ConvertPropertyBindingResult(stmts, outputExpr);
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
|
|
|
|
fix(compiler): evaluate safe navigation expressions in correct binding order (#37911)
When using the safe navigation operator in a binding expression, a temporary
variable may be used for storing the result of a side-effectful call.
For example, the following template uses a pipe and a safe property access:
```html
<app-person-view [enabled]="enabled" [firstName]="(person$ | async)?.name"></app-person-view>
```
The result of the pipe evaluation is stored in a temporary to be able to check
whether it is present. The temporary variable needs to be declared in a separate
statement and this would also cause the full expression itself to be pulled out
into a separate statement. This would compile into the following
pseudo-code instructions:
```js
var temp = null;
var firstName = (temp = pipe('async', ctx.person$)) == null ? null : temp.name;
property('enabled', ctx.enabled)('firstName', firstName);
```
Notice that the pipe evaluation happens before evaluating the `enabled` binding,
such that the runtime's internal binding index would correspond with `enabled`,
not `firstName`. This introduces a problem when the pipe uses `WrappedValue` to
force a change to be detected, as the runtime would then mark the binding slot
corresponding with `enabled` as dirty, instead of `firstName`. This results
in the `enabled` binding to be updated, triggering setters and affecting how
`OnChanges` is called.
In the pseudo-code above, the intermediate `firstName` variable is not strictly
necessary---it only improved readability a bit---and emitting it inline with
the binding itself avoids the out-of-order execution of the pipe:
```js
var temp = null;
property('enabled', ctx.enabled)
('firstName', (temp = pipe('async', ctx.person$)) == null ? null : temp.name);
```
This commit introduces a new `BindingForm` that results in the above code to be
generated and adds compiler and acceptance tests to verify the proper behavior.
Fixes #37194
PR Close #37911
2020-07-03 19:35:44 +02:00
|
|
|
const currValExpr = createCurrValueExpr(bindingId);
|
2017-12-12 14:20:12 -08:00
|
|
|
stmts.push(currValExpr.set(outputExpr).toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Final]));
|
2017-02-09 14:59:57 -08:00
|
|
|
return new ConvertPropertyBindingResult(stmts, currValExpr);
|
|
|
|
}
|
|
|
|
|
2019-04-25 14:27:57 -07:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
|
2019-06-06 20:01:51 +02:00
|
|
|
if (visitor.usesImplicitReceiver) {
|
|
|
|
localResolver.notifyImplicitReceiverUse();
|
|
|
|
}
|
|
|
|
|
2019-04-25 14:27:57 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-02-09 14:59:57 -08:00
|
|
|
function convertBuiltins(converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
|
|
|
|
const visitor = new _BuiltinAstConverter(converterFactory);
|
|
|
|
return ast.visit(visitor);
|
|
|
|
}
|
|
|
|
|
2016-10-19 13:18:33 -07:00
|
|
|
function temporaryName(bindingId: string, temporaryNumber: number): string {
|
|
|
|
return `tmp_${bindingId}_${temporaryNumber}`;
|
2016-08-11 21:20:54 -07:00
|
|
|
}
|
|
|
|
|
2016-10-19 13:18:33 -07:00
|
|
|
export function temporaryDeclaration(bindingId: string, temporaryNumber: number): o.Statement {
|
|
|
|
return new o.DeclareVarStmt(temporaryName(bindingId, temporaryNumber), o.NULL_EXPR);
|
2016-08-11 21:20:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function prependTemporaryDecls(
|
2016-10-19 13:18:33 -07:00
|
|
|
temporaryCount: number, bindingId: string, statements: o.Statement[]) {
|
2016-08-11 21:20:54 -07:00
|
|
|
for (let i = temporaryCount - 1; i >= 0; i--) {
|
2016-10-19 13:18:33 -07:00
|
|
|
statements.unshift(temporaryDeclaration(bindingId, i));
|
2016-08-11 21:20:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
enum _Mode {
|
|
|
|
Statement,
|
|
|
|
Expression
|
|
|
|
}
|
|
|
|
|
|
|
|
function ensureStatementMode(mode: _Mode, ast: cdAst.AST) {
|
|
|
|
if (mode !== _Mode.Statement) {
|
2016-08-25 00:50:16 -07:00
|
|
|
throw new Error(`Expected a statement, but saw ${ast}`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function ensureExpressionMode(mode: _Mode, ast: cdAst.AST) {
|
|
|
|
if (mode !== _Mode.Expression) {
|
2016-08-25 00:50:16 -07:00
|
|
|
throw new Error(`Expected an expression, but saw ${ast}`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expression|o.Statement {
|
2016-01-06 14:13:44 -08:00
|
|
|
if (mode === _Mode.Statement) {
|
|
|
|
return expr.toStmt();
|
|
|
|
} else {
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-09 14:59:57 -08:00
|
|
|
class _BuiltinAstConverter extends cdAst.AstTransformer {
|
2020-04-08 10:14:18 -07:00
|
|
|
constructor(private _converterFactory: BuiltinConverterFactory) {
|
|
|
|
super();
|
|
|
|
}
|
2017-02-09 14:59:57 -08:00
|
|
|
visitPipe(ast: cdAst.BindingPipe, context: any): any {
|
|
|
|
const args = [ast.exp, ...ast.args].map(ast => ast.visit(this, context));
|
|
|
|
return new BuiltinFunctionCall(
|
2019-07-29 13:23:29 -07:00
|
|
|
ast.span, ast.sourceSpan, args,
|
|
|
|
this._converterFactory.createPipeConverter(ast.name, args.length));
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
|
|
|
visitLiteralArray(ast: cdAst.LiteralArray, context: any): any {
|
|
|
|
const args = ast.expressions.map(ast => ast.visit(this, context));
|
|
|
|
return new BuiltinFunctionCall(
|
2019-07-29 13:23:29 -07:00
|
|
|
ast.span, ast.sourceSpan, args,
|
|
|
|
this._converterFactory.createLiteralArrayConverter(ast.expressions.length));
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
|
|
|
visitLiteralMap(ast: cdAst.LiteralMap, context: any): any {
|
|
|
|
const args = ast.values.map(ast => ast.visit(this, context));
|
2017-07-05 14:51:39 -07:00
|
|
|
|
2017-02-09 14:59:57 -08:00
|
|
|
return new BuiltinFunctionCall(
|
2019-07-29 13:23:29 -07:00
|
|
|
ast.span, ast.sourceSpan, args, this._converterFactory.createLiteralMapConverter(ast.keys));
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
class _AstToIrVisitor implements cdAst.AstVisitor {
|
2016-08-11 21:20:54 -07:00
|
|
|
private _nodeMap = new Map<cdAst.AST, cdAst.AST>();
|
|
|
|
private _resultMap = new Map<cdAst.AST, o.Expression>();
|
|
|
|
private _currentTemporary: number = 0;
|
|
|
|
public temporaryCount: number = 0;
|
2019-06-06 20:01:51 +02:00
|
|
|
public usesImplicitReceiver: boolean = false;
|
2016-01-06 14:13:44 -08:00
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
constructor(
|
2017-02-09 14:59:57 -08:00
|
|
|
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
|
2019-02-08 22:10:20 +00:00
|
|
|
private bindingId: string, private interpolationFunction: InterpolationFunction|undefined,
|
2020-02-01 13:19:31 +01:00
|
|
|
private baseSourceSpan?: ParseSourceSpan, private implicitReceiverAccesses?: Set<string>) {}
|
2016-01-06 14:13:44 -08:00
|
|
|
|
2020-07-04 01:52:40 +02:00
|
|
|
visitUnary(ast: cdAst.Unary, mode: _Mode): any {
|
|
|
|
let op: o.UnaryOperator;
|
|
|
|
switch (ast.operator) {
|
|
|
|
case '+':
|
|
|
|
op = o.UnaryOperator.Plus;
|
|
|
|
break;
|
|
|
|
case '-':
|
|
|
|
op = o.UnaryOperator.Minus;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`Unsupported operator ${ast.operator}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return convertToStatementIfNeeded(
|
|
|
|
mode,
|
|
|
|
new o.UnaryOperatorExpr(
|
|
|
|
op, this._visit(ast.expr, _Mode.Expression), undefined,
|
|
|
|
this.convertSourceSpan(ast.span)));
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
|
2016-11-12 14:08:58 +01:00
|
|
|
let op: o.BinaryOperator;
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
2021-04-03 18:10:31 +02:00
|
|
|
case '??':
|
|
|
|
return this.convertNullishCoalesce(ast, mode);
|
2016-01-06 14:13:44 -08:00
|
|
|
default:
|
2016-08-25 00:50:16 -07:00
|
|
|
throw new Error(`Unsupported operation ${ast.operation}`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return convertToStatementIfNeeded(
|
2016-06-08 16:38:52 -07:00
|
|
|
mode,
|
|
|
|
new o.BinaryOperatorExpr(
|
2019-02-08 22:10:20 +00:00
|
|
|
op, this._visit(ast.left, _Mode.Expression), this._visit(ast.right, _Mode.Expression),
|
|
|
|
undefined, this.convertSourceSpan(ast.span)));
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitChain(ast: cdAst.Chain, mode: _Mode): any {
|
|
|
|
ensureStatementMode(mode, ast);
|
|
|
|
return this.visitAll(ast.expressions, mode);
|
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitConditional(ast: cdAst.Conditional, mode: _Mode): any {
|
2017-05-09 16:16:50 -07:00
|
|
|
const value: o.Expression = this._visit(ast.condition, _Mode.Expression);
|
2016-01-06 14:13:44 -08:00
|
|
|
return convertToStatementIfNeeded(
|
2020-04-08 10:14:18 -07:00
|
|
|
mode,
|
|
|
|
value.conditional(
|
|
|
|
this._visit(ast.trueExp, _Mode.Expression), this._visit(ast.falseExp, _Mode.Expression),
|
|
|
|
this.convertSourceSpan(ast.span)));
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
|
2017-02-09 14:59:57 -08:00
|
|
|
throw new Error(
|
|
|
|
`Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
|
2017-02-09 14:59:57 -08:00
|
|
|
const convertedArgs = this.visitAll(ast.args, _Mode.Expression);
|
|
|
|
let fnResult: o.Expression;
|
|
|
|
if (ast instanceof BuiltinFunctionCall) {
|
|
|
|
fnResult = ast.converter(convertedArgs);
|
|
|
|
} else {
|
2020-04-08 10:14:18 -07:00
|
|
|
fnResult = this._visit(ast.target!, _Mode.Expression)
|
2019-02-08 22:10:20 +00:00
|
|
|
.callFn(convertedArgs, this.convertSourceSpan(ast.span));
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
|
|
|
return convertToStatementIfNeeded(mode, fnResult);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
|
|
|
|
ensureExpressionMode(mode, ast);
|
2019-06-06 20:01:51 +02:00
|
|
|
this.usesImplicitReceiver = true;
|
2016-08-04 10:14:44 -07:00
|
|
|
return this._implicitReceiver;
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
visitThisReceiver(ast: cdAst.ThisReceiver, mode: _Mode): any {
|
|
|
|
return this.visitImplicitReceiver(ast, mode);
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitInterpolation(ast: cdAst.Interpolation, mode: _Mode): any {
|
|
|
|
ensureExpressionMode(mode, ast);
|
2016-07-11 12:58:56 -07:00
|
|
|
const args = [o.literal(ast.expressions.length)];
|
|
|
|
for (let i = 0; i < ast.strings.length - 1; i++) {
|
2016-01-06 14:13:44 -08:00
|
|
|
args.push(o.literal(ast.strings[i]));
|
2017-05-09 16:16:50 -07:00
|
|
|
args.push(this._visit(ast.expressions[i], _Mode.Expression));
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
args.push(o.literal(ast.strings[ast.strings.length - 1]));
|
2016-11-07 21:23:03 +01:00
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
if (this.interpolationFunction) {
|
|
|
|
return this.interpolationFunction(args);
|
|
|
|
}
|
2016-11-07 21:23:03 +01:00
|
|
|
return ast.expressions.length <= 9 ?
|
2017-05-16 16:30:37 -07:00
|
|
|
o.importExpr(Identifiers.inlineInterpolate).callFn(args) :
|
2019-02-08 22:10:20 +00:00
|
|
|
o.importExpr(Identifiers.interpolate).callFn([
|
|
|
|
args[0], o.literalArr(args.slice(1), undefined, this.convertSourceSpan(ast.span))
|
|
|
|
]);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
|
2016-12-06 10:40:15 -08:00
|
|
|
const leftMostSafe = this.leftMostSafeNode(ast);
|
|
|
|
if (leftMostSafe) {
|
|
|
|
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
|
|
|
} else {
|
|
|
|
return convertToStatementIfNeeded(
|
2017-05-09 16:16:50 -07:00
|
|
|
mode, this._visit(ast.obj, _Mode.Expression).key(this._visit(ast.key, _Mode.Expression)));
|
2016-12-06 10:40:15 -08:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
|
2017-05-09 16:16:50 -07:00
|
|
|
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);
|
2016-01-06 14:13:44 -08:00
|
|
|
return convertToStatementIfNeeded(mode, obj.key(key).set(value));
|
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
|
2017-02-09 14:59:57 -08:00
|
|
|
throw new Error(`Illegal State: literal arrays should have been converted into functions`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
|
2017-02-09 14:59:57 -08:00
|
|
|
throw new Error(`Illegal State: literal maps should have been converted into functions`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
|
2018-02-14 17:12:05 -08:00
|
|
|
// For literal values of null, undefined, true, or false allow type interference
|
2017-12-12 14:20:12 -08:00
|
|
|
// to infer the type.
|
|
|
|
const type =
|
|
|
|
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
|
|
|
|
o.INFERRED_TYPE :
|
|
|
|
undefined;
|
2019-02-08 22:10:20 +00:00
|
|
|
return convertToStatementIfNeeded(
|
|
|
|
mode, o.literal(ast.value, type, this.convertSourceSpan(ast.span)));
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
private _getLocal(name: string, receiver: cdAst.AST): o.Expression|null {
|
|
|
|
if (this._localResolver.globals?.has(name) && receiver instanceof cdAst.ThisReceiver) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-04-08 10:14:18 -07:00
|
|
|
return this._localResolver.getLocal(name);
|
|
|
|
}
|
2016-10-19 13:18:33 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
if (ast.receiver instanceof cdAst.ImplicitReceiver &&
|
|
|
|
!(ast.receiver instanceof cdAst.ThisReceiver) && ast.name === '$any') {
|
2017-12-06 10:32:39 -08:00
|
|
|
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'}`);
|
|
|
|
}
|
2019-02-08 22:10:20 +00:00
|
|
|
return (args[0] as o.Expression).cast(o.DYNAMIC_TYPE, this.convertSourceSpan(ast.span));
|
2017-12-06 10:32:39 -08:00
|
|
|
}
|
|
|
|
|
2016-07-11 12:58:56 -07:00
|
|
|
const leftMostSafe = this.leftMostSafeNode(ast);
|
|
|
|
if (leftMostSafe) {
|
|
|
|
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
|
|
|
} else {
|
|
|
|
const args = this.visitAll(ast.args, _Mode.Expression);
|
2019-06-06 20:01:51 +02:00
|
|
|
const prevUsesImplicitReceiver = this.usesImplicitReceiver;
|
2016-07-11 12:58:56 -07:00
|
|
|
let result: any = null;
|
2017-05-09 16:16:50 -07:00
|
|
|
const receiver = this._visit(ast.receiver, _Mode.Expression);
|
2016-08-04 10:14:44 -07:00
|
|
|
if (receiver === this._implicitReceiver) {
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
const varExpr = this._getLocal(ast.name, ast.receiver);
|
2017-01-04 13:59:43 -08:00
|
|
|
if (varExpr) {
|
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;
|
2016-07-11 12:58:56 -07:00
|
|
|
result = varExpr.callFn(args);
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
this.addImplicitReceiverAccess(ast.name);
|
2016-07-11 12:58:56 -07:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2017-03-02 09:37:01 -08:00
|
|
|
if (result == null) {
|
2019-02-08 22:10:20 +00:00
|
|
|
result = receiver.callMethod(ast.name, args, this.convertSourceSpan(ast.span));
|
2016-07-11 12:58:56 -07:00
|
|
|
}
|
|
|
|
return convertToStatementIfNeeded(mode, result);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitPrefixNot(ast: cdAst.PrefixNot, mode: _Mode): any {
|
2017-05-09 16:16:50 -07:00
|
|
|
return convertToStatementIfNeeded(mode, o.not(this._visit(ast.expression, _Mode.Expression)));
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2017-05-11 10:15:54 -07:00
|
|
|
visitNonNullAssert(ast: cdAst.NonNullAssert, mode: _Mode): any {
|
|
|
|
return convertToStatementIfNeeded(
|
|
|
|
mode, o.assertNotNull(this._visit(ast.expression, _Mode.Expression)));
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any {
|
2016-07-11 12:58:56 -07:00
|
|
|
const leftMostSafe = this.leftMostSafeNode(ast);
|
|
|
|
if (leftMostSafe) {
|
|
|
|
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
|
|
|
} else {
|
|
|
|
let result: any = null;
|
2019-06-06 20:01:51 +02:00
|
|
|
const prevUsesImplicitReceiver = this.usesImplicitReceiver;
|
2017-05-09 16:16:50 -07:00
|
|
|
const receiver = this._visit(ast.receiver, _Mode.Expression);
|
2016-08-04 10:14:44 -07:00
|
|
|
if (receiver === this._implicitReceiver) {
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
result = this._getLocal(ast.name, ast.receiver);
|
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;
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
this.addImplicitReceiverAccess(ast.name);
|
2019-06-06 20:01:51 +02:00
|
|
|
}
|
2016-07-11 12:58:56 -07:00
|
|
|
}
|
2017-03-02 09:37:01 -08:00
|
|
|
if (result == null) {
|
2016-07-11 12:58:56 -07:00
|
|
|
result = receiver.prop(ast.name);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-07-11 12:58:56 -07:00
|
|
|
return convertToStatementIfNeeded(mode, result);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
|
2017-05-09 16:16:50 -07:00
|
|
|
const receiver: o.Expression = this._visit(ast.receiver, _Mode.Expression);
|
2019-06-06 20:01:51 +02:00
|
|
|
const prevUsesImplicitReceiver = this.usesImplicitReceiver;
|
2018-08-06 11:48:26 +02:00
|
|
|
|
|
|
|
let varExpr: o.ReadPropExpr|null = null;
|
2016-08-04 10:14:44 -07:00
|
|
|
if (receiver === this._implicitReceiver) {
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
const localExpr = this._getLocal(ast.name, ast.receiver);
|
2018-08-06 11:48:26 +02:00
|
|
|
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;
|
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;
|
2020-02-01 13:19:31 +01:00
|
|
|
this.addImplicitReceiverAccess(ast.name);
|
2018-08-06 11:48:26 +02:00
|
|
|
} else {
|
|
|
|
// Otherwise it's an error.
|
2019-09-19 00:44:21 +03:00
|
|
|
const receiver = ast.name;
|
|
|
|
const value = (ast.value instanceof cdAst.PropertyRead) ? ast.value.name : undefined;
|
2020-04-08 10:14:18 -07:00
|
|
|
throw new Error(`Cannot assign value "${value}" to template variable "${
|
|
|
|
receiver}". Template variables are read-only.`);
|
2018-08-06 11:48:26 +02:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
}
|
2018-08-06 11:48:26 +02:00
|
|
|
// 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)));
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitSafePropertyRead(ast: cdAst.SafePropertyRead, mode: _Mode): any {
|
2016-07-11 12:58:56 -07:00
|
|
|
return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitSafeMethodCall(ast: cdAst.SafeMethodCall, mode: _Mode): any {
|
2016-07-11 12:58:56 -07:00
|
|
|
return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2020-04-08 10:14:18 -07:00
|
|
|
visitAll(asts: cdAst.AST[], mode: _Mode): any {
|
|
|
|
return asts.map(ast => this._visit(ast, mode));
|
|
|
|
}
|
2016-08-04 10:14:44 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
visitQuote(ast: cdAst.Quote, mode: _Mode): any {
|
2017-04-11 16:26:12 -07:00
|
|
|
throw new Error(`Quotes are not supported for evaluation!
|
|
|
|
Statement: ${ast.uninterpretedExpression} located at ${ast.location}`);
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
2016-07-11 12:58:56 -07:00
|
|
|
|
2017-05-09 16:16:50 -07:00
|
|
|
private _visit(ast: cdAst.AST, mode: _Mode): any {
|
2016-08-11 21:20:54 -07:00
|
|
|
const result = this._resultMap.get(ast);
|
|
|
|
if (result) return result;
|
|
|
|
return (this._nodeMap.get(ast) || ast).visit(this, mode);
|
2016-07-11 12:58:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2019-11-22 01:01:10 +02:00
|
|
|
// the top of the AST. The desired result requires lifting a copy of the left part of the
|
2016-07-11 12:58:56 -07:00
|
|
|
// 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
|
|
|
|
// / \ / \
|
2019-05-30 17:09:46 +08:00
|
|
|
// a b . d
|
2016-07-11 12:58:56 -07:00
|
|
|
// / \
|
|
|
|
// . 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.
|
|
|
|
|
2017-05-09 16:16:50 -07:00
|
|
|
let guardedExpression = this._visit(leftMostSafe.receiver, _Mode.Expression);
|
2020-04-08 10:14:18 -07:00
|
|
|
let temporary: o.ReadVarExpr = undefined!;
|
2021-04-03 18:10:31 +02:00
|
|
|
if (this.needsTemporaryInSafeAccess(leftMostSafe.receiver)) {
|
2016-08-11 21:20:54 -07:00
|
|
|
// 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();
|
2016-07-11 12:58:56 -07:00
|
|
|
|
|
|
|
// 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) {
|
2016-08-11 21:20:54 -07:00
|
|
|
this._nodeMap.set(
|
2020-04-08 10:14:18 -07:00
|
|
|
leftMostSafe,
|
|
|
|
new cdAst.MethodCall(
|
2020-04-27 18:54:30 -07:00
|
|
|
leftMostSafe.span, leftMostSafe.sourceSpan, leftMostSafe.nameSpan,
|
2021-04-12 10:12:36 -04:00
|
|
|
leftMostSafe.receiver, leftMostSafe.name, leftMostSafe.args,
|
|
|
|
leftMostSafe.argumentSpan));
|
2016-07-11 12:58:56 -07:00
|
|
|
} else {
|
2016-08-11 21:20:54 -07:00
|
|
|
this._nodeMap.set(
|
2020-04-08 10:14:18 -07:00
|
|
|
leftMostSafe,
|
|
|
|
new cdAst.PropertyRead(
|
2020-04-27 18:54:30 -07:00
|
|
|
leftMostSafe.span, leftMostSafe.sourceSpan, leftMostSafe.nameSpan,
|
|
|
|
leftMostSafe.receiver, leftMostSafe.name));
|
2016-07-11 12:58:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Recursively convert the node now without the guarded member access.
|
2017-05-09 16:16:50 -07:00
|
|
|
const access = this._visit(ast, _Mode.Expression);
|
2016-07-11 12:58:56 -07:00
|
|
|
|
|
|
|
// 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.
|
2016-08-11 21:20:54 -07:00
|
|
|
this._nodeMap.delete(leftMostSafe);
|
|
|
|
|
2017-03-23 11:14:56 +11:00
|
|
|
// If we allocated a temporary, release it.
|
2016-08-11 21:20:54 -07:00
|
|
|
if (temporary) {
|
|
|
|
this.releaseTemporary(temporary);
|
|
|
|
}
|
2016-07-11 12:58:56 -07:00
|
|
|
|
|
|
|
// Produce the conditional
|
2016-09-20 14:54:53 -07:00
|
|
|
return convertToStatementIfNeeded(mode, condition.conditional(o.literal(null), access));
|
2016-07-11 12:58:56 -07:00
|
|
|
}
|
|
|
|
|
2021-04-03 18:10:31 +02:00
|
|
|
private convertNullishCoalesce(ast: cdAst.Binary, mode: _Mode): any {
|
|
|
|
// Allocate the temporary variable before visiting the LHS and RHS, because they
|
|
|
|
// may allocate temporary variables too and we don't want them to be reused.
|
|
|
|
const temporary = this.allocateTemporary();
|
|
|
|
const left: o.Expression = this._visit(ast.left, _Mode.Expression);
|
|
|
|
const right: o.Expression = this._visit(ast.right, _Mode.Expression);
|
|
|
|
this.releaseTemporary(temporary);
|
|
|
|
|
|
|
|
// Generate the following expression. It is identical to how TS
|
|
|
|
// transpiles binary expressions with a nullish coalescing operator.
|
|
|
|
// let temp;
|
|
|
|
// (temp = a) !== null && temp !== undefined ? temp : b;
|
|
|
|
return convertToStatementIfNeeded(
|
|
|
|
mode,
|
|
|
|
temporary.set(left)
|
|
|
|
.notIdentical(o.NULL_EXPR)
|
|
|
|
.and(temporary.notIdentical(o.literal(undefined)))
|
|
|
|
.conditional(temporary, right));
|
|
|
|
}
|
|
|
|
|
2019-11-22 01:01:10 +02:00
|
|
|
// Given an expression of the form a?.b.c?.d.e then the left most safe node is
|
2016-07-11 12:58:56 -07:00
|
|
|
// 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
|
2019-11-22 01:01:10 +02:00
|
|
|
// safe method call as this needs to be transformed initially to:
|
2016-07-11 12:58:56 -07:00
|
|
|
// 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 {
|
2016-08-11 21:20:54 -07:00
|
|
|
const visit = (visitor: cdAst.AstVisitor, ast: cdAst.AST): any => {
|
|
|
|
return (this._nodeMap.get(ast) || ast).visit(visitor);
|
2016-07-11 12:58:56 -07:00
|
|
|
};
|
|
|
|
return ast.visit({
|
2020-07-04 01:52:40 +02:00
|
|
|
visitUnary(ast: cdAst.Unary) {
|
|
|
|
return null;
|
|
|
|
},
|
2020-04-08 10:14:18 -07:00
|
|
|
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;
|
|
|
|
},
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
visitThisReceiver(ast: cdAst.ThisReceiver) {
|
|
|
|
return null;
|
|
|
|
},
|
2020-04-08 10:14:18 -07:00
|
|
|
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;
|
|
|
|
},
|
2016-07-11 12:58:56 -07:00
|
|
|
visitSafePropertyRead(ast: cdAst.SafePropertyRead) {
|
|
|
|
return visit(this, ast.receiver) || ast;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-08-11 21:20:54 -07:00
|
|
|
|
|
|
|
// 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.
|
2021-04-03 18:10:31 +02:00
|
|
|
private needsTemporaryInSafeAccess(ast: cdAst.AST): boolean {
|
2016-08-11 21:20:54 -07:00
|
|
|
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({
|
2020-07-04 01:52:40 +02:00
|
|
|
visitUnary(ast: cdAst.Unary): boolean {
|
|
|
|
return visit(this, ast.expr);
|
|
|
|
},
|
2020-04-08 10:14:18 -07:00
|
|
|
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;
|
|
|
|
},
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
visitThisReceiver(ast: cdAst.ThisReceiver) {
|
|
|
|
return false;
|
|
|
|
},
|
2020-04-08 10:14:18 -07:00
|
|
|
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;
|
|
|
|
}
|
2016-08-11 21:20:54 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private allocateTemporary(): o.ReadVarExpr {
|
|
|
|
const tempNumber = this._currentTemporary++;
|
|
|
|
this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount);
|
2016-10-19 13:18:33 -07:00
|
|
|
return new o.ReadVarExpr(temporaryName(this.bindingId, tempNumber));
|
2016-08-11 21:20:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private releaseTemporary(temporary: o.ReadVarExpr) {
|
|
|
|
this._currentTemporary--;
|
2016-10-19 13:18:33 -07:00
|
|
|
if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) {
|
2016-08-25 00:50:16 -07:00
|
|
|
throw new Error(`Temporary ${temporary.name} released out of order`);
|
2016-08-11 21:20:54 -07:00
|
|
|
}
|
|
|
|
}
|
2019-02-08 22:10:20 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2019-11-22 01:01:10 +02:00
|
|
|
* @returns a `ParseSourceSpan` for the given span or null if no
|
2019-02-08 22:10:20 +00:00
|
|
|
* `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);
|
2020-10-28 21:35:06 +00:00
|
|
|
const fullStart = this.baseSourceSpan.fullStart.moveBy(span.start);
|
|
|
|
return new ParseSourceSpan(start, end, fullStart);
|
2019-02-08 22:10:20 +00:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2020-02-01 13:19:31 +01:00
|
|
|
|
|
|
|
/** Adds the name of an AST to the list of implicit receiver accesses. */
|
|
|
|
private addImplicitReceiverAccess(name: string) {
|
|
|
|
if (this.implicitReceiverAccesses) {
|
|
|
|
this.implicitReceiverAccesses.add(name);
|
|
|
|
}
|
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
function flattenStatements(arg: any, output: o.Statement[]) {
|
2016-10-19 13:42:39 -07:00
|
|
|
if (Array.isArray(arg)) {
|
2016-01-06 14:13:44 -08:00
|
|
|
(<any[]>arg).forEach((entry) => flattenStatements(entry, output));
|
|
|
|
} else {
|
|
|
|
output.push(arg);
|
|
|
|
}
|
2016-04-25 21:47:33 -07:00
|
|
|
}
|
2016-10-19 13:18:33 -07:00
|
|
|
|
2017-02-09 14:59:57 -08:00
|
|
|
class DefaultLocalResolver implements LocalResolver {
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and
`this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered
the same implicit receiver as when the receiver is omitted. This introduces the following issues:
1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter.
2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`,
rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This
applies to listener, property and text bindings.
These changes resolve the first issue and part of the second one by preserving anything that
is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`.
Furthermore, these changes only expose the `$event` global variable inside event listeners,
whereas previously it was available everywhere.
Fixes #30278.
PR Close #39323
2020-10-18 17:41:29 +02:00
|
|
|
constructor(public globals?: Set<string>) {}
|
2019-06-06 20:01:51 +02:00
|
|
|
notifyImplicitReceiverUse(): void {}
|
2017-03-24 09:59:58 -07:00
|
|
|
getLocal(name: string): o.Expression|null {
|
2017-02-09 14:59:57 -08:00
|
|
|
if (name === EventHandlerVars.event.name) {
|
|
|
|
return EventHandlerVars.event;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2016-10-19 13:18:33 -07:00
|
|
|
|
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
|
2017-03-24 09:59:58 -07:00
|
|
|
function convertStmtIntoExpression(stmt: o.Statement): o.Expression|null {
|
2016-10-19 13:18:33 -07:00
|
|
|
if (stmt instanceof o.ExpressionStatement) {
|
|
|
|
return stmt.expr;
|
|
|
|
} else if (stmt instanceof o.ReturnStatement) {
|
|
|
|
return stmt.value;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-09 14:59:57 -08:00
|
|
|
|
2018-02-14 17:12:05 -08:00
|
|
|
export class BuiltinFunctionCall extends cdAst.FunctionCall {
|
2019-07-29 13:23:29 -07:00
|
|
|
constructor(
|
|
|
|
span: cdAst.ParseSpan, sourceSpan: cdAst.AbsoluteSourceSpan, public args: cdAst.AST[],
|
|
|
|
public converter: BuiltinConverter) {
|
|
|
|
super(span, sourceSpan, null, args);
|
2017-02-09 14:59:57 -08:00
|
|
|
}
|
2017-03-02 09:37:01 -08:00
|
|
|
}
|