Instead of watching directories, watch tsconfig files of inferred project root
This commit is contained in:
parent
00011a52af
commit
0572b15adc
|
@ -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]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue