Skip to content

Commit 5aa0053

Browse files
authored
Use changeCompilerHostLikeToUseCache in synchronizeHostData (#48980)
* Remove unnecessary members of HostCache * Standardize on calling compiler host members in preparation for adding caching at that layer * Call changeCompilerHostLikeToUseCache to cache existence checks * Drop now-redundant HostCache * Don't make directoryExists caching contingent on createDirectory * Clear compilerHost rather than tracking state
1 parent 12ed012 commit 5aa0053

File tree

2 files changed

+62
-149
lines changed

2 files changed

+62
-149
lines changed

src/compiler/program.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ namespace ts {
282282
}
283283

284284
// directoryExists
285-
if (originalDirectoryExists && originalCreateDirectory) {
285+
if (originalDirectoryExists) {
286286
host.directoryExists = directory => {
287287
const key = toPath(directory);
288288
const value = directoryExistsCache.get(key);
@@ -291,11 +291,14 @@ namespace ts {
291291
directoryExistsCache.set(key, !!newValue);
292292
return newValue;
293293
};
294-
host.createDirectory = directory => {
295-
const key = toPath(directory);
296-
directoryExistsCache.delete(key);
297-
originalCreateDirectory.call(host, directory);
298-
};
294+
295+
if (originalCreateDirectory) {
296+
host.createDirectory = directory => {
297+
const key = toPath(directory);
298+
directoryExistsCache.delete(key);
299+
originalCreateDirectory.call(host, directory);
300+
};
301+
}
299302
}
300303

301304
return {

src/services/services.ts

Lines changed: 53 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -927,14 +927,6 @@ namespace ts {
927927

928928
/// Language Service
929929

930-
// Information about a specific host file.
931-
interface HostFileInformation {
932-
hostFileName: string;
933-
version: string;
934-
scriptSnapshot: IScriptSnapshot;
935-
scriptKind: ScriptKind;
936-
}
937-
938930
/* @internal */
939931
export interface DisplayPartsSymbolWriter extends EmitTextWriter {
940932
displayParts(): SymbolDisplayPart[];
@@ -988,82 +980,6 @@ namespace ts {
988980
return codefix.getSupportedErrorCodes();
989981
}
990982

991-
// Either it will be file name if host doesnt have file or it will be the host's file information
992-
type CachedHostFileInformation = HostFileInformation | string;
993-
994-
// Cache host information about script Should be refreshed
995-
// at each language service public entry point, since we don't know when
996-
// the set of scripts handled by the host changes.
997-
class HostCache {
998-
private fileNameToEntry: ESMap<Path, CachedHostFileInformation>;
999-
private currentDirectory: string;
1000-
1001-
constructor(private host: LanguageServiceHost, getCanonicalFileName: GetCanonicalFileName) {
1002-
// script id => script index
1003-
this.currentDirectory = host.getCurrentDirectory();
1004-
this.fileNameToEntry = new Map();
1005-
1006-
// Initialize the list with the root file names
1007-
const rootFileNames = host.getScriptFileNames();
1008-
tracing?.push(tracing.Phase.Session, "initializeHostCache", { count: rootFileNames.length });
1009-
for (const fileName of rootFileNames) {
1010-
this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName));
1011-
}
1012-
tracing?.pop();
1013-
}
1014-
1015-
private createEntry(fileName: string, path: Path) {
1016-
let entry: CachedHostFileInformation;
1017-
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
1018-
if (scriptSnapshot) {
1019-
entry = {
1020-
hostFileName: fileName,
1021-
version: this.host.getScriptVersion(fileName),
1022-
scriptSnapshot,
1023-
scriptKind: getScriptKind(fileName, this.host)
1024-
};
1025-
}
1026-
else {
1027-
entry = fileName;
1028-
}
1029-
1030-
this.fileNameToEntry.set(path, entry);
1031-
return entry;
1032-
}
1033-
1034-
public getEntryByPath(path: Path): CachedHostFileInformation | undefined {
1035-
return this.fileNameToEntry.get(path);
1036-
}
1037-
1038-
public getHostFileInformation(path: Path): HostFileInformation | undefined {
1039-
const entry = this.fileNameToEntry.get(path);
1040-
return !isString(entry) ? entry : undefined;
1041-
}
1042-
1043-
public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
1044-
const info = this.getEntryByPath(path) || this.createEntry(fileName, path);
1045-
return isString(info) ? undefined! : info; // TODO: GH#18217
1046-
}
1047-
1048-
public getRootFileNames(): string[] {
1049-
const names: string[] = [];
1050-
this.fileNameToEntry.forEach(entry => {
1051-
if (isString(entry)) {
1052-
names.push(entry);
1053-
}
1054-
else {
1055-
names.push(entry.hostFileName);
1056-
}
1057-
});
1058-
return names;
1059-
}
1060-
1061-
public getScriptSnapshot(path: Path): IScriptSnapshot {
1062-
const file = this.getHostFileInformation(path);
1063-
return (file && file.scriptSnapshot)!; // TODO: GH#18217
1064-
}
1065-
}
1066-
1067983
class SyntaxTreeCache {
1068984
// For our syntactic only features, we also keep a cache of the syntax tree for the
1069985
// currently edited file.
@@ -1367,37 +1283,17 @@ namespace ts {
13671283
lastTypesRootVersion = typeRootsVersion;
13681284
}
13691285

1286+
const rootFileNames = host.getScriptFileNames();
1287+
13701288
// Get a fresh cache of the host information
1371-
let hostCache: HostCache | undefined = new HostCache(host, getCanonicalFileName);
1372-
const rootFileNames = hostCache.getRootFileNames();
13731289
const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
13741290
const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
13751291
const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames);
13761292
const projectReferences = host.getProjectReferences?.();
13771293
let parsedCommandLines: ESMap<Path, ParsedCommandLine | false> | undefined;
1378-
const parseConfigHost: ParseConfigFileHost = {
1379-
useCaseSensitiveFileNames,
1380-
fileExists,
1381-
readFile,
1382-
readDirectory,
1383-
trace: maybeBind(host, host.trace),
1384-
getCurrentDirectory: () => currentDirectory,
1385-
onUnRecoverableConfigFileDiagnostic: noop,
1386-
};
1387-
1388-
// If the program is already up-to-date, we can reuse it
1389-
if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
1390-
return;
1391-
}
1392-
1393-
// IMPORTANT - It is critical from this moment onward that we do not check
1394-
// cancellation tokens. We are about to mutate source files from a previous program
1395-
// instance. If we cancel midway through, we may end up in an inconsistent state where
1396-
// the program points to old source files that have been invalidated because of
1397-
// incremental parsing.
13981294

13991295
// Now create a new compiler
1400-
const compilerHost: CompilerHost = {
1296+
let compilerHost: CompilerHost | undefined = {
14011297
getSourceFile: getOrCreateSourceFile,
14021298
getSourceFileByPath: getOrCreateSourceFileByPath,
14031299
getCancellationToken: () => cancellationToken,
@@ -1407,8 +1303,8 @@ namespace ts {
14071303
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
14081304
writeFile: noop,
14091305
getCurrentDirectory: () => currentDirectory,
1410-
fileExists,
1411-
readFile,
1306+
fileExists: fileName => host.fileExists(fileName),
1307+
readFile: fileName => host.readFile && host.readFile(fileName),
14121308
getSymlinkCache: maybeBind(host, host.getSymlinkCache),
14131309
realpath: maybeBind(host, host.realpath),
14141310
directoryExists: directoryName => {
@@ -1417,20 +1313,54 @@ namespace ts {
14171313
getDirectories: path => {
14181314
return host.getDirectories ? host.getDirectories(path) : [];
14191315
},
1420-
readDirectory,
1316+
readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => {
1317+
Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
1318+
return host.readDirectory!(path, extensions, exclude, include, depth);
1319+
},
14211320
onReleaseOldSourceFile,
14221321
onReleaseParsedCommandLine,
14231322
hasInvalidatedResolution,
14241323
hasChangedAutomaticTypeDirectiveNames,
1425-
trace: parseConfigHost.trace,
1324+
trace: maybeBind(host, host.trace),
14261325
resolveModuleNames: maybeBind(host, host.resolveModuleNames),
14271326
getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache),
14281327
resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives),
14291328
useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect),
14301329
getParsedCommandLine,
14311330
};
1331+
1332+
const originalGetSourceFile = compilerHost.getSourceFile;
1333+
1334+
const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache(
1335+
compilerHost,
1336+
fileName => toPath(fileName, currentDirectory, getCanonicalFileName),
1337+
(...args) => originalGetSourceFile.call(compilerHost, ...args)
1338+
);
1339+
compilerHost.getSourceFile = getSourceFileWithCache!;
1340+
14321341
host.setCompilerHost?.(compilerHost);
14331342

1343+
const parseConfigHost: ParseConfigFileHost = {
1344+
useCaseSensitiveFileNames,
1345+
fileExists: fileName => compilerHost!.fileExists(fileName),
1346+
readFile: fileName => compilerHost!.readFile(fileName),
1347+
readDirectory: (...args) => compilerHost!.readDirectory!(...args),
1348+
trace: compilerHost.trace,
1349+
getCurrentDirectory: compilerHost.getCurrentDirectory,
1350+
onUnRecoverableConfigFileDiagnostic: noop,
1351+
};
1352+
1353+
// If the program is already up-to-date, we can reuse it
1354+
if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
1355+
return;
1356+
}
1357+
1358+
// IMPORTANT - It is critical from this moment onward that we do not check
1359+
// cancellation tokens. We are about to mutate source files from a previous program
1360+
// instance. If we cancel midway through, we may end up in an inconsistent state where
1361+
// the program points to old source files that have been invalidated because of
1362+
// incremental parsing.
1363+
14341364
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
14351365
const options: CreateProgramOptions = {
14361366
rootNames: rootFileNames,
@@ -1441,9 +1371,9 @@ namespace ts {
14411371
};
14421372
program = createProgram(options);
14431373

1444-
// hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point.
1445-
// It needs to be cleared to allow all collected snapshots to be released
1446-
hostCache = undefined;
1374+
// 'getOrCreateSourceFile' depends on caching but should be used past this point.
1375+
// After this point, the cache needs to be cleared to allow all collected snapshots to be released
1376+
compilerHost = undefined;
14471377
parsedCommandLines = undefined;
14481378

14491379
// We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above,
@@ -1492,29 +1422,6 @@ namespace ts {
14921422
}
14931423
}
14941424

1495-
function fileExists(fileName: string): boolean {
1496-
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
1497-
const entry = hostCache && hostCache.getEntryByPath(path);
1498-
return entry ?
1499-
!isString(entry) :
1500-
(!!host.fileExists && host.fileExists(fileName));
1501-
}
1502-
1503-
function readFile(fileName: string) {
1504-
// stub missing host functionality
1505-
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
1506-
const entry = hostCache && hostCache.getEntryByPath(path);
1507-
if (entry) {
1508-
return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
1509-
}
1510-
return host.readFile && host.readFile(fileName);
1511-
}
1512-
1513-
function readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) {
1514-
Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
1515-
return host.readDirectory!(path, extensions, exclude, include, depth);
1516-
}
1517-
15181425
// Release any files we have acquired in the old program but are
15191426
// not part of the new program.
15201427
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
@@ -1527,15 +1434,18 @@ namespace ts {
15271434
}
15281435

15291436
function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
1530-
Debug.assert(hostCache !== undefined, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host.");
1437+
Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host.");
15311438
// The program is asking for this file, check first if the host can locate it.
15321439
// If the host can not locate the file, then it does not exist. return undefined
15331440
// to the program to allow reporting of errors for missing files.
1534-
const hostFileInformation = hostCache && hostCache.getOrCreateEntryByPath(fileName, path);
1535-
if (!hostFileInformation) {
1441+
const scriptSnapshot = host.getScriptSnapshot(fileName);
1442+
if (!scriptSnapshot) {
15361443
return undefined;
15371444
}
15381445

1446+
const scriptKind = getScriptKind(fileName, host);
1447+
const scriptVersion = host.getScriptVersion(fileName);
1448+
15391449
// Check if the language version has changed since we last created a program; if they are the same,
15401450
// it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
15411451
// can not be reused. we have to dump all syntax trees and create new ones.
@@ -1568,8 +1478,8 @@ namespace ts {
15681478
// We do not support the scenario where a host can modify a registered
15691479
// file's script kind, i.e. in one project some file is treated as ".ts"
15701480
// and in another as ".js"
1571-
if (hostFileInformation.scriptKind === oldSourceFile.scriptKind) {
1572-
return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
1481+
if (scriptKind === oldSourceFile.scriptKind) {
1482+
return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind);
15731483
}
15741484
else {
15751485
// Release old source file and fall through to aquire new file with new script kind
@@ -1581,7 +1491,7 @@ namespace ts {
15811491
}
15821492

15831493
// Could not find this file in the old program, create a new SourceFile for it.
1584-
return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
1494+
return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind);
15851495
}
15861496
}
15871497

0 commit comments

Comments
 (0)