diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index ad8b795dded..d2d8eab5b86 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -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(); diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts b/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts index 0c515f8a252..164f94b3509 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts @@ -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; diff --git a/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts b/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts index 07af668366e..1be25a59826 100644 --- a/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts +++ b/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts @@ -71,8 +71,9 @@ export interface SimpleChanges { } export type ɵɵNgModuleDeclaration = unknown; -export type ɵɵDirectiveDeclaration = - unknown; +export type ɵɵDirectiveDeclaration< + DirT, SelectorT, ExportAsT, InputsT, OutputsT, QueriesT, A = never, B = never, + HostDirectivesT = never> = unknown; export type ɵɵPipeDeclaration = unknown; export enum ViewEncapsulation { diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index 1b39ccf51b1..03fd34b0deb 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -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 ?? [])); } } } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/trait.ts b/packages/compiler-cli/src/ngtsc/transform/src/trait.ts index 83c171619be..958dd0ccbeb 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/trait.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/trait.ts @@ -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 { resolution: Readonly|null = null; analysisDiagnostics: ts.Diagnostic[]|null = null; resolveDiagnostics: ts.Diagnostic[]|null = null; + typeCheckDiagnostics: ts.Diagnostic[]|null = null; constructor(handler: DecoratorHandler, detected: DetectResult) { this.handler = handler; @@ -220,6 +223,7 @@ class TraitImpl { this.resolution = resolution; this.state = TraitState.Resolved; this.resolveDiagnostics = diagnostics; + this.typeCheckDiagnostics = null; return this as ResolvedTrait; } diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index 080a445ba4c..ad068bb12ce 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -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; + } + export declare class PostComponent { + static ɵcmp: i0.ɵɵComponentDeclaration; + } + export declare class PostModule { + static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; + } + `); + env.write('test.ts', ` + import {Component} from '@angular/core'; + import {PostModule} from 'post'; + + @Component({ + template: '', + imports: [PostModule], + standalone: true, + }) + export class Main { } + `); + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '')) + .toContain('HostBindDirective'); + }); }); }); });