refactor(compiler-cli): add abstract TemplateCheckWithVisitor class (#43232)

Add an abstract class that has a default implementation of the run
function and visits all nodes. Authors of template checks can extend
this class and override the `visitNode` method to return diagnostics.

Refs #42966

PR Close #43232
This commit is contained in:
Daniel Trevino
2021-08-13 19:39:02 +00:00
committed by Jessica Janiuk
parent dee04bd96e
commit 46e68df2cd
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {TmplAstNode} from '@angular/compiler';
import {AST, RecursiveAstVisitor, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstElement, TmplAstIcu, TmplAstNode, TmplAstRecursiveVisitor, TmplAstReference, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
import {ASTWithSource} from '@angular/compiler/src/compiler';
import * as ts from 'typescript';
import {ErrorCode} from '../../../diagnostics';
@@ -38,3 +39,103 @@ export interface TemplateContext {
*/
typeChecker: ts.TypeChecker;
}
/**
* This abstract class provides a base implementation for the run method.
*/
export abstract class TemplateCheckWithVisitor<T extends ErrorCode> implements TemplateCheck<T> {
abstract code: T;
/**
* Base implementation for run function, visits all nodes in template and calls
* `visitNode()` for each one.
*/
run(ctx: TemplateContext, component: ts.ClassDeclaration,
template: TmplAstNode[]): NgTemplateDiagnostic<T>[] {
const visitor = new TemplateVisitor<T>(ctx, component, this);
return visitor.getDiagnostics(template);
}
/**
* Visit a TmplAstNode or AST node of the template. Authors should override this
* method to implement the check and return diagnostics.
*/
abstract visitNode(ctx: TemplateContext, component: ts.ClassDeclaration, node: TmplAstNode|AST):
NgTemplateDiagnostic<T>[];
}
/**
* Visits all nodes in a template (TmplAstNode and AST) and calls `visitNode` for each one.
*/
class TemplateVisitor<T extends ErrorCode> extends RecursiveAstVisitor implements
TmplAstRecursiveVisitor {
diagnostics: NgTemplateDiagnostic<T>[] = [];
constructor(
private readonly ctx: TemplateContext, private readonly component: ts.ClassDeclaration,
private readonly check: TemplateCheckWithVisitor<T>) {
super();
}
override visit(node: AST|TmplAstNode, context?: any) {
this.diagnostics.push(...this.check.visitNode(this.ctx, this.component, node));
node.visit(this);
}
visitAllNodes(nodes: TmplAstNode[]) {
for (const node of nodes) {
this.visit(node);
}
}
visitAst(ast: AST) {
if (ast instanceof ASTWithSource) {
ast = ast.ast;
}
this.visit(ast);
}
visitElement(element: TmplAstElement) {
this.visitAllNodes(element.attributes);
this.visitAllNodes(element.inputs);
this.visitAllNodes(element.outputs);
this.visitAllNodes(element.references);
this.visitAllNodes(element.children);
}
visitTemplate(template: TmplAstTemplate) {
this.visitAllNodes(template.attributes);
if (template.tagName === 'ng-template') {
// Only visit input/outputs/templateAttrs if this isn't an inline template node
// generated for a structural directive (like `<div *ngIf></div>`). These nodes
// would be visited when the underlying element of an inline template node is processed.
this.visitAllNodes(template.inputs);
this.visitAllNodes(template.outputs);
this.visitAllNodes(template.templateAttrs);
}
this.visitAllNodes(template.variables);
this.visitAllNodes(template.references);
this.visitAllNodes(template.children);
}
visitContent(content: TmplAstContent): void {}
visitVariable(variable: TmplAstVariable): void {}
visitReference(reference: TmplAstReference): void {}
visitTextAttribute(attribute: TmplAstTextAttribute): void {}
visitBoundAttribute(attribute: TmplAstBoundAttribute): void {
this.visitAst(attribute.value);
}
visitBoundEvent(attribute: TmplAstBoundEvent): void {
this.visitAst(attribute.handler);
}
visitText(text: TmplAstText): void {}
visitBoundText(text: TmplAstBoundText): void {
this.visitAst(text.value);
}
visitIcu(icu: TmplAstIcu): void {}
getDiagnostics(template: TmplAstNode[]): NgTemplateDiagnostic<T>[] {
this.diagnostics = [];
this.visitAllNodes(template);
return this.diagnostics;
}
}