diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3ea03ff035..7009619b26 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2109,7 +2109,20 @@ namespace ts.server { } private getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined, hostToQueryFileExistsOn: DirectoryStructureHost | undefined) { - return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + if (isRootedDiskPath(fileName) || isDynamicFileName(fileName)) { + return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + } + + // This is non rooted path with different current directory than project service current directory + // Only paths recognized are open relative file paths + const info = this.openFilesWithNonRootedDiskPath.get(this.toCanonicalFileName(fileName)); + if (info) { + return info; + } + + // This means triple slash references wont be resolved in dynamic and unsaved files + // which is intentional since we dont know what it means to be relative to non disk files + return undefined; } private getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined) { @@ -2126,7 +2139,7 @@ namespace ts.server { let info = this.getScriptInfoForPath(path); if (!info) { const isDynamic = isDynamicFileName(fileName); - Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nScript info with non-dynamic relative file name can only be open script info`); + Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nScript info with non-dynamic relative file name can only be open script info or in context of host currentDirectory`); Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nOpen script files with non rooted disk path opened with current directory context cannot have same canonical names`); Debug.assert(!isDynamic || this.currentDirectory === currentDirectory, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nDynamic files must always have current directory context since containing external project name will always match the script info name.`); // If the file is not opened by client and the file doesnot exist on the disk, return @@ -2139,7 +2152,7 @@ namespace ts.server { if (!openedByClient) { this.watchClosedScriptInfo(info); } - else if (!isRootedDiskPath(fileName) && currentDirectory !== this.currentDirectory) { + else if (!isRootedDiskPath(fileName) && !isDynamic) { // File that is opened by user but isn't rooted disk path this.openFilesWithNonRootedDiskPath.set(this.toCanonicalFileName(fileName), info); } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 31ca940321..a113ea14d7 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3301,7 +3301,7 @@ namespace ts.projectSystem { }); }); - it("dynamic file with reference paths external project", () => { + it("dynamic file with reference paths without external project", () => { const file: File = { path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", content: `/// @@ -3899,18 +3899,30 @@ var x = 10;` describe("when opening new file that doesnt exist on disk yet", () => { function verifyNonExistentFile(useProjectRoot: boolean) { - const host = createServerHost([libFile]); + const folderPath = "/user/someuser/projects/someFolder"; + const fileInRoot: File = { + path: `/src/somefile.d.ts`, + content: "class c { }" + }; + const fileInProjectRoot: File = { + path: `${folderPath}/src/somefile.d.ts`, + content: "class c { }" + }; + const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); const { hasError, errorLogger } = createErrorLogger(); const session = createSession(host, { canUseEvents: true, logger: errorLogger, useInferredProjectPerProjectRoot: true }); - const folderPath = "/user/someuser/projects/someFolder"; const projectService = session.getProjectService(); const untitledFile = "untitled:Untitled-1"; + const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; + const refPathNotFound2 = "./src/somefile.d.ts"; + const fileContent = `/// +/// `; session.executeCommandSeq({ command: server.CommandNames.Open, arguments: { file: untitledFile, - fileContent: `/// `, + fileContent, scriptKindName: "TS", projectRootPath: useProjectRoot ? folderPath : undefined } @@ -3918,6 +3930,8 @@ var x = 10;` checkNumberOfProjects(projectService, { inferredProjects: 1 }); const infoForUntitledAtProjectRoot = projectService.getScriptInfoForPath(`${folderPath.toLowerCase()}/${untitledFile.toLowerCase()}` as Path); const infoForUnitiledAtRoot = projectService.getScriptInfoForPath(`/${untitledFile.toLowerCase()}` as Path); + const infoForSomefileAtProjectRoot = projectService.getScriptInfoForPath(`/${folderPath.toLowerCase()}/src/somefile.d.ts` as Path); + const infoForSomefileAtRoot = projectService.getScriptInfoForPath(`${fileInRoot.path.toLowerCase()}` as Path); if (useProjectRoot) { assert.isDefined(infoForUntitledAtProjectRoot); assert.isUndefined(infoForUnitiledAtRoot); @@ -3926,7 +3940,11 @@ var x = 10;` assert.isDefined(infoForUnitiledAtRoot); assert.isUndefined(infoForUntitledAtProjectRoot); } - host.checkTimeoutQueueLength(2); + assert.isUndefined(infoForSomefileAtRoot); + assert.isUndefined(infoForSomefileAtProjectRoot); + + // Since this is not js project so no typings are queued + host.checkTimeoutQueueLength(0); const newTimeoutId = host.getNextTimeoutId(); const expectedSequenceId = session.getNextSeq(); @@ -3937,19 +3955,26 @@ var x = 10;` files: [untitledFile] } }); - host.checkTimeoutQueueLength(3); + host.checkTimeoutQueueLength(1); // Run the last one = get error request host.runQueuedTimeoutCallbacks(newTimeoutId); assert.isFalse(hasError()); - host.checkTimeoutQueueLength(2); + host.checkTimeoutQueueLength(0); checkErrorMessage(session, "syntaxDiag", { file: untitledFile, diagnostics: [] }); session.clearMessages(); host.runQueuedImmediateCallbacks(); assert.isFalse(hasError()); - checkErrorMessage(session, "semanticDiag", { file: untitledFile, diagnostics: [] }); + const errorOffset = fileContent.indexOf(refPathNotFound1) + 1; + checkErrorMessage(session, "semanticDiag", { + file: untitledFile, + diagnostics: [ + createDiagnostic({ line: 1, offset: errorOffset }, { line: 1, offset: errorOffset + refPathNotFound1.length }, Diagnostics.File_0_not_found, [refPathNotFound1], "error"), + createDiagnostic({ line: 2, offset: errorOffset }, { line: 2, offset: errorOffset + refPathNotFound2.length }, Diagnostics.File_0_not_found, [refPathNotFound2.substr(2)], "error") + ] + }); session.clearMessages(); host.runQueuedImmediateCallbacks(1);