diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 117887d7611..c506c656de7 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -323,6 +323,7 @@ namespace ts.server { cancellationToken: HostCancellationToken; useSingleInferredProject: boolean; useInferredProjectPerProjectRoot: boolean; + lazyConfiguredProjectsFromExternalProject?: boolean; typingsInstaller: ITypingsInstaller; eventHandler?: ProjectServiceEventHandler; suppressDiagnosticEvents?: boolean; @@ -440,6 +441,7 @@ namespace ts.server { public readonly cancellationToken: HostCancellationToken; public readonly useSingleInferredProject: boolean; public readonly useInferredProjectPerProjectRoot: boolean; + private readonly lazyConfiguredProjectsFromExternalProject?: boolean; public readonly typingsInstaller: ITypingsInstaller; private readonly globalCacheLocationDirectoryPath: Path | undefined; public readonly throttleWaitMilliseconds?: number; @@ -465,6 +467,7 @@ namespace ts.server { this.cancellationToken = opts.cancellationToken; this.useSingleInferredProject = opts.useSingleInferredProject; this.useInferredProjectPerProjectRoot = opts.useInferredProjectPerProjectRoot; + this.lazyConfiguredProjectsFromExternalProject = opts.lazyConfiguredProjectsFromExternalProject; this.typingsInstaller = opts.typingsInstaller || nullTypingsInstaller; this.throttleWaitMilliseconds = opts.throttleWaitMilliseconds; this.eventHandler = opts.eventHandler; @@ -1561,6 +1564,13 @@ namespace ts.server { return project; } + /* @internal */ + private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath) { + const project = this.createAndLoadConfiguredProject(configFileName); + project.updateGraph(); + return project; + } + /** * Read the config file of the project, and update the project root file names. */ @@ -2192,8 +2202,7 @@ namespace ts.server { if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { - project = this.createAndLoadConfiguredProject(configFileName); - project.updateGraph(); + project = this.createLoadAndUpdateConfiguredProject(configFileName); // Send the event only if the project got created as part of this open request and info is part of the project if (info.isOrphan()) { // Since the file isnt part of configured project, do not send config file info @@ -2633,7 +2642,9 @@ namespace ts.server { let project = this.findConfiguredProjectByProjectName(tsconfigFile); if (!project) { // errors are stored in the project, do not need to update the graph - project = this.createConfiguredProjectWithDelayLoad(tsconfigFile); + project = this.lazyConfiguredProjectsFromExternalProject ? + this.createConfiguredProjectWithDelayLoad(tsconfigFile) : + this.createLoadAndUpdateConfiguredProject(tsconfigFile); } if (project && !contains(exisingConfigFiles, tsconfigFile)) { // keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project diff --git a/src/server/session.ts b/src/server/session.ts index 8d2fbe9e190..69328b4c73b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -465,6 +465,7 @@ namespace ts.server { cancellationToken: ServerCancellationToken; useSingleInferredProject: boolean; useInferredProjectPerProjectRoot: boolean; + lazyConfiguredProjectsFromExternalProject?: boolean; typingsInstaller: ITypingsInstaller; byteLength: (buf: string, encoding?: string) => number; hrtime: (start?: number[]) => number[]; @@ -536,6 +537,7 @@ namespace ts.server { cancellationToken: this.cancellationToken, useSingleInferredProject: opts.useSingleInferredProject, useInferredProjectPerProjectRoot: opts.useInferredProjectPerProjectRoot, + lazyConfiguredProjectsFromExternalProject: opts.lazyConfiguredProjectsFromExternalProject, typingsInstaller: this.typingsInstaller, throttleWaitMilliseconds, eventHandler: this.eventHandler, diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 589668538c7..7ef838fd6ff 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -642,37 +642,53 @@ namespace ts.projectSystem { checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); }); - it("can handle tsconfig file name with difference casing", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - include: [] - }) - }; + describe("can handle tsconfig file name with difference casing", () => { + function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + include: [] + }) + }; - const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); - const service = createProjectService(host); - const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - service.openExternalProject({ - projectFileName: "/a/b/project.csproj", - rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), - options: {} + const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); + const service = createProjectService(host, /*parameters*/ undefined, { lazyConfiguredProjectsFromExternalProject }); + const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); + service.openExternalProject({ + projectFileName: "/a/b/project.csproj", + rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + if (lazyConfiguredProjectsFromExternalProject) { + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, emptyArray); + } + else { + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, [upperCaseConfigFilePath]); + } + + service.openClientFile(f1.path); + service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated + checkProjectActualFiles(project, [upperCaseConfigFilePath]); + checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false); }); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - service.openClientFile(f1.path); - service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated - checkProjectActualFiles(project, [upperCaseConfigFilePath]); - checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); }); it("create configured project without file list", () => { @@ -2950,45 +2966,55 @@ namespace ts.projectSystem { assert.equal(navbar[0].spans[0].length, f1.content.length); }); - it("deleting config file opened from the external project works", () => { - const site = { - path: "/user/someuser/project/js/site.js", - content: "" - }; - const configFile = { - path: "/user/someuser/project/tsconfig.json", - content: "{}" - }; - const projectFileName = "/user/someuser/project/WebApplication6.csproj"; - const host = createServerHost([libFile, site, configFile]); - const projectService = createProjectService(host); + describe("deleting config file opened from the external project works", () => { + function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) { + const site = { + path: "/user/someuser/project/js/site.js", + content: "" + }; + const configFile = { + path: "/user/someuser/project/tsconfig.json", + content: "{}" + }; + const projectFileName = "/user/someuser/project/WebApplication6.csproj"; + const host = createServerHost([libFile, site, configFile]); + const projectService = createProjectService(host, /*parameters*/ undefined, { lazyConfiguredProjectsFromExternalProject }); - const externalProject: protocol.ExternalProject = { - projectFileName, - rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], - options: { allowJs: false }, - typeAcquisition: { include: [] } - }; + const externalProject: protocol.ExternalProject = { + projectFileName, + rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], + options: { allowJs: false }, + typeAcquisition: { include: [] } + }; - projectService.openExternalProjects([externalProject]); + projectService.openExternalProjects([externalProject]); - let knownProjects = projectService.synchronizeProjectList([]); - checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); + let knownProjects = projectService.synchronizeProjectList([]); + checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); - const configProject = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configProject, []); // Since no files opened from this project, its not loaded + const configProject = configuredProjectAt(projectService, 0); + checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ? + emptyArray : // Since no files opened from this project, its not loaded + [libFile.path, configFile.path]); - host.reloadFS([libFile, site]); - host.checkTimeoutQueueLengthAndRun(1); + host.reloadFS([libFile, site]); + host.checkTimeoutQueueLengthAndRun(1); - knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 - checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); + knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); - externalProject.rootFiles.length = 1; - projectService.openExternalProjects([externalProject]); + externalProject.rootFiles.length = 1; + projectService.openExternalProjects([externalProject]); - checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); - checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); + checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); }); it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => { @@ -3298,49 +3324,60 @@ namespace ts.projectSystem { }); - it("includes deferred files in the project context", () => { - const file1 = { - path: "/a.deferred", - content: "const a = 1;" - }; - // Deferred extensions should not affect JS files. - const file2 = { - path: "/b.js", - content: "const b = 1;" - }; - const tsconfig = { - path: "/tsconfig.json", - content: "" - }; + describe("includes deferred files in the project context", () => { + function verifyDeferredContext(lazyConfiguredProjectsFromExternalProject: boolean) { + const file1 = { + path: "/a.deferred", + content: "const a = 1;" + }; + // Deferred extensions should not affect JS files. + const file2 = { + path: "/b.js", + content: "const b = 1;" + }; + const tsconfig = { + path: "/tsconfig.json", + content: "" + }; - const host = createServerHost([file1, file2, tsconfig]); - const session = createSession(host); - const projectService = session.getProjectService(); + const host = createServerHost([file1, file2, tsconfig]); + const session = createSession(host, { lazyConfiguredProjectsFromExternalProject }); + const projectService = session.getProjectService(); - // Configure the deferred extension. - const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + // Configure the deferred extension. + const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); - // Open external project - const projectName = "/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([file1.path, file2.path, tsconfig.path]), - options: {} + // Open external project + const projectName = "/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([file1.path, file2.path, tsconfig.path]), + options: {} + }); + + // Assert + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + const configuredProject = configuredProjectAt(projectService, 0); + if (lazyConfiguredProjectsFromExternalProject) { + // configured project is just created and not yet loaded + checkProjectActualFiles(configuredProject, emptyArray); + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]); + + // Allow allowNonTsExtensions will be set to true for deferred extensions. + assert.isTrue(configuredProject.getCompilerOptions().allowNonTsExtensions); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyDeferredContext(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyDeferredContext(/*lazyConfiguredProjectsFromExternalProject*/ true); }); - - // Assert - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - - const configuredProject = configuredProjectAt(projectService, 0); - // configured project is just created and not yet loaded - checkProjectActualFiles(configuredProject, emptyArray); - projectService.ensureInferredProjectsUpToDate_TestOnly(); - checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]); - - // Allow allowNonTsExtensions will be set to true for deferred extensions. - assert.isTrue(configuredProject.getCompilerOptions().allowNonTsExtensions); }); it("Orphan source files are handled correctly on watch trigger", () => { @@ -3943,143 +3980,165 @@ namespace ts.projectSystem { }); describe("tsserverProjectSystem external projects", () => { - it("correctly handling add/remove tsconfig - 1", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const f2 = { - path: "/a/b/lib.ts", - content: "" - }; - const tsconfig = { - path: "/a/b/tsconfig.json", - content: "" - }; - const host = createServerHost([f1, f2]); - const projectService = createProjectService(host); + describe("correctly handling add/remove tsconfig - 1", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const f2 = { + path: "/a/b/lib.ts", + content: "" + }; + const tsconfig = { + path: "/a/b/tsconfig.json", + content: "" + }; + const host = createServerHost([f1, f2]); + const projectService = createProjectService(host, /*parameters*/ undefined, { lazyConfiguredProjectsFromExternalProject }); - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + + // rename lib.ts to tsconfig.json + host.reloadFS([f1, tsconfig]); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, tsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); + + // rename tsconfig.json back to lib.ts + host.reloadFS([f1, f2]); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - - // rename lib.ts to tsconfig.json - host.reloadFS([f1, tsconfig]); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, tsconfig.path]), - options: {} + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - - // rename tsconfig.json back to lib.ts - host.reloadFS([f1, f2]); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); }); + describe("correctly handling add/remove tsconfig - 2", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const cLib = { + path: "/a/b/c/lib.ts", + content: "" + }; + const cTsconfig = { + path: "/a/b/c/tsconfig.json", + content: "{}" + }; + const dLib = { + path: "/a/b/d/lib.ts", + content: "" + }; + const dTsconfig = { + path: "/a/b/d/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); + const projectService = createProjectService(host, /*parameters*/ undefined, { lazyConfiguredProjectsFromExternalProject }); - it("correctly handling add/remove tsconfig - 2", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const cLib = { - path: "/a/b/c/lib.ts", - content: "" - }; - const cTsconfig = { - path: "/a/b/c/tsconfig.json", - content: "{}" - }; - const dLib = { - path: "/a/b/d/lib.ts", - content: "" - }; - const dTsconfig = { - path: "/a/b/d/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); - const projectService = createProjectService(host); + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); + + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // remove one config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, dTsconfig.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); + + // remove second config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); + + // open two config files + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // close all projects - no projects should be opened + projectService.closeExternalProject(projectName); + projectService.checkNumberOfProjects({}); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // remove one config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, dTsconfig.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - - // remove second config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // open two config files - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // close all projects - no projects should be opened - projectService.closeExternalProject(projectName); - projectService.checkNumberOfProjects({}); }); it("correctly handles changes in lib section of config file", () => { diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 3e5986e9408..6b8a146d8f3 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -499,6 +499,7 @@ namespace ts.server { cancellationToken, useSingleInferredProject, useInferredProjectPerProjectRoot, + lazyConfiguredProjectsFromExternalProject, typingsInstaller: typingsInstaller || nullTypingsInstaller, byteLength: Buffer.byteLength, hrtime: process.hrtime, @@ -937,6 +938,7 @@ namespace ts.server { const useSingleInferredProject = hasArgument("--useSingleInferredProject"); const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot"); + const lazyConfiguredProjectsFromExternalProject = hasArgument("--lazyConfiguredProjectsFromExternalProject"); const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents"); const syntaxOnly = hasArgument("--syntaxOnly"); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 584557e8b0f..3194a2b79bb 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8325,6 +8325,7 @@ declare namespace ts.server { cancellationToken: HostCancellationToken; useSingleInferredProject: boolean; useInferredProjectPerProjectRoot: boolean; + lazyConfiguredProjectsFromExternalProject?: boolean; typingsInstaller: ITypingsInstaller; eventHandler?: ProjectServiceEventHandler; suppressDiagnosticEvents?: boolean; @@ -8397,6 +8398,7 @@ declare namespace ts.server { readonly cancellationToken: HostCancellationToken; readonly useSingleInferredProject: boolean; readonly useInferredProjectPerProjectRoot: boolean; + private readonly lazyConfiguredProjectsFromExternalProject?; readonly typingsInstaller: ITypingsInstaller; private readonly globalCacheLocationDirectoryPath; readonly throttleWaitMilliseconds?: number; @@ -8598,6 +8600,7 @@ declare namespace ts.server { cancellationToken: ServerCancellationToken; useSingleInferredProject: boolean; useInferredProjectPerProjectRoot: boolean; + lazyConfiguredProjectsFromExternalProject?: boolean; typingsInstaller: ITypingsInstaller; byteLength: (buf: string, encoding?: string) => number; hrtime: (start?: number[]) => number[];