refactor(compiler-cli): change TemplateId terminology (#60191)

Renames the `TemplateId` and terminology related to it, because we'll be using it for more than just templates.

PR Close #60191
This commit is contained in:
Kristiyan Kostadinov 2025-02-28 10:40:48 +01:00 committed by Andrew Kushnir
parent 9cdf950108
commit 5cc80dc72f
13 changed files with 171 additions and 177 deletions

View file

@ -116,7 +116,7 @@ import {
HandlerPrecedence,
ResolveResult,
} from '../../../transform';
import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckContext} from '../../../typecheck/api';
import {TypeCheckId, TypeCheckableDirectiveMeta, TypeCheckContext} from '../../../typecheck/api';
import {ExtendedTemplateChecker} from '../../../typecheck/extended/api';
import {TemplateSemanticsChecker} from '../../../typecheck/template_semantics/api/api';
import {getSourceFile} from '../../../util/src/typescript';
@ -695,7 +695,8 @@ export class ComponentDecoratorHandler
template.errors &&
template.errors.length > 0
) {
// Template errors are handled at the type check phase. But we skip this phase in local compilation mode. As a result we need to handle the errors now and add them to the diagnostics.
// Template errors are handled at the type check phase. But we skip this phase in local
// compilation mode. As a result we need to handle the errors now and add them to the diagnostics.
if (diagnostics === undefined) {
diagnostics = [];
}
@ -703,7 +704,10 @@ export class ComponentDecoratorHandler
diagnostics.push(
...getTemplateDiagnostics(
template.errors,
'' as TemplateId, // Template ID is required as part of the template type check, mainly for mapping the template to its component class. But here we are generating the diagnostic outside of the type check context, and so we skip the template ID.
// Type check ID is required as part of the ype check, mainly for mapping the
// diagnostic back to its source. But here we are generating the diagnostic outside
// of the type check context, and so we skip the template ID.
'' as TypeCheckId,
template.sourceMapping,
),
);

View file

@ -44,7 +44,7 @@ export interface TypeCheckableDirectiveMeta extends DirectiveMeta, DirectiveType
rawImports: ts.Expression | null;
}
export type TemplateId = string & {__brand: 'TemplateId'};
export type TypeCheckId = string & {__brand: 'TypeCheckId'};
/**
* A `ts.Diagnostic` with additional information about the diagnostic related to template
@ -54,12 +54,12 @@ export interface TemplateDiagnostic extends ts.Diagnostic {
/**
* The component with the template that resulted in this diagnostic.
*/
componentFile: ts.SourceFile;
sourceFile: ts.SourceFile;
/**
* The template id of the component that resulted in this diagnostic.
* The type check ID of the directive that resulted in this diagnostic.
*/
templateId: TemplateId;
typeCheckId: TypeCheckId;
}
/**
@ -75,9 +75,9 @@ export interface TypeCheckBlockMetadata {
/**
* A unique identifier for the class which gave rise to this TCB.
*
* This can be used to map errors back to the `ts.ClassDeclaration` for the component.
* This can be used to map errors back to the `ts.ClassDeclaration` for the directive.
*/
id: TemplateId;
id: TypeCheckId;
/**
* Semantic information about the template of the component.
@ -410,10 +410,10 @@ export interface ExternalTemplateSourceMapping {
}
/**
* A mapping of a TCB template id to a span in the corresponding template source.
* A mapping of a TCB template id to a span in the corresponding source code.
*/
export interface SourceLocation {
id: TemplateId;
id: TypeCheckId;
span: AbsoluteSourceSpan;
}

View file

