diff --git a/packages/compiler-cli/private/hybrid_analysis.ts b/packages/compiler-cli/private/hybrid_analysis.ts index cdc25d9c2d1..5c95afd9b2b 100644 --- a/packages/compiler-cli/private/hybrid_analysis.ts +++ b/packages/compiler-cli/private/hybrid_analysis.ts @@ -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'; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/index.ts index 7e667a5a114..db970d7b45d 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/api/index.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/api/index.ts @@ -12,3 +12,4 @@ export * from './completion'; export * from './context'; export * from './scope'; export * from './symbols'; +export * from './oob'; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/oob.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/oob.ts new file mode 100644 index 00000000000..effd6453e7a --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/typecheck/api/oob.ts @@ -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 { + readonly diagnostics: ReadonlyArray; + + /** + * 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, + outputConsumer: Pick | 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. ``) 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. ``). + * @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; +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts index abc7f65f477..0fb0d1a6055 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts @@ -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; /** * 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; + + /** + * 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(), + 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, ) {} /** diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts index 4d2d0bb3f4c..dd53e45d7a4 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts @@ -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; - - /** - * 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, - outputConsumer: Pick | 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. ``) 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. ``). - * @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 { 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 { + 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; + } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts index 4d681f6462a..cffa186592e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts @@ -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; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts index 22d510a3bac..6004a7bbe72 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts @@ -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, readonly id: TypeCheckId, readonly boundTarget: BoundTarget, private pipes: Map | null, diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index b28a805f1df..901fc30788d 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -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, ): string { const tcb = new Context( env, diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts index f5cdce51af8..df4fbb1d820 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts @@ -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>, meta: TypeCheckBlockMetadata, domSchemaChecker: DomSchemaChecker, - oobRecorder: OutOfBandDiagnosticRecorder, + oobRecorder: OutOfBandDiagnosticRecorder, genericContextBehavior: TcbGenericContextBehavior, ): void { const fnId = `_tcb${this.nextTcbId++}`; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts index 2948bdeec58..bcb74fbd550 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts @@ -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 { get diagnostics(): ReadonlyArray { 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 {}