refactor(ivy): cleanup translation of source spans in type checker (#34417)

This commit cleans up the template type checker regarding how
diagnostics are produced.

PR Close #34417
This commit is contained in:
JoostK 2019-12-15 15:12:14 +01:00 committed by Kara Erickson
parent 8c6468a025
commit 024e3b30ac
9 changed files with 80 additions and 85 deletions

View File

@ -25,6 +25,8 @@ export interface TypeCheckableDirectiveMeta extends DirectiveMeta {
hasNgTemplateContextGuard: boolean; hasNgTemplateContextGuard: boolean;
} }
export type TemplateId = string & {__brand: 'TemplateId'};
/** /**
* Metadata required in addition to a component class in order to generate a type check block (TCB) * Metadata required in addition to a component class in order to generate a type check block (TCB)
* for that component. * for that component.
@ -35,7 +37,7 @@ export interface TypeCheckBlockMetadata {
* *
* 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 component.
*/ */
id: string; id: TemplateId;
/** /**
* Semantic information about the template of the component. * Semantic information about the template of the component.

View File

@ -20,7 +20,7 @@ import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
import {Environment} from './environment'; import {Environment} from './environment';
import {TypeCheckProgramHost} from './host'; import {TypeCheckProgramHost} from './host';
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob'; import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
import {TcbSourceManager} from './source'; import {TemplateSourceManager} from './source';
import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check_block'; import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check_block';
import {TypeCheckFile} from './type_check_file'; import {TypeCheckFile} from './type_check_file';
import {generateInlineTypeCtor, requiresInlineTypeCtor} from './type_constructor'; import {generateInlineTypeCtor, requiresInlineTypeCtor} from './type_constructor';
@ -55,7 +55,7 @@ export class TypeCheckContext {
*/ */
private typeCtorPending = new Set<ts.ClassDeclaration>(); private typeCtorPending = new Set<ts.ClassDeclaration>();
private sourceManager = new TcbSourceManager(); private sourceManager = new TemplateSourceManager();
private domSchemaChecker = new RegistryDomSchemaChecker(this.sourceManager); private domSchemaChecker = new RegistryDomSchemaChecker(this.sourceManager);

View File

