mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-12 21:37:41 -06:00
Cache the read directory results so that it doesnt end up reading it all the time
This commit is contained in:
parent
48c651317e
commit
19a6a003f5
@ -574,7 +574,7 @@ namespace ts.projectSystem {
|
||||
const path = this.toFullPath(s);
|
||||
const folder = this.fs.get(path);
|
||||
if (isFolder(folder)) {
|
||||
return map(folder.entries, x => getBaseFileName(x.fullPath));
|
||||
return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
|
||||
}
|
||||
Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder");
|
||||
return [];
|
||||
@ -590,10 +590,10 @@ namespace ts.projectSystem {
|
||||
if (isFolder(dirEntry)) {
|
||||
dirEntry.entries.forEach((entry) => {
|
||||
if (isFolder(entry)) {
|
||||
result.directories.push(entry.fullPath);
|
||||
result.directories.push(getBaseFileName(entry.fullPath));
|
||||
}
|
||||
else if (isFile(entry)) {
|
||||
result.files.push(entry.fullPath);
|
||||
result.files.push(getBaseFileName(entry.fullPath));
|
||||
}
|
||||
else {
|
||||
Debug.fail("Unknown entry");
|
||||
|
||||
@ -608,7 +608,9 @@ namespace ts.server {
|
||||
* @param fileName the absolute file name that changed in watched directory
|
||||
*/
|
||||
/* @internal */
|
||||
onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: string) {
|
||||
onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: Path) {
|
||||
project.cachedParseConfigHost.clearCacheForFile(fileName);
|
||||
|
||||
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||
// one with the fileName being "folder/file", and the other one with "folder".
|
||||
// We don't respond to the second one.
|
||||
@ -621,7 +623,7 @@ namespace ts.server {
|
||||
const configFileSpecs = project.configFileSpecs;
|
||||
const configFilename = normalizePath(project.getConfigFilePath());
|
||||
// TODO: (sheetalkamat) use the host that caches - so we dont do file exists and read directory call
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), this.host, this.hostConfiguration.extraFileExtensions);
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.cachedParseConfigHost, this.hostConfiguration.extraFileExtensions);
|
||||
const errors = project.getAllProjectErrors();
|
||||
if (result.fileNames.length === 0) {
|
||||
if (!configFileSpecs.filesSpecs) {
|
||||
@ -945,7 +947,7 @@ namespace ts.server {
|
||||
return findProjectByName(projectFileName, this.externalProjects);
|
||||
}
|
||||
|
||||
private convertConfigFileContentToProjectOptions(configFilename: string) {
|
||||
private convertConfigFileContentToProjectOptions(configFilename: string, cachedParseConfigHost: CachedParseConfigHost) {
|
||||
configFilename = normalizePath(configFilename);
|
||||
|
||||
const configFileContent = this.host.readFile(configFilename);
|
||||
@ -957,7 +959,7 @@ namespace ts.server {
|
||||
const errors = result.parseDiagnostics;
|
||||
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
|
||||
result,
|
||||
this.host,
|
||||
cachedParseConfigHost,
|
||||
getDirectoryPath(configFilename),
|
||||
/*existingOptions*/ {},
|
||||
configFilename,
|
||||
@ -1105,7 +1107,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, clientFileName?: string) {
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedParseConfigHost: CachedParseConfigHost, clientFileName?: string) {
|
||||
const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
||||
const project = new ConfiguredProject(
|
||||
configFileName,
|
||||
@ -1115,7 +1117,7 @@ namespace ts.server {
|
||||
projectOptions.compilerOptions,
|
||||
/*languageServiceEnabled*/ !sizeLimitExceeded,
|
||||
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave);
|
||||
|
||||
project.cachedParseConfigHost = cachedParseConfigHost;
|
||||
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
|
||||
|
||||
project.configFileSpecs = configFileSpecs;
|
||||
@ -1149,12 +1151,13 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) {
|
||||
const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName);
|
||||
const cachedParseConfigHost = new CachedParseConfigHost(this.host);
|
||||
const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedParseConfigHost);
|
||||
if (success) {
|
||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||
}
|
||||
|
||||
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, clientFileName);
|
||||
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedParseConfigHost, clientFileName);
|
||||
}
|
||||
|
||||
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>) {
|
||||
@ -1239,7 +1242,8 @@ namespace ts.server {
|
||||
// note: the returned "success" is true does not mean the "configFileErrors" is empty.
|
||||
// because we might have tolerated the errors and kept going. So always return the configFileErrors
|
||||
// regardless the "success" here is true or not.
|
||||
const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath());
|
||||
project.cachedParseConfigHost.clearCache();
|
||||
const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath(), project.cachedParseConfigHost);
|
||||
project.configFileSpecs = configFileSpecs;
|
||||
if (!success) {
|
||||
// reset project settings to default
|
||||
|
||||
@ -4,6 +4,58 @@
|
||||
|
||||
namespace ts.server {
|
||||
type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean };
|
||||
|
||||
export class CachedParseConfigHost implements ParseConfigHost {
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
private getCanonicalFileName: (fileName: string) => string;
|
||||
private cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
||||
constructor(private readonly host: ServerHost) {
|
||||
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
|
||||
this.getCanonicalFileName = createGetCanonicalFileName(this.useCaseSensitiveFileNames);
|
||||
}
|
||||
|
||||
private getFileSystemEntries(rootDir: string) {
|
||||
const path = ts.toPath(rootDir, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const cachedResult = this.cachedReadDirectoryResult.get(path);
|
||||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const resultFromHost: FileSystemEntries = {
|
||||
files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
|
||||
directories: this.host.getDirectories(rootDir)
|
||||
};
|
||||
|
||||
this.cachedReadDirectoryResult.set(path, resultFromHost);
|
||||
return resultFromHost;
|
||||
}
|
||||
|
||||
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
|
||||
return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.host.getCurrentDirectory(), depth, path => this.getFileSystemEntries(path));
|
||||
}
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
const path = ts.toPath(fileName, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const result = this.getFileSystemEntries(getDirectoryPath(path));
|
||||
return contains(result.files, fileName);
|
||||
}
|
||||
|
||||
readFile(path: string): string {
|
||||
return this.host.readFile(path);
|
||||
}
|
||||
|
||||
clearCacheForFile(fileName: Path) {
|
||||
this.cachedReadDirectoryResult.delete(fileName);
|
||||
this.cachedReadDirectoryResult.delete(getDirectoryPath(fileName));
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
||||
}
|
||||
|
||||
// TODO: (sheetalkamat) to cache getFileSize as well as fileExists and readFile
|
||||
}
|
||||
|
||||
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost {
|
||||
private compilationSettings: ts.CompilerOptions;
|
||||
private readonly resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
|
||||
@ -972,6 +972,7 @@ namespace ts.server {
|
||||
|
||||
/*@internal*/
|
||||
configFileSpecs: ConfigFileSpecs;
|
||||
cachedParseConfigHost: CachedParseConfigHost;
|
||||
|
||||
private plugins: PluginModule[] = [];
|
||||
|
||||
@ -1113,14 +1114,14 @@ namespace ts.server {
|
||||
this.typeRootsWatchers = watchers;
|
||||
}
|
||||
|
||||
private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) {
|
||||
private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, getCanonicalFileName: (fileName: string) => string, replaceExisting: boolean) {
|
||||
if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) {
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`);
|
||||
this.directoriesWatchedForWildcards.set(directory, {
|
||||
watcher: this.projectService.host.watchDirectory(
|
||||
directory,
|
||||
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
||||
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toPath(path, directory, getCanonicalFileName)),
|
||||
recursive
|
||||
),
|
||||
recursive
|
||||
@ -1129,6 +1130,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
|
||||
const getCanonicalFileName = createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames);
|
||||
if (wildcardDirectories) {
|
||||
if (this.directoriesWatchedForWildcards) {
|
||||
this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => {
|
||||
@ -1145,7 +1147,7 @@ namespace ts.server {
|
||||
if (currentRecursive !== recursive) {
|
||||
this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`);
|
||||
watcher.close();
|
||||
this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true);
|
||||
this.addWatcherForDirectory(currentFlag, directory, getCanonicalFileName, /*replaceExisting*/ true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1154,7 +1156,7 @@ namespace ts.server {
|
||||
this.directoriesWatchedForWildcards = createMap<WildCardDirectoryWatchers>();
|
||||
}
|
||||
wildcardDirectories.forEach((flag, directory) =>
|
||||
this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false));
|
||||
this.addWatcherForDirectory(flag, directory, getCanonicalFileName, /*replaceExisting*/ false));
|
||||
}
|
||||
else {
|
||||
this.stopWatchingWildCards();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user