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…
Reference in New Issue