diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 08a30810568..2c386023e88 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -420,6 +420,9 @@ namespace ts { } } + const intialVersion = 1; + const intialVersionString = "1"; + /** * Creates the watch from the host for root files and compiler options */ @@ -429,11 +432,17 @@ namespace ts { */ export function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { - interface HostFileInfo { + interface FilePresentOnHost { version: number; sourceFile: SourceFile; fileWatcher: FileWatcher; } + type FileMissingOnHost = string; + interface FilePresenceUnknownOnHost { + version: number; + } + type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost; + type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost; let builderProgram: T; let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc @@ -441,7 +450,7 @@ namespace ts { let watchedWildcardDirectories: Map; // map of watchers for the wild card directories in the config file let timerToUpdateProgram: any; // timer callback to recompile the program - const sourceFilesCache = createMap(); // Cache that stores the source file and version info + const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed @@ -627,11 +636,20 @@ namespace ts { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } + function isFileMissingOnHost(hostSourceFile: HostFileInfo): hostSourceFile is FileMissingOnHost { + return isString(hostSourceFile); + } + + function isFilePresentOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresentOnHost { + return !!(hostSourceFile as FilePresentOnHost).sourceFile; + } + function fileExists(fileName: string) { const path = toPath(fileName); - const hostSourceFileInfo = sourceFilesCache.get(path); - if (hostSourceFileInfo !== undefined) { - return !isString(hostSourceFileInfo); + // If file is missing on host from cache, we can definitely say file doesnt exist + // otherwise we need to ensure from the disk + if (isFileMissingOnHost(sourceFilesCache.get(path))) { + return true; } return directoryStructureHost.fileExists(fileName); @@ -640,39 +658,42 @@ namespace ts { function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { const hostSourceFile = sourceFilesCache.get(path); // No source file on the host - if (isString(hostSourceFile)) { + if (isFileMissingOnHost(hostSourceFile)) { return undefined; } // Create new source file if requested or the versions dont match - if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { + if (!hostSourceFile || shouldCreateNewSourceFile || !isFilePresentOnHost(hostSourceFile) || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { const sourceFile = getNewSourceFile(); if (hostSourceFile) { if (shouldCreateNewSourceFile) { hostSourceFile.version++; } + if (sourceFile) { - hostSourceFile.sourceFile = sourceFile; + // Set the source file and create file watcher now that file was present on the disk + (hostSourceFile as FilePresentOnHost).sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); - if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); + if (!(hostSourceFile as FilePresentOnHost).fileWatcher) { + (hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); } } else { // There is no source file on host any more, close the watch, missing file paths will track it - hostSourceFile.fileWatcher.close(); + if (isFilePresentOnHost(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + } sourceFilesCache.set(path, hostSourceFile.version.toString()); } } else { - let fileWatcher: FileWatcher; if (sourceFile) { - sourceFile.version = "1"; - fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); - sourceFilesCache.set(path, { sourceFile, version: 1, fileWatcher }); + sourceFile.version = intialVersionString; + const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); + sourceFilesCache.set(path, { sourceFile, version: intialVersion, fileWatcher }); } else { - sourceFilesCache.set(path, "0"); + sourceFilesCache.set(path, intialVersionString); } } return sourceFile; @@ -697,20 +718,22 @@ namespace ts { } } - function removeSourceFile(path: Path) { + function nextSourceFileVersion(path: Path) { const hostSourceFile = sourceFilesCache.get(path); if (hostSourceFile !== undefined) { - if (!isString(hostSourceFile)) { - hostSourceFile.fileWatcher.close(); - resolutionCache.invalidateResolutionOfFile(path); + if (isFileMissingOnHost(hostSourceFile)) { + // The next version, lets set it as presence unknown file + sourceFilesCache.set(path, { version: Number(hostSourceFile) + 1 }); + } + else { + hostSourceFile.version++; } - sourceFilesCache.delete(path); } } function getSourceVersion(path: Path): string { const hostSourceFile = sourceFilesCache.get(path); - return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); + return !hostSourceFile || isFileMissingOnHost(hostSourceFile) ? undefined : hostSourceFile.version.toString(); } function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { @@ -721,10 +744,10 @@ namespace ts { // there was version update and new source file was created. if (hostSourceFileInfo) { // record the missing file paths so they can be removed later if watchers arent tracking them - if (isString(hostSourceFileInfo)) { + if (isFileMissingOnHost(hostSourceFileInfo)) { (missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path); } - else if (hostSourceFileInfo.sourceFile === oldSourceFile) { + else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) { sourceFilesCache.delete(oldSourceFile.path); resolutionCache.removeResolutionsOfFile(oldSourceFile.path); } @@ -808,27 +831,12 @@ namespace ts { function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { updateCachedSystemWithFile(fileName, path, eventKind); - const hostSourceFile = sourceFilesCache.get(path); - if (hostSourceFile) { - // Update the cache - if (eventKind === FileWatcherEventKind.Deleted) { - resolutionCache.invalidateResolutionOfFile(path); - if (!isString(hostSourceFile)) { - hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, (++hostSourceFile.version).toString()); - } - } - else { - // Deleted file created - if (isString(hostSourceFile)) { - sourceFilesCache.delete(path); - } - else { - // file changed - just update the version - hostSourceFile.version++; - } - } + + // Update the source file cache + if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.get(path)) { + resolutionCache.invalidateResolutionOfFile(path); } + nextSourceFileVersion(path); // Update the program scheduleProgramUpdate(); @@ -856,7 +864,7 @@ namespace ts { missingFilesMap.delete(missingFilePath); // Delete the entry in the source files cache so that new source file is created - removeSourceFile(missingFilePath); + nextSourceFileVersion(missingFilePath); // When a missing file is created, we should update the graph. scheduleProgramUpdate(); @@ -885,17 +893,10 @@ namespace ts { const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - const result = cachedDirectoryStructureHost && cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - - // Instead of deleting the file, mark it as changed instead - // Many times node calls add/remove/file when watching directories recursively - const hostSourceFile = sourceFilesCache.get(fileOrDirectoryPath); - if (hostSourceFile && !isString(hostSourceFile) && (result ? result.fileExists : directoryStructureHost.fileExists(fileOrDirectory))) { - hostSourceFile.version++; - } - else { - removeSourceFile(fileOrDirectoryPath); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } + nextSourceFileVersion(fileOrDirectoryPath); // 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 diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 45cabc2be0e..b7a477b04bf 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1730,7 +1730,7 @@ namespace ts.tscWatch { const configFile: FileOrFolder = { path: `${projectLocation}/tsconfig.json`, content: JSON.stringify({ - "include": [ + include: [ "app/**/*.ts" ] }) @@ -1740,7 +1740,7 @@ namespace ts.tscWatch { createWatchOfConfigFile("tsconfig.json", host); verifyProgram(); - file.content += "var b = 10;"; + file.content += "\nvar b = 10;"; host.reloadFS(files, { invokeFileDeleteCreateAsPartInsteadOfChange: true }); host.runQueuedTimeoutCallbacks();