| 
									
										
										
										
											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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												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
										 |  |  | import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead, ThisReceiver, Unary} from '../../../src/expression_parser/ast'; | 
					
						
							| 
									
										
										
										
											2018-04-24 14:22:55 -07:00
										 |  |  | import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config'; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-06 14:06:47 -07:00
										 |  |  | class Unparser implements AstVisitor { | 
					
						
							| 
									
										
										
										
											2015-06-23 12:46:38 +02:00
										 |  |  |   private static _quoteRegExp = /"/g; | 
					
						
							| 
									
										
										
										
											2018-06-18 16:38:33 -07:00
										 |  |  |   // TODO(issue/24571): remove '!'.
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |   private _expression!: string; | 
					
						
							| 
									
										
										
										
											2018-06-18 16:38:33 -07:00
										 |  |  |   // TODO(issue/24571): remove '!'.
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |   private _interpolationConfig!: InterpolationConfig; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-06 14:06:47 -07:00
										 |  |  |   unparse(ast: AST, interpolationConfig: InterpolationConfig) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._expression = ''; | 
					
						
							| 
									
										
										
										
											2016-06-20 09:52:41 -07:00
										 |  |  |     this._interpolationConfig = interpolationConfig; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast); | 
					
						
							|  |  |  |     return this._expression; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitPropertyRead(ast: PropertyRead, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.receiver); | 
					
						
							|  |  |  |     this._expression += ast.receiver instanceof ImplicitReceiver ? `${ast.name}` : `.${ast.name}`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitPropertyWrite(ast: PropertyWrite, context: any) { | 
					
						
							| 
									
										
										
										
											2015-08-12 16:26:21 -07:00
										 |  |  |     this._visit(ast.receiver); | 
					
						
							|  |  |  |     this._expression += | 
					
						
							|  |  |  |         ast.receiver instanceof ImplicitReceiver ? `${ast.name} = ` : `.${ast.name} = `; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.value); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-04 01:52:40 +02:00
										 |  |  |   visitUnary(ast: Unary, context: any) { | 
					
						
							|  |  |  |     this._expression += ast.operator; | 
					
						
							|  |  |  |     this._visit(ast.expr); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitBinary(ast: Binary, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.left); | 
					
						
							|  |  |  |     this._expression += ` ${ast.operation} `; | 
					
						
							|  |  |  |     this._visit(ast.right); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitChain(ast: Chain, context: any) { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     const len = ast.expressions.length; | 
					
						
							| 
									
										
										
										
											2015-06-10 11:11:01 +02:00
										 |  |  |     for (let i = 0; i < len; i++) { | 
					
						
							|  |  |  |       this._visit(ast.expressions[i]); | 
					
						
							|  |  |  |       this._expression += i == len - 1 ? ';' : '; '; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitConditional(ast: Conditional, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.condition); | 
					
						
							|  |  |  |     this._expression += ' ? '; | 
					
						
							|  |  |  |     this._visit(ast.trueExp); | 
					
						
							|  |  |  |     this._expression += ' : '; | 
					
						
							|  |  |  |     this._visit(ast.falseExp); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitPipe(ast: BindingPipe, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-04 19:06:09 +02:00
										 |  |  |     this._expression += '('; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.exp); | 
					
						
							|  |  |  |     this._expression += ` | ${ast.name}`; | 
					
						
							|  |  |  |     ast.args.forEach(arg => { | 
					
						
							|  |  |  |       this._expression += ':'; | 
					
						
							|  |  |  |       this._visit(arg); | 
					
						
							| 
									
										
										
										
											2015-06-04 19:06:09 +02:00
										 |  |  |     }); | 
					
						
							|  |  |  |     this._expression += ')'; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitFunctionCall(ast: FunctionCall, context: any) { | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |     this._visit(ast.target!); | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._expression += '('; | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     let isFirst = true; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     ast.args.forEach(arg => { | 
					
						
							|  |  |  |       if (!isFirst) this._expression += ', '; | 
					
						
							|  |  |  |       isFirst = false; | 
					
						
							|  |  |  |       this._visit(arg); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this._expression += ')'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitImplicitReceiver(ast: ImplicitReceiver, context: any) {} | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02: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: ThisReceiver, context: any) {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitInterpolation(ast: Interpolation, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     for (let i = 0; i < ast.strings.length; i++) { | 
					
						
							|  |  |  |       this._expression += ast.strings[i]; | 
					
						
							|  |  |  |       if (i < ast.expressions.length) { | 
					
						
							| 
									
										
										
										
											2016-06-20 09:52:41 -07:00
										 |  |  |         this._expression += `${this._interpolationConfig.start} `; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |         this._visit(ast.expressions[i]); | 
					
						
							| 
									
										
										
										
											2016-06-20 09:52:41 -07:00
										 |  |  |         this._expression += ` ${this._interpolationConfig.end}`; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitKeyedRead(ast: KeyedRead, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.obj); | 
					
						
							|  |  |  |     this._expression += '['; | 
					
						
							|  |  |  |     this._visit(ast.key); | 
					
						
							|  |  |  |     this._expression += ']'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitKeyedWrite(ast: KeyedWrite, context: any) { | 
					
						
							| 
									
										
										
										
											2015-08-12 16:26:21 -07:00
										 |  |  |     this._visit(ast.obj); | 
					
						
							|  |  |  |     this._expression += '['; | 
					
						
							|  |  |  |     this._visit(ast.key); | 
					
						
							|  |  |  |     this._expression += '] = '; | 
					
						
							|  |  |  |     this._visit(ast.value); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitLiteralArray(ast: LiteralArray, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._expression += '['; | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     let isFirst = true; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     ast.expressions.forEach(expression => { | 
					
						
							|  |  |  |       if (!isFirst) this._expression += ', '; | 
					
						
							|  |  |  |       isFirst = false; | 
					
						
							|  |  |  |       this._visit(expression); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._expression += ']'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitLiteralMap(ast: LiteralMap, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._expression += '{'; | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     let isFirst = true; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     for (let i = 0; i < ast.keys.length; i++) { | 
					
						
							|  |  |  |       if (!isFirst) this._expression += ', '; | 
					
						
							|  |  |  |       isFirst = false; | 
					
						
							| 
									
										
										
										
											2017-07-05 10:54:05 -07:00
										 |  |  |       const key = ast.keys[i]; | 
					
						
							|  |  |  |       this._expression += key.quoted ? JSON.stringify(key.key) : key.key; | 
					
						
							|  |  |  |       this._expression += ': '; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |       this._visit(ast.values[i]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._expression += '}'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitLiteralPrimitive(ast: LiteralPrimitive, context: any) { | 
					
						
							| 
									
										
										
										
											2016-10-19 13:42:39 -07:00
										 |  |  |     if (typeof ast.value === 'string') { | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |       this._expression += `"${ast.value.replace(Unparser._quoteRegExp, '\"')}"`; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       this._expression += `${ast.value}`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitMethodCall(ast: MethodCall, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.receiver); | 
					
						
							| 
									
										
										
										
											2015-07-10 11:29:41 +02:00
										 |  |  |     this._expression += ast.receiver instanceof ImplicitReceiver ? `${ast.name}(` : `.${ast.name}(`; | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     let isFirst = true; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     ast.args.forEach(arg => { | 
					
						
							|  |  |  |       if (!isFirst) this._expression += ', '; | 
					
						
							|  |  |  |       isFirst = false; | 
					
						
							|  |  |  |       this._visit(arg); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this._expression += ')'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitPrefixNot(ast: PrefixNot, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._expression += '!'; | 
					
						
							|  |  |  |     this._visit(ast.expression); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-11 10:15:54 -07:00
										 |  |  |   visitNonNullAssert(ast: NonNullAssert, context: any) { | 
					
						
							|  |  |  |     this._visit(ast.expression); | 
					
						
							|  |  |  |     this._expression += '!'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitSafePropertyRead(ast: SafePropertyRead, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.receiver); | 
					
						
							|  |  |  |     this._expression += `?.${ast.name}`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitSafeMethodCall(ast: SafeMethodCall, context: any) { | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     this._visit(ast.receiver); | 
					
						
							|  |  |  |     this._expression += `?.${ast.name}(`; | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     let isFirst = true; | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  |     ast.args.forEach(arg => { | 
					
						
							|  |  |  |       if (!isFirst) this._expression += ', '; | 
					
						
							|  |  |  |       isFirst = false; | 
					
						
							|  |  |  |       this._visit(arg); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this._expression += ')'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-06 14:13:44 -08:00
										 |  |  |   visitQuote(ast: Quote, context: any) { | 
					
						
							|  |  |  |     this._expression += `${ast.prefix}:${ast.uninterpretedExpression}`; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-11-23 17:58:12 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |   private _visit(ast: AST) { | 
					
						
							|  |  |  |     ast.visit(this); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-06-06 11:15:14 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-07-06 14:06:47 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const sharedUnparser = new Unparser(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function unparse( | 
					
						
							|  |  |  |     ast: AST, interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): string { | 
					
						
							|  |  |  |   return sharedUnparser.unparse(ast, interpolationConfig); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | // [unparsed AST, original source code of AST]
 | 
					
						
							|  |  |  | type UnparsedWithSpan = [string, string]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function unparseWithSpan( | 
					
						
							|  |  |  |     ast: ASTWithSource, | 
					
						
							|  |  |  |     interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): UnparsedWithSpan[] { | 
					
						
							|  |  |  |   const unparsed: UnparsedWithSpan[] = []; | 
					
						
							|  |  |  |   const source = ast.source!; | 
					
						
							|  |  |  |   const recursiveSpanUnparser = new class extends RecursiveAstVisitor { | 
					
						
							|  |  |  |     private recordUnparsed(ast: any, spanKey: string, unparsedList: UnparsedWithSpan[]) { | 
					
						
							|  |  |  |       const span = ast[spanKey]; | 
					
						
							|  |  |  |       const prefix = spanKey === 'span' ? '' : `[${spanKey}] `; | 
					
						
							|  |  |  |       const src = source.substring(span.start, span.end); | 
					
						
							|  |  |  |       unparsedList.push([ | 
					
						
							|  |  |  |         unparse(ast, interpolationConfig), | 
					
						
							|  |  |  |         prefix + src, | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     visit(ast: AST, unparsedList: UnparsedWithSpan[]) { | 
					
						
							|  |  |  |       this.recordUnparsed(ast, 'span', unparsedList); | 
					
						
							|  |  |  |       if (ast.hasOwnProperty('nameSpan')) { | 
					
						
							|  |  |  |         this.recordUnparsed(ast, 'nameSpan', unparsedList); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       ast.visit(this, unparsedList); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   recursiveSpanUnparser.visitAll([ast.ast], unparsed); | 
					
						
							|  |  |  |   return unparsed; | 
					
						
							|  |  |  | } |