Skip to content

Commit 6b7811f

Browse files
alxhubprofanis
authored andcommitted
refactor(compiler-cli): support type-checking a single component (angular#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 angular#38105
1 parent 22fdf96 commit 6b7811f

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-5
lines changed

packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ export interface TemplateTypeChecker {
5454
*/
5555
getDiagnosticsForFile(sf: ts.SourceFile, optimizeFor: OptimizeFor): ts.Diagnostic[];
5656

57+
/**
58+
* Get all `ts.Diagnostic`s currently available that pertain to the given component.
59+
*
60+
* This method always runs in `OptimizeFor.SingleFile` mode.
61+
*/
62+
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[];
63+
5764
/**
5865
* Retrieve the top-level node representing the TCB for the given component.
5966
*

packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {getSourceFileOrNull} from '../../util/src/typescript';
1818
import {OptimizeFor, ProgramTypeCheckAdapter, TemplateId, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
1919

2020
import {InliningMode, ShimTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from './context';
21-
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
21+
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
2222
import {TemplateSourceManager} from './source';
2323

2424
/**
@@ -112,10 +112,44 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
112112
diagnostics.push(...shimRecord.genesisDiagnostics);
113113
}
114114

115-
116115
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
117116
}
118117

118+
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] {
119+
this.ensureShimForComponent(component);
120+
121+
const sf = component.getSourceFile();
122+
const sfPath = absoluteFromSourceFile(sf);
123+
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
124+
125+
const fileRecord = this.getFileData(sfPath);
126+
127+
if (!fileRecord.shimData.has(shimPath)) {
128+
return [];
129+
}
130+
131+
const templateId = fileRecord.sourceManager.getTemplateId(component);
132+
const shimRecord = fileRecord.shimData.get(shimPath)!;
133+
134+
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
135+
136+
const diagnostics: (TemplateDiagnostic|null)[] = [];
137+
if (shimRecord.hasInlines) {
138+
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
139+
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
140+
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
141+
}
142+
143+
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
144+
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
145+
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
146+
diagnostics.push(...shimRecord.genesisDiagnostics);
147+
148+
return diagnostics.filter(
149+
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
150+
diag !== null && diag.templateId === templateId);
151+
}
152+
119153
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null {
120154
this.ensureAllShimsForOneFile(component.getSourceFile());
121155

@@ -219,6 +253,28 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
219253
this.updateFromContext(ctx);
220254
}
221255

256+
private ensureShimForComponent(component: ts.ClassDeclaration): void {
257+
const sf = component.getSourceFile();
258+
const sfPath = absoluteFromSourceFile(sf);
259+
260+
this.maybeAdoptPriorResultsForFile(sf);
261+
262+
const fileData = this.getFileData(sfPath);
263+
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
264+
265+
if (fileData.shimData.has(shimPath)) {
266+
// All data for this component is available.
267+
return;
268+
}
269+
270+
const host =
271+
new SingleShimTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this, shimPath);
272+
const ctx = this.newContext(host);
273+
274+
this.typeCheckAdapter.typeCheck(sf, ctx);
275+
this.updateFromContext(ctx);
276+
}
277+
222278
private newContext(host: TypeCheckingHost): TypeCheckContextImpl {
223279
const inlining = this.typeCheckingStrategy.supportsInlineOperations ? InliningMode.InlineOps :
224280
InliningMode.Error;
@@ -272,7 +328,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
272328
}
273329

274330
function convertDiagnostic(
275-
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): ts.Diagnostic|null {
331+
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): TemplateDiagnostic|null {
276332
if (!shouldReportDiagnostic(diag)) {
277333
return null;
278334
}
@@ -367,8 +423,8 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost {
367423
private seenInlines = false;
368424

369425
constructor(
370-
private sfPath: AbsoluteFsPath, private fileData: FileTypeCheckingData,
371-
private strategy: TypeCheckingProgramStrategy, private impl: TemplateTypeCheckerImpl) {}
426+
protected sfPath: AbsoluteFsPath, protected fileData: FileTypeCheckingData,
427+
protected strategy: TypeCheckingProgramStrategy, protected impl: TemplateTypeCheckerImpl) {}
372428

373429
private assertPath(sfPath: AbsoluteFsPath): void {
374430
if (this.sfPath !== sfPath) {
@@ -431,3 +487,30 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost {
431487
this.fileData.isComplete = true;
432488
}
433489
}
490+
491+
/**
492+
* Drives a `TypeCheckContext` to generate type-checking code efficiently for only those components
493+
* which map to a single shim of a single input file.
494+
*/
495+
class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost {
496+
constructor(
497+
sfPath: AbsoluteFsPath, fileData: FileTypeCheckingData, strategy: TypeCheckingProgramStrategy,
498+
impl: TemplateTypeCheckerImpl, private shimPath: AbsoluteFsPath) {
499+
super(sfPath, fileData, strategy, impl);
500+
}
501+
502+
shouldCheckNode(node: ts.ClassDeclaration): boolean {
503+
if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
504+
return false;
505+
}
506+
507+
// Only generate a TCB for the component if it maps to the requested shim file.
508+
const shimPath = this.strategy.shimPathForComponent(node);
509+
if (shimPath !== this.shimPath) {
510+
return false;
511+
}
512+
513+
// Only need to generate a TCB for the class if no shim exists for it currently.
514+
return !this.fileData.shimData.has(shimPath);
515+
}
516+
}

packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,5 +318,30 @@ runInEachFileSystem(() => {
318318
expect(currentTcb).toBe(originalTcb);
319319
});
320320
});
321+
322+
it('should allow get diagnostics for a single component', () => {
323+
const fileName = absoluteFrom('/main.ts');
324+
325+
const {program, templateTypeChecker} = setup([{
326+
fileName,
327+
templates: {
328+
'Cmp1': '<invalid-element-a></invalid-element-a>',
329+
'Cmp2': '<invalid-element-b></invalid-element-b>'
330+
},
331+
}]);
332+
const sf = getSourceFileOrError(program, fileName);
333+
const cmp1 = getClass(sf, 'Cmp1');
334+
const cmp2 = getClass(sf, 'Cmp2');
335+
336+
const diags1 = templateTypeChecker.getDiagnosticsForComponent(cmp1);
337+
expect(diags1.length).toBe(1);
338+
expect(diags1[0].messageText).toContain('invalid-element-a');
339+
expect(diags1[0].messageText).not.toContain('invalid-element-b');
340+
341+
const diags2 = templateTypeChecker.getDiagnosticsForComponent(cmp2);
342+
expect(diags2.length).toBe(1);
343+
expect(diags2[0].messageText).toContain('invalid-element-b');
344+
expect(diags2[0].messageText).not.toContain('invalid-element-a');
345+
});
321346
});
322347
});

0 commit comments

Comments
 (0)