refactor(compiler-cli): support type-checking a single component (#38105)
This commit adds a method `getDiagnosticsForComponent` to the `TemplateTypeChecker`, which does the minimum amount of work to retrieve diagnostics for a single component. With the normal `ReusedProgramStrategy` this offers virtually no improvement over the standard `getDiagnosticsForFile` operation, but if the `TypeCheckingProgramStrategy` supports separate shims for each component, this operation can yield a faster turnaround for components that are declared in files with many other components. PR Close #38105
This commit is contained in:
parent
8f73169979
commit
d8c07b83c3
|
@ -54,6 +54,13 @@ export interface TemplateTypeChecker {
|
|||
*/
|
||||
getDiagnosticsForFile(sf: ts.SourceFile, optimizeFor: OptimizeFor): ts.Diagnostic[];
|
||||
|
||||
/**
|
||||
* Get all `ts.Diagnostic`s currently available that pertain to the given component.
|
||||
*
|
||||
* This method always runs in `OptimizeFor.SingleFile` mode.
|
||||
*/
|
||||
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[];
|
||||
|
||||
/**
|
||||
* Retrieve the top-level node representing the TCB for the given component.
|
||||
*
|
||||
|
|
|
@ -18,7 +18,7 @@ import {getSourceFileOrNull} from '../../util/src/typescript';
|
|||
import {OptimizeFor, ProgramTypeCheckAdapter, TemplateId, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
|
||||
|
||||
import {InliningMode, ShimTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from './context';
|
||||
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
|
||||
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
|
||||
import {TemplateSourceManager} from './source';
|
||||
|
||||
/**
|
||||
|
@ -112,10 +112,44 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
diagnostics.push(...shimRecord.genesisDiagnostics);
|
||||
}
|
||||
|
||||
|
||||
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
|
||||
}
|
||||
|
||||
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] {
|
||||
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)!;
|
||||
|
||||
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
||||
|
||||
const diagnostics: (TemplateDiagnostic|null)[] = [];
|
||||
if (shimRecord.hasInlines) {
|
||||
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
|
||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||
}
|
||||
|
||||
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
|
||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||
diagnostics.push(...shimRecord.genesisDiagnostics);
|
||||
|
||||
return diagnostics.filter(
|
||||
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
|
||||
diag !== null && diag.templateId === templateId);
|
||||
}
|
||||
|
||||
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null {
|
||||
this.ensureAllShimsForOneFile(component.getSourceFile());
|
||||
|
||||
|
@ -219,6 +253,28 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
this.updateFromContext(ctx);
|
||||
}
|
||||
|
||||
private ensureShimForComponent(component: ts.ClassDeclaration): void {
|
||||
const sf = component.getSourceFile();
|
||||
const sfPath = absoluteFromSourceFile(sf);
|
||||
|
||||
this.maybeAdoptPriorResultsForFile(sf);
|
||||
|
||||
const fileData = this.getFileData(sfPath);
|
||||
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
||||
|
||||
if (fileData.shimData.has(shimPath)) {
|
||||
// All data for this component is available.
|
||||
return;
|
||||
}
|
||||
|
||||
const host =
|
||||
new SingleShimTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this, shimPath);
|
||||
const ctx = this.newContext(host);
|
||||
|
||||
this.typeCheckAdapter.typeCheck(sf, ctx);
|
||||
this.updateFromContext(ctx);
|
||||
}
|
||||
|
||||
private newContext(host: TypeCheckingHost): TypeCheckContextImpl {
|
||||
const inlining = this.typeCheckingStrategy.supportsInlineOperations ? InliningMode.InlineOps :
|
||||
InliningMode.Error;
|
||||
|
@ -272,7 +328,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
}
|
||||
|
||||
function convertDiagnostic(
|
||||
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): ts.Diagnostic|null {
|
||||
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): TemplateDiagnostic|null {
|
||||
if (!shouldReportDiagnostic(diag)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -367,8 +423,8 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost {
|
|||
private seenInlines = false;
|
||||
|
||||
constructor(
|
||||
private sfPath: AbsoluteFsPath, private fileData: FileTypeCheckingData,
|
||||
private strategy: TypeCheckingProgramStrategy, private impl: TemplateTypeCheckerImpl) {}
|
||||
protected sfPath: AbsoluteFsPath, protected fileData: FileTypeCheckingData,
|
||||
protected strategy: TypeCheckingProgramStrategy, protected impl: TemplateTypeCheckerImpl) {}
|
||||
|
||||
private assertPath(sfPath: AbsoluteFsPath): void {
|
||||
if (this.sfPath !== sfPath) {
|
||||
|
@ -431,3 +487,30 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost {
|
|||
this.fileData.isComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drives a `TypeCheckContext` to generate type-checking code efficiently for only those components
|
||||
* which map to a single shim of a single input file.
|
||||
*/
|
||||
class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost {
|
||||
constructor(
|
||||
sfPath: AbsoluteFsPath, fileData: FileTypeCheckingData, strategy: TypeCheckingProgramStrategy,
|
||||
impl: TemplateTypeCheckerImpl, private shimPath: AbsoluteFsPath) {
|
||||
super(sfPath, fileData, strategy, impl);
|
||||
}
|
||||
|
||||
shouldCheckNode(node: ts.ClassDeclaration): boolean {
|
||||
if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only generate a TCB for the component if it maps to the requested shim file.
|
||||
const shimPath = this.strategy.shimPathForComponent(node);
|
||||
if (shimPath !== this.shimPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only need to generate a TCB for the class if no shim exists for it currently.
|
||||
return !this.fileData.shimData.has(shimPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -318,5 +318,30 @@ runInEachFileSystem(() => {
|
|||
expect(currentTcb).toBe(originalTcb);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow get diagnostics for a single component', () => {
|
||||
const fileName = absoluteFrom('/main.ts');
|
||||
|
||||
const {program, templateTypeChecker} = setup([{
|
||||
fileName,
|
||||
templates: {
|
||||
'Cmp1': '<invalid-element-a></invalid-element-a>',
|
||||
'Cmp2': '<invalid-element-b></invalid-element-b>'
|
||||
},
|
||||
}]);
|
||||
const sf = getSourceFileOrError(program, fileName);
|
||||
const cmp1 = getClass(sf, 'Cmp1');
|
||||
const cmp2 = getClass(sf, 'Cmp2');
|
||||
|
||||
const diags1 = templateTypeChecker.getDiagnosticsForComponent(cmp1);
|
||||
expect(diags1.length).toBe(1);
|
||||
expect(diags1[0].messageText).toContain('invalid-element-a');
|
||||
expect(diags1[0].messageText).not.toContain('invalid-element-b');
|
||||
|
||||
const diags2 = templateTypeChecker.getDiagnosticsForComponent(cmp2);
|
||||
expect(diags2.length).toBe(1);
|
||||
expect(diags2[0].messageText).toContain('invalid-element-b');
|
||||
expect(diags2[0].messageText).not.toContain('invalid-element-a');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue