Skip to content

Commit ce00738

Browse files
atscottpkozlowski-opensource
authored andcommitted
fix(compiler-cli): catch fatal diagnostic when getting diagnostics for components (#50046)
This commit adds similar handling to what was done in ed817e3. The language service calls the `getDiagnosticsForComponent` function when the file is not a typescript file. fixes angular/vscode-ng-language-service#1881 PR Close #50046
1 parent c650b40 commit ce00738

File tree

5 files changed

+66
-12
lines changed

5 files changed

+66
-12
lines changed

packages/compiler-cli/src/ngtsc/core/src/compiler.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -448,11 +448,18 @@ export class NgCompiler {
448448
const compilation = this.ensureAnalyzed();
449449
const ttc = compilation.templateTypeChecker;
450450
const diagnostics: ts.Diagnostic[] = [];
451-
diagnostics.push(...ttc.getDiagnosticsForComponent(component));
451+
try {
452+
diagnostics.push(...ttc.getDiagnosticsForComponent(component));
452453

453-
const extendedTemplateChecker = compilation.extendedTemplateChecker;
454-
if (this.options.strictTemplates && extendedTemplateChecker) {
455-
diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component));
454+
const extendedTemplateChecker = compilation.extendedTemplateChecker;
455+
if (this.options.strictTemplates && extendedTemplateChecker) {
456+
diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component));
457+
}
458+
} catch (err: unknown) {
459+
if (!(err instanceof FatalDiagnosticError)) {
460+
throw err;
461+
}
462+
diagnostics.push(err.toDiagnostic());
456463
}
457464
return this.addMessageTextDetails(diagnostics);
458465
}

packages/language-service/test/diagnostic_spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,49 @@ describe('getSemanticDiagnostics', () => {
492492
const diags = project.getDiagnosticsForFile('app.html');
493493
expect(diags.length).toEqual(0);
494494
});
495+
496+
it('generates diagnostic when the library does not export the host directive', () => {
497+
const files = {
498+
// export post module and component but not the host directive. This is not valid. We won't
499+
// be able to import the host directive for template type checking.
500+
'dist/post/index.d.ts': `
501+
export { PostComponent, PostModule } from './lib/post.component';
502+
`,
503+
'dist/post/lib/post.component.d.ts': `
504+
import * as i0 from "@angular/core";
505+
export declare class HostBindDirective {
506+
static ɵdir: i0.ɵɵDirectiveDeclaration<HostBindDirective, never, never, {}, {}, never, never, true, never>;
507+
}
508+
export declare class PostComponent {
509+
static ɵcmp: i0.ɵɵComponentDeclaration<PostComponent, "lib-post", never, {}, {}, never, never, false, [{ directive: typeof HostBindDirective; inputs: {}; outputs: {}; }]>;
510+
}
511+
export declare class PostModule {
512+
static ɵmod: i0.ɵɵNgModuleDeclaration<PostModule, [typeof PostComponent], never, [typeof PostComponent]>;
513+
static ɵinj: i0.ɵɵInjectorDeclaration<PostModule>;
514+
}
515+
`,
516+
'test.ts': `
517+
import {Component} from '@angular/core';
518+
import {PostModule} from 'post';
519+
520+
@Component({
521+
templateUrl: './test.ng.html',
522+
imports: [PostModule],
523+
standalone: true,
524+
})
525+
export class Main { }
526+
`,
527+
'test.ng.html': '<lib-post />'
528+
};
529+
530+
const tsCompilerOptions = {paths: {'post': ['dist/post']}};
531+
const project = env.addProject('test', files, {}, tsCompilerOptions);
532+
533+
const diags = project.getDiagnosticsForFile('test.ng.html');
534+
expect(diags.length).toBe(1);
535+
expect(ts.flattenDiagnosticMessageText(diags[0].messageText, ''))
536+
.toContain('HostBindDirective');
537+
});
495538
});
496539

497540
function getTextOfDiagnostic(diag: ts.Diagnostic): string {

packages/language-service/testing/src/env.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ export class LanguageServiceTestEnv {
4848

4949
constructor(private host: MockServerHost, private projectService: ts.server.ProjectService) {}
5050

51-
addProject(name: string, files: ProjectFiles, options: TestableOptions = {}): Project {
51+
addProject(
52+
name: string, files: ProjectFiles, angularCompilerOptions: TestableOptions = {},
53+
tsCompilerOptions = {}): Project {
5254
if (this.projects.has(name)) {
5355
throw new Error(`Project ${name} is already defined`);
5456
}
5557

56-
const project = Project.initialize(name, this.projectService, files, options);
58+
const project = Project.initialize(
59+
name, this.projectService, files, angularCompilerOptions, tsCompilerOptions);
5760
this.projects.set(name, project);
5861
return project;
5962
}

packages/language-service/testing/src/project.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type ProjectFiles = {
2121

2222
function writeTsconfig(
2323
fs: FileSystem, tsConfigPath: AbsoluteFsPath, entryFiles: AbsoluteFsPath[],
24-
options: TestableOptions): void {
24+
angularCompilerOptions: TestableOptions, tsCompilerOptions: {}): void {
2525
fs.writeFile(
2626
tsConfigPath,
2727
JSON.stringify(
@@ -36,11 +36,12 @@ function writeTsconfig(
3636
'dom',
3737
'es2015',
3838
],
39+
...tsCompilerOptions,
3940
},
4041
files: entryFiles,
4142
angularCompilerOptions: {
4243
strictTemplates: true,
43-
...options,
44+
...angularCompilerOptions,
4445
}
4546
},
4647
null, 2));
@@ -57,7 +58,7 @@ export class Project {
5758

5859
static initialize(
5960
projectName: string, projectService: ts.server.ProjectService, files: ProjectFiles,
60-
options: TestableOptions = {}): Project {
61+
angularCompilerOptions: TestableOptions = {}, tsCompilerOptions = {}): Project {
6162
const fs = getFileSystem();
6263
const tsConfigPath = absoluteFrom(`/${projectName}/tsconfig.json`);
6364

@@ -73,7 +74,7 @@ export class Project {
7374
}
7475
}
7576

76-
writeTsconfig(fs, tsConfigPath, entryFiles, options);
77+
writeTsconfig(fs, tsConfigPath, entryFiles, angularCompilerOptions, tsCompilerOptions);
7778

7879
// Ensure the project is live in the ProjectService.
7980
projectService.openClientFile(entryFiles[0]);

packages/language-service/testing/src/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function getFirstClassDeclaration(declaration: string) {
4949

5050
export function createModuleAndProjectWithDeclarations(
5151
env: LanguageServiceTestEnv, projectName: string, projectFiles: ProjectFiles,
52-
options: TestableOptions = {}, standaloneFiles: ProjectFiles = {}): Project {
52+
angularCompilerOptions: TestableOptions = {}, standaloneFiles: ProjectFiles = {}): Project {
5353
const externalClasses: string[] = [];
5454
const externalImports: string[] = [];
5555
for (const [fileName, fileContents] of Object.entries(projectFiles)) {
@@ -72,7 +72,7 @@ export function createModuleAndProjectWithDeclarations(
7272
export class AppModule {}
7373
`;
7474
projectFiles['app-module.ts'] = moduleContents;
75-
return env.addProject(projectName, {...projectFiles, ...standaloneFiles}, options);
75+
return env.addProject(projectName, {...projectFiles, ...standaloneFiles}, angularCompilerOptions);
7676
}
7777

7878
export function humanizeDocumentSpanLike<T extends ts.DocumentSpan>(

0 commit comments

Comments
 (0)