@ -14,7 +14,7 @@ import {
ExternalTemplateSourceMapping,
IndirectTemplateSourceMapping,
TemplateDiagnostic,
TemplateId,
TypeCheckId,
TemplateSourceMapping,
} from '../../api';
@ -22,7 +22,7 @@ import {
* Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
*/
export function makeTemplateDiagnostic(
templateId: TemplateId,
id: TypeCheckId,
mapping: TemplateSourceMapping,
span: ParseSourceSpan,
category: ts.DiagnosticCategory,
@ -59,8 +59,8 @@ export function makeTemplateDiagnostic(
category,
messageText,
file: mapping.node.getSourceFile(),
componentFile: mapping.node.getSourceFile(),
templateId,
sourceFile: mapping.node.getSourceFile(),
typeCheckId: id,
start: span.start.offset,
length: span.end.offset - span.start.offset,
relatedInformation,
@ -107,8 +107,8 @@ export function makeTemplateDiagnostic(
code,
messageText: addDiagnosticChain(messageText, [failureChain]),
file: componentSf,
componentFile: componentSf,
templateId,
sourceFile: componentSf,
typeCheckId: id,
// mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
// and getEnd() are used because they don't include surrounding whitespace.
start: mapping.node.getStart(),
@ -134,8 +134,8 @@ export function makeTemplateDiagnostic(
code,
messageText,
file: sf,
componentFile: componentSf,
templateId,
sourceFile: componentSf,
typeCheckId: id,
start: span.start.offset,
length: span.end.offset - span.start.offset,
// Show a secondary message indicating the component whose template contains the error.

View file

@ -9,21 +9,21 @@
import ts from 'typescript';
import {DeclarationNode} from '../../../reflection';
import {TemplateId} from '../../api';
import {TypeCheckId} from '../../api';
const TEMPLATE_ID_MAP = Symbol('ngTemplateId');
const TYPE_CHECK_ID_MAP = Symbol('TypeCheckId');
interface HasNextTemplateId {
[TEMPLATE_ID_MAP]: Map<ts.Node, TemplateId>;
interface HasNextTypeCheckId {
[TYPE_CHECK_ID_MAP]: Map<ts.Node, TypeCheckId>;
}
export function getTemplateId(clazz: DeclarationNode): TemplateId {
const sf = clazz.getSourceFile() as ts.SourceFile & Partial<HasNextTemplateId>;
if (sf[TEMPLATE_ID_MAP] === undefined) {
sf[TEMPLATE_ID_MAP] = new Map();
export function getTypeCheckId(clazz: DeclarationNode): TypeCheckId {
const sf = clazz.getSourceFile() as ts.SourceFile & Partial<HasNextTypeCheckId>;
if (sf[TYPE_CHECK_ID_MAP] === undefined) {
sf[TYPE_CHECK_ID_MAP] = new Map();
}
if (sf[TEMPLATE_ID_MAP].get(clazz) === undefined) {
sf[TEMPLATE_ID_MAP].set(clazz, `tcb${sf[TEMPLATE_ID_MAP].size + 1}` as TemplateId);
if (sf[TYPE_CHECK_ID_MAP].get(clazz) === undefined) {
sf[TYPE_CHECK_ID_MAP].set(clazz, `tcb${sf[TYPE_CHECK_ID_MAP].size + 1}` as TypeCheckId);
}
return sf[TEMPLATE_ID_MAP].get(clazz)!;
return sf[TYPE_CHECK_ID_MAP].get(clazz)!;
}

View file

@ -63,7 +63,6 @@ import {
Symbol,
TcbLocation,
TemplateDiagnostic,
TemplateId,
TemplateSymbol,
TemplateTypeChecker,
TypeCheckableDirectiveMeta,
@ -193,9 +192,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return {data: null, tcb: null, tcbPath: shimPath, tcbIsShim: true};
}
const templateId = fileRecord.sourceManager.getTemplateId(component);
const id = fileRecord.sourceManager.getTypeCheckId(component);
const shimRecord = fileRecord.shimData.get(shimPath)!;
const id = fileRecord.sourceManager.getTemplateId(component);
const program = this.programDriver.getProgram();
const shimSf = getSourceFileOrNull(program, shimPath);
@ -218,8 +216,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
}
let data: TemplateData | null = null;
if (shimRecord.templates.has(templateId)) {
data = shimRecord.templates.get(templateId)!;
if (shimRecord.templates.has(id)) {
data = shimRecord.templates.get(id)!;
}
return {data, tcb, tcbPath, tcbIsShim: tcbPath === shimPath};
@ -350,7 +348,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return [];
}
const templateId = fileRecord.sourceManager.getTemplateId(component);
const id = fileRecord.sourceManager.getTypeCheckId(component);
const shimRecord = fileRecord.shimData.get(shimPath)!;
const typeCheckProgram = this.programDriver.getProgram();
@ -379,7 +377,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return diagnostics.filter(
(diag: TemplateDiagnostic | null): diag is TemplateDiagnostic =>
diag !== null && diag.templateId === templateId,
diag !== null && diag.typeCheckId === id,
);
});
}
@ -438,7 +436,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
const sfPath = absoluteFromSourceFile(sf);
const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
const fileData = this.getFileData(sfPath);
const templateId = fileData.sourceManager.getTemplateId(clazz);
const id = fileData.sourceManager.getTypeCheckId(clazz);
fileData.shimData.delete(shimPath);
fileData.isComplete = false;
@ -467,12 +465,12 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
): NgTemplateDiagnostic<T> {
const sfPath = absoluteFromSourceFile(clazz.getSourceFile());
const fileRecord = this.state.get(sfPath)!;
const templateId = fileRecord.sourceManager.getTemplateId(clazz);
const mapping = fileRecord.sourceManager.getSourceMapping(templateId);
const id = fileRecord.sourceManager.getTypeCheckId(clazz);
const mapping = fileRecord.sourceManager.getSourceMapping(id);
return {
...makeTemplateDiagnostic(
templateId,
id,
mapping,
sourceSpan,
category,

View file

@ -27,7 +27,7 @@ import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ImportManager} from '../../translator';
import {
TemplateDiagnostic,
TemplateId,
TypeCheckId,
TemplateSourceMapping,
TypeCheckableDirectiveMeta,
TypeCheckBlockMetadata,
@ -67,10 +67,10 @@ export interface ShimTypeCheckingData {
hasInlines: boolean;
/**
* Map of `TemplateId` to information collected about the template during the template
* Map of `TypeCheckId` to information collected about the template during the template
* type-checking process.
*/
templates: Map<TemplateId, TemplateData>;
templates: Map<TypeCheckId, TemplateData>;
}
/**
@ -132,9 +132,9 @@ export interface PendingShimData {
file: TypeCheckFile;
/**
* Map of `TemplateId` to information collected about the template as it's ingested.
* Map of `TypeCheckId` to information collected about the template as it's ingested.
*/
templates: Map<TemplateId, TemplateData>;
templates: Map<TypeCheckId, TemplateData>;
}
/**
@ -245,12 +245,12 @@ export class TypeCheckContextImpl implements TypeCheckContext {
const fileData = this.dataForFile(ref.node.getSourceFile());
const shimData = this.pendingShimForComponent(ref.node);
const templateId = fileData.sourceManager.getTemplateId(ref.node);
const id = fileData.sourceManager.getTypeCheckId(ref.node);
const templateDiagnostics: TemplateDiagnostic[] = [];
if (parseErrors !== null) {
templateDiagnostics.push(...getTemplateDiagnostics(parseErrors, templateId, sourceMapping));
templateDiagnostics.push(...getTemplateDiagnostics(parseErrors, id, sourceMapping));
}
const boundTarget = binder.bind({template});
@ -283,7 +283,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
}
}
shimData.templates.set(templateId, {
shimData.templates.set(id, {
template,
boundTarget,
templateDiagnostics,
@ -314,7 +314,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
// and inlining would be required.
// Record diagnostics to indicate the issues with this template.
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
shimData.oobRecorder.requiresInlineTcb(id, ref.node);
// Checking this template would be unsupported, so don't try.
this.perf.eventCount(PerfEvent.SkipGenerateTcbNoInline);
@ -537,7 +537,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
this.reflector,
this.compilerHost,
),
templates: new Map<TemplateId, TemplateData>(),
templates: new Map<TypeCheckId, TemplateData>(),
});
}
return fileData.shimData.get(shimPath)!;
@ -561,7 +561,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
export function getTemplateDiagnostics(
parseErrors: ParseError[],
templateId: TemplateId,
templateId: TypeCheckId,
sourceMapping: TemplateSourceMapping,
): TemplateDiagnostic[] {
return parseErrors.map((error) => {

View file

@ -8,7 +8,7 @@
import {AbsoluteSourceSpan, ParseSourceSpan} from '@angular/compiler';
import ts from 'typescript';
import {TemplateDiagnostic, TemplateId} from '../api';
import {TemplateDiagnostic, TypeCheckId} from '../api';
import {makeTemplateDiagnostic} from '../diagnostics';
import {getTemplateMapping, TemplateSourceResolver} from './tcb_util';
@ -58,10 +58,10 @@ export function addParseSpanInfo(node: ts.Node, span: AbsoluteSourceSpan | Parse
}
/**
* Adds a synthetic comment to the function declaration that contains the template id
* Adds a synthetic comment to the function declaration that contains the type checking ID
* of the class declaration.
*/
export function addTemplateId(tcb: ts.FunctionDeclaration, id: TemplateId): void {
export function addTypeCheckId(tcb: ts.FunctionDeclaration, id: TypeCheckId): void {
ts.addSyntheticLeadingComment(tcb, ts.SyntaxKind.MultiLineCommentTrivia, id, true);
}

View file

@ -15,7 +15,7 @@ import {
import ts from 'typescript';
import {ErrorCode, ngErrorCode} from '../../diagnostics';
import {TemplateDiagnostic, TemplateId} from '../api';
import {TemplateDiagnostic, TypeCheckId} from '../api';
import {makeTemplateDiagnostic} from '../diagnostics';
import {TemplateSourceResolver} from './tcb_util';
@ -91,7 +91,7 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
constructor(private resolver: TemplateSourceResolver) {}
checkElement(
id: TemplateId,
id: TypeCheckId,
element: TmplAstElement,
schemas: SchemaMetadata[],
hostIsStandalone: boolean,
@ -130,7 +130,7 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
}
checkProperty(
id: TemplateId,
id: TypeCheckId,
element: TmplAstElement,
name: string,
span: ParseSourceSpan,

View file

@ -30,7 +30,7 @@ import ts from 'typescript';
import {ErrorCode, makeDiagnostic, makeRelatedInformation, ngErrorCode} from '../../diagnostics';
import {ClassDeclaration} from '../../reflection';
import {TemplateDiagnostic, TemplateId} from '../api';
import {TemplateDiagnostic, TypeCheckId} from '../api';
import {makeTemplateDiagnostic} from '../diagnostics';
import {TemplateSourceResolver} from './tcb_util';
@ -51,60 +51,56 @@ export interface OutOfBandDiagnosticRecorder {
* Reports a `#ref="target"` expression in the template for which a target directive could not be
* found.
*
* @param templateId the template type-checking ID of the template which contains the broken
* reference.
* @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(templateId: TemplateId, ref: TmplAstReference): void;
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 templateId the template type-checking ID of the template which contains the unknown
* pipe.
* @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.
*/
missingPipe(templateId: TemplateId, ast: BindingPipe): void;
missingPipe(id: TypeCheckId, ast: BindingPipe): void;
/**
* Reports usage of a pipe imported via `@Component.deferredImports` outside
* of a `@defer` block in a template.
*
* @param templateId the template type-checking ID of the template which contains the unknown
* pipe.
* @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(templateId: TemplateId, ast: BindingPipe): void;
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 templateId the template type-checking ID of the template which contains the unknown
* pipe.
* @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(templateId: TemplateId, element: TmplAstElement): void;
deferredComponentUsedEagerly(id: TypeCheckId, element: TmplAstElement): void;
/**
* Reports a duplicate declaration of a template variable.
*
* @param templateId the template type-checking ID of the template which contains the duplicate
* @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(
templateId: TemplateId,
id: TypeCheckId,
variable: TmplAstVariable,
firstDecl: TmplAstVariable,
): void;
requiresInlineTcb(templateId: TemplateId, node: ClassDeclaration): void;
requiresInlineTcb(id: TypeCheckId, node: ClassDeclaration): void;
requiresInlineTypeConstructors(
templateId: TemplateId,
id: TypeCheckId,
node: ClassDeclaration,
directives: ClassDeclaration[],
): void;
@ -113,13 +109,13 @@ export interface OutOfBandDiagnosticRecorder {
* Report a warning when structural directives support context guards, but the current
* type-checking configuration prohibits their usage.
*/
suboptimalTypeInference(templateId: TemplateId, variables: TmplAstVariable[]): void;
suboptimalTypeInference(id: TypeCheckId, variables: TmplAstVariable[]): void;
/**
* Reports a split two way binding error message.
*/
splitTwoWayBinding(
templateId: TemplateId,
id: TypeCheckId,
input: TmplAstBoundAttribute,
output: TmplAstBoundEvent,
inputConsumer: ClassDeclaration,
@ -128,7 +124,7 @@ export interface OutOfBandDiagnosticRecorder {
/** Reports required inputs that haven't been bound. */
missingRequiredInputs(
templateId: TemplateId,
id: TypeCheckId,
element: TmplAstElement | TmplAstTemplate,
directiveName: string,
isComponent: boolean,
@ -139,7 +135,7 @@ export interface OutOfBandDiagnosticRecorder {
* Reports accesses of properties that aren't available in a `for` block's tracking expression.
*/
illegalForLoopTrackAccess(
templateId: TemplateId,
id: TypeCheckId,
block: TmplAstForLoopBlock,
access: PropertyRead,
): void;
@ -148,7 +144,7 @@ export interface OutOfBandDiagnosticRecorder {
* Reports deferred triggers that cannot access the element they're referring to.
*/
inaccessibleDeferredTriggerElement(
templateId: TemplateId,
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
@ -159,7 +155,7 @@ export interface OutOfBandDiagnosticRecorder {
* Reports cases where control flow nodes prevent content projection.
*/
controlFlowPreventingContentProjection(
templateId: TemplateId,
id: TypeCheckId,
category: ts.DiagnosticCategory,
projectionNode: TmplAstElement | TmplAstTemplate,
componentName: string,
@ -174,25 +170,21 @@ export interface OutOfBandDiagnosticRecorder {
/** Reports cases where users are writing to `@let` declarations. */
illegalWriteToLetDeclaration(
templateId: TemplateId,
id: TypeCheckId,
node: PropertyWrite,
target: TmplAstLetDeclaration,
): void;
/** Reports cases where users are accessing an `@let` before it is defined.. */
letUsedBeforeDefinition(
templateId: TemplateId,
node: PropertyRead,
target: TmplAstLetDeclaration,
): void;
letUsedBeforeDefinition(id: TypeCheckId, node: PropertyRead, target: TmplAstLetDeclaration): void;
/**
* Reports a `@let` declaration that conflicts with another symbol in the same scope.
*
* @param templateId the template type-checking ID of the template which contains the declaration.
* @param id the type-checking ID of the template which contains the declaration.
* @param current the `TmplAstLetDeclaration` which is invalid.
*/
conflictingDeclaration(templateId: TemplateId, current: TmplAstLetDeclaration): void;
conflictingDeclaration(id: TypeCheckId, current: TmplAstLetDeclaration): void;
}
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
@ -210,14 +202,14 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
return this._diagnostics;
}
missingReferenceTarget(templateId: TemplateId, ref: TmplAstReference): void {
const mapping = this.resolver.getSourceMapping(templateId);
missingReferenceTarget(id: TypeCheckId, ref: TmplAstReference): void {
const mapping = this.resolver.getSourceMapping(id);
const value = ref.value.trim();
const errorMsg = `No directive found with exportAs '${value}'.`;
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
ref.valueSpan || ref.sourceSpan,
ts.DiagnosticCategory.Error,
@ -227,15 +219,15 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
);
}
missingPipe(templateId: TemplateId, ast: BindingPipe): void {
missingPipe(id: TypeCheckId, ast: BindingPipe): void {
if (this.recordedPipes.has(ast)) {
return;
}
const mapping = this.resolver.getSourceMapping(templateId);
const mapping = this.resolver.getSourceMapping(id);
const errorMsg = `No pipe found with name '${ast.name}'.`;
const sourceSpan = this.resolver.toParseSourceSpan(templateId, ast.nameSpan);
const sourceSpan = this.resolver.toParseSourceSpan(id, ast.nameSpan);
if (sourceSpan === null) {
throw new Error(
`Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`,
@ -243,7 +235,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
sourceSpan,
ts.DiagnosticCategory.Error,
@ -254,19 +246,19 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this.recordedPipes.add(ast);
}
deferredPipeUsedEagerly(templateId: TemplateId, ast: BindingPipe): void {
deferredPipeUsedEagerly(id: TypeCheckId, ast: BindingPipe): void {
if (this.recordedPipes.has(ast)) {
return;
}
const mapping = this.resolver.getSourceMapping(templateId);
const mapping = this.resolver.getSourceMapping(id);
const errorMsg =
`Pipe '${ast.name}' was imported via \`@Component.deferredImports\`, ` +
`but was used outside of a \`@defer\` block in a template. To fix this, either ` +
`use the '${ast.name}' pipe inside of a \`@defer\` block or import this dependency ` +
`using the \`@Component.imports\` field.`;
const sourceSpan = this.resolver.toParseSourceSpan(templateId, ast.nameSpan);
const sourceSpan = this.resolver.toParseSourceSpan(id, ast.nameSpan);
if (sourceSpan === null) {
throw new Error(
`Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`,
@ -274,7 +266,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
sourceSpan,
ts.DiagnosticCategory.Error,
@ -285,8 +277,8 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this.recordedPipes.add(ast);
}
deferredComponentUsedEagerly(templateId: TemplateId, element: TmplAstElement): void {
const mapping = this.resolver.getSourceMapping(templateId);
deferredComponentUsedEagerly(id: TypeCheckId, element: TmplAstElement): void {
const mapping = this.resolver.getSourceMapping(id);
const errorMsg =
`Element '${element.name}' contains a component or a directive that ` +
`was imported via \`@Component.deferredImports\`, but the element itself is located ` +
@ -296,7 +288,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
const {start, end} = element.startSourceSpan;
const absoluteSourceSpan = new AbsoluteSourceSpan(start.offset, end.offset);
const sourceSpan = this.resolver.toParseSourceSpan(templateId, absoluteSourceSpan);
const sourceSpan = this.resolver.toParseSourceSpan(id, absoluteSourceSpan);
if (sourceSpan === null) {
throw new Error(
`Assertion failure: no SourceLocation found for usage of pipe '${element.name}'.`,
@ -304,7 +296,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
sourceSpan,
ts.DiagnosticCategory.Error,
@ -315,11 +307,11 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
duplicateTemplateVar(
templateId: TemplateId,
id: TypeCheckId,
variable: TmplAstVariable,
firstDecl: TmplAstVariable,
): void {
const mapping = this.resolver.getSourceMapping(templateId);
const mapping = this.resolver.getSourceMapping(id);
const errorMsg = `Cannot redeclare variable '${variable.name}' as it was previously declared elsewhere for the same template.`;
// The allocation of the error here is pretty useless for variables declared in microsyntax,
@ -329,7 +321,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
// TODO(alxhub): allocate to a tighter span once one is available.
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
variable.sourceSpan,
ts.DiagnosticCategory.Error,
@ -347,10 +339,10 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
);
}
requiresInlineTcb(templateId: TemplateId, node: ClassDeclaration): void {
requiresInlineTcb(id: TypeCheckId, node: ClassDeclaration): void {
this._diagnostics.push(
makeInlineDiagnostic(
templateId,
id,
ErrorCode.INLINE_TCB_REQUIRED,
node.name,
`This component requires inline template type-checking, which is not supported by the current environment.`,
@ -359,7 +351,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
requiresInlineTypeConstructors(
templateId: TemplateId,
id: TypeCheckId,
node: ClassDeclaration,
directives: ClassDeclaration[],
): void {
@ -372,7 +364,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeInlineDiagnostic(
templateId,
id,
ErrorCode.INLINE_TYPE_CTOR_REQUIRED,
node.name,
message,
@ -383,8 +375,8 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
);
}
suboptimalTypeInference(templateId: TemplateId, variables: TmplAstVariable[]): void {
const mapping = this.resolver.getSourceMapping(templateId);
suboptimalTypeInference(id: TypeCheckId, variables: TmplAstVariable[]): void {
const mapping = this.resolver.getSourceMapping(id);
// Select one of the template variables that's most suitable for reporting the diagnostic. Any
// variable will do, but prefer one bound to the context's $implicit if present.
@ -409,7 +401,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
diagnosticVar.keySpan,
ts.DiagnosticCategory.Suggestion,
@ -420,13 +412,13 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
splitTwoWayBinding(
templateId: TemplateId,
id: TypeCheckId,
input: TmplAstBoundAttribute,
output: TmplAstBoundEvent,
inputConsumer: ClassDeclaration,
outputConsumer: ClassDeclaration | TmplAstElement,
): void {
const mapping = this.resolver.getSourceMapping(templateId);
const mapping = this.resolver.getSourceMapping(id);
const errorMsg = `The property and event halves of the two-way binding '${input.name}' are not bound to the same target.
Find more at https://angular.dev/guide/templates/two-way-binding#how-two-way-binding-works`;
@ -462,7 +454,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
input.keySpan,
ts.DiagnosticCategory.Error,
@ -474,7 +466,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
missingRequiredInputs(
templateId: TemplateId,
id: TypeCheckId,
element: TmplAstElement | TmplAstTemplate,
directiveName: string,
isComponent: boolean,
@ -488,8 +480,8 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
this.resolver.getSourceMapping(templateId),
id,
this.resolver.getSourceMapping(id),
element.startSourceSpan,
ts.DiagnosticCategory.Error,
ngErrorCode(ErrorCode.MISSING_REQUIRED_INPUTS),
@ -499,11 +491,11 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
illegalForLoopTrackAccess(
templateId: TemplateId,
id: TypeCheckId,
block: TmplAstForLoopBlock,
access: PropertyRead,
): void {
const sourceSpan = this.resolver.toParseSourceSpan(templateId, access.sourceSpan);
const sourceSpan = this.resolver.toParseSourceSpan(id, access.sourceSpan);
if (sourceSpan === null) {
throw new Error(`Assertion failure: no SourceLocation found for property read.`);
}
@ -517,8 +509,8 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
this.resolver.getSourceMapping(templateId),
id,
this.resolver.getSourceMapping(id),
sourceSpan,
ts.DiagnosticCategory.Error,
ngErrorCode(ErrorCode.ILLEGAL_FOR_LOOP_TRACK_ACCESS),
@ -528,7 +520,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
inaccessibleDeferredTriggerElement(
templateId: TemplateId,
id: TypeCheckId,
trigger:
| TmplAstHoverDeferredTrigger
| TmplAstInteractionDeferredTrigger
@ -549,8 +541,8 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
this.resolver.getSourceMapping(templateId),
id,
this.resolver.getSourceMapping(id),
trigger.sourceSpan,
ts.DiagnosticCategory.Error,
ngErrorCode(ErrorCode.INACCESSIBLE_DEFERRED_TRIGGER_ELEMENT),
@ -560,7 +552,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
controlFlowPreventingContentProjection(
templateId: TemplateId,
id: TypeCheckId,
category: ts.DiagnosticCategory,
projectionNode: TmplAstElement | TmplAstTemplate,
componentName: string,
@ -595,8 +587,8 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
this.resolver.getSourceMapping(templateId),
id,
this.resolver.getSourceMapping(id),
projectionNode.startSourceSpan,
category,
ngErrorCode(ErrorCode.CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION),
@ -606,19 +598,19 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
illegalWriteToLetDeclaration(
templateId: TemplateId,
id: TypeCheckId,
node: PropertyWrite,
target: TmplAstLetDeclaration,
): void {
const sourceSpan = this.resolver.toParseSourceSpan(templateId, node.sourceSpan);
const sourceSpan = this.resolver.toParseSourceSpan(id, node.sourceSpan);
if (sourceSpan === null) {
throw new Error(`Assertion failure: no SourceLocation found for property write.`);
}
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
this.resolver.getSourceMapping(templateId),
id,
this.resolver.getSourceMapping(id),
sourceSpan,
ts.DiagnosticCategory.Error,
ngErrorCode(ErrorCode.ILLEGAL_LET_WRITE),
@ -628,19 +620,19 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
letUsedBeforeDefinition(
templateId: TemplateId,
id: TypeCheckId,
node: PropertyRead,
target: TmplAstLetDeclaration,
): void {
const sourceSpan = this.resolver.toParseSourceSpan(templateId, node.sourceSpan);
const sourceSpan = this.resolver.toParseSourceSpan(id, node.sourceSpan);
if (sourceSpan === null) {
throw new Error(`Assertion failure: no SourceLocation found for property read.`);
}
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
this.resolver.getSourceMapping(templateId),
id,
this.resolver.getSourceMapping(id),
sourceSpan,
ts.DiagnosticCategory.Error,
ngErrorCode(ErrorCode.LET_USED_BEFORE_DEFINITION),
@ -649,13 +641,13 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
);
}
conflictingDeclaration(templateId: TemplateId, decl: TmplAstLetDeclaration): void {
const mapping = this.resolver.getSourceMapping(templateId);
conflictingDeclaration(id: TypeCheckId, decl: TmplAstLetDeclaration): void {
const mapping = this.resolver.getSourceMapping(id);
const errorMsg = `Cannot declare @let called '${decl.name}' as there is another symbol in the template with the same name.`;
this._diagnostics.push(
makeTemplateDiagnostic(
templateId,
id,
mapping,
decl.sourceSpan,
ts.DiagnosticCategory.Error,
@ -667,7 +659,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
}
function makeInlineDiagnostic(
templateId: TemplateId,
id: TypeCheckId,
code: ErrorCode.INLINE_TCB_REQUIRED | ErrorCode.INLINE_TYPE_CTOR_REQUIRED,
node: ts.Node,
messageText: string | ts.DiagnosticMessageChain,
@ -675,7 +667,7 @@ function makeInlineDiagnostic(
): TemplateDiagnostic {
return {
...makeDiagnostic(code, node, messageText, relatedInformation),
componentFile: node.getSourceFile(),
templateId,
sourceFile: node.getSourceFile(),
typeCheckId: id,
};
}

View file

@ -14,8 +14,8 @@ import {
} from '@angular/compiler';
import ts from 'typescript';
import {TemplateId, TemplateSourceMapping} from '../api';
import {getTemplateId} from '../diagnostics';
import {TypeCheckId, TemplateSourceMapping} from '../api';
import {getTypeCheckId} from '../diagnostics';
import {computeLineStartsMap, getLineAndCharacterFromPosition} from './line_mappings';
import {TemplateSourceResolver} from './tcb_util';
@ -53,7 +53,7 @@ export class TemplateSource {
}
/**
* Assigns IDs to templates and keeps track of their origins.
* Assigns IDs for type checking and keeps track of their origins.
*
* Implements `TemplateSourceResolver` to resolve the source of a template based on these IDs.
*/
@ -63,30 +63,30 @@ export class TemplateSourceManager implements TemplateSourceResolver {
* attached to a TCB's function declaration as leading trivia. This enables translation of
* diagnostics produced for TCB code to their source location in the template.
*/
private templateSources = new Map<TemplateId, TemplateSource>();
private templateSources = new Map<TypeCheckId, TemplateSource>();
getTemplateId(node: ts.ClassDeclaration): TemplateId {
return getTemplateId(node);
getTypeCheckId(node: ts.ClassDeclaration): TypeCheckId {
return getTypeCheckId(node);
}
captureSource(
node: ts.ClassDeclaration,
mapping: TemplateSourceMapping,
file: ParseSourceFile,
): TemplateId {
const id = getTemplateId(node);
): TypeCheckId {
const id = getTypeCheckId(node);
this.templateSources.set(id, new TemplateSource(mapping, file));
return id;
}
getSourceMapping(id: TemplateId): TemplateSourceMapping {
getSourceMapping(id: TypeCheckId): TemplateSourceMapping {
if (!this.templateSources.has(id)) {
throw new Error(`Unexpected unknown template ID: ${id}`);
}
return this.templateSources.get(id)!.mapping;
}
toParseSourceSpan(id: TemplateId, span: AbsoluteSourceSpan): ParseSourceSpan | null {
toParseSourceSpan(id: TypeCheckId, span: AbsoluteSourceSpan): ParseSourceSpan | null {
if (!this.templateSources.has(id)) {
return null;
}

View file

@ -12,7 +12,7 @@ import ts from 'typescript';
import {ClassDeclaration, ReflectionHost} from '../../../../src/ngtsc/reflection';
import {Reference} from '../../imports';
import {getTokenAtPosition} from '../../util/src/typescript';
import {FullTemplateMapping, SourceLocation, TemplateId, TemplateSourceMapping} from '../api';
import {FullTemplateMapping, SourceLocation, TypeCheckId, TemplateSourceMapping} from '../api';
import {hasIgnoreForDiagnosticsMarker, readSpanComment} from './comments';
import {ReferenceEmitEnvironment} from './reference_emit_environment';
@ -37,24 +37,24 @@ const TCB_FILE_IMPORT_GRAPH_PREPARE_IDENTIFIERS = [
];
/**
* Adapter interface which allows the template type-checking diagnostics code to interpret offsets
* Adapter interface which allows the directive type-checking diagnostics code to interpret offsets
* in a TCB and map them back to original locations in the template.
*/
export interface TemplateSourceResolver {
getTemplateId(node: ts.ClassDeclaration): TemplateId;
getTypeCheckId(node: ts.ClassDeclaration): TypeCheckId;
/**
* For the given template id, retrieve the original source mapping which describes how the offsets
* in the template should be interpreted.
* For the given type checking id, retrieve the original source mapping which describes how the
* offsets in the template should be interpreted.
*/
getSourceMapping(id: TemplateId): TemplateSourceMapping;
getSourceMapping(id: TypeCheckId): TemplateSourceMapping;
/**
* Convert an absolute source span associated with the given template id into a full
* Convert an absolute source span associated with the given type checking id into a full
* `ParseSourceSpan`. The returned parse span has line and column numbers in addition to only
* absolute offsets and gives access to the original template source.
* absolute offsets and gives access to the original source code.
*/
toParseSourceSpan(id: TemplateId, span: AbsoluteSourceSpan): ParseSourceSpan | null;
toParseSourceSpan(id: TypeCheckId, span: AbsoluteSourceSpan): ParseSourceSpan | null;
}
/**
@ -131,7 +131,7 @@ export function getTemplateMapping(
export function findTypeCheckBlock(
file: ts.SourceFile,
id: TemplateId,
id: TypeCheckId,
isDiagnosticRequest: boolean,
): ts.Node | null {
for (const stmt of file.statements) {
@ -181,7 +181,7 @@ function getTemplateId(
node: ts.Node,
sourceFile: ts.SourceFile,
isDiagnosticRequest: boolean,
): TemplateId | null {
): TypeCheckId | null {
// Walk up to the function declaration of the TCB, the file information is attached there.
while (!ts.isFunctionDeclaration(node)) {
if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticRequest) {
@ -204,7 +204,7 @@ function getTemplateId(
}
const commentText = sourceFile.text.substring(pos + 2, end - 2);
return commentText;
}) as TemplateId) || null
}) as TypeCheckId) || null
);
}

View file

@ -58,12 +58,12 @@ import ts from 'typescript';
import {Reference} from '../../imports';
import {BindingPropertyName, ClassPropertyName, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata} from '../api';
import {TypeCheckId, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata} from '../api';
import {addExpressionIdentifier, ExpressionIdentifier, markIgnoreDiagnostics} from './comments';
import {
addParseSpanInfo,
addTemplateId,
addTypeCheckId,
wrapForDiagnostics,
wrapForTypeChecker,
} from './diagnostics';
@ -213,7 +213,7 @@ export function generateTypeCheckBlock(
/* type */ undefined,
/* body */ body,
);
addTemplateId(fnDecl, meta.id);
addTypeCheckId(fnDecl, meta.id);
return fnDecl;
}
@ -1934,7 +1934,7 @@ export class Context {
readonly env: Environment,
readonly domSchemaChecker: DomSchemaChecker,
readonly oobRecorder: OutOfBandDiagnosticRecorder,
readonly id: TemplateId,
readonly id: TypeCheckId,
readonly boundTarget: BoundTarget<TypeCheckableDirectiveMeta>,
private pipes: Map<string, PipeMeta>,
readonly schemas: SchemaMetadata[],

View file

@ -79,7 +79,7 @@ import {
TypeCheckContext,
} from '../api';
import {
TemplateId,
TypeCheckId,
TemplateSourceMapping,
TypeCheckableDirectiveMeta,
TypeCheckBlockMetadata,
@ -388,7 +388,7 @@ export function tcb(
const binder = new R3TargetBinder<DirectiveMeta>(matcher);
const boundTarget = binder.bind({template: nodes});
const id = 'tcb' as TemplateId;
const id = 'tcb' as TypeCheckId;
const meta: TypeCheckBlockMetadata = {
id,
boundTarget,
@ -1039,8 +1039,8 @@ export class NoopOobRecorder implements OutOfBandDiagnosticRecorder {
}
missingReferenceTarget(): void {}
missingPipe(): void {}
deferredPipeUsedEagerly(templateId: TemplateId, ast: BindingPipe): void {}
deferredComponentUsedEagerly(templateId: TemplateId, element: TmplAstElement): void {}
deferredPipeUsedEagerly(id: TypeCheckId, ast: BindingPipe): void {}
deferredComponentUsedEagerly(id: TypeCheckId, element: TmplAstElement): void {}
duplicateTemplateVar(): void {}
requiresInlineTcb(): void {}
requiresInlineTypeConstructors(): void {}
@ -1051,14 +1051,14 @@ export class NoopOobRecorder implements OutOfBandDiagnosticRecorder {
inaccessibleDeferredTriggerElement(): void {}
controlFlowPreventingContentProjection(): void {}
illegalWriteToLetDeclaration(
templateId: TemplateId,
id: TypeCheckId,
node: PropertyWrite,
target: TmplAstLetDeclaration,
): void {}
letUsedBeforeDefinition(
templateId: TemplateId,
id: TypeCheckId,
node: PropertyRead,
target: TmplAstLetDeclaration,
): void {}
conflictingDeclaration(templateId: TemplateId, current: TmplAstLetDeclaration): void {}
conflictingDeclaration(id: TypeCheckId, current: TmplAstLetDeclaration): void {}
}