From 8fcadaad48b2b1328f47b7603b230445a26f95a8 Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 18 Sep 2022 15:12:37 +0200 Subject: [PATCH] perf(compiler-cli): cache source file for reporting type-checking diagnostics (#47471) When reporting type-checking diagnostics in external templates we create a `ts.SourceFile` of the template text, as this is needed to report Angular template diagnostics using TypeScript's diagnostics infrastructure. Each reported diagnostic would create its own `ts.SourceFile`, resulting in repeatedly parsing of the template text and potentially high memory usage if the template is large and there are many diagnostics reported. This commit caches the parsed template in the template mapping, such that all reported diagnostics get to reuse the same `ts.SourceFile`. Closes #47470 PR Close #47471 --- .../typecheck/diagnostics/src/diagnostic.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts b/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts index d91104e3c59cb..af34a62f66a9c 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts @@ -10,7 +10,7 @@ import {ParseSourceSpan} from '@angular/compiler'; import ts from 'typescript'; import {addDiagnosticChain, makeDiagnosticChain} from '../../../diagnostics'; -import {ExternalTemplateSourceMapping, TemplateDiagnostic, TemplateId, TemplateSourceMapping} from '../../api'; +import {ExternalTemplateSourceMapping, IndirectTemplateSourceMapping, TemplateDiagnostic, TemplateId, TemplateSourceMapping} from '../../api'; /** * Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template. @@ -82,7 +82,7 @@ export function makeTemplateDiagnostic( let sf: ts.SourceFile; try { - sf = parseTemplateAsSourceFile(fileName, mapping.template); + sf = getParsedTemplateSourceFile(fileName, mapping); } catch (e) { const failureChain = makeDiagnosticChain( `Failed to report an error in '${fileName}' at ${span.start.line + 1}:${ @@ -135,6 +135,22 @@ export function makeTemplateDiagnostic( } } +const TemplateSourceFile = Symbol('TemplateSourceFile'); + +type TemplateSourceMappingWithSourceFile = + (ExternalTemplateSourceMapping|IndirectTemplateSourceMapping)&{ + [TemplateSourceFile]?: ts.SourceFile; +}; + +function getParsedTemplateSourceFile( + fileName: string, mapping: TemplateSourceMappingWithSourceFile): ts.SourceFile { + if (mapping[TemplateSourceFile] === undefined) { + mapping[TemplateSourceFile] = parseTemplateAsSourceFile(fileName, mapping.template); + } + + return mapping[TemplateSourceFile]; +} + let parseTemplateAsSourceFileForTest: typeof parseTemplateAsSourceFile|null = null; export function setParseTemplateAsSourceFileForTest(fn: typeof parseTemplateAsSourceFile): void { @@ -152,7 +168,6 @@ function parseTemplateAsSourceFile(fileName: string, template: string): ts.Sourc // TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS // parser against the template (HTML is just really syntactically invalid TypeScript code ;). - // Also investigate caching the file to avoid running the parser multiple times. return ts.createSourceFile( fileName, template, ts.ScriptTarget.Latest, /* setParentNodes */ false, ts.ScriptKind.JSX); }