diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts index 32a804c4e7..3eac4dfebd 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts @@ -28,6 +28,14 @@ export interface TemplateTypeChecker { */ resetOverrides(): void; + /** + * Retrieve the template in use for the given component. + * + * If the template has been overridden via `overrideComponentTemplate`, this will retrieve the + * overridden template nodes. + */ + getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null; + /** * Provide a new template string that will be used in place of the user-defined template when * checking or operating on the given component. diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts index defdc7f705..b91825c55f 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts @@ -48,6 +48,29 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { } } + getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null { + this.ensureShimForComponent(component); + + const sf = component.getSourceFile(); + const sfPath = absoluteFromSourceFile(sf); + const shimPath = this.typeCheckingStrategy.shimPathForComponent(component); + + const fileRecord = this.getFileData(sfPath); + + if (!fileRecord.shimData.has(shimPath)) { + return []; + } + + const templateId = fileRecord.sourceManager.getTemplateId(component); + const shimRecord = fileRecord.shimData.get(shimPath)!; + + if (!shimRecord.templates.has(templateId)) { + return null; + } + + return shimRecord.templates.get(templateId)!.template; + } + overrideComponentTemplate(component: ts.ClassDeclaration, template: string): {nodes: TmplAstNode[], errors?: ParseError[]} { const {nodes, errors} = parseTemplate(template, 'override.html', { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts index 748ee71d74..fa80d5b0b3 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ParseSourceFile, R3TargetBinder, SchemaMetadata, TmplAstNode} from '@angular/compiler'; +import {BoundTarget, ParseSourceFile, R3TargetBinder, SchemaMetadata, TmplAstNode} from '@angular/compiler'; import * as ts from 'typescript'; import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system'; import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports'; import {ClassDeclaration, ReflectionHost} from '../../reflection'; import {ImportManager} from '../../translator'; -import {ComponentToShimMappingStrategy, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api'; +import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api'; import {TemplateDiagnostic} from './diagnostics'; import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom'; @@ -41,6 +41,28 @@ export interface ShimTypeCheckingData { * Whether any inline operations for the input file were required to generate this shim. */ hasInlines: boolean; + + /** + * Map of `TemplateId` to information collected about the template during the template + * type-checking process. + */ + templates: Map; +} + +/** + * Data tracked for each template processed by the template type-checking system. + */ +export interface TemplateData { + /** + * Template nodes for which the TCB was generated. + */ + template: TmplAstNode[]; + + /** + * `BoundTarget` which was used to generate the TCB, and contains bindings for the associated + * template nodes. + */ + boundTarget: BoundTarget; } /** @@ -79,6 +101,12 @@ export interface PendingShimData { * Shim file in the process of being generated. */ file: TypeCheckFile; + + + /** + * Map of `TemplateId` to information collected about the template as it's ingested. + */ + templates: Map; } /** @@ -195,6 +223,7 @@ export class TypeCheckContextImpl implements TypeCheckContext { const fileData = this.dataForFile(ref.node.getSourceFile()); const shimData = this.pendingShimForComponent(ref.node); const boundTarget = binder.bind({template}); + // Get all of the directives used in the template and record type constructors for all of them. for (const dir of boundTarget.getUsedDirectives()) { const dirRef = dir.ref as Reference>; @@ -221,6 +250,11 @@ export class TypeCheckContextImpl implements TypeCheckContext { }); } } + const templateId = fileData.sourceManager.getTemplateId(ref.node); + shimData.templates.set(templateId, { + template, + boundTarget, + }); const tcbRequiresInline = requiresInlineTypeCheckBlock(ref.node); @@ -231,7 +265,6 @@ export class TypeCheckContextImpl implements TypeCheckContext { // and inlining would be required. // Record diagnostics to indicate the issues with this template. - const templateId = fileData.sourceManager.getTemplateId(ref.node); if (tcbRequiresInline) { shimData.oobRecorder.requiresInlineTcb(templateId, ref.node); } @@ -348,6 +381,7 @@ export class TypeCheckContextImpl implements TypeCheckContext { ], hasInlines: pendingFileData.hasInlines, path: pendingShimData.file.fileName, + templates: pendingShimData.templates, }); updates.set(pendingShimData.file.fileName, pendingShimData.file.render()); } @@ -380,6 +414,7 @@ export class TypeCheckContextImpl implements TypeCheckContext { oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager), file: new TypeCheckFile( shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost), + templates: new Map(), }); } return fileData.shimData.get(shimPath)!; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts index c6e148b961..b4d95d1b5a 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts @@ -353,5 +353,40 @@ runInEachFileSystem(os => { expect(diags2[0].messageText).toContain('invalid-element-b'); expect(diags2[0].messageText).not.toContain('invalid-element-a'); }); + + describe('getTemplateOfComponent()', () => { + it('should provide access to a component\'s real template', () => { + const fileName = absoluteFrom('/main.ts'); + const {program, templateTypeChecker} = setup([{ + fileName, + templates: { + 'Cmp': '
Template
', + }, + }]); + const cmp = getClass(getSourceFileOrError(program, fileName), 'Cmp'); + + const nodes = templateTypeChecker.getTemplate(cmp)!; + expect(nodes).not.toBeNull(); + expect(nodes[0].sourceSpan.start.file.content).toBe('
Template
'); + }); + + it('should provide access to an overridden template', () => { + const fileName = absoluteFrom('/main.ts'); + const {program, templateTypeChecker} = setup([{ + fileName, + templates: { + 'Cmp': '
Template
', + }, + }]); + const cmp = getClass(getSourceFileOrError(program, fileName), 'Cmp'); + + templateTypeChecker.overrideComponentTemplate(cmp, '
Overridden
'); + templateTypeChecker.getDiagnosticsForComponent(cmp); + + const nodes = templateTypeChecker.getTemplate(cmp)!; + expect(nodes).not.toBeNull(); + expect(nodes[0].sourceSpan.start.file.content).toBe('
Overridden
'); + }); + }); }); });