From a5861af00e06ee8201f90446cb0ae37173ddde0b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Oct 2017 14:13:12 -0700 Subject: [PATCH] Handle when directory watcher is invoked on file change Fixes #19206 --- src/compiler/core.ts | 17 ++++++++-- src/compiler/watch.ts | 13 ++++++-- src/harness/unittests/tscWatchMode.ts | 40 +++++++++++++++++++++++ src/harness/virtualFileSystemWithWatch.ts | 9 +++-- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 542435b84f901..8f3eacae667bb 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2701,8 +2701,14 @@ namespace ts { export function assertTypeIsNever(_: never): void { } + export interface FileAndDirectoryExistence { + fileExists: boolean; + directoryExists: boolean; + } + export interface CachedDirectoryStructureHost extends DirectoryStructureHost { - addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void; + /** Returns the queried result for the file exists and directory exists if at all it was done */ + addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; clearCache(): void; } @@ -2872,8 +2878,13 @@ namespace ts { if (parentResult) { const baseName = getBaseNameOfFileName(fileOrDirectory); if (parentResult) { - updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath)); - updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath)); + const fsQueryResult: FileAndDirectoryExistence = { + fileExists: host.fileExists(fileOrDirectoryPath), + directoryExists: host.directoryExists(fileOrDirectoryPath) + }; + updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); + updateFileSystemEntry(parentResult.directories, baseName, fsQueryResult.directoryExists); + return fsQueryResult; } } } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 4fc67c1cc8fc2..6f61469197b21 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -605,8 +605,17 @@ namespace ts { const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - removeSourceFile(fileOrDirectoryPath); + const result = (directoryStructureHost as 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 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 b25a7b1eb53c2..3c2856f3caffc 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1819,4 +1819,44 @@ declare module "fs" { checkOutputErrors(host); }); }); + + describe("tsc-watch with when module emit is specified as node", () => { + it("when instead of filechanged recursive directory watcher is invoked", () => { + const configFile: FileOrFolder = { + path: "/a/rootFolder/project/tsconfig.json", + content: JSON.stringify({ + "compilerOptions": { + "module": "none", + "allowJs": true, + "outDir": "Static/scripts/" + }, + "include": [ + "Scripts/**/*" + ], + }) + }; + const outputFolder = "/a/rootFolder/project/Static/scripts/"; + const file1: FileOrFolder = { + path: "/a/rootFolder/project/Scripts/TypeScript.ts", + content: "var z = 10;" + }; + const file2: FileOrFolder = { + path: "/a/rootFolder/project/Scripts/Javascript.js", + content: "var zz = 10;" + }; + const files = [configFile, file1, file2, libFile]; + const host = createWatchedSystem(files); + const watch = createWatchModeWithConfigFile(configFile.path, host); + + checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); + file1.content = "var zz30 = 100;"; + host.reloadFS(files, /*invokeDirectoryWatcherInsteadOfFileChanged*/ true); + host.runQueuedTimeoutCallbacks(); + + checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); + const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js"); + assert.isTrue(host.fileExists(outputFile1)); + assert.equal(host.readFile(outputFile1), file1.content + host.newLine); + }); + }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 67b3e264db4ad..c74be545f8fc4 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -250,7 +250,7 @@ namespace ts.TestFSWithWatch { return this.toPath(this.toNormalizedAbsolutePath(s)); } - reloadFS(fileOrFolderList: ReadonlyArray) { + reloadFS(fileOrFolderList: ReadonlyArray, invokeDirectoryWatcherInsteadOfFileChanged?: boolean) { const mapNewLeaves = createMap(); const isNewFs = this.fs.size === 0; // always inject safelist file in the list of files @@ -265,7 +265,12 @@ namespace ts.TestFSWithWatch { // Update file if (currentEntry.content !== fileOrDirectory.content) { currentEntry.content = fileOrDirectory.content; - this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); + if (invokeDirectoryWatcherInsteadOfFileChanged) { + this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath); + } + else { + this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); + } } } else {