Skip to content

Commit

Permalink
perf(compiler-cli): cache source file for reporting type-checking dia…
Browse files Browse the repository at this point in the history
…gnostics

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
  • Loading branch information
JoostK committed Sep 20, 2022
1 parent 59aa2c0 commit e73ea93
Showing 1 changed file with 18 additions and 3 deletions.
Expand Up @@ -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.
Expand Down Expand Up @@ -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}:${
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down

0 comments on commit e73ea93

Please sign in to comment.