diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 3242a44ee220c..68c32d26c5930 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -16,6 +16,8 @@ namespace ts { setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap): void; createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; hasChangedAutomaticTypeDirectiveNames(): boolean; + isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean; + startCachingPerDirectoryResolution(): void; finishCachingPerDirectoryResolution(): void; @@ -208,6 +210,7 @@ namespace ts { invalidateResolutionsOfFailedLookupLocations, setFilesWithInvalidatedNonRelativeUnresolvedImports, createHasInvalidatedResolution, + isFileWithInvalidatedNonRelativeUnresolvedImports, updateTypeRootsWatch, closeTypeRootsWatch, clear diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5787a5f01a678..864cc0268e2ce 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -922,13 +922,12 @@ namespace ts.server { case ActionSet: // Update the typing files and update the project project.updateTypingFiles(this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings)); - break; + return; case ActionInvalidate: // Do not clear resolution cache, there was changes detected in typings, so enque typing request and let it get us correct results this.typingsCache.enqueueInstallTypingsForProject(project, project.lastCachedUnresolvedImportsList, /*forceRefresh*/ true); return; } - this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project); } /*@internal*/ diff --git a/src/server/project.ts b/src/server/project.ts index 21f3f55eb2696..7c95a633dae79 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1057,13 +1057,16 @@ namespace ts.server { /*@internal*/ updateTypingFiles(typingFiles: SortedReadonlyArray) { - enumerateInsertsAndDeletes(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()), + if (enumerateInsertsAndDeletes(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()), /*inserted*/ noop, removed => this.detachScriptInfoFromProject(removed) - ); - this.typingFiles = typingFiles; - // Invalidate files with unresolved imports - this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile); + )) { + // If typing files changed, then only schedule project update + this.typingFiles = typingFiles; + // Invalidate files with unresolved imports + this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile); + this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); + } } /* @internal */ diff --git a/src/testRunner/unittests/tsserver/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts index 40312f9303ab0..4a5926afaca8e 100644 --- a/src/testRunner/unittests/tsserver/projectErrors.ts +++ b/src/testRunner/unittests/tsserver/projectErrors.ts @@ -409,7 +409,7 @@ namespace ts.projectSystem { checkErrors([serverUtilities.path, app.path]); function checkErrors(openFiles: [string, string]) { - verifyGetErrRequestNoErrors({ session, host, files: openFiles, existingTimeouts: 2 }); + verifyGetErrRequestNoErrors({ session, host, files: openFiles }); } }); diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index 65938314f115a..566d098416311 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -419,7 +419,7 @@ namespace ts.projectSystem { unresolvedImports: response.unresolvedImports, }); - host.checkTimeoutQueueLengthAndRun(1); + host.checkTimeoutQueueLength(0); assert.isUndefined(request); }); diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 7a88d42da050e..d3d4ff1361774 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -198,10 +198,7 @@ namespace ts.projectSystem { checkNumberOfProjects(service, { inferredProjects: 1 }); session.clearMessages(); - host.checkTimeoutQueueLengthAndRun(2); - - checkProjectUpdatedInBackgroundEvent(session, [file.path]); - + host.checkTimeoutQueueLength(0); verifyGetErrRequest({ session, host, @@ -240,10 +237,7 @@ namespace ts.projectSystem { checkNumberOfProjects(service, { inferredProjects: 1 }); session.clearMessages(); - host.checkTimeoutQueueLengthAndRun(2); - - checkProjectUpdatedInBackgroundEvent(session, [file.path]); - + host.checkTimeoutQueueLength(0); verifyGetErrRequest({ session, host, diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index 8a449bccad7cd..5bb0fc9792a83 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -244,7 +244,7 @@ namespace ts.projectSystem { checkProjectActualFiles(p, [jqueryJs.path]); installer.installAll(/*expectedCount*/ 0); - host.checkTimeoutQueueLengthAndRun(2); + host.checkTimeoutQueueLength(0); checkNumberOfProjects(projectService, { inferredProjects: 1 }); // files should not be removed from project if ATA is skipped checkProjectActualFiles(p, [jqueryJs.path]); @@ -1024,9 +1024,8 @@ namespace ts.projectSystem { service.openClientFile(f.path); installer.checkPendingCommands(/*expectedCount*/ 0); - host.writeFile(fixedPackageJson.path, fixedPackageJson.content); - host.checkTimeoutQueueLengthAndRun(2); // To refresh the project and refresh inferred projects + host.checkTimeoutQueueLength(0); // expected install request installer.installAll(/*expectedCount*/ 1); host.checkTimeoutQueueLengthAndRun(2); @@ -1212,7 +1211,8 @@ namespace ts.projectSystem { } }; session.executeCommand(changeRequest); - host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects + host.checkTimeoutQueueLength(0); + proj.updateGraph(); const version2 = proj.lastCachedUnresolvedImportsList; assert.strictEqual(version1, version2, "set of unresolved imports should change"); }); @@ -1837,6 +1837,7 @@ namespace ts.projectSystem { const appPath = "/a/b/app.js" as Path; const foooPath = "/a/b/node_modules/fooo/index.d.ts"; function verifyResolvedModuleOfFooo(project: server.Project) { + server.updateProjectIfDirty(project); const foooResolution = project.getLanguageService().getProgram()!.getSourceFileByPath(appPath)!.resolvedModules!.get("fooo")!; assert.equal(foooResolution.resolvedFileName, foooPath); return foooResolution; @@ -1851,6 +1852,7 @@ namespace ts.projectSystem { path: foooPath, content: `export var x: string;` }; + const host = createServerHost([app, fooo]); const installer = new (class extends Installer { constructor() { @@ -1873,6 +1875,17 @@ namespace ts.projectSystem { checkProjectActualFiles(proj, typingFiles.map(f => f.path).concat(app.path, fooo.path)); const foooResolution2 = verifyResolvedModuleOfFooo(proj); assert.strictEqual(foooResolution1, foooResolution2); + projectService.applyChangesInOpenFiles(/*openFiles*/ undefined, arrayIterator([{ + fileName: app.path, + changes: arrayIterator([{ + span: { start: 0, length: 0 }, + newText: `import * as bar from "bar";` + }]) + }])); + host.runQueuedTimeoutCallbacks(); // Update the graph + // Update the typing + host.checkTimeoutQueueLength(0); + assert.isFalse(proj.resolutionCache.isFileWithInvalidatedNonRelativeUnresolvedImports(app.path as Path)); } it("correctly invalidate the resolutions with typing names", () => { @@ -1883,6 +1896,10 @@ namespace ts.projectSystem { }); it("correctly invalidate the resolutions with typing names that are trimmed", () => { + const fooIndex: File = { + path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`, + content: "export function aa(): void;" + }; const fooAA: File = { path: `${globalTypingsCacheLocation}/node_modules/foo/a/a.d.ts`, content: "export function a (): void;" @@ -1899,7 +1916,7 @@ namespace ts.projectSystem { import * as a from "foo/a/a"; import * as b from "foo/a/b"; import * as c from "foo/a/c"; - `, ["foo"], [fooAA, fooAB, fooAC]); + `, ["foo"], [fooIndex, fooAA, fooAB, fooAC]); }); it("should handle node core modules", () => { @@ -1958,12 +1975,21 @@ declare module "stream" { host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); - // Here, since typings doesnt contain node typings and resolution fails and - // node typings go out of project, - // but because we handle core node modules when resolving from typings cache - // node typings are included in the project - host.checkTimeoutQueueLengthAndRun(2); + // Here, since typings dont change, there is no timeout scheduled + host.checkTimeoutQueueLength(0); + checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); + projectService.applyChangesInOpenFiles(/*openFiles*/ undefined, arrayIterator([{ + fileName: file.path, + changes: arrayIterator([{ + span: { start: file.content.indexOf("const"), length: 0 }, + newText: `const bar = require("bar");` + }]) + }])); + proj.updateGraph(); // Update the graph checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); + // Update the typing + host.checkTimeoutQueueLength(0); + assert.isFalse(proj.resolutionCache.isFileWithInvalidatedNonRelativeUnresolvedImports(file.path as Path)); }); });