Merge pull request #19786 from Microsoft/directoryRename

Handle the watch when folders are added/removed/renamed in wild card folder
This commit is contained in:
Sheetal Nandi 2017-11-07 11:13:47 -08:00 committed by GitHub
commit fe40873664
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 162 additions and 39 deletions

View file

@ -3097,10 +3097,9 @@ namespace ts {
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
if (existingResult) { if (existingResult) {
// This was a folder already present, remove it if this doesnt exist any more // Just clear the cache for now
if (!host.directoryExists(fileOrDirectory)) { // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated
cachedReadDirectoryResult.delete(fileOrDirectoryPath); clearCache();
}
} }
else { else {
// This was earlier a file (hence not in cached directory contents) // This was earlier a file (hence not in cached directory contents)
@ -3113,8 +3112,14 @@ namespace ts {
fileExists: host.fileExists(fileOrDirectoryPath), fileExists: host.fileExists(fileOrDirectoryPath),
directoryExists: host.directoryExists(fileOrDirectoryPath) directoryExists: host.directoryExists(fileOrDirectoryPath)
}; };
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) {
updateFileSystemEntry(parentResult.directories, baseName, fsQueryResult.directoryExists); // Folder added or removed, clear the cache instead of updating the folder and its structure
clearCache();
}
else {
// No need to update the directory structure, just files
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
}
return fsQueryResult; return fsQueryResult;
} }
} }

View file

