refactor(migrations): provide AST path for resolved template and host references (#57947)

This allows us to detect usages in templates and figure out if they are
e.g. invoking a problematic method like `.pipe` for the output
migration, or in queries `.toArray` etc.

PR Close #57947
This commit is contained in:
Paul Gschwendtner 2024-09-24 16:49:44 +00:00
parent af66e2475d
commit 313dfb7e99
4 changed files with 35 additions and 16 deletions

View file

@ -162,6 +162,7 @@ export function identifyHostBindingReferences<D extends ClassFieldDescriptor>(
kind: ReferenceKind.InHostBinding,
from: {
read: ref.read,
readAstPath: ref.readAstPath,
isObjectShorthandExpression: ref.isObjectShorthandExpression,
isWrite: ref.isWrite,
file: projectFile(ref.context.getSourceFile(), programInfo),

View file

@ -78,6 +78,7 @@ export function identifyTemplateReferences<D extends ClassFieldDescriptor>(
kind: ReferenceKind.InTemplate,
from: {
read: res.read,
readAstPath: res.readAstPath,
node: res.context,
isObjectShorthandExpression: res.isObjectShorthandExpression,
originatingTsFile: projectFile(node.getSourceFile(), programInfo),

View file

@ -15,7 +15,7 @@
*/
import ts from 'typescript';
import {PropertyRead, TmplAstNode} from '@angular/compiler';
import {AST, PropertyRead, TmplAstNode} from '@angular/compiler';
import {ProjectFile} from '../../../../../utils/tsurge';
import {ClassFieldDescriptor} from './known_fields';
@ -40,6 +40,11 @@ export interface TemplateReference<D extends ClassFieldDescriptor> {
node: TmplAstNode;
/** Expression AST node that represents the reference. */
read: PropertyRead;
/**
* Expression AST sequentially visited to reach the given read.
* This follows top-down down ordering. The last element is the actual read.
*/
readAstPath: AST[];
/** Whether the reference is part of an object shorthand expression. */
isObjectShorthandExpression: boolean;
/** Whether this reference is part of a likely-narrowing expression. */
@ -62,6 +67,11 @@ export interface HostBindingReference<D extends ClassFieldDescriptor> {
hostPropertyNode: ts.Node;
/** Expression AST node that represents the reference. */
read: PropertyRead;
/**
* Expression AST sequentially visited to reach the given read.
* This follows top-down down ordering. The last element is the actual read.
*/
readAstPath: AST[];
/** Whether the reference is part of an object shorthand expression. */
isObjectShorthandExpression: boolean;
/** Whether the reference is a write. E.g. an event assignment. */

View file

@ -45,6 +45,7 @@ export interface TmplInputExpressionReference<ExprContext, D extends ClassFieldD
targetNode: ts.Node;
targetField: D;
read: PropertyRead;
readAstPath: AST[];
context: ExprContext;
isObjectShorthandExpression: boolean;
isLikelyNarrowed: boolean;
@ -236,7 +237,6 @@ export class TemplateExpressionReferenceVisitor<
> extends RecursiveAstVisitor {
private activeTmplAstNode: ExprContext | null = null;
private detectedInputReferences: TmplInputExpressionReference<ExprContext, D>[] = [];
private isInsideObjectShorthandExpression = false;
constructor(
@ -256,10 +256,14 @@ export class TemplateExpressionReferenceVisitor<
this.detectedInputReferences = [];
this.activeTmplAstNode = activeNode;
expressionNode.visit(this);
expressionNode.visit(this, []);
return this.detectedInputReferences;
}
override visit(ast: AST, context: AST[]) {
super.visit(ast, [...context, ast]);
}
// Keep track when we are inside an object shorthand expression. This is
// necessary as we need to expand the shorthand to invoke a potential new signal.
// E.g. `{bla}` may be transformed to `{bla: bla()}`.
@ -269,35 +273,34 @@ export class TemplateExpressionReferenceVisitor<
(ast.values[idx] as AST).visit(this, context);
this.isInsideObjectShorthandExpression = false;
}
super.visitLiteralMap(ast, context);
}
override visitPropertyRead(ast: PropertyRead) {
this._inspectPropertyAccess(ast);
super.visitPropertyRead(ast, null);
override visitPropertyRead(ast: PropertyRead, context: AST[]) {
this._inspectPropertyAccess(ast, context);
super.visitPropertyRead(ast, context);
}
override visitSafePropertyRead(ast: SafePropertyRead) {
this._inspectPropertyAccess(ast);
super.visitPropertyRead(ast, null);
override visitSafePropertyRead(ast: SafePropertyRead, context: AST[]) {
this._inspectPropertyAccess(ast, context);
super.visitPropertyRead(ast, context);
}
override visitPropertyWrite(ast: PropertyWrite) {
this._inspectPropertyAccess(ast);
super.visitPropertyWrite(ast, null);
override visitPropertyWrite(ast: PropertyWrite, context: AST[]) {
this._inspectPropertyAccess(ast, context);
super.visitPropertyWrite(ast, context);
}
/**
* Inspects the property access and attempts to resolve whether they access
* a known field. If so, the result is captured.
*/
private _inspectPropertyAccess(ast: PropertyRead | PropertyWrite) {
private _inspectPropertyAccess(ast: PropertyRead | PropertyWrite, astPath: AST[]) {
const isWrite = !!(
ast instanceof PropertyWrite ||
(this.activeTmplAstNode && isTwoWayBindingNode(this.activeTmplAstNode))
);
this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite) ||
this._checkAccessViaOwningComponentClassType(ast, isWrite);
this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) ||
this._checkAccessViaOwningComponentClassType(ast, isWrite, astPath);
}
/**
@ -307,6 +310,7 @@ export class TemplateExpressionReferenceVisitor<
private _checkAccessViaTemplateTypeCheckBlock(
ast: PropertyRead | PropertyWrite,
isWrite: boolean,
astPath: AST[],
): boolean {
// There might be no template type checker. E.g. if we check host bindings.
if (this.templateTypeChecker === null) {
@ -331,6 +335,7 @@ export class TemplateExpressionReferenceVisitor<
targetNode: targetInput.node,
targetField: targetInput,
read: ast,
readAstPath: astPath,
context: this.activeTmplAstNode!,
isLikelyNarrowed: false,
isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
@ -350,6 +355,7 @@ export class TemplateExpressionReferenceVisitor<
private _checkAccessViaOwningComponentClassType(
ast: PropertyRead | PropertyWrite,
isWrite: boolean,
astPath: AST[],
): void {
// We might check host bindings, which can never point to template variables or local refs.
const expressionTemplateTarget =
@ -382,6 +388,7 @@ export class TemplateExpressionReferenceVisitor<
targetNode: matchingTarget.node,
targetField: matchingTarget,
read: ast,
readAstPath: astPath,
context: this.activeTmplAstNode!,
isLikelyNarrowed: false,
isObjectShorthandExpression: this.isInsideObjectShorthandExpression,