perf(language-service): use lightweight project warmup for Angular analysis

avoid per-file semantic diagnostics work when warming up a newly loaded project.
add ensureProjectAnalyzed() to the language-service API and use it from the server startup path.
implement warmup through public compiler API access with existing perf tracing, and add legacy test coverage for the new warmup flow.
This commit is contained in:
kbrilla 2026-02-19 22:36:06 +01:00 committed by Jessica Janiuk
parent 550b3ba01b
commit 39f62fa408
5 changed files with 57 additions and 4 deletions

View file

@ -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;
/**

View file

@ -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[] = [];

View file

@ -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,

View file

@ -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();
});
});

View file

@ -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);
}