fix(compiler-cli): Catch FatalDiagnosticError during template type checking (#49527)

This commit updates the type checking operation to catch
`FatalDiagnosticError` and surface them as diagnostics rather than
crashing.

Fixes https://github.com/angular/vscode-ng-language-service/issues/1881

PR Close #49527
This commit is contained in:
Andrew Scott 2023-03-21 14:04:21 -07:00 committed by Andrew Kushnir
parent 079f4bc1ef
commit 8a75a8ad26
6 changed files with 71 additions and 8 deletions

View file

@ -11,7 +11,7 @@ import ts from 'typescript';
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from '../../annotations';
import {InjectableClassRegistry} from '../../annotations/common';
import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../cycles';
import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, ngErrorCode} from '../../diagnostics';
import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics';
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
import {absoluteFromSourceFile, AbsoluteFsPath, LogicalFileSystem, resolve} from '../../file_system';
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
@ -831,8 +831,15 @@ export class NgCompiler {
continue;
}
diagnostics.push(
...compilation.templateTypeChecker.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram));
try {
diagnostics.push(
...compilation.templateTypeChecker.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram));
} catch (err) {
if (!(err instanceof FatalDiagnosticError)) {
throw err;
}
diagnostics.push(err.toDiagnostic());
}
}
const program = this.programDriver.getProgram();
@ -849,7 +856,14 @@ export class NgCompiler {
// Get the diagnostics.
const diagnostics: ts.Diagnostic[] = [];
if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
try {
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
} catch (err) {
if (!(err instanceof FatalDiagnosticError)) {
throw err;
}
diagnostics.push(err.toDiagnostic());
}
}
const program = this.programDriver.getProgram();

View file

@ -99,6 +99,7 @@ export class DtsMetadataReader implements MetadataReader {
const inputs = ClassPropertyMapping.fromMappedObject(readInputsType(def.type.typeArguments[3]));
const outputs = ClassPropertyMapping.fromMappedObject(
readMapType(def.type.typeArguments[4], readStringType));
const hostDirectives = def.type.typeArguments.length > 8 ?
readHostDirectivesType(this.checker, def.type.typeArguments[8], ref.bestGuessOwningModule) :
null;

View file

@ -71,8 +71,9 @@ export interface SimpleChanges {
}
export type ɵɵNgModuleDeclaration<ModuleT, DeclarationsT, ImportsT, ExportsT> = unknown;
export type ɵɵDirectiveDeclaration<DirT, SelectorT, ExportAsT, InputsT, OutputsT, QueriesT> =
unknown;
export type ɵɵDirectiveDeclaration<
DirT, SelectorT, ExportAsT, InputsT, OutputsT, QueriesT, A = never, B = never,
HostDirectivesT = never> = unknown;
export type ɵɵPipeDeclaration<PipeT, NameT> = unknown;
export enum ViewEncapsulation {

View file

@ -654,8 +654,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
trait.analysisDiagnostics !== null) {
diagnostics.push(...trait.analysisDiagnostics);
}
if (trait.state === TraitState.Resolved && trait.resolveDiagnostics !== null) {
diagnostics.push(...trait.resolveDiagnostics);
if (trait.state === TraitState.Resolved) {
diagnostics.push(...(trait.resolveDiagnostics ?? []));
}
}
}

View file

@ -7,7 +7,9 @@
*/
import ts from 'typescript';
import {SemanticSymbol} from '../../incremental/semantic_graph';
import {DecoratorHandler, DetectResult} from './api';
export enum TraitState {
@ -194,6 +196,7 @@ class TraitImpl<D, A, S extends SemanticSymbol|null, R> {
resolution: Readonly<R>|null = null;
analysisDiagnostics: ts.Diagnostic[]|null = null;
resolveDiagnostics: ts.Diagnostic[]|null = null;
typeCheckDiagnostics: ts.Diagnostic[]|null = null;
constructor(handler: DecoratorHandler<D, A, S, R>, detected: DetectResult<D>) {
this.handler = handler;
@ -220,6 +223,7 @@ class TraitImpl<D, A, S extends SemanticSymbol|null, R> {
this.resolution = resolution;
this.state = TraitState.Resolved;
this.resolveDiagnostics = diagnostics;
this.typeCheckDiagnostics = null;
return this as ResolvedTrait<D, A, S, R>;
}

View file

@ -3072,6 +3072,49 @@ suppress
`Argument of type 'string' is not assignable to parameter of type 'number'.`
]);
});
it('generates diagnostic when the library does not export the host directive', () => {
env.tsconfig({
paths: {'post': ['dist/post']},
strictTemplates: true,
_enableTemplateTypeChecker: true,
});
// export post module and component but not the host directive. This is not valid. We won't
// be able to import the host directive for template type checking.
env.write('dist/post/index.d.ts', `
export { PostComponent, PostModule } from './lib/post.component';
`);
env.write('dist/post/lib/post.component.d.ts', `
import * as i0 from "@angular/core";
export declare class HostBindDirective {
static ɵdir: i0.ɵɵDirectiveDeclaration<HostBindDirective, never, never, {}, {}, never, never, true, never>;
}
export declare class PostComponent {
static ɵcmp: i0.ɵɵComponentDeclaration<PostComponent, "lib-post", never, {}, {}, never, never, false, [{ directive: typeof HostBindDirective; inputs: {}; outputs: {}; }]>;
}
export declare class PostModule {
static ɵmod: i0.ɵɵNgModuleDeclaration<PostModule, [typeof PostComponent], never, [typeof PostComponent]>;
static ɵinj: i0.ɵɵInjectorDeclaration<PostModule>;
}
`);
env.write('test.ts', `
import {Component} from '@angular/core';
import {PostModule} from 'post';
@Component({
template: '<lib-post />',
imports: [PostModule],
standalone: true,
})
export class Main { }
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(ts.flattenDiagnosticMessageText(diags[0].messageText, ''))
.toContain('HostBindDirective');
});
});
});
});