refactor(compiler): recursive ast expression visitor not passing context (#31085)
Currently the `RecursiveAstVisitor` that is part of the template expression parser does not _always_ properly pass through the context that can be specified when visting a given expression. Only a handful of AST types pass through the context while others are accidentally left out. This causes unexpected and inconsistent behavior and basically makes the `context` parameter not usable if the type of template expression is not known. e.g. the template variable assignment migration currently depends on the `RecursiveAstVisitor` but sometimes breaks if developers use things like conditionals in their template variable assignments. Fixes #31043 PR Close #31085
This commit is contained in:
		
							parent
							
								
									75ac724842
								
							
						
					
					
						commit
						70ad91ed8b
					
				| @ -268,24 +268,24 @@ export class NullAstVisitor implements AstVisitor { | |||||||
| 
 | 
 | ||||||
| export class RecursiveAstVisitor implements AstVisitor { | export class RecursiveAstVisitor implements AstVisitor { | ||||||
|   visitBinary(ast: Binary, context: any): any { |   visitBinary(ast: Binary, context: any): any { | ||||||
|     ast.left.visit(this); |     ast.left.visit(this, context); | ||||||
|     ast.right.visit(this); |     ast.right.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitChain(ast: Chain, context: any): any { return this.visitAll(ast.expressions, context); } |   visitChain(ast: Chain, context: any): any { return this.visitAll(ast.expressions, context); } | ||||||
|   visitConditional(ast: Conditional, context: any): any { |   visitConditional(ast: Conditional, context: any): any { | ||||||
|     ast.condition.visit(this); |     ast.condition.visit(this, context); | ||||||
|     ast.trueExp.visit(this); |     ast.trueExp.visit(this, context); | ||||||
|     ast.falseExp.visit(this); |     ast.falseExp.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitPipe(ast: BindingPipe, context: any): any { |   visitPipe(ast: BindingPipe, context: any): any { | ||||||
|     ast.exp.visit(this); |     ast.exp.visit(this, context); | ||||||
|     this.visitAll(ast.args, context); |     this.visitAll(ast.args, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitFunctionCall(ast: FunctionCall, context: any): any { |   visitFunctionCall(ast: FunctionCall, context: any): any { | ||||||
|     ast.target !.visit(this); |     ast.target !.visit(this, context); | ||||||
|     this.visitAll(ast.args, context); |     this.visitAll(ast.args, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
| @ -294,14 +294,14 @@ export class RecursiveAstVisitor implements AstVisitor { | |||||||
|     return this.visitAll(ast.expressions, context); |     return this.visitAll(ast.expressions, context); | ||||||
|   } |   } | ||||||
|   visitKeyedRead(ast: KeyedRead, context: any): any { |   visitKeyedRead(ast: KeyedRead, context: any): any { | ||||||
|     ast.obj.visit(this); |     ast.obj.visit(this, context); | ||||||
|     ast.key.visit(this); |     ast.key.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitKeyedWrite(ast: KeyedWrite, context: any): any { |   visitKeyedWrite(ast: KeyedWrite, context: any): any { | ||||||
|     ast.obj.visit(this); |     ast.obj.visit(this, context); | ||||||
|     ast.key.visit(this); |     ast.key.visit(this, context); | ||||||
|     ast.value.visit(this); |     ast.value.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitLiteralArray(ast: LiteralArray, context: any): any { |   visitLiteralArray(ast: LiteralArray, context: any): any { | ||||||
| @ -310,32 +310,32 @@ export class RecursiveAstVisitor implements AstVisitor { | |||||||
|   visitLiteralMap(ast: LiteralMap, context: any): any { return this.visitAll(ast.values, context); } |   visitLiteralMap(ast: LiteralMap, context: any): any { return this.visitAll(ast.values, context); } | ||||||
|   visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any { return null; } |   visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any { return null; } | ||||||
|   visitMethodCall(ast: MethodCall, context: any): any { |   visitMethodCall(ast: MethodCall, context: any): any { | ||||||
|     ast.receiver.visit(this); |     ast.receiver.visit(this, context); | ||||||
|     return this.visitAll(ast.args, context); |     return this.visitAll(ast.args, context); | ||||||
|   } |   } | ||||||
|   visitPrefixNot(ast: PrefixNot, context: any): any { |   visitPrefixNot(ast: PrefixNot, context: any): any { | ||||||
|     ast.expression.visit(this); |     ast.expression.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitNonNullAssert(ast: NonNullAssert, context: any): any { |   visitNonNullAssert(ast: NonNullAssert, context: any): any { | ||||||
|     ast.expression.visit(this); |     ast.expression.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitPropertyRead(ast: PropertyRead, context: any): any { |   visitPropertyRead(ast: PropertyRead, context: any): any { | ||||||
|     ast.receiver.visit(this); |     ast.receiver.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitPropertyWrite(ast: PropertyWrite, context: any): any { |   visitPropertyWrite(ast: PropertyWrite, context: any): any { | ||||||
|     ast.receiver.visit(this); |     ast.receiver.visit(this, context); | ||||||
|     ast.value.visit(this); |     ast.value.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitSafePropertyRead(ast: SafePropertyRead, context: any): any { |   visitSafePropertyRead(ast: SafePropertyRead, context: any): any { | ||||||
|     ast.receiver.visit(this); |     ast.receiver.visit(this, context); | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   visitSafeMethodCall(ast: SafeMethodCall, context: any): any { |   visitSafeMethodCall(ast: SafeMethodCall, context: any): any { | ||||||
|     ast.receiver.visit(this); |     ast.receiver.visit(this, context); | ||||||
|     return this.visitAll(ast.args, context); |     return this.visitAll(ast.args, context); | ||||||
|   } |   } | ||||||
|   visitAll(asts: AST[], context: any): any { |   visitAll(asts: AST[], context: any): any { | ||||||
|  | |||||||
| @ -211,6 +211,30 @@ describe('template variable assignment migration', () => { | |||||||
|        expect(warnOutput.length).toBe(0); |        expect(warnOutput.length).toBe(0); | ||||||
|      }); |      }); | ||||||
| 
 | 
 | ||||||
|  |   it('should warn for template variable assignments in expression conditional', async() => { | ||||||
|  |     writeFile('/index.ts', ` | ||||||
|  |       import {Component} from '@angular/core'; | ||||||
|  | 
 | ||||||
|  |       @Component({ | ||||||
|  |         templateUrl: './sub_dir/tmpl.html', | ||||||
|  |       }) | ||||||
|  |       export class MyComp { | ||||||
|  |         otherVar = false; | ||||||
|  |       } | ||||||
|  |     `);
 | ||||||
|  | 
 | ||||||
|  |     writeFile('/sub_dir/tmpl.html', ` | ||||||
|  |       <ng-template let-tmplVar> | ||||||
|  |         <p (click)="enabled ? tmplVar = true : otherVar = true"></p> | ||||||
|  |       </ng-template> | ||||||
|  |     `);
 | ||||||
|  | 
 | ||||||
|  |     await runMigration(); | ||||||
|  | 
 | ||||||
|  |     expect(warnOutput.length).toBe(1); | ||||||
|  |     expect(warnOutput[0]).toMatch(/^⮑ {3}sub_dir\/tmpl.html@3:31: Found assignment/); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   it('should not warn for property writes with template variable name but different scope', |   it('should not warn for property writes with template variable name but different scope', | ||||||
|      async() => { |      async() => { | ||||||
|        writeFile('/index.ts', ` |        writeFile('/index.ts', ` | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user