@ -10,34 +10,26 @@ import * as ts from 'typescript';
import {getTokenAtPosition} from '../../util/src/typescript'; import {getTokenAtPosition} from '../../util/src/typescript';
import {ExternalTemplateSourceMapping, TemplateSourceMapping} from './api'; import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from './api';
export interface SourceLocation {
id: string;
start: number;
end: number;
}
/** /**
* Adapter interface which allows the template type-checking diagnostics code to interpret offsets * Adapter interface which allows the template type-checking diagnostics code to interpret offsets
* in a TCB and map them back to original locations in the template. * in a TCB and map them back to original locations in the template.
*/ */
export interface TcbSourceResolver { export interface TemplateSourceResolver {
/** /**
* For the given template id, retrieve the original source mapping which describes how the offsets * For the given template id, retrieve the original source mapping which describes how the offsets
* in the template should be interpreted. * in the template should be interpreted.
*/ */
getSourceMapping(id: string): TemplateSourceMapping; getSourceMapping(id: TemplateId): TemplateSourceMapping;
/** /**
* Convert a location extracted from a TCB into a `ParseSourceSpan` if possible. * Convert an absolute source span associated with the given template 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.
*/ */
sourceLocationToSpan(location: SourceLocation): ParseSourceSpan|null; toParseSourceSpan(id: TemplateId, span: AbsoluteSourceSpan): ParseSourceSpan|null;
}
export function absoluteSourceSpanToSourceLocation(
id: string, span: AbsoluteSourceSpan): SourceLocation {
return {id, ...span};
} }
/** /**
@ -70,10 +62,10 @@ export function addParseSpanInfo(node: ts.Node, span: AbsoluteSourceSpan | Parse
} }
/** /**
* Adds a synthetic comment to the function declaration that contains the source location * Adds a synthetic comment to the function declaration that contains the template id
* of the class declaration. * of the class declaration.
*/ */
export function addSourceId(tcb: ts.FunctionDeclaration, id: string): void { export function addTemplateId(tcb: ts.FunctionDeclaration, id: TemplateId): void {
ts.addSyntheticLeadingComment(tcb, ts.SyntaxKind.MultiLineCommentTrivia, id, true); ts.addSyntheticLeadingComment(tcb, ts.SyntaxKind.MultiLineCommentTrivia, id, true);
} }
@ -105,7 +97,7 @@ export function shouldReportDiagnostic(diagnostic: ts.Diagnostic): boolean {
* file from being reported as type-check errors. * file from being reported as type-check errors.
*/ */
export function translateDiagnostic( export function translateDiagnostic(
diagnostic: ts.Diagnostic, resolver: TcbSourceResolver): ts.Diagnostic|null { diagnostic: ts.Diagnostic, resolver: TemplateSourceResolver): ts.Diagnostic|null {
if (diagnostic.file === undefined || diagnostic.start === undefined) { if (diagnostic.file === undefined || diagnostic.start === undefined) {
return null; return null;
} }
@ -118,7 +110,7 @@ export function translateDiagnostic(
} }
// Now use the external resolver to obtain the full `ParseSourceFile` of the template. // Now use the external resolver to obtain the full `ParseSourceFile` of the template.
const span = resolver.sourceLocationToSpan(sourceLocation); const span = resolver.toParseSourceSpan(sourceLocation.id, sourceLocation.span);
if (span === null) { if (span === null) {
return null; return null;
} }
@ -217,10 +209,15 @@ export function makeTemplateDiagnostic(
} }
} }
interface SourceLocation {
id: TemplateId;
span: AbsoluteSourceSpan;
}
function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null { function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
// Search for comments until the TCB's function declaration is encountered. // Search for comments until the TCB's function declaration is encountered.
while (node !== undefined && !ts.isFunctionDeclaration(node)) { while (node !== undefined && !ts.isFunctionDeclaration(node)) {
const sourceSpan = const span =
ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => { ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) { if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
return null; return null;
@ -228,10 +225,14 @@ function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLoc
const commentText = sourceFile.text.substring(pos, end); const commentText = sourceFile.text.substring(pos, end);
return parseSourceSpanComment(commentText); return parseSourceSpanComment(commentText);
}) || null; }) || null;
if (sourceSpan !== null) { if (span !== null) {
// Once the positional information has been extracted, search further up the TCB to extract // Once the positional information has been extracted, search further up the TCB to extract
// the file information that is attached with the TCB's function declaration. // the unique id that is attached with the TCB's function declaration.
return toSourceLocation(sourceSpan, node, sourceFile); const id = getTemplateId(node, sourceFile);
if (id === null) {
return null;
}
return {id, span};
} }
node = node.parent; node = node.parent;
@ -240,8 +241,7 @@ function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLoc
return null; return null;
} }
function toSourceLocation( function getTemplateId(node: ts.Node, sourceFile: ts.SourceFile): TemplateId|null {
sourceSpan: AbsoluteSourceSpan, node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
// Walk up to the function declaration of the TCB, the file information is attached there. // Walk up to the function declaration of the TCB, the file information is attached there.
let tcb = node; let tcb = node;
while (!ts.isFunctionDeclaration(tcb)) { while (!ts.isFunctionDeclaration(tcb)) {
@ -253,23 +253,13 @@ function toSourceLocation(
} }
} }
const id = return ts.forEachLeadingCommentRange(sourceFile.text, tcb.getFullStart(), (pos, end, kind) => {
ts.forEachLeadingCommentRange(sourceFile.text, tcb.getFullStart(), (pos, end, kind) => { if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) { return null;
return null; }
} const commentText = sourceFile.text.substring(pos + 2, end - 2);
const commentText = sourceFile.text.substring(pos, end); return commentText;
return commentText.substring(2, commentText.length - 2); }) as TemplateId || null;
}) || null;
if (id === null) {
return null;
}
return {
id,
start: sourceSpan.start,
end: sourceSpan.end,
};
} }
const parseSpanComment = /^\/\*(\d+),(\d+)\*\/$/; const parseSpanComment = /^\/\*(\d+),(\d+)\*\/$/;

View File

