diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6e0384219a1..fb04d7b7a18 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -699,6 +699,11 @@ namespace ts { // If something to do with folder/file starting with "." in node_modules folder, skip it if (isPathIgnored(fileOrDirectoryPath)) return false; + // prevent saving an open file from over-eagerly triggering invalidation + if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + return false; + } + // 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); @@ -714,10 +719,6 @@ namespace ts { if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { return false; } - // prevent saving an open file from over-eagerly triggering invalidation - if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { - return false; - } // Ignore emits from the program if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { return false; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6bfce902a04..b0a64432633 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1002,7 +1002,13 @@ namespace ts.server { directory, fileOrDirectory => { const fileOrDirectoryPath = this.toPath(fileOrDirectory); - project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + const fileSystemResult = project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + + // don't trigger callback on open, existing files + if (project.fileIsOpen(fileOrDirectoryPath) && fileSystemResult && fileSystemResult.fileExists) { + return; + } + if (isPathIgnored(fileOrDirectoryPath)) return; const configFilename = project.getConfigFilePath(); diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index ba1eae236a5..dcbff895f1d 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1341,6 +1341,26 @@ var x = 10;` } }); + it("no project structure update on directory watch invoke on open file save", () => { + const projectRootPath = "/users/username/projects/project"; + const fileA: File = { + path: `${projectRootPath}/a.ts`, + content: "export const a = 10;" + }; + const config: File = { + path: `${projectRootPath}/tsconfig.json`, + content: "{}" + }; + const files = [fileA, config]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(fileA.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + + host.invokeWatchedDirectoriesRecursiveCallback(projectRootPath, "a.ts"); + host.checkTimeoutQueueLength(0); + }); + it("handles delayed directory watch invoke on file creation", () => { const projectRootPath = "/users/username/projects/project"; const fileB: File = { diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 11b604bb20c..05cbf8b9388 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -977,7 +977,7 @@ export const x = 10;` }); describe("avoid unnecessary invalidation", () => { - it("failed lookup invalidation", () => { + it("unnecessary lookup invalidation on save", () => { const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`]; const module1Name = "module1"; const module2Name = "module2"; @@ -999,6 +999,7 @@ export const x = 10;` verifyTrace(resolutionTrace, expectedTrace); verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + // invoke callback to simulate saving host.invokeWatchedDirectoriesRecursiveCallback(projectLocation + "/src", "file1.ts"); host.checkTimeoutQueueLengthAndRun(0); });