diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 01c7bc59fdd..0b6b676f7f6 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -588,7 +588,8 @@ namespace ts { // Some file or directory in the watching directory is created // Return early if it does not have any of the watching extension or not the custom failed lookup path const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); - if (isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { + if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || + isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { // Invalidate any resolution from this directory isChangedFailedLookupLocation = location => { const locationPath = resolutionHost.toPath(location); diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 47116cd5b01..e056153518a 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -2113,6 +2113,28 @@ declare module "fs" { }; } }); + + it("works when renaming node_modules folder that already contains @types folder", () => { + const currentDirectory = "/user/username/projects/myproject"; + const file: FileOrFolder = { + path: `${currentDirectory}/a.ts`, + content: `import * as q from "qqq";` + }; + const module: FileOrFolder = { + path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, + content: "export {}" + }; + const files = [file, module, libFile]; + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfFilesAndCompilerOptions([file.path], host); + checkProgramActualFiles(watch(), [file.path, libFile.path]); + checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); + + host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); + checkOutputErrorsIncremental(host, emptyArray); + }); }); describe("tsc-watch with when module emit is specified as node", () => { diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 8b62c210abb..c206d219e2a 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -455,16 +455,24 @@ interface Array {}` 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.renameFolderEntries(folder, newFolder); + } + + private renameFolderEntries(oldFolder: Folder, newFolder: Folder) { + for (const entry of oldFolder.entries) { this.fs.delete(entry.path); this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Deleted); - entry.fullPath = combinePaths(newFullPath, getBaseFileName(entry.fullPath)); + entry.fullPath = combinePaths(newFolder.fullPath, getBaseFileName(entry.fullPath)); entry.path = this.toPath(entry.fullPath); - newFolder.entries.push(entry); + if (newFolder !== oldFolder) { + newFolder.entries.push(entry); + } this.fs.set(entry.path, entry); this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Created); + if (isFolder(entry)) { + this.renameFolderEntries(entry, entry); + } } }