refactor(compiler-cli): introduce `getExtendedTemplateDiagnosticsForComponent` function (#42984)
Add function to get extended template diagnostics generated by all the template checks. Refs #42966 PR Close #42984
This commit is contained in:
parent
c055cfe281
commit
be8a8e17a8
|
@ -0,0 +1,15 @@
|
|||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "extended",
|
||||
srcs = glob(
|
||||
["**/*.ts"],
|
||||
),
|
||||
visibility = ["//packages/compiler-cli/src/ngtsc:__subpackages__"],
|
||||
deps = [
|
||||
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 * as ts from 'typescript';
|
||||
import {ErrorCode} from '../../../diagnostics';
|
||||
import {TemplateTypeChecker} from '../../api';
|
||||
import {TemplateCheck, TemplateContext} from '../api/api';
|
||||
|
||||
/**
|
||||
* Run all `TemplateChecks` for a component and return the generated `ts.Diagnostic`s.
|
||||
* @param component the `@Component()` class from which the template is obtained
|
||||
* @param templateTypeChecker interface to get information about template nodes
|
||||
* @param typeChecker program's type checker
|
||||
* @param templateChecks specific checks to be run
|
||||
* @returns generated `ts.Diagnostic[]`
|
||||
*/
|
||||
export function getExtendedTemplateDiagnosticsForComponent(
|
||||
component: ts.ClassDeclaration, templateTypeChecker: TemplateTypeChecker,
|
||||
typeChecker: ts.TypeChecker, templateChecks: TemplateCheck<ErrorCode>[]): ts.Diagnostic[] {
|
||||
const template = templateTypeChecker.getTemplate(component);
|
||||
// Skip checks if component has no template. This can happen if the user writes a
|
||||
// `@Component()` but doesn't add the template, could happen in the language service
|
||||
// when users are in the middle of typing code.
|
||||
if (template === null) {
|
||||
return [];
|
||||
}
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
const ctx = {templateTypeChecker, typeChecker, component} as TemplateContext;
|
||||
|
||||
for (const check of templateChecks) {
|
||||
diagnostics.push(...deduplicateDiagnostics(check.run(ctx, template)));
|
||||
}
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
// Filter out duplicated diagnostics, this is possible due to the way the compiler
|
||||
// handles desugaring and produces `AST`s. Ex.
|
||||
//
|
||||
// ```
|
||||
// <div *ngIf="true" (foo)="bar">test</div>
|
||||
// ```
|
||||
//
|
||||
// Would result in the following AST:
|
||||
//
|
||||
// ```
|
||||
// Template {
|
||||
// outputs: [
|
||||
// BoundEvent {
|
||||
// name: 'foo',
|
||||
// /.../
|
||||
// }
|
||||
// ],
|
||||
// children: [
|
||||
// Element {
|
||||
// outputs: [
|
||||
// BoundEvent {
|
||||
// name: 'foo',
|
||||
// /.../
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// /.../
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// In this case a duplicated diagnostic could be generated for the output `foo`.
|
||||
// TODO(danieltrevino): handle duplicated diagnostics when they are being generated
|
||||
// to avoid extra work (could be directly in the visitor).
|
||||
// https://github.com/angular/angular/pull/42984#discussion_r684823926
|
||||
function deduplicateDiagnostics(diagnostics: ts.Diagnostic[]): ts.Diagnostic[] {
|
||||
const result: ts.Diagnostic[] = [];
|
||||
for (const newDiag of diagnostics) {
|
||||
const isDuplicateDiag = result.some(existingDiag => areDiagnosticsEqual(newDiag, existingDiag));
|
||||
if (!isDuplicateDiag) {
|
||||
result.push(newDiag);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function areDiagnosticsEqual(first: ts.Diagnostic, second: ts.Diagnostic): boolean {
|
||||
return first.file?.fileName === second.file?.fileName && first.start === second.start &&
|
||||
first.length === second.length && first.code === second.code;
|
||||
}
|
Loading…
Reference in New Issue