@ -230,7 +230,7 @@ namespace ts {
function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) { function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) {
let program: Program; let program: Program;
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
let watchedWildcardDirectories: Map<WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file let watchedWildcardDirectories: Map<WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
let timerToUpdateProgram: any; // timer callback to recompile the program let timerToUpdateProgram: any; // timer callback to recompile the program
@ -488,7 +488,7 @@ namespace ts {
function scheduleProgramReload() { function scheduleProgramReload() {
Debug.assert(!!configFileName); Debug.assert(!!configFileName);
needsReload = true; reloadLevel = ConfigFileProgramReloadLevel.Full;
scheduleProgramUpdate(); scheduleProgramUpdate();
} }
@ -496,17 +496,30 @@ namespace ts {
timerToUpdateProgram = undefined; timerToUpdateProgram = undefined;
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
if (needsReload) { switch (reloadLevel) {
reloadConfigFile(); case ConfigFileProgramReloadLevel.Partial:
return reloadFileNamesFromConfigFile();
case ConfigFileProgramReloadLevel.Full:
return reloadConfigFile();
default:
return synchronizeProgram();
} }
else { }
synchronizeProgram();
function reloadFileNamesFromConfigFile() {
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost);
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
} }
rootFileNames = result.fileNames;
// Update the program
synchronizeProgram();
} }
function reloadConfigFile() { function reloadConfigFile() {
writeLog(`Reloading config file: ${configFileName}`); writeLog(`Reloading config file: ${configFileName}`);
needsReload = false; reloadLevel = ConfigFileProgramReloadLevel.None;
const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; const cachedHost = directoryStructureHost as CachedDirectoryStructureHost;
cachedHost.clearCache(); cachedHost.clearCache();
@ -611,18 +624,14 @@ namespace ts {
// If the the added or created file or directory is not supported file name, ignore the file // If the the added or created file or directory is not supported file name, ignore the file
// But when watched directory is added/removed, we need to reload the file list // But when watched directory is added/removed, we need to reload the file list
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) { if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return; return;
} }
// Reload is pending, do the reload // Reload is pending, do the reload
if (!needsReload) { if (reloadLevel !== ConfigFileProgramReloadLevel.Full) {
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); reloadLevel = ConfigFileProgramReloadLevel.Partial;
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
rootFileNames = result.fileNames;
// Schedule Update the program // Schedule Update the program
scheduleProgramUpdate(); scheduleProgramUpdate();

View file

@ -2,6 +2,14 @@
/* @internal */ /* @internal */
namespace ts { namespace ts {
export enum ConfigFileProgramReloadLevel {
None,
/** Update the file name list from the disk */
Partial,
/** Reload completely by re-reading contents of config file from disk and updating program */
Full
}
/** /**
* Updates the existing missing file watches with the new set of missing files after new program is created * Updates the existing missing file watches with the new set of missing files after new program is created
*/ */

File diff suppressed because one or more lines are too long

View file

@ -346,6 +346,39 @@ interface Array<T> {}`
} }
} }
renameFolder(folderName: string, newFolderName: string) {
const fullPath = getNormalizedAbsolutePath(folderName, this.currentDirectory);
const path = this.toPath(fullPath);
const folder = this.fs.get(path) as Folder;
Debug.assert(!!folder);
// Only remove the folder
this.removeFileOrFolder(folder, returnFalse, /*isRenaming*/ true);
// Add updated folder with new folder name
const newFullPath = getNormalizedAbsolutePath(newFolderName, this.currentDirectory);
const newFolder = this.toFolder(newFullPath);
const newPath = newFolder.path;
const basePath = getDirectoryPath(path);
Debug.assert(basePath !== path);
Debug.assert(basePath === getDirectoryPath(newPath));
const baseFolder = this.fs.get(basePath) as Folder;
this.addFileOrFolderInFolder(baseFolder, newFolder);
// Invoke watches for files in the folder as deleted (from old path)
for (const entry of folder.entries) {
Debug.assert(isFile(entry));
this.fs.delete(entry.path);
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Deleted);
entry.fullPath = combinePaths(newFullPath, getBaseFileName(entry.fullPath));
entry.path = this.toPath(entry.fullPath);
newFolder.entries.push(entry);
this.fs.set(entry.path, entry);
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Created);
}
}
ensureFileOrFolder(fileOrDirectory: FileOrFolder, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean) { ensureFileOrFolder(fileOrDirectory: FileOrFolder, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean) {
if (isString(fileOrDirectory.content)) { if (isString(fileOrDirectory.content)) {
const file = this.toFile(fileOrDirectory); const file = this.toFile(fileOrDirectory);
@ -393,7 +426,7 @@ interface Array<T> {}`
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath); this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
} }
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
const basePath = getDirectoryPath(fileOrDirectory.path); const basePath = getDirectoryPath(fileOrDirectory.path);
const baseFolder = this.fs.get(basePath) as Folder; const baseFolder = this.fs.get(basePath) as Folder;
if (basePath !== fileOrDirectory.path) { if (basePath !== fileOrDirectory.path) {
@ -406,7 +439,7 @@ interface Array<T> {}`
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted); this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
} }
else { else {
Debug.assert(fileOrDirectory.entries.length === 0); Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath); const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
// Invoke directory and recursive directory watcher for the folder // Invoke directory and recursive directory watcher for the folder
// Here we arent invoking recursive directory watchers for the base folders // Here we arent invoking recursive directory watchers for the base folders

View file

@ -801,17 +801,14 @@ namespace ts.server {
// If the the added or created file or directory is not supported file name, ignore the file // If the the added or created file or directory is not supported file name, ignore the file
// But when watched directory is added/removed, we need to reload the file list // But when watched directory is added/removed, we need to reload the file list
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) {
this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrDirectory}`); this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return; return;
} }
// Reload is pending, do the reload // Reload is pending, do the reload
if (!project.pendingReload) { if (project.pendingReload !== ConfigFileProgramReloadLevel.Full) {
const configFileSpecs = project.configFileSpecs; project.pendingReload = ConfigFileProgramReloadLevel.Partial;
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(result.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader);
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
} }
}, },
@ -844,7 +841,7 @@ namespace ts.server {
} }
else { else {
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles); this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
project.pendingReload = true; project.pendingReload = ConfigFileProgramReloadLevel.Full;
this.delayUpdateProjectGraph(project); this.delayUpdateProjectGraph(project);
// As we scheduled the update on configured project graph, // As we scheduled the update on configured project graph,
// we would need to schedule the project reload for only the root of inferred projects // we would need to schedule the project reload for only the root of inferred projects
@ -1592,6 +1589,19 @@ namespace ts.server {
this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, newTypeAcquisition); this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, newTypeAcquisition);
} }
/**
* Reload the file names from config file specs and update the project graph
*/
/*@internal*/
reloadFileNamesOfConfiguredProject(project: ConfiguredProject): boolean {
const configFileSpecs = project.configFileSpecs;
const configFileName = project.getConfigFilePath();
const fileNamesResult = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(fileNamesResult.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames, fileNamePropertyReader);
return project.updateGraph();
}
/** /**
* Read the config file of the project again and update the project * Read the config file of the project again and update the project
*/ */
@ -1886,7 +1896,7 @@ namespace ts.server {
} }
else if (!updatedProjects.has(configFileName)) { else if (!updatedProjects.has(configFileName)) {
if (delayReload) { if (delayReload) {
project.pendingReload = true; project.pendingReload = ConfigFileProgramReloadLevel.Full;
this.delayUpdateProjectGraph(project); this.delayUpdateProjectGraph(project);
} }
else { else {

View file

@ -1122,7 +1122,7 @@ namespace ts.server {
readonly canonicalConfigFilePath: NormalizedPath; readonly canonicalConfigFilePath: NormalizedPath;
/* @internal */ /* @internal */
pendingReload: boolean; pendingReload: ConfigFileProgramReloadLevel;
/*@internal*/ /*@internal*/
configFileSpecs: ConfigFileSpecs; configFileSpecs: ConfigFileSpecs;
@ -1162,12 +1162,17 @@ namespace ts.server {
* @returns: true if set of files in the project stays the same and false - otherwise. * @returns: true if set of files in the project stays the same and false - otherwise.
*/ */
updateGraph(): boolean { updateGraph(): boolean {
if (this.pendingReload) { const reloadLevel = this.pendingReload;
this.pendingReload = false; this.pendingReload = ConfigFileProgramReloadLevel.None;
this.projectService.reloadConfiguredProject(this); switch (reloadLevel) {
return true; case ConfigFileProgramReloadLevel.Partial:
return this.projectService.reloadFileNamesOfConfiguredProject(this);
case ConfigFileProgramReloadLevel.Full:
this.projectService.reloadConfiguredProject(this);
return true;
default:
return super.updateGraph();
} }
return super.updateGraph();
} }
/*@internal*/ /*@internal*/