mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Reduce the number of watched config files by watching the chain only in case of inferred root
This commit is contained in:
@@ -467,13 +467,20 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U[] | undefined): U[] {
|
||||
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U | U[] | undefined): U[] {
|
||||
const result: U[] = [];
|
||||
while (true) {
|
||||
const { value, done } = iter.next();
|
||||
if (done) break;
|
||||
const res = mapfn(value);
|
||||
if (res) result.push(...res);
|
||||
if (res) {
|
||||
if (isArray(res)) {
|
||||
result.push(...res);
|
||||
}
|
||||
else {
|
||||
result.push(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -881,8 +881,7 @@ namespace ts.projectSystem {
|
||||
checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]);
|
||||
checkProjectRootFiles(project, [file1.path, file2.path]);
|
||||
// watching all files except one that was open
|
||||
// And also tsconfig files for the open files
|
||||
checkWatchedFiles(host, [configFile.path, file2.path, libFile.path, "/a/b/c/tsconfig.json", "/a/b/c/jsconfig.json"]);
|
||||
checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]);
|
||||
checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true);
|
||||
});
|
||||
|
||||
|
||||
@@ -755,7 +755,7 @@ namespace ts.projectSystem {
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = configuredProjectAt(projectService, 0);
|
||||
checkProjectActualFiles(p, [app.path, jsconfig.path]);
|
||||
checkWatchedFiles(host, ["/tsconfig.json", jsconfig.path, "/bower_components", "/node_modules", libFile.path]);
|
||||
checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
|
||||
|
||||
+256
-123
@@ -252,7 +252,7 @@ namespace ts.server {
|
||||
WildCardDirectories = "Wild card directory",
|
||||
TypeRoot = "Type root of the project",
|
||||
ClosedScriptInfo = "Closed Script info",
|
||||
ConfigFileForOpenFile = "Config file changes for the open script info"
|
||||
ConfigFileForInferredRoot = "Config file for the inferred project root"
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -271,18 +271,32 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const enum ConfigFileWatcherStatus {
|
||||
ReloadingFiles = "Reloading configured projects files",
|
||||
ReloadingFiles = "Reloading configured projects for files",
|
||||
ReloadingInferredRootFiles = "Reloading configured projects for only inferred root files",
|
||||
UpdatedCallback = "Updated the callback",
|
||||
TrackingFileAdded = "Tracking file added",
|
||||
TrackingFileRemoved = "Tracking file removed"
|
||||
TrackingFileRemoved = "Tracking file removed",
|
||||
InferredRootAdded = "Inferred Root file added",
|
||||
InferredRootRemoved = "Inferred Root file removed",
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void;
|
||||
|
||||
type ConfigFileExistence = {
|
||||
/**
|
||||
* Cached value of existence of config file
|
||||
*/
|
||||
exists: boolean;
|
||||
trackingOpenFileSet?: Map<true>;
|
||||
/**
|
||||
* The value in the open files map is true if the file is inferred project root
|
||||
* Otherwise its false
|
||||
*/
|
||||
trackingOpenFilesMap: Map<boolean>;
|
||||
/**
|
||||
* The file watcher corresponding to this config file for the inferred project root
|
||||
* The watcher is present only when there is no open configured project for this config file
|
||||
*/
|
||||
configFileWatcher?: FileWatcher;
|
||||
};
|
||||
|
||||
@@ -675,14 +689,16 @@ namespace ts.server {
|
||||
this.removeProject(project);
|
||||
|
||||
// Reload the configured projects for the open files in the map as they are affectected by this config file
|
||||
this.logConfigFileWatch(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet);
|
||||
this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
// Since the configured project was deleted, we want to reload projects for all the open files
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false);
|
||||
}
|
||||
else {
|
||||
this.logConfigFileWatch(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
|
||||
project.pendingReload = true;
|
||||
this.delayUpdateProjectGraph(project);
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet);
|
||||
// As we scheduled the updated project graph, we would need to only schedule the project reload for the inferred project roots
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,8 +711,9 @@ namespace ts.server {
|
||||
const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName);
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath);
|
||||
configFilePresenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted);
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
// The tracking opens files would only contaion the inferred root so no need to check
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false);
|
||||
}
|
||||
|
||||
private removeProject(project: Project) {
|
||||
@@ -712,9 +729,9 @@ namespace ts.server {
|
||||
this.projectToSizeMap.delete((project as ExternalProject).externalProjectName);
|
||||
break;
|
||||
case ProjectKind.Configured:
|
||||
this.setConfigFilePresenceByClosedConfigFile(<ConfiguredProject>project);
|
||||
this.configuredProjects.delete((<ConfiguredProject>project).canonicalConfigFilePath);
|
||||
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
|
||||
this.setConfigFilePresenceByClosedConfigFile(<ConfiguredProject>project);
|
||||
break;
|
||||
case ProjectKind.Inferred:
|
||||
removeItemFromSet(this.inferredProjects, <InferredProject>project);
|
||||
@@ -774,7 +791,7 @@ namespace ts.server {
|
||||
// because the user may chose to discard the buffer content before saving
|
||||
// to the disk, and the server's version of the file can be out of sync.
|
||||
info.close();
|
||||
this.stopWatchingConfigFileForScriptInfo(info);
|
||||
this.stopWatchingConfigFilesForClosedScriptInfo(info);
|
||||
|
||||
removeItemFromSet(this.openFiles, info);
|
||||
|
||||
@@ -847,16 +864,34 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private configFileExists(configFileName: NormalizedPath, info: ScriptInfo) {
|
||||
const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName);
|
||||
// TODO: (sheetalkamat) Need to reduce the number of watches by not watching directories right here.
|
||||
// Theorotically current approach is correct but in practice there will be very few scenarios where
|
||||
// the config file gets added somewhere inside the another config file. And technically we could handle that case in configDirectory watcher in some cases
|
||||
// But given that its a rare scenario it seems like too much overhead.
|
||||
// So what we want to be watching is: configFile if the project is open
|
||||
// And the whole chain only for the inferred project roots
|
||||
// instead create the cache and send it across, and put watches only if info gets added to the inferred project root?
|
||||
return this.watchConfigFileForScriptInfo(configFileName, canonicalConfigFilePath, info).exists;
|
||||
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
// By default the info is belong to the config file.
|
||||
// Only adding the info as a root to inferred project will make it the root
|
||||
if (!configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) {
|
||||
configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
}
|
||||
return configFilePresenceInfo.exists;
|
||||
}
|
||||
|
||||
// Theorotically we should be adding watch for the directory here itself.
|
||||
// In practice there will be very few scenarios where the config file gets added
|
||||
// somewhere inside the another config file directory.
|
||||
// And technically we could handle that case in configFile's directory watcher in some cases
|
||||
// But given that its a rare scenario it seems like too much overhead. (we werent watching those directories earlier either)
|
||||
// So what we are now watching is: configFile if the project is open
|
||||
// And the whole chain of config files only for the inferred project roots
|
||||
|
||||
// Cache the host value of file exists and add the info tio to the tracked root
|
||||
const trackingOpenFilesMap = createMap<boolean>();
|
||||
trackingOpenFilesMap.set(info.path, false);
|
||||
const exists = this.host.fileExists(configFileName);
|
||||
configFilePresenceInfo = { exists, trackingOpenFilesMap };
|
||||
this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
return exists;
|
||||
}
|
||||
|
||||
private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) {
|
||||
@@ -867,29 +902,41 @@ namespace ts.server {
|
||||
if (configFilePresenceInfo.configFileWatcher) {
|
||||
const configFileName = project.getConfigFilePath();
|
||||
this.closeFileWatcher(
|
||||
WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName,
|
||||
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated
|
||||
);
|
||||
configFilePresenceInfo.configFileWatcher = undefined;
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Mark existence of the config file with the project creation
|
||||
this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, { exists: true });
|
||||
// We could be in this scenario if it is the external project tracked configured file
|
||||
// Since that route doesnt check if the config file is present or not
|
||||
this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, {
|
||||
exists: true,
|
||||
trackingOpenFilesMap: createMap<boolean>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private configFileExistenceTracksInferredRoot(configFilePresenceInfo: ConfigFileExistence) {
|
||||
return forEachEntry(configFilePresenceInfo.trackingOpenFilesMap, (value, __key) => value);
|
||||
}
|
||||
|
||||
private setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath);
|
||||
Debug.assert(!!configFilePresenceInfo);
|
||||
if (configFilePresenceInfo.trackingOpenFileSet) {
|
||||
const trackingOpenFilesMap = configFilePresenceInfo.trackingOpenFilesMap;
|
||||
if (trackingOpenFilesMap.size) {
|
||||
const configFileName = closedProject.getConfigFilePath();
|
||||
configFilePresenceInfo.configFileWatcher = this.addFileWatcher(
|
||||
WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName,
|
||||
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
);
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
if (this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) {
|
||||
Debug.assert(!configFilePresenceInfo.configFileWatcher);
|
||||
configFilePresenceInfo.configFileWatcher = this.addFileWatcher(
|
||||
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is no one tracking anymore. Remove the status
|
||||
@@ -897,81 +944,172 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private logConfigFileWatch(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) {
|
||||
const watchType = configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForOpenFile : WatchType.ConfigFilePath;
|
||||
const files = configFilePresenceInfo.trackingOpenFileSet ?
|
||||
arrayFrom(configFilePresenceInfo.trackingOpenFileSet.keys(), key =>
|
||||
this.getScriptInfoForPath(key as Path).fileName) :
|
||||
[];
|
||||
this.logger.info(`FileWatcher:: ${watchType}: File: ${configFileName} Currently Tracking for files: ${files} Status: ${status}`);
|
||||
private logConfigFileWatchUpdate(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) {
|
||||
if (this.logger.loggingEnabled()) {
|
||||
const inferredRoots: string[] = [];
|
||||
const otherFiles: string[] = [];
|
||||
configFilePresenceInfo.trackingOpenFilesMap.forEach((value, key: Path) => {
|
||||
const info = this.getScriptInfoForPath(key);
|
||||
if (value) {
|
||||
inferredRoots.push(info.fileName);
|
||||
}
|
||||
else {
|
||||
otherFiles.push(info.fileName);
|
||||
}
|
||||
});
|
||||
const watchType = status === ConfigFileWatcherStatus.UpdatedCallback ||
|
||||
status === ConfigFileWatcherStatus.ReloadingFiles ||
|
||||
status === ConfigFileWatcherStatus.ReloadingInferredRootFiles ?
|
||||
(configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath) :
|
||||
"";
|
||||
this.logger.info(`ConfigFilePresence ${watchType}:: File: ${configFileName} Currently Tracking: InferredRootFiles: ${inferredRoots} OtherFiles: ${otherFiles} Status: ${status}`);
|
||||
}
|
||||
}
|
||||
|
||||
private watchConfigFileForScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
// Existing information - just add to tracking files
|
||||
if (!configFilePresenceInfo.trackingOpenFileSet) {
|
||||
configFilePresenceInfo.trackingOpenFileSet = createMap<true>();
|
||||
configFilePresenceInfo.trackingOpenFileSet.set(info.path, true);
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
}
|
||||
else if (!configFilePresenceInfo.trackingOpenFileSet.has(info.path)) {
|
||||
configFilePresenceInfo.trackingOpenFileSet.set(info.path, true);
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
}
|
||||
private closeConfigFileWatcherIfInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string,
|
||||
configFilePresenceInfo: ConfigFileExistence, infoIsInferredRoot: boolean, reason: WatcherCloseReason) {
|
||||
// Close the config file watcher if it was the last inferred root
|
||||
if (infoIsInferredRoot &&
|
||||
configFilePresenceInfo.configFileWatcher &&
|
||||
!this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) {
|
||||
this.closeFileWatcher(
|
||||
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
configFilePresenceInfo.configFileWatcher, reason
|
||||
);
|
||||
configFilePresenceInfo.configFileWatcher = undefined;
|
||||
}
|
||||
else {
|
||||
// Add new callback
|
||||
const trackingOpenFileSet = createMap<true>();
|
||||
trackingOpenFileSet.set(info.path, true);
|
||||
const exists = this.host.fileExists(configFileName);
|
||||
configFilePresenceInfo = {
|
||||
exists,
|
||||
trackingOpenFileSet,
|
||||
configFileWatcher: this.addFileWatcher(
|
||||
WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName,
|
||||
(_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
)
|
||||
};
|
||||
this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo);
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
|
||||
// If this was the last tracking file open for this config file, remove the cached value
|
||||
if (!configFilePresenceInfo.trackingOpenFilesMap.size &&
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
this.mapOfConfigFilePresence.delete(canonicalConfigFilePath);
|
||||
}
|
||||
return configFilePresenceInfo;
|
||||
}
|
||||
|
||||
private closeConfigFileWatchForScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
private closeConfigFileWatchForClosedScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
if (configFilePresenceInfo.trackingOpenFileSet.size === 1) {
|
||||
configFilePresenceInfo.trackingOpenFileSet = undefined;
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved);
|
||||
if (configFilePresenceInfo.configFileWatcher) {
|
||||
this.closeFileWatcher(
|
||||
WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName,
|
||||
configFilePresenceInfo.configFileWatcher, WatcherCloseReason.FileClosed
|
||||
);
|
||||
this.mapOfConfigFilePresence.delete(canonicalConfigFilePath);
|
||||
}
|
||||
}
|
||||
else {
|
||||
configFilePresenceInfo.trackingOpenFileSet.delete(info.path);
|
||||
this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved);
|
||||
}
|
||||
const isInferredRoot = configFilePresenceInfo.trackingOpenFilesMap.get(info.path);
|
||||
|
||||
// Delete the info from tracking
|
||||
configFilePresenceInfo.trackingOpenFilesMap.delete(info.path);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved);
|
||||
|
||||
// Close the config file watcher if it was the last inferred root
|
||||
this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath,
|
||||
configFilePresenceInfo, isInferredRoot, WatcherCloseReason.FileClosed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private stopWatchingConfigFileForScriptInfo(info: ScriptInfo) {
|
||||
/**
|
||||
* This is called on file close, so that we stop watching the config file for this script info
|
||||
* @param info
|
||||
*/
|
||||
private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) {
|
||||
Debug.assert(!info.isScriptOpen());
|
||||
let current = info.fileName;
|
||||
let currentPath = getDirectoryPath(info.path);
|
||||
let parentPath = getDirectoryPath(currentPath);
|
||||
while (currentPath !== parentPath) {
|
||||
current = asNormalizedPath(getDirectoryPath(current));
|
||||
this.closeConfigFileWatchForScriptInfo(asNormalizedPath(combinePaths(current, "tsconfig.json")), combinePaths(currentPath, "tsconfig.json"), info);
|
||||
this.closeConfigFileWatchForScriptInfo(asNormalizedPath(combinePaths(current, "jsconfig.json")), combinePaths(currentPath, "jsconfig.json"), info);
|
||||
currentPath = parentPath;
|
||||
parentPath = getDirectoryPath(parentPath);
|
||||
this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.closeConfigFileWatchForClosedScriptInfo(configFileName, canonicalConfigFilePath, info)
|
||||
);
|
||||
}
|
||||
|
||||
private watchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (!configFilePresenceInfo) {
|
||||
// Create the cache
|
||||
configFilePresenceInfo = {
|
||||
exists: this.host.fileExists(configFileName),
|
||||
trackingOpenFilesMap: createMap<boolean>()
|
||||
};
|
||||
this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo);
|
||||
}
|
||||
|
||||
// Set this file as inferred root
|
||||
configFilePresenceInfo.trackingOpenFilesMap.set(info.path, true);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootAdded);
|
||||
|
||||
// If there is no configured project for this config file, create the watcher
|
||||
if (!configFilePresenceInfo.configFileWatcher &&
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
configFilePresenceInfo.configFileWatcher = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
(_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by inferred project whenever script info is added as a root
|
||||
*/
|
||||
/* @internal */
|
||||
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.watchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info)
|
||||
);
|
||||
}
|
||||
|
||||
private closeWatchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo, reason: WatcherCloseReason) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
// Set this as not inferred root
|
||||
if (configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) {
|
||||
configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootRemoved);
|
||||
}
|
||||
|
||||
// Close the watcher if present
|
||||
this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath,
|
||||
configFilePresenceInfo, /*infoIsInferredRoot*/ true, reason
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by inferred project whenever root script info is removed from it
|
||||
*/
|
||||
/* @internal */
|
||||
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) {
|
||||
this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.closeWatchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info, reason)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to search for a tsconfig.json for the given file.
|
||||
* This is different from the method the compiler uses because
|
||||
* the compiler can assume it will always start searching in the
|
||||
* current directory (the directory in which tsc was invoked).
|
||||
* The server must start searching from the directory containing
|
||||
* the newly opened file.
|
||||
*/
|
||||
private enumerateConfigFileLocations(info: ScriptInfo,
|
||||
action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void,
|
||||
projectRootPath?: NormalizedPath) {
|
||||
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
|
||||
|
||||
while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) {
|
||||
const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName);
|
||||
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
|
||||
let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json"));
|
||||
if (result) {
|
||||
return tsconfigFileName;
|
||||
}
|
||||
|
||||
const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json"));
|
||||
result = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json"));
|
||||
if (result) {
|
||||
return jsconfigFileName;
|
||||
}
|
||||
|
||||
const parentPath = asNormalizedPath(getDirectoryPath(searchPath));
|
||||
if (parentPath === searchPath) {
|
||||
break;
|
||||
}
|
||||
searchPath = parentPath;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -984,31 +1122,19 @@ namespace ts.server {
|
||||
*/
|
||||
private getConfigFileNameForFile(info: ScriptInfo, projectRootPath?: NormalizedPath) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
let searchPath = getDirectoryPath(info.fileName);
|
||||
this.logger.info(`Search path: ${searchPath}`);
|
||||
|
||||
// check if this file is already included in one of external projects
|
||||
while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) {
|
||||
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
|
||||
if (this.configFileExists(tsconfigFileName, info)) {
|
||||
this.logger.info(`Config file name: ${tsconfigFileName}`);
|
||||
return tsconfigFileName;
|
||||
}
|
||||
|
||||
const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json"));
|
||||
if (this.configFileExists(jsconfigFileName, info)) {
|
||||
this.logger.info(`Config file name: ${jsconfigFileName}`);
|
||||
return jsconfigFileName;
|
||||
}
|
||||
|
||||
const parentPath = asNormalizedPath(getDirectoryPath(searchPath));
|
||||
if (parentPath === searchPath) {
|
||||
break;
|
||||
}
|
||||
searchPath = parentPath;
|
||||
this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`);
|
||||
const configFileName = this.enumerateConfigFileLocations(info,
|
||||
(configFileName: NormalizedPath, canonicalConfigFilePath: string) =>
|
||||
this.configFileExists(configFileName, canonicalConfigFilePath, info),
|
||||
projectRootPath
|
||||
);
|
||||
if (configFileName) {
|
||||
this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`);
|
||||
}
|
||||
this.logger.info("No config files found.");
|
||||
return undefined;
|
||||
else {
|
||||
this.logger.info(`For info: ${info.fileName} :: No config files found.`);
|
||||
}
|
||||
return configFileName;
|
||||
}
|
||||
|
||||
private printProjects() {
|
||||
@@ -1045,6 +1171,10 @@ namespace ts.server {
|
||||
private findConfiguredProjectByProjectName(configFileName: NormalizedPath) {
|
||||
// make sure that casing of config file name is consistent
|
||||
const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName));
|
||||
return this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath);
|
||||
}
|
||||
|
||||
private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath: string) {
|
||||
return this.configuredProjects.get(canonicalConfigFilePath);
|
||||
}
|
||||
|
||||
@@ -1497,11 +1627,14 @@ namespace ts.server {
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
|
||||
delayReloadConfiguredProjectForFiles(openFileSet: Map<true>) {
|
||||
if (openFileSet) {
|
||||
const openFiles = arrayFrom(openFileSet.keys(), path => this.getScriptInfoForPath(path as Path));
|
||||
this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true);
|
||||
}
|
||||
delayReloadConfiguredProjectForFiles(openFilesMap: Map<boolean>, ignoreIfNotInferredProjectRoot: boolean) {
|
||||
// Get open files to reload projects for
|
||||
const openFiles = flatMapIter(openFilesMap.keys(), path => {
|
||||
if (!ignoreIfNotInferredProjectRoot || openFilesMap.get(path)) {
|
||||
return this.getScriptInfoForPath(path as Path);
|
||||
}
|
||||
});
|
||||
this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true);
|
||||
this.delayInferredProjectsRefresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -924,6 +924,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
addRoot(info: ScriptInfo) {
|
||||
this.projectService.startWatchingConfigFilesForInferredProjectRoot(info);
|
||||
if (!this._isJsInferredProject && info.isJavaScript()) {
|
||||
this.toggleJsInferredProject(/*isJsInferredProject*/ true);
|
||||
}
|
||||
@@ -931,6 +932,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
removeRoot(info: ScriptInfo) {
|
||||
this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.NotNeeded);
|
||||
super.removeRoot(info);
|
||||
if (this._isJsInferredProject && info.isJavaScript()) {
|
||||
if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) {
|
||||
@@ -955,6 +957,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
close() {
|
||||
forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.ProjectClose));
|
||||
super.close();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user