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;
|
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
|
* Provide a new template string that will be used in place of the user-defined template when
|
||||||
* checking or operating on the given component.
|
* 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):
|
overrideComponentTemplate(component: ts.ClassDeclaration, template: string):
|
||||||
{nodes: TmplAstNode[], errors?: ParseError[]} {
|
{nodes: TmplAstNode[], errors?: ParseError[]} {
|
||||||
const {nodes, errors} = parseTemplate(template, 'override.html', {
|
const {nodes, errors} = parseTemplate(template, 'override.html', {
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 * as ts from 'typescript';
|
||||||
|
|
||||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||||
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||||
import {ImportManager} from '../../translator';
|
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 {TemplateDiagnostic} from './diagnostics';
|
||||||
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
|
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.
|
* Whether any inline operations for the input file were required to generate this shim.
|
||||||
*/
|
*/
|
||||||
hasInlines: boolean;
|
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.
|
* Shim file in the process of being generated.
|
||||||
*/
|
*/
|
||||||
file: TypeCheckFile;
|
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 fileData = this.dataForFile(ref.node.getSourceFile());
|
||||||
const shimData = this.pendingShimForComponent(ref.node);
|
const shimData = this.pendingShimForComponent(ref.node);
|
||||||
const boundTarget = binder.bind({template});
|
const boundTarget = binder.bind({template});
|
||||||
|
|
||||||
// Get all of the directives used in the template and record type constructors for all of them.
|
// Get all of the directives used in the template and record type constructors for all of them.
|
||||||
for (const dir of boundTarget.getUsedDirectives()) {
|
for (const dir of boundTarget.getUsedDirectives()) {
|
||||||
const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;
|
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);
|
const tcbRequiresInline = requiresInlineTypeCheckBlock(ref.node);
|
||||||
|
|
||||||
|
@ -231,7 +265,6 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||||
// and inlining would be required.
|
// and inlining would be required.
|
||||||
|
|
||||||
// Record diagnostics to indicate the issues with this template.
|
// Record diagnostics to indicate the issues with this template.
|
||||||
const templateId = fileData.sourceManager.getTemplateId(ref.node);
|
|
||||||
if (tcbRequiresInline) {
|
if (tcbRequiresInline) {
|
||||||
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
||||||
}
|
}
|
||||||
|
@ -348,6 +381,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||||
],
|
],
|
||||||
hasInlines: pendingFileData.hasInlines,
|
hasInlines: pendingFileData.hasInlines,
|
||||||
path: pendingShimData.file.fileName,
|
path: pendingShimData.file.fileName,
|
||||||
|
templates: pendingShimData.templates,
|
||||||
});
|
});
|
||||||
updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
|
updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
|
||||||
}
|
}
|
||||||
|
@ -380,6 +414,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
||||||
oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
|
oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
|
||||||
file: new TypeCheckFile(
|
file: new TypeCheckFile(
|
||||||
shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
|
shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
|
||||||
|
templates: new Map<TemplateId, TemplateData>(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return fileData.shimData.get(shimPath)!;
|
return fileData.shimData.get(shimPath)!;
|
||||||
|
|
|
@ -353,5 +353,40 @@ runInEachFileSystem(os => {
|
||||||
expect(diags2[0].messageText).toContain('invalid-element-b');
|
expect(diags2[0].messageText).toContain('invalid-element-b');
|
||||||
expect(diags2[0].messageText).not.toContain('invalid-element-a');
|
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