Instead of watching directories, watch tsconfig files of inferred project root

This commit is contained in:
Sheetal Nandi 2017-07-15 19:11:27 -07:00
parent 00011a52af
commit 0572b15adc
3 changed files with 215 additions and 101 deletions

View file

@ -281,7 +281,7 @@ namespace ts.projectSystem {
} }
export function checkMapKeys(caption: string, map: Map<any>, expectedKeys: string[]) { export function checkMapKeys(caption: string, map: Map<any>, expectedKeys: string[]) {
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map`); assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`);
for (const name of expectedKeys) { for (const name of expectedKeys) {
assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`);
} }
@ -800,7 +800,7 @@ namespace ts.projectSystem {
const project = projectService.inferredProjects[0]; const project = projectService.inferredProjects[0];
checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
checkWatchedDirectories(host, ["/a/b/c", "/a/b", "/a"]); checkWatchedFiles(host, ["/a/b/c/tsconfig.json", "/a/b/tsconfig.json", "/a/tsconfig.json", libFile.path, moduleFile.path]);
}); });
it("can handle tsconfig file name with difference casing", () => { it("can handle tsconfig file name with difference casing", () => {
@ -890,15 +890,15 @@ namespace ts.projectSystem {
projectService.openClientFile(commonFile2.path); projectService.openClientFile(commonFile2.path);
checkNumberOfInferredProjects(projectService, 2); checkNumberOfInferredProjects(projectService, 2);
checkWatchedDirectories(host, ["/a/b", "/a"]); checkWatchedFiles(host, [configFile.path, "/a/tsconfig.json", libFile.path]);
// Add a tsconfig file // Add a tsconfig file
host.reloadFS(filesWithConfig); host.reloadFS(filesWithConfig);
host.checkTimeoutQueueLengthAndRun(1);
checkNumberOfInferredProjects(projectService, 1); checkNumberOfInferredProjects(projectService, 1);
checkNumberOfConfiguredProjects(projectService, 1); checkNumberOfConfiguredProjects(projectService, 1);
// watching all files except one that was open // watching all files except one that was open
checkWatchedFiles(host, [libFile.path, configFile.path]); checkWatchedFiles(host, [libFile.path, configFile.path, "/a/tsconfig.json"]);
// remove the tsconfig file // remove the tsconfig file
host.reloadFS(filesWithoutConfig); host.reloadFS(filesWithoutConfig);
@ -908,7 +908,7 @@ namespace ts.projectSystem {
checkNumberOfInferredProjects(projectService, 2); checkNumberOfInferredProjects(projectService, 2);
checkNumberOfConfiguredProjects(projectService, 0); checkNumberOfConfiguredProjects(projectService, 0);
checkWatchedDirectories(host, ["/a/b", "/a"]); checkWatchedFiles(host, ["/a/b/tsconfig.json", "/a/tsconfig.json", libFile.path]);
}); });
it("add new files to a configured project without file list", () => { it("add new files to a configured project without file list", () => {
@ -1315,7 +1315,7 @@ namespace ts.projectSystem {
host.reloadFS([file1, configFile, file2, file3, libFile]); host.reloadFS([file1, configFile, file2, file3, libFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkNumberOfConfiguredProjects(projectService, 1); checkNumberOfConfiguredProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 1); checkNumberOfInferredProjects(projectService, 1);
checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]);
@ -1728,6 +1728,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);
host.reloadFS([file1, file2, file3, configFile]); host.reloadFS([file1, file2, file3, configFile]);
host.checkTimeoutQueueLengthAndRun(1);
checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path, configFile.path]); checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path, configFile.path]);
}); });

View file

@ -245,57 +245,14 @@ namespace ts.server {
} }
} }
class DirectoryWatchers {
/**
* a path to directory watcher map that detects added tsconfig files
*/
private readonly directoryWatchersForTsconfig: Map<FileWatcher> = createMap<FileWatcher>();
/**
* count of how many projects are using the directory watcher.
* If the number becomes 0 for a watcher, then we should close it.
*/
private readonly directoryWatchersRefCount: Map<number> = createMap<number>();
constructor(private readonly projectService: ProjectService) {
}
stopWatchingDirectory(directory: string) {
// if the ref count for this directory watcher drops to 0, it's time to close it
const refCount = this.directoryWatchersRefCount.get(directory) - 1;
this.directoryWatchersRefCount.set(directory, refCount);
if (refCount === 0) {
this.projectService.logger.info(`Close directory watcher for: ${directory}`);
this.directoryWatchersForTsconfig.get(directory).close();
this.directoryWatchersForTsconfig.delete(directory);
}
}
startWatchingContainingDirectoriesForFile(fileName: string, project: InferredProject, callback: (fileName: string) => void) {
let currentPath = getDirectoryPath(fileName);
let parentPath = getDirectoryPath(currentPath);
while (currentPath !== parentPath) {
if (!this.directoryWatchersForTsconfig.has(currentPath)) {
this.projectService.logger.info(`Add watcher for: ${currentPath}`);
this.directoryWatchersForTsconfig.set(currentPath, this.projectService.host.watchDirectory(currentPath, callback));
this.directoryWatchersRefCount.set(currentPath, 1);
}
else {
this.directoryWatchersRefCount.set(currentPath, this.directoryWatchersRefCount.get(currentPath) + 1);
}
project.directoriesWatchedForTsconfig.push(currentPath);
currentPath = parentPath;
parentPath = getDirectoryPath(parentPath);
}
}
}
/* @internal */ /* @internal */
export const enum WatchType { export const enum WatchType {
ConfigFilePath = "Config file for the program", ConfigFilePath = "Config file for the program",
MissingFilePath = "Missing file from program", MissingFilePath = "Missing file from program",
WildCardDirectories = "Wild card directory", WildCardDirectories = "Wild card directory",
TypeRoot = "Type root of the project", TypeRoot = "Type root of the project",
ClosedScriptInfo = "Closed Script info" ClosedScriptInfo = "Closed Script info",
ConfigFileForInferredRoot = "Config file for the root script info of the inferred project"
} }
/* @internal */ /* @internal */
@ -308,12 +265,27 @@ namespace ts.server {
OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more", OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more",
OrphanScriptInfo = "Removing Orphan script info as part of cleanup", OrphanScriptInfo = "Removing Orphan script info as part of cleanup",
FileDeleted = "File was deleted", FileDeleted = "File was deleted",
FileOpened = "File opened" FileOpened = "File opened",
ConfigProjectCreated = "Config file project created"
}
const enum ConfigFileWatcherStatus {
ReloadingFiles = "Reloading configured projects files",
NoAction = "No action on files",
UpdatedCallback = "Updated the callback",
TrackingFileAdded = "Tracking file added",
TrackingFileRemoved = "Tracking file removed"
} }
/* @internal */ /* @internal */
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void;
type ConfigFileExistence = {
exists: boolean;
trackingOpenFiles?: ScriptInfo[];
configFileWatcher?: FileWatcher;
};
export interface ProjectServiceOptions { export interface ProjectServiceOptions {
host: ServerHost; host: ServerHost;
logger: Logger; logger: Logger;
@ -362,8 +334,7 @@ namespace ts.server {
private compilerOptionsForInferredProjects: CompilerOptions; private compilerOptionsForInferredProjects: CompilerOptions;
private compileOnSaveForInferredProjects: boolean; private compileOnSaveForInferredProjects: boolean;
private readonly projectToSizeMap: Map<number> = createMap<number>(); private readonly projectToSizeMap: Map<number> = createMap<number>();
private readonly directoryWatchers: DirectoryWatchers; private readonly mapOfConfigFilePresence: Map<ConfigFileExistence>;
private readonly mapOfTsConfigPresence: Map<Project>;
private readonly throttledOperations: ThrottledOperations; private readonly throttledOperations: ThrottledOperations;
private readonly hostConfiguration: HostConfiguration; private readonly hostConfiguration: HostConfiguration;
@ -407,8 +378,7 @@ namespace ts.server {
this.currentDirectory = this.host.getCurrentDirectory(); this.currentDirectory = this.host.getCurrentDirectory();
this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
this.directoryWatchers = new DirectoryWatchers(this); this.mapOfConfigFilePresence = createMap<ConfigFileExistence>();
this.mapOfTsConfigPresence = createMap<Project>();
this.throttledOperations = new ThrottledOperations(this.host); this.throttledOperations = new ThrottledOperations(this.host);
this.typingsInstaller.attach(this); this.typingsInstaller.attach(this);
@ -519,10 +489,6 @@ namespace ts.server {
this.delayUpdateProjectGraphs(this.inferredProjects); this.delayUpdateProjectGraphs(this.inferredProjects);
} }
stopWatchingDirectory(directory: string) {
this.directoryWatchers.stopWatchingDirectory(directory);
}
findProject(projectName: string): Project { findProject(projectName: string): Project {
if (projectName === undefined) { if (projectName === undefined) {
return undefined; return undefined;
@ -701,36 +667,53 @@ namespace ts.server {
} }
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath);
if (eventKind === FileWatcherEventKind.Deleted) { if (eventKind === FileWatcherEventKind.Deleted) {
// Update the cached status
// No action needed on tracking open files since the existing config file anyways didnt affect the tracking file
configFilePresenceInfo.exists = false;
this.logTrackingFiles(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.NoAction);
this.removeProject(project); this.removeProject(project);
// Reload the configured projects for these open files in the project as // Reload the configured projects for these open files in the project as
// they could be held up by another config file somewhere in the parent directory // they could be held up by another config file somewhere in the parent directory
const openFilesInProject = filter(this.openFiles, file => file.containingProjects.length === 0); const orphanFiles = filter(this.openFiles, file => file.containingProjects.length === 0);
this.reloadConfiguredProjectForFiles(openFilesInProject, project => { project.pendingReload = true; this.delayUpdateProjectGraph(project); }); this.delayReloadConfiguredProjectForFiles(orphanFiles);
this.delayInferredProjectsRefresh();
} }
else { else {
project.pendingReload = true; project.pendingReload = true;
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); this.logTrackingFiles(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
if (configFilePresenceInfo.trackingOpenFiles) {
this.delayUpdateProjectGraph(project);
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFiles);
}
else {
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
}
} }
} }
/** /**
* This is the callback function when a watched directory has an added tsconfig file. * This is the callback function for the config file add/remove/change for the root in the inferred project
*/ */
private onConfigFileAddedForInferredProject(fileName: string) { private onConfigFileAddedForInferredProject(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) {
// TODO: check directory separators // This callback is called only if we dont have config file project for this config file
if (getBaseFileName(fileName) !== "tsconfig.json") { const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName);
this.logger.info(`${fileName} is not tsconfig.json`); const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath);
return;
}
// TODO: (sheetalkamat) if (eventKind === FileWatcherEventKind.Deleted) {
// 1. We should only watch tsconfig/jsconfig file here instead of watching directory // No action needed if the event was for deletion of the file
// 2. We should try reloading projects with open files in Inferred project only // - because the existing config file didnt affect the inferred project roots anyways
// 3. We should use this watcher to answer questions to findConfigFile rather than calling host everytime configFilePresenceInfo.exists = false;
this.logger.info(`Detected newly added tsconfig file: ${fileName}`); this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.NoAction);
this.reloadProjects(); }
else {
// Either the config file was created or changed
// Reload the projects
configFilePresenceInfo.exists = true;
this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFiles);
}
} }
private removeProject(project: Project) { private removeProject(project: Project) {
@ -882,13 +865,131 @@ namespace ts.server {
private configFileExists(configFileName: NormalizedPath) { private configFileExists(configFileName: NormalizedPath) {
const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName);
const cachedResult = this.mapOfTsConfigPresence.get(canonicalConfigFilePath); const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
if (cachedResult) { if (configFilePresenceInfo) {
// Use the information here to answer the question return configFilePresenceInfo.exists;
} }
return this.host.fileExists(configFileName); return this.host.fileExists(configFileName);
} }
private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) {
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath);
if (configFilePresenceInfo) {
configFilePresenceInfo.exists = true;
// close existing watcher
if (configFilePresenceInfo.configFileWatcher) {
const configFileName = project.getConfigFilePath();
this.closeFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated
);
configFilePresenceInfo.configFileWatcher = undefined;
this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
}
}
else {
// Mark existence of the config file with the project creation
this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, { exists: true });
}
}
/* @internal */
setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) {
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath);
Debug.assert(!!configFilePresenceInfo);
if (configFilePresenceInfo.trackingOpenFiles) {
const configFileName = closedProject.getConfigFilePath();
configFilePresenceInfo.configFileWatcher = this.addFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
(_filename, eventKind) => this.onConfigFileAddedForInferredProject(configFileName, eventKind)
);
this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
}
else {
// There is no one tracking anymore. Remove the status
this.mapOfConfigFilePresence.delete(closedProject.canonicalConfigFilePath);
}
}
private logTrackingFiles(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) {
const watchType = configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath;
const files = map(configFilePresenceInfo.trackingOpenFiles, info => info.fileName);
this.logger.info(`FileWatcher:: ${watchType}: File: ${configFileName} Currently Tracking for files: ${files} Status: ${status}`);
}
private watchConfigFileForInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo) {
let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
if (configFilePresenceInfo) {
// Existing information - just add to tracking files
(configFilePresenceInfo.trackingOpenFiles || (configFilePresenceInfo.trackingOpenFiles = [])).push(root);
}
else {
// Add new callback
configFilePresenceInfo = {
exists: this.host.fileExists(configFileName),
trackingOpenFiles: [root],
configFileWatcher: this.addFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
(_fileName, eventKind) => this.onConfigFileAddedForInferredProject(configFileName, eventKind)
)
};
this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo);
}
this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
}
private closeWatchConfigFileForInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo, reason: WatcherCloseReason) {
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
Debug.assert(!!configFilePresenceInfo);
if (configFilePresenceInfo.trackingOpenFiles.length === 1) {
configFilePresenceInfo.trackingOpenFiles = undefined;
this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved);
if (configFilePresenceInfo.configFileWatcher) {
this.closeFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined,
configFileName, configFilePresenceInfo.configFileWatcher, reason
);
this.mapOfConfigFilePresence.delete(canonicalConfigFilePath);
}
}
else {
removeItemFromSet(configFilePresenceInfo.trackingOpenFiles, root);
this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved);
}
}
private enumerateWatchingRootOfInferredProject(root: ScriptInfo,
action: (configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo) => void) {
let current = root.fileName;
let currentPath = getDirectoryPath(root.path);
let parentPath = getDirectoryPath(currentPath);
while (currentPath !== parentPath) {
current = asNormalizedPath(getDirectoryPath(current));
action(asNormalizedPath(combinePaths(current, "tsconfig.json")), combinePaths(currentPath, "tsconfig.json"), root);
//if (root.isJavaScript()) {
// this.watchConfigFileForInferredRoot(asNormalizedPath(combinePaths(current, "jsconfig.json")), combinePaths(currentPath, "jsconfig.json"), root);
//}
currentPath = parentPath;
parentPath = getDirectoryPath(parentPath);
}
}
/*@internal*/
startWatchingRootOfInferredProject(root: ScriptInfo) {
this.enumerateWatchingRootOfInferredProject(root,
(configFileName, canonicalConfigFilePath, root) =>
this.watchConfigFileForInferredRoot(configFileName, canonicalConfigFilePath, root)
);
}
/*@internal*/
stopWatchingRootOfInferredProject(root: ScriptInfo, reason: WatcherCloseReason) {
this.enumerateWatchingRootOfInferredProject(root,
(configFileName, canonicalConfigFilePath, root) =>
this.closeWatchConfigFileForInferredRoot(configFileName, canonicalConfigFilePath, root, reason)
);
}
/** /**
* This function tries to search for a tsconfig.json for the given file. * This function tries to search for a tsconfig.json for the given file.
* This is different from the method the compiler uses because * This is different from the method the compiler uses because
@ -1124,6 +1225,7 @@ namespace ts.server {
this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
this.configuredProjects.push(project); this.configuredProjects.push(project);
this.setConfigFilePresenceByNewConfiguredProject(project);
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions);
return project; return project;
} }
@ -1172,7 +1274,7 @@ namespace ts.server {
if (scriptInfo.isScriptOpen()) { if (scriptInfo.isScriptOpen()) {
// if file is already root in some inferred project // if file is already root in some inferred project
// - remove the file from that project and delete the project if necessary // - remove the file from that project and delete the project if necessary
this.removRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo); this.removeRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo);
} }
} }
} }
@ -1261,11 +1363,7 @@ namespace ts.server {
: new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects); : new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects);
project.addRoot(root); project.addRoot(root);
this.startWatchingRootOfInferredProject(root);
this.directoryWatchers.startWatchingContainingDirectoriesForFile(
root.fileName,
project,
fileName => this.onConfigFileAddedForInferredProject(fileName));
project.updateGraph(); project.updateGraph();
if (!useExistingProject) { if (!useExistingProject) {
@ -1415,11 +1513,22 @@ namespace ts.server {
*/ */
reloadProjects() { reloadProjects() {
this.logger.info("reload projects."); this.logger.info("reload projects.");
this.reloadConfiguredProjectForFiles(this.openFiles, project => this.reloadConfiguredProject(project)); this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false);
this.refreshInferredProjects(); this.refreshInferredProjects();
} }
reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], reload: (project: ConfiguredProject) => void) { delayReloadConfiguredProjectForFiles(openFiles: ScriptInfo[]) {
this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true);
this.delayInferredProjectsRefresh();
}
/**
* This function goes through all the openFiles and tries to file the config file for them.
* If the config file is found and it refers to existing project, it reloads it either immediately
* or schedules it for reload depending on delayedReload option
* If the there is no existing project it just opens the configured project for the config file
*/
reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) {
const mapUpdatedProjects = createMap<true>(); const mapUpdatedProjects = createMap<true>();
// try to reload config file for all open files // try to reload config file for all open files
for (const info of openFiles) { for (const info of openFiles) {
@ -1435,7 +1544,13 @@ namespace ts.server {
mapUpdatedProjects.set(configFileName, true); mapUpdatedProjects.set(configFileName, true);
} }
else if (!mapUpdatedProjects.has(configFileName)) { else if (!mapUpdatedProjects.has(configFileName)) {
reload(project); if (delayReload) {
project.pendingReload = true;
this.delayUpdateProjectGraph(project);
}
else {
this.reloadConfiguredProject(project);
}
mapUpdatedProjects.set(configFileName, true); mapUpdatedProjects.set(configFileName, true);
} }
} }
@ -1448,7 +1563,7 @@ namespace ts.server {
* - references in inferred project supercede the root part * - references in inferred project supercede the root part
* - root/reference in non-inferred project beats root in inferred project * - root/reference in non-inferred project beats root in inferred project
*/ */
private removRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { private removeRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) {
if (info.containingProjects.length > 1 && if (info.containingProjects.length > 1 &&
info.containingProjects[0].projectKind === ProjectKind.Inferred && info.containingProjects[0].projectKind === ProjectKind.Inferred &&
info.containingProjects[0].isRoot(info)) { info.containingProjects[0].isRoot(info)) {
@ -1478,7 +1593,7 @@ namespace ts.server {
} }
// Or remove the root of inferred project if is referenced in more than one projects // Or remove the root of inferred project if is referenced in more than one projects
else { else {
this.removRootOfInferredProjectIfNowPartOfOtherProject(info); this.removeRootOfInferredProjectIfNowPartOfOtherProject(info);
} }
} }
@ -1766,7 +1881,8 @@ namespace ts.server {
const rootFiles: protocol.ExternalFile[] = []; const rootFiles: protocol.ExternalFile[] = [];
for (const file of proj.rootFiles) { for (const file of proj.rootFiles) {
const normalized = toNormalizedPath(file.fileName); const normalized = toNormalizedPath(file.fileName);
if (getBaseFileName(normalized) === "tsconfig.json") { const baseFileName = getBaseFileName(normalized);
if (baseFileName === "tsconfig.json" || baseFileName === "jsconfig.json") {
if (this.host.fileExists(normalized)) { if (this.host.fileExists(normalized)) {
(tsConfigFiles || (tsConfigFiles = [])).push(normalized); (tsConfigFiles || (tsConfigFiles = [])).push(normalized);
} }

View file

@ -648,7 +648,7 @@ namespace ts.server {
missingFilePath => { missingFilePath => {
const fileWatcher = this.projectService.addFileWatcher( const fileWatcher = this.projectService.addFileWatcher(
WatchType.MissingFilePath, this, missingFilePath, WatchType.MissingFilePath, this, missingFilePath,
(filename: string, eventKind: FileWatcherEventKind) => { (filename, eventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath); this.missingFilesMap.delete(missingFilePath);
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
@ -911,9 +911,6 @@ namespace ts.server {
super.setCompilerOptions(newOptions); super.setCompilerOptions(newOptions);
} }
// Used to keep track of what directories are watched for this project
directoriesWatchedForTsconfig: string[] = [];
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) {
super(InferredProject.newName(), super(InferredProject.newName(),
ProjectKind.Inferred, ProjectKind.Inferred,
@ -927,6 +924,7 @@ namespace ts.server {
} }
addRoot(info: ScriptInfo) { addRoot(info: ScriptInfo) {
this.projectService.startWatchingRootOfInferredProject(info);
if (!this._isJsInferredProject && info.isJavaScript()) { if (!this._isJsInferredProject && info.isJavaScript()) {
this.toggleJsInferredProject(/*isJsInferredProject*/ true); this.toggleJsInferredProject(/*isJsInferredProject*/ true);
} }
@ -934,6 +932,7 @@ namespace ts.server {
} }
removeRoot(info: ScriptInfo) { removeRoot(info: ScriptInfo) {
this.projectService.stopWatchingRootOfInferredProject(info, WatcherCloseReason.NotNeeded);
super.removeRoot(info); super.removeRoot(info);
if (this._isJsInferredProject && info.isJavaScript()) { if (this._isJsInferredProject && info.isJavaScript()) {
if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) { if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) {
@ -958,11 +957,8 @@ namespace ts.server {
} }
close() { close() {
forEach(this.getRootScriptInfos(), root => this.projectService.stopWatchingRootOfInferredProject(root, WatcherCloseReason.ProjectClose));
super.close(); super.close();
for (const directory of this.directoriesWatchedForTsconfig) {
this.projectService.stopWatchingDirectory(directory);
}
} }
getTypeAcquisition(): TypeAcquisition { getTypeAcquisition(): TypeAcquisition {
@ -1212,6 +1208,7 @@ namespace ts.server {
if (this.configFileWatcher) { if (this.configFileWatcher) {
this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose); this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose);
this.configFileWatcher = undefined; this.configFileWatcher = undefined;
this.projectService.setConfigFilePresenceByClosedConfigFile(this);
} }
this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose); this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose);