refactor(compiler-cli): generalize out of band diagnostics recorder

Currently the `OutOfBandDiagnosticRecorder` is tied to producing TypeScript diagnostics, however some of our use cases might call for a different form.

These changes decouple the recorder from TypeScript and make the diagnostic type generic.
This commit is contained in:
Kristiyan Kostadinov
2026-03-25 14:31:09 +01:00
committed by Leon Senft
parent 1938054f34
commit 3382e8a34b
10 changed files with 317 additions and 299 deletions
@@ -18,10 +18,11 @@ export type {
TemplateDiagnostic,
TcbReferenceMetadata,
SourceMapping,
OutOfBandDiagnosticRecorder,
OutOfBadDiagnosticCategory,
} from '../src/ngtsc/typecheck/api';
export {DomSchemaChecker, RegistryDomSchemaChecker} from '../src/ngtsc/typecheck/src/dom';
export {Environment} from '../src/ngtsc/typecheck/src/environment';
export {OutOfBandDiagnosticRecorder} from '../src/ngtsc/typecheck/src/oob';
export {TcbGenericContextBehavior} from '../src/ngtsc/typecheck/src/ops/context';
export {ImportManager} from '../src/ngtsc/translator';
export type {ReferenceEmitter} from '../src/ngtsc/imports';
@@ -12,3 +12,4 @@ export * from './completion';
export * from './context';
export * from './scope';
export * from './symbols';
export * from './oob';
@@ -0,0 +1,247 @@
/**
* @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.dev/license
*/
import {
AST,
BindingPipe,
PropertyRead,
TmplAstBoundAttribute,
TmplAstBoundEvent,
TmplAstComponent,
TmplAstDirective,
TmplAstElement,
TmplAstForLoopBlock,
TmplAstForLoopBlockEmpty,
TmplAstHoverDeferredTrigger,
TmplAstIfBlockBranch,
TmplAstInteractionDeferredTrigger,
TmplAstLetDeclaration,
TmplAstReference,
TmplAstSwitchBlockCase,
TmplAstTemplate,
TmplAstTextAttribute,
TmplAstVariable,
TmplAstViewportDeferredTrigger,
} from '@angular/compiler';
import {TcbDirectiveMetadata, TypeCheckId} from './api';
/** Categories of diagnostics that can be reported by a `OutOfBandDiagnosticRecorder`. */
export enum OutOfBadDiagnosticCategory {
Error,
Warning,
}
/**
* Collects diagnostics on problems which occur in the template which aren't directly sourced
* from type check blocks.
*
* During the creation of a type check block, the template is traversed and the
* `OutOfBandDiagnosticRecorder` is called to record cases when a correct interpretation for the
* template cannot be found. These operations create diagnostics which are stored by the
* recorder for later display.
*/
export interface OutOfBandDiagnosticRecorder<T> {
readonly diagnostics: ReadonlyArray<T>;
/**
* Reports a `#ref="target"` expression in the template for which a target directive could not be
* found.
*
* @param id the type-checking ID of the template which contains the broken reference.
* @param ref the `TmplAstReference` which could not be matched to a directive.
*/
missingReferenceTarget(id: TypeCheckId, ref: TmplAstReference): void;
/**
* Reports usage of a `| pipe` expression in the template for which the named pipe could not be
* found.
*
* @param id the type-checking ID of the template which contains the unknown pipe.
* @param ast the `BindingPipe` invocation of the pipe which could not be found.
* @param isStandalone whether the host component is standalone.
*/
missingPipe(id: TypeCheckId, ast: BindingPipe, isStandalone: boolean): void;
/**
* Reports usage of a pipe imported via `@Component.deferredImports` outside
* of a `@defer` block in a template.
*
* @param id the type-checking ID of the template which contains the unknown pipe.
* @param ast the `BindingPipe` invocation of the pipe which could not be found.
*/
deferredPipeUsedEagerly(id: TypeCheckId, ast: BindingPipe): void;
/**
* Reports usage of a component/directive imported via `@Component.deferredImports` outside
* of a `@defer` block in a template.
*
* @param id the type-checking ID of the template which contains the unknown pipe.
* @param element the element which hosts a component that was defer-loaded.
*/
deferredComponentUsedEagerly(id: TypeCheckId, element: TmplAstElement): void;
/**
* Reports a duplicate declaration of a template variable.
*
* @param id the type-checking ID of the template which contains the duplicate
* declaration.
* @param variable the `TmplAstVariable` which duplicates a previously declared variable.
* @param firstDecl the first variable declaration which uses the same name as `variable`.
*/
duplicateTemplateVar(
id: TypeCheckId,
variable: TmplAstVariable,
firstDecl: TmplAstVariable,
): void;
/**
* Report a warning when structural directives support context guards, but the current
* type-checking configuration prohibits their usage.
*/
suboptimalTypeInference(id: TypeCheckId, variables: TmplAstVariable[]): void;
/**
* Reports a split two way binding error message.
*/
splitTwoWayBinding(
id: TypeCheckId,
input: TmplAstBoundAttribute,
output: TmplAstBoundEvent,
inputConsumer: Pick<TcbDirectiveMetadata, 'name' | 'isComponent' | 'ref'>,
outputConsumer: Pick<TcbDirectiveMetadata, 'name' | 'isComponent' | 'ref'> | TmplAstElement,
): void;
/** Reports required inputs that haven't been bound. */
missingRequiredInputs(
id: TypeCheckId,
element: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
directiveName: string,
isComponent: boolean,
inputAliases: string[],
): void;
/**
* Reports accesses of properties that aren't available in a `for` block's tracking expression.
*/
illegalForLoopTrackAccess(
id: TypeCheckId,
block: TmplAstForLoopBlock,
access: PropertyRead,
): void;
/**
* Reports deferred triggers that cannot access the element they're referring to.
*/
inaccessibleDeferredTriggerElement(
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
| TmplAstViewportDeferredTrigger,
): void;
/**
* Reports cases where control flow nodes prevent content projection.
*/
controlFlowPreventingContentProjection(
id: TypeCheckId,
category: OutOfBadDiagnosticCategory,
projectionNode: TmplAstElement | TmplAstTemplate,
componentName: string,
slotSelector: string,
controlFlowNode:
| TmplAstIfBlockBranch
| TmplAstSwitchBlockCase
| TmplAstForLoopBlock
| TmplAstForLoopBlockEmpty,
preservesWhitespaces: boolean,
): void;
/** Reports cases where users are writing to `@let` declarations. */
illegalWriteToLetDeclaration(id: TypeCheckId, node: AST, target: TmplAstLetDeclaration): void;
/** Reports cases where users are accessing an `@let` before it is defined.. */
letUsedBeforeDefinition(id: TypeCheckId, node: PropertyRead, target: TmplAstLetDeclaration): void;
/**
* Reports a `@let` declaration that conflicts with another symbol in the same scope.
*
* @param id the type-checking ID of the template which contains the declaration.
* @param current the `TmplAstLetDeclaration` which is invalid.
*/
conflictingDeclaration(id: TypeCheckId, current: TmplAstLetDeclaration): void;
/**
* Reports that a named template dependency (e.g. `<Missing/>`) is not available.
* @param id Type checking ID of the template in which the dependency is declared.
* @param node Node that declares the dependency.
*/
missingNamedTemplateDependency(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void;
/**
* Reports that a templace dependency of the wrong kind has been referenced at a specific position
* (e.g. `<SomeDirective/>`).
* @param id Type checking ID of the template in which the dependency is declared.
* @param node Node that declares the dependency.
*/
incorrectTemplateDependencyType(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void;
/**
* Reports a binding inside directive syntax that does not match any of the inputs/outputs of
* the directive.
* @param id Type checking ID of the template in which the directive was defined.
* @param directive Directive that contains the binding.
* @param node Node declaring the binding.
*/
unclaimedDirectiveBinding(
id: TypeCheckId,
directive: TmplAstDirective,
node: TmplAstBoundAttribute | TmplAstTextAttribute | TmplAstBoundEvent,
): void;
/**
* Reports that an implicit deferred trigger is set on a block that does not have a placeholder.
*/
deferImplicitTriggerMissingPlaceholder(
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
| TmplAstViewportDeferredTrigger,
): void;
/**
* Reports that an implicit deferred trigger is set on a block whose placeholder is not set up
* correctly (e.g. more than one root node).
*/
deferImplicitTriggerInvalidPlaceholder(
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
| TmplAstViewportDeferredTrigger,
): void;
/**
* Reports an unsupported binding on a form `FormField` node.
*/
formFieldUnsupportedBinding(
id: TypeCheckId,
node: TmplAstBoundAttribute | TmplAstTextAttribute,
): void;
/**
* Reports that multiple components in the compilation scope match a given element.
*/
multipleMatchingComponents(
id: TypeCheckId,
element: TmplAstElement,
componentNames: string[],
): void;
}
@@ -18,7 +18,7 @@ import {
import MagicString from 'magic-string';
import ts from 'typescript';
import {ErrorCode, ngErrorCode} from '../../../../src/ngtsc/diagnostics';
import {ErrorCode, makeDiagnostic, ngErrorCode} from '../../../../src/ngtsc/diagnostics';
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
import {Reference, ReferenceEmitter} from '../../imports';
import {PerfEvent, PerfRecorder} from '../../perf';
@@ -36,13 +36,14 @@ import {
TypeCheckingConfig,
TypeCtorMetadata,
TemplateContext,
OutOfBandDiagnosticRecorder,
} from '../api';
import {makeTemplateDiagnostic} from '../diagnostics';
import {adaptTypeCheckBlockMetadata} from './tcb_adapter';
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
import {Environment} from './environment';
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
import {OutOfBandDiagnosticRecorderImpl} from './oob';
import {ReferenceEmitEnvironment} from './reference_emit_environment';
import {TypeCheckShimGenerator} from './shim';
import {DirectiveSourceManager} from './source';
@@ -128,7 +129,7 @@ export interface PendingShimData {
/**
* Recorder for out-of-band diagnostics which are raised during generation.
*/
oobRecorder: OutOfBandDiagnosticRecorder;
oobRecorder: OutOfBandDiagnosticRecorder<TemplateDiagnostic>;
/**
* The `DomSchemaChecker` in use for this template, which records any schema-related diagnostics.
@@ -144,6 +145,11 @@ export interface PendingShimData {
* Map of `TypeCheckId` to information collected about the template as it's ingested.
*/
data: Map<TypeCheckId, TypeCheckData>;
/**
* Diagnostics produced during shim creation.
*/
shimDiagnostics: TemplateDiagnostic[] | null;
}
/**
@@ -335,7 +341,16 @@ export class TypeCheckContextImpl implements TypeCheckContext {
// and inlining would be required.
// Record diagnostics to indicate the issues with this template.
shimData.oobRecorder.requiresInlineTcb(id, ref.node);
shimData.shimDiagnostics ??= [];
shimData.shimDiagnostics.push({
...makeDiagnostic(
ErrorCode.INLINE_TCB_REQUIRED,
ref.node.name,
`This component requires inline template type-checking, which is not supported by the current environment.`,
),
sourceFile: ref.node.getSourceFile(),
typeCheckId: id,
});
// Checking this template would be unsupported, so don't try.
this.perf.eventCount(PerfEvent.SkipGenerateTcbNoInline);
@@ -516,11 +531,17 @@ export class TypeCheckContextImpl implements TypeCheckContext {
for (const [sfPath, pendingFileData] of this.fileMap) {
// For each input file, consider generation operations for each of its shims.
for (const pendingShimData of pendingFileData.shimData.values()) {
const genesisDiagnostics = [
...pendingShimData.domSchemaChecker.diagnostics,
...pendingShimData.oobRecorder.diagnostics,
];
if (pendingShimData.shimDiagnostics !== null) {
genesisDiagnostics.unshift(...pendingShimData.shimDiagnostics);
}
this.host.recordShimData(sfPath, {
genesisDiagnostics: [
...pendingShimData.domSchemaChecker.diagnostics,
...pendingShimData.oobRecorder.diagnostics,
],
genesisDiagnostics,
hasInlines: pendingFileData.hasInlines,
path: pendingShimData.file.fileName,
data: pendingShimData.data,
@@ -579,6 +600,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
this.compilerHost,
),
data: new Map<TypeCheckId, TypeCheckData>(),
shimDiagnostics: null,
});
}
return fileData.shimData.get(shimPath)!;
@@ -657,7 +679,7 @@ class InlineTcbOp implements Op {
readonly config: TypeCheckingConfig,
readonly reflector: ReflectionHost,
readonly domSchemaChecker: DomSchemaChecker,
readonly oobRecorder: OutOfBandDiagnosticRecorder,
readonly oobRecorder: OutOfBandDiagnosticRecorder<unknown>,
) {}
/**
@@ -33,232 +33,20 @@ import {
} from '@angular/compiler';
import ts from 'typescript';
import {ErrorCode, makeDiagnostic, makeRelatedInformation, ngErrorCode} from '../../diagnostics';
import {ClassDeclaration} from '../../reflection';
import {TcbDirectiveMetadata, TemplateDiagnostic, TypeCheckId} from '../api';
import {ErrorCode, ngErrorCode} from '../../diagnostics';
import {
OutOfBadDiagnosticCategory,
OutOfBandDiagnosticRecorder,
TcbDirectiveMetadata,
TemplateDiagnostic,
TypeCheckId,
} from '../api';
import {makeTemplateDiagnostic} from '../diagnostics';
import {TypeCheckSourceResolver} from './tcb_util';
import {DOC_PAGE_BASE_URL} from '../../diagnostics/src/error_details_base_url';
/**
* Collects `ts.Diagnostic`s on problems which occur in the template which aren't directly sourced
* from Type Check Blocks.
*
* During the creation of a Type Check Block, the template is traversed and the
* `OutOfBandDiagnosticRecorder` is called to record cases when a correct interpretation for the
* template cannot be found. These operations create `ts.Diagnostic`s which are stored by the
* recorder for later display.
*/
export interface OutOfBandDiagnosticRecorder {
readonly diagnostics: ReadonlyArray<TemplateDiagnostic>;
/**
* Reports a `#ref="target"` expression in the template for which a target directive could not be
* found.
*
* @param id the type-checking ID of the template which contains the broken reference.
* @param ref the `TmplAstReference` which could not be matched to a directive.
*/
missingReferenceTarget(id: TypeCheckId, ref: TmplAstReference): void;
/**
* Reports usage of a `| pipe` expression in the template for which the named pipe could not be
* found.
*
* @param id the type-checking ID of the template which contains the unknown pipe.
* @param ast the `BindingPipe` invocation of the pipe which could not be found.
* @param isStandalone whether the host component is standalone.
*/
missingPipe(id: TypeCheckId, ast: BindingPipe, isStandalone: boolean): void;
/**
* Reports usage of a pipe imported via `@Component.deferredImports` outside
* of a `@defer` block in a template.
*
* @param id the type-checking ID of the template which contains the unknown pipe.
* @param ast the `BindingPipe` invocation of the pipe which could not be found.
*/
deferredPipeUsedEagerly(id: TypeCheckId, ast: BindingPipe): void;
/**
* Reports usage of a component/directive imported via `@Component.deferredImports` outside
* of a `@defer` block in a template.
*
* @param id the type-checking ID of the template which contains the unknown pipe.
* @param element the element which hosts a component that was defer-loaded.
*/
deferredComponentUsedEagerly(id: TypeCheckId, element: TmplAstElement): void;
/**
* Reports a duplicate declaration of a template variable.
*
* @param id the type-checking ID of the template which contains the duplicate
* declaration.
* @param variable the `TmplAstVariable` which duplicates a previously declared variable.
* @param firstDecl the first variable declaration which uses the same name as `variable`.
*/
duplicateTemplateVar(
id: TypeCheckId,
variable: TmplAstVariable,
firstDecl: TmplAstVariable,
): void;
requiresInlineTcb(id: TypeCheckId, node: ClassDeclaration): void;
requiresInlineTypeConstructors(
id: TypeCheckId,
node: ClassDeclaration,
directives: ClassDeclaration[],
): void;
/**
* Report a warning when structural directives support context guards, but the current
* type-checking configuration prohibits their usage.
*/
suboptimalTypeInference(id: TypeCheckId, variables: TmplAstVariable[]): void;
/**
* Reports a split two way binding error message.
*/
splitTwoWayBinding(
id: TypeCheckId,
input: TmplAstBoundAttribute,
output: TmplAstBoundEvent,
inputConsumer: Pick<TcbDirectiveMetadata, 'name' | 'isComponent' | 'ref'>,
outputConsumer: Pick<TcbDirectiveMetadata, 'name' | 'isComponent' | 'ref'> | TmplAstElement,
): void;
/** Reports required inputs that haven't been bound. */
missingRequiredInputs(
id: TypeCheckId,
element: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
directiveName: string,
isComponent: boolean,
inputAliases: string[],
): void;
/**
* Reports accesses of properties that aren't available in a `for` block's tracking expression.
*/
illegalForLoopTrackAccess(
id: TypeCheckId,
block: TmplAstForLoopBlock,
access: PropertyRead,
): void;
/**
* Reports deferred triggers that cannot access the element they're referring to.
*/
inaccessibleDeferredTriggerElement(
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
| TmplAstViewportDeferredTrigger,
): void;
/**
* Reports cases where control flow nodes prevent content projection.
*/
controlFlowPreventingContentProjection(
id: TypeCheckId,
category: ts.DiagnosticCategory,
projectionNode: TmplAstElement | TmplAstTemplate,
componentName: string,
slotSelector: string,
controlFlowNode:
| TmplAstIfBlockBranch
| TmplAstSwitchBlockCase
| TmplAstForLoopBlock
| TmplAstForLoopBlockEmpty,
preservesWhitespaces: boolean,
): void;
/** Reports cases where users are writing to `@let` declarations. */
illegalWriteToLetDeclaration(id: TypeCheckId, node: AST, target: TmplAstLetDeclaration): void;
/** Reports cases where users are accessing an `@let` before it is defined.. */
letUsedBeforeDefinition(id: TypeCheckId, node: PropertyRead, target: TmplAstLetDeclaration): void;
/**
* Reports a `@let` declaration that conflicts with another symbol in the same scope.
*
* @param id the type-checking ID of the template which contains the declaration.
* @param current the `TmplAstLetDeclaration` which is invalid.
*/
conflictingDeclaration(id: TypeCheckId, current: TmplAstLetDeclaration): void;
/**
* Reports that a named template dependency (e.g. `<Missing/>`) is not available.
* @param id Type checking ID of the template in which the dependency is declared.
* @param node Node that declares the dependency.
*/
missingNamedTemplateDependency(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void;
/**
* Reports that a templace dependency of the wrong kind has been referenced at a specific position
* (e.g. `<SomeDirective/>`).
* @param id Type checking ID of the template in which the dependency is declared.
* @param node Node that declares the dependency.
*/
incorrectTemplateDependencyType(id: TypeCheckId, node: TmplAstComponent | TmplAstDirective): void;
/**
* Reports a binding inside directive syntax that does not match any of the inputs/outputs of
* the directive.
* @param id Type checking ID of the template in which the directive was defined.
* @param directive Directive that contains the binding.
* @param node Node declaring the binding.
*/
unclaimedDirectiveBinding(
id: TypeCheckId,
directive: TmplAstDirective,
node: TmplAstBoundAttribute | TmplAstTextAttribute | TmplAstBoundEvent,
): void;
/**
* Reports that an implicit deferred trigger is set on a block that does not have a placeholder.
*/
deferImplicitTriggerMissingPlaceholder(
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
| TmplAstViewportDeferredTrigger,
): void;
/**
* Reports that an implicit deferred trigger is set on a block whose placeholder is not set up
* correctly (e.g. more than one root node).
*/
deferImplicitTriggerInvalidPlaceholder(
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
| TmplAstViewportDeferredTrigger,
): void;
/**
* Reports an unsupported binding on a form `FormField` node.
*/
formFieldUnsupportedBinding(
id: TypeCheckId,
node: TmplAstBoundAttribute | TmplAstTextAttribute,
): void;
/**
* Reports that multiple components in the compilation scope match a given element.
*/
multipleMatchingComponents(
id: TypeCheckId,
element: TmplAstElement,
componentNames: string[],
): void;
}
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder<TemplateDiagnostic> {
private readonly _diagnostics: TemplateDiagnostic[] = [];
/**
@@ -289,7 +77,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
private getSourceFile: (fileName: string) => ts.SourceFile | undefined = (name) => undefined,
) {}
get diagnostics(): ReadonlyArray<TemplateDiagnostic> {
get diagnostics() {
return this._diagnostics;
}
@@ -446,42 +234,6 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
);
}
requiresInlineTcb(id: TypeCheckId, node: ClassDeclaration): void {
this._diagnostics.push(
makeInlineDiagnostic(
id,
ErrorCode.INLINE_TCB_REQUIRED,
node.name,
`This component requires inline template type-checking, which is not supported by the current environment.`,
),
);
}
requiresInlineTypeConstructors(
id: TypeCheckId,
node: ClassDeclaration,
directives: ClassDeclaration[],
): void {
let message: string;
if (directives.length > 1) {
message = `This component uses directives which require inline type constructors, which are not supported by the current environment.`;
} else {
message = `This component uses a directive which requires an inline type constructor, which is not supported by the current environment.`;
}
this._diagnostics.push(
makeInlineDiagnostic(
id,
ErrorCode.INLINE_TYPE_CTOR_REQUIRED,
node.name,
message,
directives.map((dir) =>
makeRelatedInformation(dir.name, `Requires an inline type constructor.`),
),
),
);
}
suboptimalTypeInference(id: TypeCheckId, variables: TmplAstVariable[]): void {
const mapping = this.resolver.getTemplateSourceMapping(id);
@@ -692,7 +444,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
controlFlowPreventingContentProjection(
id: TypeCheckId,
category: ts.DiagnosticCategory,
category: OutOfBadDiagnosticCategory,
projectionNode: TmplAstElement | TmplAstTemplate,
componentName: string,
slotSelector: string,
@@ -729,7 +481,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
id,
this.resolver.getTemplateSourceMapping(id),
projectionNode.startSourceSpan,
category,
translateCategory(category),
ngErrorCode(ErrorCode.CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION),
lines.join('\n'),
),
@@ -944,16 +696,11 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
}
function makeInlineDiagnostic(
id: TypeCheckId,
code: ErrorCode.INLINE_TCB_REQUIRED | ErrorCode.INLINE_TYPE_CTOR_REQUIRED,
node: ts.Node,
messageText: string | ts.DiagnosticMessageChain,
relatedInformation?: ts.DiagnosticRelatedInformation[],
): TemplateDiagnostic {
return {
...makeDiagnostic(code, node, messageText, relatedInformation),
sourceFile: node.getSourceFile(),
typeCheckId: id,
};
function translateCategory(category: OutOfBadDiagnosticCategory): ts.DiagnosticCategory {
switch (category) {
case OutOfBadDiagnosticCategory.Error:
return ts.DiagnosticCategory.Error;
case OutOfBadDiagnosticCategory.Warning:
return ts.DiagnosticCategory.Warning;
}
}
@@ -22,9 +22,9 @@ import {
TmplAstTemplate,
TmplAstText,
} from '@angular/compiler';
import ts from 'typescript';
import {TcbOp} from './base';
import {Context} from './context';
import {OutOfBadDiagnosticCategory} from '../../api';
/**
* A `TcbOp` that finds and flags control flow nodes that interfere with content projection.
@@ -39,7 +39,7 @@ import {Context} from './context';
* flow node didn't exist.
*/
export class TcbControlFlowContentProjectionOp extends TcbOp {
private readonly category: ts.DiagnosticCategory;
private readonly category: OutOfBadDiagnosticCategory;
constructor(
private tcb: Context,
@@ -53,8 +53,8 @@ export class TcbControlFlowContentProjectionOp extends TcbOp {
// this check won't be enabled for `suppress`.
this.category =
tcb.env.config.controlFlowPreventingContentProjection === 'error'
? ts.DiagnosticCategory.Error
: ts.DiagnosticCategory.Warning;
? OutOfBadDiagnosticCategory.Error
: OutOfBadDiagnosticCategory.Warning;
}
override readonly optional = false;
@@ -8,8 +8,12 @@
import {BoundTarget, SchemaMetadata} from '@angular/compiler';
import {DomSchemaChecker} from '../dom';
import {OutOfBandDiagnosticRecorder} from '../oob';
import {TypeCheckId, TcbDirectiveMetadata, TcbPipeMetadata} from '../../api';
import {
TypeCheckId,
TcbDirectiveMetadata,
TcbPipeMetadata,
OutOfBandDiagnosticRecorder,
} from '../../api';
import {Environment} from '../environment';
/**
@@ -53,7 +57,7 @@ export class Context {
constructor(
readonly env: Environment,
readonly domSchemaChecker: DomSchemaChecker,
readonly oobRecorder: OutOfBandDiagnosticRecorder,
readonly oobRecorder: OutOfBandDiagnosticRecorder<unknown>,
readonly id: TypeCheckId,
readonly boundTarget: BoundTarget<TcbDirectiveMetadata>,
private pipes: Map<string, TcbPipeMetadata> | null,
@@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {TcbComponentMetadata, TcbTypeCheckBlockMetadata} from '../api';
import {OutOfBandDiagnosticRecorder, TcbComponentMetadata, TcbTypeCheckBlockMetadata} from '../api';
import {DomSchemaChecker} from './dom';
import {Environment} from './environment';
import {OutOfBandDiagnosticRecorder} from './oob';
import {createHostBindingsBlockGuard} from './host_bindings';
import {Context} from './ops/context';
import {Scope} from './ops/scope';
@@ -45,7 +44,7 @@ export function generateTypeCheckBlock(
name: string,
meta: TcbTypeCheckBlockMetadata,
domSchemaChecker: DomSchemaChecker,
oobRecorder: OutOfBandDiagnosticRecorder,
oobRecorder: OutOfBandDiagnosticRecorder<unknown>,
): string {
const tcb = new Context(
env,
@@ -11,11 +11,10 @@ import {AbsoluteFsPath} from '../../file_system';
import {Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ImportManager} from '../../translator';
import {TypeCheckBlockMetadata, TypeCheckingConfig} from '../api';
import {OutOfBandDiagnosticRecorder, TypeCheckBlockMetadata, TypeCheckingConfig} from '../api';
import {DomSchemaChecker} from './dom';
import {Environment} from './environment';
import {OutOfBandDiagnosticRecorder} from './oob';
import {ensureTypeCheckFilePreparationImports} from './tcb_util';
import {generateTypeCheckBlock} from './type_check_block';
import {adaptTypeCheckBlockMetadata} from './tcb_adapter';
@@ -66,7 +65,7 @@ export class TypeCheckFile extends Environment {
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
meta: TypeCheckBlockMetadata,
domSchemaChecker: DomSchemaChecker,
oobRecorder: OutOfBandDiagnosticRecorder,
oobRecorder: OutOfBandDiagnosticRecorder<unknown>,
genericContextBehavior: TcbGenericContextBehavior,
): void {
const fnId = `_tcb${this.nextTcbId++}`;
@@ -85,6 +85,7 @@ import {makeProgram, resolveFromRunfiles} from '../../testing';
import {getRootDirs} from '../../util/src/typescript';
import {
OptimizeFor,
OutOfBandDiagnosticRecorder,
ProgramTypeCheckAdapter,
TemplateContext,
TemplateDiagnostic,
@@ -99,7 +100,6 @@ import {
} from '../api/api';
import {TemplateTypeCheckerImpl} from '../src/checker';
import {DomSchemaChecker} from '../src/dom';
import {OutOfBandDiagnosticRecorder} from '../src/oob';
import {TypeCheckShimGenerator} from '../src/shim';
import {TypeCheckFile} from '../src/type_check_file';
import {sfExtensionData} from '../../shims';
@@ -1037,7 +1037,7 @@ export class NoopSchemaChecker implements DomSchemaChecker {
checkHostElementProperty(): void {}
}
export class NoopOobRecorder implements OutOfBandDiagnosticRecorder {
export class NoopOobRecorder implements OutOfBandDiagnosticRecorder<TemplateDiagnostic> {
get diagnostics(): ReadonlyArray<TemplateDiagnostic> {
return [];
}
@@ -1046,8 +1046,6 @@ export class NoopOobRecorder implements OutOfBandDiagnosticRecorder {
deferredPipeUsedEagerly(id: TypeCheckId, ast: BindingPipe): void {}
deferredComponentUsedEagerly(id: TypeCheckId, element: TmplAstElement): void {}
duplicateTemplateVar(): void {}
requiresInlineTcb(): void {}
requiresInlineTypeConstructors(): void {}
suboptimalTypeInference(): void {}
splitTwoWayBinding(): void {}
missingRequiredInputs(): void {}