diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
index 793dc4e090..32a804c4e7 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
@@ -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.
*
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts
index 4c5dccfd74..defdc7f705 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts
@@ -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);
+ }
+}
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 3e1763319a..63f7a8148f 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
@@ -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': '',
+ 'Cmp2': ''
+ },
+ }]);
+ 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');
+ });
});
});