mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
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
This commit is contained in:
parent
12ed01203c
commit
5aa0053c74
@ -282,7 +282,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// directoryExists
|
||||
if (originalDirectoryExists && originalCreateDirectory) {
|
||||
if (originalDirectoryExists) {
|
||||
host.directoryExists = directory => {
|
||||
const key = toPath(directory);
|
||||
const value = directoryExistsCache.get(key);
|
||||
@ -291,11 +291,14 @@ namespace ts {
|
||||
directoryExistsCache.set(key, !!newValue);
|
||||
return newValue;
|
||||
};
|
||||
host.createDirectory = directory => {
|
||||
const key = toPath(directory);
|
||||
directoryExistsCache.delete(key);
|
||||
originalCreateDirectory.call(host, directory);
|
||||
};
|
||||
|
||||
if (originalCreateDirectory) {
|
||||
host.createDirectory = directory => {
|
||||
const key = toPath(directory);
|
||||
directoryExistsCache.delete(key);
|
||||
originalCreateDirectory.call(host, directory);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -927,14 +927,6 @@ namespace ts {
|
||||
|
||||
/// Language Service
|
||||
|
||||
// Information about a specific host file.
|
||||
interface HostFileInformation {
|
||||
hostFileName: string;
|
||||
version: string;
|
||||
scriptSnapshot: IScriptSnapshot;
|
||||
scriptKind: ScriptKind;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface DisplayPartsSymbolWriter extends EmitTextWriter {
|
||||
displayParts(): SymbolDisplayPart[];
|
||||
@ -988,82 +980,6 @@ namespace ts {
|
||||
return codefix.getSupportedErrorCodes();
|
||||
}
|
||||
|
||||
// Either it will be file name if host doesnt have file or it will be the host's file information
|
||||
type CachedHostFileInformation = HostFileInformation | string;
|
||||
|
||||
// Cache host information about script Should be refreshed
|
||||
// at each language service public entry point, since we don't know when
|
||||
// the set of scripts handled by the host changes.
|
||||
class HostCache {
|
||||
private fileNameToEntry: ESMap<Path, CachedHostFileInformation>;
|
||||
private currentDirectory: string;
|
||||
|
||||
constructor(private host: LanguageServiceHost, getCanonicalFileName: GetCanonicalFileName) {
|
||||
// script id => script index
|
||||
this.currentDirectory = host.getCurrentDirectory();
|
||||
this.fileNameToEntry = new Map();
|
||||
|
||||
// Initialize the list with the root file names
|
||||
const rootFileNames = host.getScriptFileNames();
|
||||
tracing?.push(tracing.Phase.Session, "initializeHostCache", { count: rootFileNames.length });
|
||||
for (const fileName of rootFileNames) {
|
||||
this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName));
|
||||
}
|
||||
tracing?.pop();
|
||||
}
|
||||
|
||||
private createEntry(fileName: string, path: Path) {
|
||||
let entry: CachedHostFileInformation;
|
||||
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
|
||||
if (scriptSnapshot) {
|
||||
entry = {
|
||||
hostFileName: fileName,
|
||||
version: this.host.getScriptVersion(fileName),
|
||||
scriptSnapshot,
|
||||
scriptKind: getScriptKind(fileName, this.host)
|
||||
};
|
||||
}
|
||||
else {
|
||||
entry = fileName;
|
||||
}
|
||||
|
||||
this.fileNameToEntry.set(path, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public getEntryByPath(path: Path): CachedHostFileInformation | undefined {
|
||||
return this.fileNameToEntry.get(path);
|
||||
}
|
||||
|
||||
public getHostFileInformation(path: Path): HostFileInformation | undefined {
|
||||
const entry = this.fileNameToEntry.get(path);
|
||||
return !isString(entry) ? entry : undefined;
|
||||
}
|
||||
|
||||
public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
|
||||
const info = this.getEntryByPath(path) || this.createEntry(fileName, path);
|
||||
return isString(info) ? undefined! : info; // TODO: GH#18217
|
||||
}
|
||||
|
||||
public getRootFileNames(): string[] {
|
||||
const names: string[] = [];
|
||||
this.fileNameToEntry.forEach(entry => {
|
||||
if (isString(entry)) {
|
||||
names.push(entry);
|
||||
}
|
||||
else {
|
||||
names.push(entry.hostFileName);
|
||||
}
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
public getScriptSnapshot(path: Path): IScriptSnapshot {
|
||||
const file = this.getHostFileInformation(path);
|
||||
return (file && file.scriptSnapshot)!; // TODO: GH#18217
|
||||
}
|
||||
}
|
||||
|
||||
class SyntaxTreeCache {
|
||||
// For our syntactic only features, we also keep a cache of the syntax tree for the
|
||||
// currently edited file.
|
||||
@ -1367,37 +1283,17 @@ namespace ts {
|
||||
lastTypesRootVersion = typeRootsVersion;
|
||||
}
|
||||
|
||||
const rootFileNames = host.getScriptFileNames();
|
||||
|
||||
// Get a fresh cache of the host information
|
||||
let hostCache: HostCache | undefined = new HostCache(host, getCanonicalFileName);
|
||||
const rootFileNames = hostCache.getRootFileNames();
|
||||
const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
|
||||
const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
|
||||
const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames);
|
||||
const projectReferences = host.getProjectReferences?.();
|
||||
let parsedCommandLines: ESMap<Path, ParsedCommandLine | false> | undefined;
|
||||
const parseConfigHost: ParseConfigFileHost = {
|
||||
useCaseSensitiveFileNames,
|
||||
fileExists,
|
||||
readFile,
|
||||
readDirectory,
|
||||
trace: maybeBind(host, host.trace),
|
||||
getCurrentDirectory: () => currentDirectory,
|
||||
onUnRecoverableConfigFileDiagnostic: noop,
|
||||
};
|
||||
|
||||
// If the program is already up-to-date, we can reuse it
|
||||
if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IMPORTANT - It is critical from this moment onward that we do not check
|
||||
// cancellation tokens. We are about to mutate source files from a previous program
|
||||
// instance. If we cancel midway through, we may end up in an inconsistent state where
|
||||
// the program points to old source files that have been invalidated because of
|
||||
// incremental parsing.
|
||||
|
||||
// Now create a new compiler
|
||||
const compilerHost: CompilerHost = {
|
||||
let compilerHost: CompilerHost | undefined = {
|
||||
getSourceFile: getOrCreateSourceFile,
|
||||
getSourceFileByPath: getOrCreateSourceFileByPath,
|
||||
getCancellationToken: () => cancellationToken,
|
||||
@ -1407,8 +1303,8 @@ namespace ts {
|
||||
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
|
||||
writeFile: noop,
|
||||
getCurrentDirectory: () => currentDirectory,
|
||||
fileExists,
|
||||
readFile,
|
||||
fileExists: fileName => host.fileExists(fileName),
|
||||
readFile: fileName => host.readFile && host.readFile(fileName),
|
||||
getSymlinkCache: maybeBind(host, host.getSymlinkCache),
|
||||
realpath: maybeBind(host, host.realpath),
|
||||
directoryExists: directoryName => {
|
||||
@ -1417,20 +1313,54 @@ namespace ts {
|
||||
getDirectories: path => {
|
||||
return host.getDirectories ? host.getDirectories(path) : [];
|
||||
},
|
||||
readDirectory,
|
||||
readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => {
|
||||
Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
|
||||
return host.readDirectory!(path, extensions, exclude, include, depth);
|
||||
},
|
||||
onReleaseOldSourceFile,
|
||||
onReleaseParsedCommandLine,
|
||||
hasInvalidatedResolution,
|
||||
hasChangedAutomaticTypeDirectiveNames,
|
||||
trace: parseConfigHost.trace,
|
||||
trace: maybeBind(host, host.trace),
|
||||
resolveModuleNames: maybeBind(host, host.resolveModuleNames),
|
||||
getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache),
|
||||
resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives),
|
||||
useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect),
|
||||
getParsedCommandLine,
|
||||
};
|
||||
|
||||
const originalGetSourceFile = compilerHost.getSourceFile;
|
||||
|
||||
const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache(
|
||||
compilerHost,
|
||||
fileName => toPath(fileName, currentDirectory, getCanonicalFileName),
|
||||
(...args) => originalGetSourceFile.call(compilerHost, ...args)
|
||||
);
|
||||
compilerHost.getSourceFile = getSourceFileWithCache!;
|
||||
|
||||
host.setCompilerHost?.(compilerHost);
|
||||
|
||||
const parseConfigHost: ParseConfigFileHost = {
|
||||
useCaseSensitiveFileNames,
|
||||
fileExists: fileName => compilerHost!.fileExists(fileName),
|
||||
readFile: fileName => compilerHost!.readFile(fileName),
|
||||
readDirectory: (...args) => compilerHost!.readDirectory!(...args),
|
||||
trace: compilerHost.trace,
|
||||
getCurrentDirectory: compilerHost.getCurrentDirectory,
|
||||
onUnRecoverableConfigFileDiagnostic: noop,
|
||||
};
|
||||
|
||||
// If the program is already up-to-date, we can reuse it
|
||||
if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IMPORTANT - It is critical from this moment onward that we do not check
|
||||
// cancellation tokens. We are about to mutate source files from a previous program
|
||||
// instance. If we cancel midway through, we may end up in an inconsistent state where
|
||||
// the program points to old source files that have been invalidated because of
|
||||
// incremental parsing.
|
||||
|
||||
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
|
||||
const options: CreateProgramOptions = {
|
||||
rootNames: rootFileNames,
|
||||
@ -1441,9 +1371,9 @@ namespace ts {
|
||||
};
|
||||
program = createProgram(options);
|
||||
|
||||
// hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point.
|
||||
// It needs to be cleared to allow all collected snapshots to be released
|
||||
hostCache = undefined;
|
||||
// 'getOrCreateSourceFile' depends on caching but should be used past this point.
|
||||
// After this point, the cache needs to be cleared to allow all collected snapshots to be released
|
||||
compilerHost = undefined;
|
||||
parsedCommandLines = undefined;
|
||||
|
||||
// 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 {
|
||||
}
|
||||
}
|
||||
|
||||
function fileExists(fileName: string): boolean {
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
const entry = hostCache && hostCache.getEntryByPath(path);
|
||||
return entry ?
|
||||
!isString(entry) :
|
||||
(!!host.fileExists && host.fileExists(fileName));
|
||||
}
|
||||
|
||||
function readFile(fileName: string) {
|
||||
// stub missing host functionality
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
const entry = hostCache && hostCache.getEntryByPath(path);
|
||||
if (entry) {
|
||||
return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
|
||||
}
|
||||
return host.readFile && host.readFile(fileName);
|
||||
}
|
||||
|
||||
function readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) {
|
||||
Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
|
||||
return host.readDirectory!(path, extensions, exclude, include, depth);
|
||||
}
|
||||
|
||||
// Release any files we have acquired in the old program but are
|
||||
// not part of the new program.
|
||||
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
|
||||
@ -1527,15 +1434,18 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
|
||||
Debug.assert(hostCache !== undefined, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host.");
|
||||
Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host.");
|
||||
// The program is asking for this file, check first if the host can locate it.
|
||||
// If the host can not locate the file, then it does not exist. return undefined
|
||||
// to the program to allow reporting of errors for missing files.
|
||||
const hostFileInformation = hostCache && hostCache.getOrCreateEntryByPath(fileName, path);
|
||||
if (!hostFileInformation) {
|
||||
const scriptSnapshot = host.getScriptSnapshot(fileName);
|
||||
if (!scriptSnapshot) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const scriptKind = getScriptKind(fileName, host);
|
||||
const scriptVersion = host.getScriptVersion(fileName);
|
||||
|
||||
// Check if the language version has changed since we last created a program; if they are the same,
|
||||
// it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
|
||||
// can not be reused. we have to dump all syntax trees and create new ones.
|
||||
@ -1568,8 +1478,8 @@ namespace ts {
|
||||
// We do not support the scenario where a host can modify a registered
|
||||
// file's script kind, i.e. in one project some file is treated as ".ts"
|
||||
// and in another as ".js"
|
||||
if (hostFileInformation.scriptKind === oldSourceFile.scriptKind) {
|
||||
return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
|
||||
if (scriptKind === oldSourceFile.scriptKind) {
|
||||
return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind);
|
||||
}
|
||||
else {
|
||||
// Release old source file and fall through to aquire new file with new script kind
|
||||
@ -1581,7 +1491,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Could not find this file in the old program, create a new SourceFile for it.
|
||||
return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
|
||||
return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user