diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 19ec12d7c5b..74972d23d96 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -63,7 +63,7 @@ namespace ts { const projectService = new server.ProjectService(svcOpts); const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined); - const project = projectService.assignScriptInfoToInferredProject(rootScriptInfo); + const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); return { project, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 8abec41de41..20fc4cb8a58 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -202,7 +202,7 @@ namespace ts.projectSystem { typingsInstaller: undefined, byteLength: Utils.byteLength, hrtime: process.hrtime, - logger: nullLogger, + logger: opts.logger || nullLogger, canUseEvents: false }; @@ -2634,6 +2634,47 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); }); + + it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => { + let hasErrorMsg = false; + const { close, hasLevel, loggingEnabled, info, group, perftrc, getLogFileName } = nullLogger; + const logger: server.Logger = { + close, hasLevel, loggingEnabled, info, group, perftrc, getLogFileName, + err: () => { + hasErrorMsg = true; + } + }; + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = createServerHost([f1, libFile, config]); + const session = createSession(host, { logger }); + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: f1.path + } + }); + session.executeCommandSeq({ + command: server.CommandNames.Close, + arguments: { + file: f1.path + } + }); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [f1.path] + } + }); + assert.isFalse(hasErrorMsg); + }); }); describe("Proper errors", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 02e039ae88d..9f24f1ffa3f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -564,7 +564,7 @@ namespace ts.server { this.ensureProjectStructuresUptoDate(); } const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); - return scriptInfo && scriptInfo.getDefaultProject(); + return scriptInfo && !scriptInfo.isOrphan() && scriptInfo.getDefaultProject(); } /** @@ -588,7 +588,7 @@ namespace ts.server { else { projectsToUpdate = []; for (const f of this.changedFiles) { - projectsToUpdate = projectsToUpdate.concat(f.containingProjects); + addRange(projectsToUpdate, f.containingProjects); } } this.changedFiles = undefined; @@ -789,8 +789,8 @@ namespace ts.server { } /*@internal*/ - assignScriptInfoToInferredProject(info: ScriptInfo, projectRootPath?: string) { - Debug.assert(info.containingProjects.length === 0); + assignOrphanScriptInfoToInferredProject(info: ScriptInfo, projectRootPath?: string) { + Debug.assert(info.isOrphan()); const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) || this.getOrCreateSingleInferredProjectIfEnabled() || @@ -823,7 +823,7 @@ namespace ts.server { } private addToListOfOpenFiles(info: ScriptInfo) { - Debug.assert(info.containingProjects.length !== 0); + Debug.assert(!info.isOrphan()); for (const p of info.containingProjects) { // file is the part of configured project, addref the project if (p.projectKind === ProjectKind.Configured) { @@ -885,8 +885,8 @@ namespace ts.server { // collect orphaned files and assign them to inferred project just like we treat open of a file for (const f of this.openFiles) { - if (f.containingProjects.length === 0) { - this.assignScriptInfoToInferredProject(f); + if (f.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(f); } } @@ -907,7 +907,7 @@ namespace ts.server { private deleteOrphanScriptInfoNotInAnyProject() { this.filenameToScriptInfo.forEach(info => { - if (!info.isScriptOpen() && info.containingProjects.length === 0) { + if (!info.isScriptOpen() && info.isOrphan()) { // if there are not projects that include this script info - delete it this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo); this.filenameToScriptInfo.delete(info.path); @@ -1809,8 +1809,8 @@ namespace ts.server { for (const info of this.openFiles) { // collect all orphaned script infos from open files - if (info.containingProjects.length === 0) { - this.assignScriptInfoToInferredProject(info); + if (info.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(info); } else { // Or remove the root of inferred project if is referenced in more than one projects @@ -1867,8 +1867,8 @@ namespace ts.server { // At this point if file is part of any any configured or external project, then it would be present in the containing projects // So if it still doesnt have any containing projects, it needs to be part of inferred project - if (info.containingProjects.length === 0) { - this.assignScriptInfoToInferredProject(info, projectRootPath); + if (info.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(info, projectRootPath); } this.addToListOfOpenFiles(info); diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 6d439e92385..133879a2dce 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -328,6 +328,10 @@ namespace ts.server { } } + isOrphan() { + return this.containingProjects.length === 0; + } + /** * @param line 1 based index */