mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Leon Senft
parent
1938054f34
commit
3382e8a34b
@@ -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 {}
|
||||
|
||||
Reference in New Issue
Block a user