diff --git a/packages/language-service/api.ts b/packages/language-service/api.ts index d493b6328fd..f8f377713b6 100644 --- a/packages/language-service/api.ts +++ b/packages/language-service/api.ts @@ -104,6 +104,14 @@ export interface LinkedEditingRanges { * whose API surface is a strict superset of TypeScript's language service. */ export interface NgLanguageService extends ts.LanguageService { + /** + * Triggers the Angular compiler's analysis pipeline without performing + * per-file type checking. This is a lighter alternative to calling + * `getSemanticDiagnostics()` when the goal is only to ensure that the + * Angular project has been analyzed (e.g. during project initialization). + */ + ensureProjectAnalyzed(): void; + getTcb(fileName: string, position: number): GetTcbResponse | undefined; /** diff --git a/packages/language-service/src/language_service.ts b/packages/language-service/src/language_service.ts index 80d0c2b7f5f..b06d00bc9ed 100644 --- a/packages/language-service/src/language_service.ts +++ b/packages/language-service/src/language_service.ts @@ -101,6 +101,18 @@ export class LanguageService { return this.options; } + /** + * Triggers the Angular compiler's analysis pipeline without performing + * per-file type checking. + */ + ensureProjectAnalyzed(): void { + this.withCompilerAndPerfTracing(PerfPhase.LsDiagnostics, (compiler) => { + // Accessing the template type checker forces compiler analysis through + // public API without requiring per-file diagnostics computation. + compiler.getTemplateTypeChecker(); + }); + } + getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { return this.withCompilerAndPerfTracing(PerfPhase.LsDiagnostics, (compiler) => { let diagnostics: ts.Diagnostic[] = []; diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index 5fd6217fb1b..6729856d0ee 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -346,8 +346,13 @@ export function create(info: ts.server.PluginCreateInfo): NgLanguageService { return undefined; } + function ensureProjectAnalyzed(): void { + ngLS.ensureProjectAnalyzed(); + } + return { ...tsLS, + ensureProjectAnalyzed, getSyntacticDiagnostics, getSemanticDiagnostics, getSuggestionDiagnostics, diff --git a/packages/language-service/test/legacy/ts_plugin_spec.ts b/packages/language-service/test/legacy/ts_plugin_spec.ts index ada7fc4f410..6840f4e4927 100644 --- a/packages/language-service/test/legacy/ts_plugin_spec.ts +++ b/packages/language-service/test/legacy/ts_plugin_spec.ts @@ -31,4 +31,26 @@ describe('getExternalFiles()', () => { expect(externalFiles?.length).toBe(3); expect(externalFiles?.[0].endsWith('app.component.ngtypecheck.ts')).toBeTrue(); }); + + it('should return all typecheck files when using ensureProjectAnalyzed', () => { + const {project, tsLS} = setup(); + const plugin = initialize({typescript: ts}); + + let externalFiles = plugin.getExternalFiles?.(project, ts.ProgramUpdateLevel.Full); + expect(externalFiles).toEqual([]); + // Trigger compilation using the lighter ensureProjectAnalyzed() method + // instead of getSemanticDiagnostics(). This initializes the Angular compiler + // (analysis + resolution) without per-file type-checking overhead. + const ngLS = new LanguageService(project, tsLS, {}); + ngLS.ensureProjectAnalyzed(); + // After ensureProjectAnalyzed(), the Angular compiler state is initialized. + // Typecheck files are created lazily during diagnostics, so they don't exist yet. + // But subsequent getSemanticDiagnostics() calls should work correctly since + // the compiler is already analyzed. + ngLS.getSemanticDiagnostics(APP_COMPONENT); + externalFiles = plugin.getExternalFiles?.(project, ts.ProgramUpdateLevel.Full); + // Includes 1 typecheck file, 1 template, and 1 css files + expect(externalFiles?.length).toBe(3); + expect(externalFiles?.[0].endsWith('app.component.ngtypecheck.ts')).toBeTrue(); + }); }); diff --git a/vscode-ng-language-service/server/src/session.ts b/vscode-ng-language-service/server/src/session.ts index 6374e718abd..38673ad2461 100644 --- a/vscode-ng-language-service/server/src/session.ts +++ b/vscode-ng-language-service/server/src/session.ts @@ -292,13 +292,19 @@ export class Session { if (!project.hasRoots()) { return; } - const fileName = project.getRootScriptInfos()[0].fileName; - const label = `Global analysis - getSemanticDiagnostics for ${fileName}`; + const languageService = project.getLanguageService(); + if (!isNgLanguageService(languageService)) { + return; + } + const label = `Global analysis - ensureProjectAnalyzed for ${project.getProjectName()}`; if (isDebugMode) { console.time(label); } - // Getting semantic diagnostics will trigger a global analysis. - project.getLanguageService().getSemanticDiagnostics(fileName); + // Trigger Angular compilation without per-file type checking overhead. + // Previously this used getSemanticDiagnostics() which also ran per-file + // TypeScript type checking on the first root file — wasted work since those + // results were never consumed. + languageService.ensureProjectAnalyzed(); if (isDebugMode) { console.timeEnd(label); }