refactor(compiler-cli): add getTemplateOfComponent to TemplateTypeChecker (#38355)
This commit adds a `getTemplateOfComponent` method to the `TemplateTypeChecker` API, which retrieves the actual nodes parsed and used by the compiler for template type-checking. This is advantageous for the language service, which may need to query other APIs in `TemplateTypeChecker` that require the same nodes used to bind the template while generating the TCB. Fixes #38352 PR Close #38355
This commit is contained in:
parent
1ec609946f
commit
0b54c0c6b4
|
@ -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.
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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<TemplateId, TemplateData>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<TypeCheckableDirectiveMeta>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<TemplateId, TemplateData>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<ClassDeclaration<ts.ClassDeclaration>>;
|
||||
|
@ -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<TemplateId, TemplateData>(),
|
||||
});
|
||||
}
|
||||
return fileData.shimData.get(shimPath)!;
|
||||
|
|
|
@ -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': '<div>Template</div>',
|
||||
},
|
||||
}]);
|
||||
const cmp = getClass(getSourceFileOrError(program, fileName), 'Cmp');
|
||||
|
||||
const nodes = templateTypeChecker.getTemplate(cmp)!;
|
||||
expect(nodes).not.toBeNull();
|
||||
expect(nodes[0].sourceSpan.start.file.content).toBe('<div>Template</div>');
|
||||
});
|
||||
|
||||
it('should provide access to an overridden template', () => {
|
||||
const fileName = absoluteFrom('/main.ts');
|
||||
const {program, templateTypeChecker} = setup([{
|
||||
fileName,
|
||||
templates: {
|
||||
'Cmp': '<div>Template</div>',
|
||||
},
|
||||
}]);
|
||||
const cmp = getClass(getSourceFileOrError(program, fileName), 'Cmp');
|
||||
|
||||
templateTypeChecker.overrideComponentTemplate(cmp, '<div>Overridden</div>');
|
||||
templateTypeChecker.getDiagnosticsForComponent(cmp);
|
||||
|
||||
const nodes = templateTypeChecker.getTemplate(cmp)!;
|
||||
expect(nodes).not.toBeNull();
|
||||
expect(nodes[0].sourceSpan.start.file.content).toBe('<div>Overridden</div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue