diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index 460ec49ab86ea..6c7ad8bc88484 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -448,11 +448,18 @@ export class NgCompiler { const compilation = this.ensureAnalyzed(); const ttc = compilation.templateTypeChecker; const diagnostics: ts.Diagnostic[] = []; - diagnostics.push(...ttc.getDiagnosticsForComponent(component)); + try { + diagnostics.push(...ttc.getDiagnosticsForComponent(component)); - const extendedTemplateChecker = compilation.extendedTemplateChecker; - if (this.options.strictTemplates && extendedTemplateChecker) { - diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component)); + const extendedTemplateChecker = compilation.extendedTemplateChecker; + if (this.options.strictTemplates && extendedTemplateChecker) { + diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component)); + } + } catch (err: unknown) { + if (!(err instanceof FatalDiagnosticError)) { + throw err; + } + diagnostics.push(err.toDiagnostic()); } return this.addMessageTextDetails(diagnostics); } diff --git a/packages/language-service/test/diagnostic_spec.ts b/packages/language-service/test/diagnostic_spec.ts index 11d032449a1fe..8c8cd73fa7565 100644 --- a/packages/language-service/test/diagnostic_spec.ts +++ b/packages/language-service/test/diagnostic_spec.ts @@ -492,6 +492,49 @@ describe('getSemanticDiagnostics', () => { const diags = project.getDiagnosticsForFile('app.html'); expect(diags.length).toEqual(0); }); + + it('generates diagnostic when the library does not export the host directive', () => { + const files = { + // 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. + 'dist/post/index.d.ts': ` + export { PostComponent, PostModule } from './lib/post.component'; + `, + '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; + } + `, + 'test.ts': ` + import {Component} from '@angular/core'; + import {PostModule} from 'post'; + + @Component({ + templateUrl: './test.ng.html', + imports: [PostModule], + standalone: true, + }) + export class Main { } + `, + 'test.ng.html': '' + }; + + const tsCompilerOptions = {paths: {'post': ['dist/post']}}; + const project = env.addProject('test', files, {}, tsCompilerOptions); + + const diags = project.getDiagnosticsForFile('test.ng.html'); + expect(diags.length).toBe(1); + expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '')) + .toContain('HostBindDirective'); + }); }); function getTextOfDiagnostic(diag: ts.Diagnostic): string { diff --git a/packages/language-service/testing/src/env.ts b/packages/language-service/testing/src/env.ts index 6485a82d539f9..4d3d1b31b765c 100644 --- a/packages/language-service/testing/src/env.ts +++ b/packages/language-service/testing/src/env.ts @@ -48,12 +48,15 @@ export class LanguageServiceTestEnv { constructor(private host: MockServerHost, private projectService: ts.server.ProjectService) {} - addProject(name: string, files: ProjectFiles, options: TestableOptions = {}): Project { + addProject( + name: string, files: ProjectFiles, angularCompilerOptions: TestableOptions = {}, + tsCompilerOptions = {}): Project { if (this.projects.has(name)) { throw new Error(`Project ${name} is already defined`); } - const project = Project.initialize(name, this.projectService, files, options); + const project = Project.initialize( + name, this.projectService, files, angularCompilerOptions, tsCompilerOptions); this.projects.set(name, project); return project; } diff --git a/packages/language-service/testing/src/project.ts b/packages/language-service/testing/src/project.ts index 5e84317c0fcb0..228ba36fc21e4 100644 --- a/packages/language-service/testing/src/project.ts +++ b/packages/language-service/testing/src/project.ts @@ -21,7 +21,7 @@ export type ProjectFiles = { function writeTsconfig( fs: FileSystem, tsConfigPath: AbsoluteFsPath, entryFiles: AbsoluteFsPath[], - options: TestableOptions): void { + angularCompilerOptions: TestableOptions, tsCompilerOptions: {}): void { fs.writeFile( tsConfigPath, JSON.stringify( @@ -36,11 +36,12 @@ function writeTsconfig( 'dom', 'es2015', ], + ...tsCompilerOptions, }, files: entryFiles, angularCompilerOptions: { strictTemplates: true, - ...options, + ...angularCompilerOptions, } }, null, 2)); @@ -57,7 +58,7 @@ export class Project { static initialize( projectName: string, projectService: ts.server.ProjectService, files: ProjectFiles, - options: TestableOptions = {}): Project { + angularCompilerOptions: TestableOptions = {}, tsCompilerOptions = {}): Project { const fs = getFileSystem(); const tsConfigPath = absoluteFrom(`/${projectName}/tsconfig.json`); @@ -73,7 +74,7 @@ export class Project { } } - writeTsconfig(fs, tsConfigPath, entryFiles, options); + writeTsconfig(fs, tsConfigPath, entryFiles, angularCompilerOptions, tsCompilerOptions); // Ensure the project is live in the ProjectService. projectService.openClientFile(entryFiles[0]); diff --git a/packages/language-service/testing/src/util.ts b/packages/language-service/testing/src/util.ts index 3d345e0479d4f..d6bb74ff3a2ca 100644 --- a/packages/language-service/testing/src/util.ts +++ b/packages/language-service/testing/src/util.ts @@ -49,7 +49,7 @@ function getFirstClassDeclaration(declaration: string) { export function createModuleAndProjectWithDeclarations( env: LanguageServiceTestEnv, projectName: string, projectFiles: ProjectFiles, - options: TestableOptions = {}, standaloneFiles: ProjectFiles = {}): Project { + angularCompilerOptions: TestableOptions = {}, standaloneFiles: ProjectFiles = {}): Project { const externalClasses: string[] = []; const externalImports: string[] = []; for (const [fileName, fileContents] of Object.entries(projectFiles)) { @@ -72,7 +72,7 @@ export function createModuleAndProjectWithDeclarations( export class AppModule {} `; projectFiles['app-module.ts'] = moduleContents; - return env.addProject(projectName, {...projectFiles, ...standaloneFiles}, options); + return env.addProject(projectName, {...projectFiles, ...standaloneFiles}, angularCompilerOptions); } export function humanizeDocumentSpanLike(