@ -11,7 +11,8 @@ import * as ts from 'typescript';
import {ErrorCode} from '../../diagnostics'; import {ErrorCode} from '../../diagnostics';
import {TcbSourceResolver, makeTemplateDiagnostic} from './diagnostics'; import {TemplateId} from './api';
import {TemplateSourceResolver, makeTemplateDiagnostic} from './diagnostics';
const REGISTRY = new DomElementSchemaRegistry(); const REGISTRY = new DomElementSchemaRegistry();
const REMOVE_XHTML_REGEX = /^:xhtml:/; const REMOVE_XHTML_REGEX = /^:xhtml:/;
@ -67,9 +68,9 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
get diagnostics(): ReadonlyArray<ts.Diagnostic> { return this._diagnostics; } get diagnostics(): ReadonlyArray<ts.Diagnostic> { return this._diagnostics; }
constructor(private resolver: TcbSourceResolver) {} constructor(private resolver: TemplateSourceResolver) {}
checkElement(id: string, element: TmplAstElement, schemas: SchemaMetadata[]): void { checkElement(id: TemplateId, element: TmplAstElement, schemas: SchemaMetadata[]): void {
// HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace. // HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
// We need to strip it before handing it over to the registry because all HTML tag names // We need to strip it before handing it over to the registry because all HTML tag names
// in the registry are without a namespace. // in the registry are without a namespace.
@ -97,7 +98,7 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
} }
checkProperty( checkProperty(
id: string, element: TmplAstElement, name: string, span: ParseSourceSpan, id: TemplateId, element: TmplAstElement, name: string, span: ParseSourceSpan,
schemas: SchemaMetadata[]): void { schemas: SchemaMetadata[]): void {
if (!REGISTRY.hasProperty(element.name, name, schemas)) { if (!REGISTRY.hasProperty(element.name, name, schemas)) {
const mapping = this.resolver.getSourceMapping(id); const mapping = this.resolver.getSourceMapping(id);

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteSourceSpan, BindingPipe, PropertyWrite, TmplAstReference, TmplAstVariable} from '@angular/compiler'; import {BindingPipe, PropertyWrite, TmplAstReference, TmplAstVariable} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, ngErrorCode} from '../../diagnostics'; import {ErrorCode, ngErrorCode} from '../../diagnostics';
import {TcbSourceResolver, absoluteSourceSpanToSourceLocation, makeTemplateDiagnostic} from './diagnostics'; import {TemplateId} from './api';
import {TemplateSourceResolver, makeTemplateDiagnostic} from './diagnostics';
@ -35,7 +36,7 @@ export interface OutOfBandDiagnosticRecorder {
* reference. * reference.
* @param ref the `TmplAstReference` which could not be matched to a directive. * @param ref the `TmplAstReference` which could not be matched to a directive.
*/ */
missingReferenceTarget(templateId: string, ref: TmplAstReference): void; missingReferenceTarget(templateId: TemplateId, ref: TmplAstReference): void;
/** /**
* Reports usage of a `| pipe` expression in the template for which the named pipe could not be * Reports usage of a `| pipe` expression in the template for which the named pipe could not be
@ -45,20 +46,20 @@ export interface OutOfBandDiagnosticRecorder {
* pipe. * pipe.
* @param ast the `BindingPipe` invocation of the pipe which could not be found. * @param ast the `BindingPipe` invocation of the pipe which could not be found.
*/ */
missingPipe(templateId: string, ast: BindingPipe): void; missingPipe(templateId: TemplateId, ast: BindingPipe): void;
illegalAssignmentToTemplateVar( illegalAssignmentToTemplateVar(
templateId: string, assignment: PropertyWrite, target: TmplAstVariable): void; templateId: TemplateId, assignment: PropertyWrite, target: TmplAstVariable): void;
} }
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder { export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
private _diagnostics: ts.Diagnostic[] = []; private _diagnostics: ts.Diagnostic[] = [];
constructor(private resolver: TcbSourceResolver) {} constructor(private resolver: TemplateSourceResolver) {}
get diagnostics(): ReadonlyArray<ts.Diagnostic> { return this._diagnostics; } get diagnostics(): ReadonlyArray<ts.Diagnostic> { return this._diagnostics; }
missingReferenceTarget(templateId: string, ref: TmplAstReference): void { missingReferenceTarget(templateId: TemplateId, ref: TmplAstReference): void {
const mapping = this.resolver.getSourceMapping(templateId); const mapping = this.resolver.getSourceMapping(templateId);
const value = ref.value.trim(); const value = ref.value.trim();
@ -68,12 +69,11 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
ngErrorCode(ErrorCode.MISSING_REFERENCE_TARGET), errorMsg)); ngErrorCode(ErrorCode.MISSING_REFERENCE_TARGET), errorMsg));
} }
missingPipe(templateId: string, ast: BindingPipe): void { missingPipe(templateId: TemplateId, ast: BindingPipe): void {
const mapping = this.resolver.getSourceMapping(templateId); const mapping = this.resolver.getSourceMapping(templateId);
const errorMsg = `No pipe found with name '${ast.name}'.`; const errorMsg = `No pipe found with name '${ast.name}'.`;
const location = absoluteSourceSpanToSourceLocation(templateId, ast.nameSpan); const sourceSpan = this.resolver.toParseSourceSpan(templateId, ast.nameSpan);
const sourceSpan = this.resolver.sourceLocationToSpan(location);
if (sourceSpan === null) { if (sourceSpan === null) {
throw new Error( throw new Error(
`Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`); `Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`);
@ -84,13 +84,12 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
} }
illegalAssignmentToTemplateVar( illegalAssignmentToTemplateVar(
templateId: string, assignment: PropertyWrite, target: TmplAstVariable): void { templateId: TemplateId, assignment: PropertyWrite, target: TmplAstVariable): void {
const mapping = this.resolver.getSourceMapping(templateId); const mapping = this.resolver.getSourceMapping(templateId);
const errorMsg = const errorMsg =
`Cannot use variable '${assignment.name}' as the left-hand side of an assignment expression. Template variables are read-only.`; `Cannot use variable '${assignment.name}' as the left-hand side of an assignment expression. Template variables are read-only.`;
const location = absoluteSourceSpanToSourceLocation(templateId, assignment.sourceSpan); const sourceSpan = this.resolver.toParseSourceSpan(templateId, assignment.sourceSpan);
const sourceSpan = this.resolver.sourceLocationToSpan(location);
if (sourceSpan === null) { if (sourceSpan === null) {
throw new Error(`Assertion failure: no SourceLocation found for property binding.`); throw new Error(`Assertion failure: no SourceLocation found for property binding.`);
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler'; import {AbsoluteSourceSpan, ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
import {TemplateSourceMapping} from './api'; import {TemplateId, TemplateSourceMapping} from './api';
import {SourceLocation, TcbSourceResolver} from './diagnostics'; import {TemplateSourceResolver} from './diagnostics';
import {computeLineStartsMap, getLineAndCharacterFromPosition} from './line_mappings'; import {computeLineStartsMap, getLineAndCharacterFromPosition} from './line_mappings';
/** /**
@ -44,35 +44,35 @@ export class TemplateSource {
/** /**
* Assigns IDs to templates and keeps track of their origins. * Assigns IDs to templates and keeps track of their origins.
* *
* Implements `TcbSourceResolver` to resolve the source of a template based on these IDs. * Implements `TemplateSourceResolver` to resolve the source of a template based on these IDs.
*/ */
export class TcbSourceManager implements TcbSourceResolver { export class TemplateSourceManager implements TemplateSourceResolver {
private nextTcbId: number = 1; private nextTemplateId: number = 1;
/** /**
* This map keeps track of all template sources that have been type-checked by the id that is * This map keeps track of all template sources that have been type-checked by the id that is
* attached to a TCB's function declaration as leading trivia. This enables translation of * 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. * diagnostics produced for TCB code to their source location in the template.
*/ */
private templateSources = new Map<string, TemplateSource>(); private templateSources = new Map<TemplateId, TemplateSource>();
captureSource(mapping: TemplateSourceMapping, file: ParseSourceFile): string { captureSource(mapping: TemplateSourceMapping, file: ParseSourceFile): TemplateId {
const id = `tcb${this.nextTcbId++}`; const id = `tcb${this.nextTemplateId++}` as TemplateId;
this.templateSources.set(id, new TemplateSource(mapping, file)); this.templateSources.set(id, new TemplateSource(mapping, file));
return id; return id;
} }
getSourceMapping(id: string): TemplateSourceMapping { getSourceMapping(id: TemplateId): TemplateSourceMapping {
if (!this.templateSources.has(id)) { if (!this.templateSources.has(id)) {
throw new Error(`Unexpected unknown TCB ID: ${id}`); throw new Error(`Unexpected unknown template ID: ${id}`);
} }
return this.templateSources.get(id) !.mapping; return this.templateSources.get(id) !.mapping;
} }
sourceLocationToSpan(location: SourceLocation): ParseSourceSpan|null { toParseSourceSpan(id: TemplateId, span: AbsoluteSourceSpan): ParseSourceSpan|null {
if (!this.templateSources.has(location.id)) { if (!this.templateSources.has(id)) {
return null; return null;
} }
const templateSource = this.templateSources.get(location.id) !; const templateSource = this.templateSources.get(id) !;
return templateSource.toParseSourceSpan(location.start, location.end); return templateSource.toParseSourceSpan(span.start, span.end);
} }
} }

View File

@ -8,6 +8,7 @@
import {AST, BoundTarget, ImplicitReceiver, PropertyWrite, RecursiveAstVisitor, TmplAstVariable} from '@angular/compiler'; import {AST, BoundTarget, ImplicitReceiver, PropertyWrite, RecursiveAstVisitor, TmplAstVariable} from '@angular/compiler';
import {TemplateId} from './api';
import {OutOfBandDiagnosticRecorder} from './oob'; import {OutOfBandDiagnosticRecorder} from './oob';
/** /**
@ -15,7 +16,7 @@ import {OutOfBandDiagnosticRecorder} from './oob';
*/ */
export class ExpressionSemanticVisitor extends RecursiveAstVisitor { export class ExpressionSemanticVisitor extends RecursiveAstVisitor {
constructor( constructor(
private templateId: string, private boundTarget: BoundTarget<any>, private templateId: TemplateId, private boundTarget: BoundTarget<any>,
private oob: OutOfBandDiagnosticRecorder) { private oob: OutOfBandDiagnosticRecorder) {
super(); super();
} }
@ -35,7 +36,8 @@ export class ExpressionSemanticVisitor extends RecursiveAstVisitor {
} }
static visit( static visit(
ast: AST, id: string, boundTarget: BoundTarget<any>, oob: OutOfBandDiagnosticRecorder): void { ast: AST, id: TemplateId, boundTarget: BoundTarget<any>,
oob: OutOfBandDiagnosticRecorder): void {
ast.visit(new ExpressionSemanticVisitor(id, boundTarget, oob)); ast.visit(new ExpressionSemanticVisitor(id, boundTarget, oob));
} }
} }

View File

@ -12,8 +12,8 @@ import * as ts from 'typescript';
import {Reference} from '../../imports'; import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection'; import {ClassDeclaration} from '../../reflection';
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api'; import {TemplateId, TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api';
import {addParseSpanInfo, addSourceId, wrapForDiagnostics} from './diagnostics'; import {addParseSpanInfo, addTemplateId, wrapForDiagnostics} from './diagnostics';
import {DomSchemaChecker} from './dom'; import {DomSchemaChecker} from './dom';
import {Environment} from './environment'; import {Environment} from './environment';
import {NULL_AS_ANY, astToTypescript} from './expression'; import {NULL_AS_ANY, astToTypescript} from './expression';
@ -77,7 +77,7 @@ export function generateTypeCheckBlock(
/* parameters */ paramList, /* parameters */ paramList,
/* type */ undefined, /* type */ undefined,
/* body */ body); /* body */ body);
addSourceId(fnDecl, meta.id); addTemplateId(fnDecl, meta.id);
return fnDecl; return fnDecl;
} }
@ -579,7 +579,7 @@ export class Context {
constructor( constructor(
readonly env: Environment, readonly domSchemaChecker: DomSchemaChecker, readonly env: Environment, readonly domSchemaChecker: DomSchemaChecker,
readonly oobRecorder: OutOfBandDiagnosticRecorder, readonly id: string, readonly oobRecorder: OutOfBandDiagnosticRecorder, readonly id: TemplateId,
readonly boundTarget: BoundTarget<TypeCheckableDirectiveMeta>, readonly boundTarget: BoundTarget<TypeCheckableDirectiveMeta>,
private pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>, private pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>,
readonly schemas: SchemaMetadata[]) {} readonly schemas: SchemaMetadata[]) {}

View File

@ -15,7 +15,7 @@ import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy,
import {ClassDeclaration, TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection'; import {ClassDeclaration, TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {makeProgram} from '../../testing'; import {makeProgram} from '../../testing';
import {getRootDirs} from '../../util/src/typescript'; import {getRootDirs} from '../../util/src/typescript';
import {TemplateSourceMapping, TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../src/api'; import {TemplateId, TemplateSourceMapping, TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../src/api';
import {TypeCheckContext} from '../src/context'; import {TypeCheckContext} from '../src/context';
import {DomSchemaChecker} from '../src/dom'; import {DomSchemaChecker} from '../src/dom';
import {Environment} from '../src/environment'; import {Environment} from '../src/environment';
@ -197,7 +197,8 @@ export function tcb(
const binder = new R3TargetBinder(matcher); const binder = new R3TargetBinder(matcher);
const boundTarget = binder.bind({template: nodes}); const boundTarget = binder.bind({template: nodes});
const meta: TypeCheckBlockMetadata = {boundTarget, pipes, id: 'tcb', schemas: []}; const id = 'tcb' as TemplateId;
const meta: TypeCheckBlockMetadata = {id, boundTarget, pipes, schemas: []};
config = config || { config = config || {
applyTemplateContextGuards: true, applyTemplateContextGuards: true,