refactor(core): template-var-assignment migration incorrectly warns (#30026)
Currently the `template-var-assignment` migration incorrectly warns if the template writes to a property in the component that has the same `ast.PropertyWrite´ name as a template input variable but different receiver. e.g. ```html <!-- "someProp.element" will be incorrectly reported as template variable assignment --> <button *ngFor="let element of list" (click)="someProp.element = null">Reset</button> ``` Similarly if an output writes to a component property with the same name as a template input variable, but the expression is within a different template scope, the schematic currently incorrectly warns. e.g. ```html <button *ngFor="let element of list">{{element}}</button> <!-- The "element = null" expression does not refer to the "element" template input variable --> <button (click)="element = null"></button> ``` PR Close #30026
This commit is contained in:

committed by
Ben Lesh

parent
94aeeec1dc
commit
a9242c4fc2
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ImplicitReceiver, ParseSourceSpan, PropertyWrite, RecursiveAstVisitor} from '@angular/compiler';
|
||||
import {BoundEvent, Element, NullVisitor, Template, Variable, visitAll} from '@angular/compiler/src/render3/r3_ast';
|
||||
|
||||
export interface TemplateVariableAssignment {
|
||||
start: number;
|
||||
end: number;
|
||||
node: PropertyWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML AST visitor that traverses the Render3 HTML AST in order to find all
|
||||
* expressions that write to local template variables within bound events.
|
||||
*/
|
||||
export class HtmlVariableAssignmentVisitor extends NullVisitor {
|
||||
variableAssignments: TemplateVariableAssignment[] = [];
|
||||
|
||||
private currentVariables: Variable[] = [];
|
||||
private expressionAstVisitor =
|
||||
new ExpressionAstVisitor(this.variableAssignments, this.currentVariables);
|
||||
|
||||
visitElement(element: Element): void {
|
||||
visitAll(this, element.outputs);
|
||||
visitAll(this, element.children);
|
||||
}
|
||||
|
||||
visitTemplate(template: Template): void {
|
||||
// Keep track of the template variables which can be accessed by the template
|
||||
// child nodes through the implicit receiver.
|
||||
this.currentVariables.push(...template.variables);
|
||||
|
||||
// Visit all children of the template. The template proxies the outputs of the
|
||||
// immediate child elements, so we just ignore outputs on the "Template" in order
|
||||
// to not visit similar bound events twice.
|
||||
visitAll(this, template.children);
|
||||
|
||||
// Remove all previously added variables since all children that could access
|
||||
// these have been visited already.
|
||||
template.variables.forEach(v => {
|
||||
const variableIdx = this.currentVariables.indexOf(v);
|
||||
|
||||
if (variableIdx !== -1) {
|
||||
this.currentVariables.splice(variableIdx, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
visitBoundEvent(node: BoundEvent) {
|
||||
node.handler.visit(this.expressionAstVisitor, node.handlerSpan);
|
||||
}
|
||||
}
|
||||
|
||||
/** AST visitor that resolves all variable assignments within a given expression AST. */
|
||||
class ExpressionAstVisitor extends RecursiveAstVisitor {
|
||||
constructor(
|
||||
private variableAssignments: TemplateVariableAssignment[],
|
||||
private currentVariables: Variable[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
visitPropertyWrite(node: PropertyWrite, span: ParseSourceSpan) {
|
||||
if (node.receiver instanceof ImplicitReceiver &&
|
||||
this.currentVariables.some(v => v.name === node.name)) {
|
||||
this.variableAssignments.push({
|
||||
node: node,
|
||||
start: span.start.offset,
|
||||
end: span.end.offset,
|
||||
});
|
||||
}
|
||||
super.visitPropertyWrite(node, span);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ParseSourceSpan, PropertyWrite, RecursiveAstVisitor} from '@angular/compiler';
|
||||
import {BoundEvent, Element, NullVisitor, Template, Variable, visitAll} from '@angular/compiler/src/render3/r3_ast';
|
||||
|
||||
export interface PropertyAssignment {
|
||||
start: number;
|
||||
end: number;
|
||||
node: PropertyWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* AST visitor that traverses the Render3 HTML AST in order to find all declared
|
||||
* template variables and property assignments within bound events.
|
||||
*/
|
||||
export class PropertyWriteHtmlVisitor extends NullVisitor {
|
||||
templateVariables: Variable[] = [];
|
||||
propertyAssignments: PropertyAssignment[] = [];
|
||||
|
||||
private expressionAstVisitor = new ExpressionAstVisitor(this.propertyAssignments);
|
||||
|
||||
visitElement(element: Element): void {
|
||||
visitAll(this, element.outputs);
|
||||
visitAll(this, element.children);
|
||||
}
|
||||
|
||||
visitTemplate(template: Template): void {
|
||||
// Visit all children of the template. The template proxies the outputs of the
|
||||
// immediate child elements, so we just ignore outputs on the "Template" in order
|
||||
// to not visit similar bound events twice.
|
||||
visitAll(this, template.children);
|
||||
|
||||
// Keep track of all declared local template variables.
|
||||
this.templateVariables.push(...template.variables);
|
||||
}
|
||||
|
||||
visitBoundEvent(node: BoundEvent) {
|
||||
node.handler.visit(this.expressionAstVisitor, node.handlerSpan);
|
||||
}
|
||||
}
|
||||
|
||||
/** AST visitor that resolves all property assignments with a given expression AST. */
|
||||
class ExpressionAstVisitor extends RecursiveAstVisitor {
|
||||
constructor(private propertyAssignments: PropertyAssignment[]) { super(); }
|
||||
|
||||
visitPropertyWrite(node: PropertyWrite, span: ParseSourceSpan) {
|
||||
this.propertyAssignments.push({
|
||||
node: node,
|
||||
start: span.start.offset,
|
||||
end: span.end.offset,
|
||||
});
|
||||
|
||||
super.visitPropertyWrite(node, span);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user