From 668ac108902fc6f7fd3d55b0fd50aed70f64d998 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 3 Nov 2017 11:51:16 -0700 Subject: [PATCH 1/3] Test where script info path and program path differ because of current directory --- .../unittests/tsserverProjectSystem.ts | 148 ++++++++++++------ src/harness/virtualFileSystemWithWatch.ts | 20 ++- 2 files changed, 121 insertions(+), 47 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index be38176fb15..5df045335cb 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -437,6 +437,50 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } + function assertEvent(actualOutput: string, expectedEvent: protocol.Event, host: TestServerHost) { + assert.equal(actualOutput, server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, host.newLine)); + } + + function checkErrorMessage(host: TestServerHost, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) { + const outputs = host.getOutput(); + assert.isTrue(outputs.length >= 1, outputs.toString()); + const event: protocol.Event = { + seq: 0, + type: "event", + event: eventName, + body: diagnostics + }; + assertEvent(outputs[0], event, host); + } + + function checkCompleteEvent(host: TestServerHost, numberOfCurrentEvents: number, expectedSequenceId: number) { + const outputs = host.getOutput(); + assert.equal(outputs.length, numberOfCurrentEvents, outputs.toString()); + const event: protocol.RequestCompletedEvent = { + seq: 0, + type: "event", + event: "requestCompleted", + body: { + request_seq: expectedSequenceId + } + }; + assertEvent(outputs[numberOfCurrentEvents - 1], event, host); + } + + function checkProjectUpdatedInBackgroundEvent(host: TestServerHost, openFiles: string[]) { + const outputs = host.getOutput(); + assert.equal(outputs.length, 1, outputs.toString()); + const event: protocol.ProjectsUpdatedInBackgroundEvent = { + seq: 0, + type: "event", + event: "projectsUpdatedInBackground", + body: { + openFiles + } + }; + assertEvent(outputs[0], event, host); + } + describe("tsserverProjectSystem", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -2744,6 +2788,66 @@ namespace ts.projectSystem { const project = projectService.findProject(corruptedConfig.path); checkProjectRootFiles(project, [file1.path]); }); + + it("when opening new file that doesnt exist on disk yet", () => { + const host = createServerHost([libFile]); + let hasError = false; + const errLogger: server.Logger = { + close: noop, + hasLevel: () => true, + loggingEnabled: () => true, + perftrc: noop, + info: noop, + msg: (_s, type) => { + if (type === server.Msg.Err) { + hasError = true; + } + }, + startGroup: noop, + endGroup: noop, + getLogFileName: (): string => undefined + }; + const session = createSession(host, { canUseEvents: true, logger: errLogger, useInferredProjectPerProjectRoot: true }); + + const folderPath = "/user/someuser/projects/someFolder"; + const projectService = session.getProjectService(); + const untitledFile = "untitled:Untitled-1"; + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: untitledFile, + fileContent: "", + scriptKindName: "JS", + projectRootPath: folderPath + } + }); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + host.checkTimeoutQueueLength(2); + + const newTimeoutId = host.getNextTimeoutId(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [untitledFile] + } + }); + host.checkTimeoutQueueLength(3); + + // Run the last one = get error request + host.runQueuedTimeoutCallbacks(newTimeoutId); + host.checkTimeoutQueueLength(2); + + checkErrorMessage(host, "syntaxDiag", { file: untitledFile, diagnostics: [] }); + host.clearOutput(); + + host.runQueuedImmediateCallbacks(); + assert.isFalse(hasError); + checkErrorMessage(host, "semanticDiag", { file: untitledFile, diagnostics: [] }); + + checkCompleteEvent(host, 2, expectedSequenceId); + }); }); describe("autoDiscovery", () => { @@ -3446,50 +3550,6 @@ namespace ts.projectSystem { verifyNoDiagnostics(diags); }); - function assertEvent(actualOutput: string, expectedEvent: protocol.Event, host: TestServerHost) { - assert.equal(actualOutput, server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, host.newLine)); - } - - function checkErrorMessage(host: TestServerHost, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) { - const outputs = host.getOutput(); - assert.isTrue(outputs.length >= 1, outputs.toString()); - const event: protocol.Event = { - seq: 0, - type: "event", - event: eventName, - body: diagnostics - }; - assertEvent(outputs[0], event, host); - } - - function checkCompleteEvent(host: TestServerHost, numberOfCurrentEvents: number, expectedSequenceId: number) { - const outputs = host.getOutput(); - assert.equal(outputs.length, numberOfCurrentEvents, outputs.toString()); - const event: protocol.RequestCompletedEvent = { - seq: 0, - type: "event", - event: "requestCompleted", - body: { - request_seq: expectedSequenceId - } - }; - assertEvent(outputs[numberOfCurrentEvents - 1], event, host); - } - - function checkProjectUpdatedInBackgroundEvent(host: TestServerHost, openFiles: string[]) { - const outputs = host.getOutput(); - assert.equal(outputs.length, 1, outputs.toString()); - const event: protocol.ProjectsUpdatedInBackgroundEvent = { - seq: 0, - type: "event", - event: "projectsUpdatedInBackground", - body: { - openFiles - } - }; - assertEvent(outputs[0], event, host); - } - it("npm install @types works", () => { const folderPath = "/a/b/projects/temp"; const file1: FileOrFolder = { diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index fe40cb42844..6c3bd8a635a 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -182,6 +182,10 @@ interface Array {}` private map: TimeOutCallback[] = []; private nextId = 1; + getNextId() { + return this.nextId; + } + register(cb: (...args: any[]) => void, args: any[]) { const timeoutId = this.nextId; this.nextId++; @@ -203,7 +207,13 @@ interface Array {}` return n; } - invoke() { + invoke(invokeKey?: number) { + if (invokeKey) { + this.map[invokeKey](); + delete this.map[invokeKey]; + return; + } + // Note: invoking a callback may result in new callbacks been queued, // so do not clear the entire callback list regardless. Only remove the // ones we have invoked. @@ -553,6 +563,10 @@ interface Array {}` return this.timeoutCallbacks.register(callback, args); } + getNextTimeoutId() { + return this.timeoutCallbacks.getNextId(); + } + clearTimeout(timeoutId: any): void { this.timeoutCallbacks.unregister(timeoutId); } @@ -567,9 +581,9 @@ interface Array {}` assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`); } - runQueuedTimeoutCallbacks() { + runQueuedTimeoutCallbacks(timeoutId?: number) { try { - this.timeoutCallbacks.invoke(); + this.timeoutCallbacks.invoke(timeoutId); } catch (e) { if (e.message === this.existMessage) { From 373510c4d9f1673fde5f3e17d9a4b63728acb4e7 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 3 Nov 2017 15:28:28 -0700 Subject: [PATCH 2/3] Handle the script infos that are opened with non rooted disk path Fixes #19588 --- .../unittests/tsserverProjectSystem.ts | 3 +- src/server/editorServices.ts | 46 ++++++++++++++----- src/server/project.ts | 10 ++-- src/server/scriptInfo.ts | 4 +- .../reference/api/tsserverlibrary.d.ts | 10 +++- 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 5df045335cb..ae90ec7d741 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2837,8 +2837,9 @@ namespace ts.projectSystem { // Run the last one = get error request host.runQueuedTimeoutCallbacks(newTimeoutId); - host.checkTimeoutQueueLength(2); + assert.isFalse(hasError); + host.checkTimeoutQueueLength(2); checkErrorMessage(host, "syntaxDiag", { file: untitledFile, diagnostics: [] }); host.clearOutput(); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9ec190c152f..1730e545257 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -353,6 +353,10 @@ namespace ts.server { * Open files: with value being project root path, and key being Path of the file that is open */ readonly openFiles = createMap(); + /** + * Map of open files that are opened without complete path but have projectRoot as current directory + */ + private readonly openFilesWithNonRootedDiskPath = createMap(); private compilerOptionsForInferredProjects: CompilerOptions; private compilerOptionsForInferredProjectsPerProjectRoot = createMap(); @@ -930,12 +934,16 @@ namespace ts.server { // Closing file should trigger re-reading the file content from disk. This is // because the user may chose to discard the buffer content before saving // to the disk, and the server's version of the file can be out of sync. - info.close(); + const fileExists = this.host.fileExists(info.fileName); + info.close(fileExists); this.stopWatchingConfigFilesForClosedScriptInfo(info); this.openFiles.delete(info.path); + const canonicalFileName = this.toCanonicalFileName(info.fileName); + if (this.openFilesWithNonRootedDiskPath.get(canonicalFileName) === info) { + this.openFilesWithNonRootedDiskPath.delete(canonicalFileName); + } - const fileExists = this.host.fileExists(info.fileName); // collect all projects that should be removed let projectsToRemove: Project[]; @@ -1535,7 +1543,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f, this.hostConfiguration.extraFileExtensions); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, scriptKind, hasMixedContent, project.directoryStructureHost); + scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, project.currentDirectory, scriptKind, hasMixedContent, project.directoryStructureHost); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { @@ -1689,9 +1697,9 @@ namespace ts.server { } /*@internal*/ - getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, hostToQueryFileExistsOn: DirectoryStructureHost) { + getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, currentDirectory: string, hostToQueryFileExistsOn: DirectoryStructureHost) { return this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath( - toNormalizedPath(uncheckedFileName), /*scriptKind*/ undefined, + toNormalizedPath(uncheckedFileName), currentDirectory, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn ); } @@ -1722,20 +1730,26 @@ namespace ts.server { } /*@internal*/ - getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { - return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + 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); } /*@internal*/ - getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { - return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined) { + return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent); } getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { + return this.getOrCreateScriptInfoWorker(fileName, this.currentDirectory, openedByClient, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + } + + private getOrCreateScriptInfoWorker(fileName: NormalizedPath, currentDirectory: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { Debug.assert(fileContent === undefined || openedByClient, "ScriptInfo needs to be opened by client to be able to set its user defined content"); - const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); + const path = normalizedPathToPath(fileName, currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { + Debug.assert(isRootedDiskPath(fileName) || openedByClient, "Script info with relative file name can only be open script info"); + Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "Open script files with non rooted disk path opened with current directory context cannot have same canonical names"); const isDynamic = isDynamicFileName(fileName); // If the file is not opened by client and the file doesnot exist on the disk, return if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) { @@ -1746,6 +1760,10 @@ namespace ts.server { if (!openedByClient) { this.watchClosedScriptInfo(info); } + else if (!isRootedDiskPath(fileName) && currentDirectory !== this.currentDirectory) { + // File that is opened by user but isn't rooted disk path + this.openFilesWithNonRootedDiskPath.set(this.toCanonicalFileName(fileName), info); + } } if (openedByClient && !info.isScriptOpen()) { // Opening closed script info @@ -1762,8 +1780,12 @@ namespace ts.server { return info; } + /** + * This gets the script info for the normalized path. If the path is not rooted disk path then the open script info with project root context is preferred + */ getScriptInfoForNormalizedPath(fileName: NormalizedPath) { - return this.getScriptInfoForPath(normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName)); + return !isRootedDiskPath(fileName) && this.openFilesWithNonRootedDiskPath.get(this.toCanonicalFileName(fileName)) || + this.getScriptInfoForPath(normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName)); } getScriptInfoForPath(fileName: Path) { @@ -1948,7 +1970,7 @@ namespace ts.server { let sendConfigFileDiagEvent = false; let configFileErrors: ReadonlyArray; - const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, fileContent, scriptKind, hasMixedContent); + const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent); let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); if (!project) { configFileName = this.getConfigFileNameForFile(info, projectRootPath); diff --git a/src/server/project.ts b/src/server/project.ts index 0444e9304b5..edb1d6730e9 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -285,7 +285,7 @@ namespace ts.server { } private getOrCreateScriptInfoAndAttachToProject(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.directoryStructureHost); + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); if (existingValue !== scriptInfo && existingValue !== undefined) { @@ -365,7 +365,7 @@ namespace ts.server { /*@internal*/ toPath(fileName: string) { - return this.projectService.toPath(fileName); + return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName); } /*@internal*/ @@ -658,7 +658,7 @@ namespace ts.server { } containsFile(filename: NormalizedPath, requireOpen?: boolean) { - const info = this.projectService.getScriptInfoForNormalizedPath(filename); + const info = this.projectService.getScriptInfoForPath(this.toPath(filename)); if (info && (info.isScriptOpen() || !requireOpen)) { return this.containsScriptInfo(info); } @@ -855,7 +855,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.directoryStructureHost); + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.currentDirectory, this.directoryStructureHost); scriptInfo.attachToProject(this); }, removed => this.detachScriptInfoFromProject(removed) @@ -901,7 +901,7 @@ namespace ts.server { } getScriptInfoForNormalizedPath(fileName: NormalizedPath) { - const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(fileName); + const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName)); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 9f029c00f0a..f800a1117d0 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -248,9 +248,9 @@ namespace ts.server { } } - public close() { + public close(fileExists = true) { this.textStorage.isOpen = false; - if (this.isDynamicOrHasMixedContent()) { + if (this.isDynamicOrHasMixedContent() || !fileExists) { if (this.textStorage.reload("")) { this.markContainingProjectsAsDirty(); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index cffa1375608..64bbdd60c7c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7058,7 +7058,7 @@ declare namespace ts.server { constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent: boolean, path: Path); isScriptOpen(): boolean; open(newText: string): void; - close(): void; + close(fileExists?: boolean): void; getSnapshot(): IScriptSnapshot; getFormatCodeSettings(): FormatCodeSettings; attachToProject(project: Project): boolean; @@ -7482,6 +7482,10 @@ declare namespace ts.server { * Open files: with value being project root path, and key being Path of the file that is open */ readonly openFiles: Map; + /** + * Map of open files that are opened without complete path but have projectRoot as current directory + */ + private readonly openFilesWithNonRootedDiskPath; private compilerOptionsForInferredProjects; private compilerOptionsForInferredProjectsPerProjectRoot; /** @@ -7621,6 +7625,10 @@ declare namespace ts.server { private watchClosedScriptInfo(info); private stopWatchingScriptInfo(info); getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost): ScriptInfo; + private getOrCreateScriptInfoWorker(fileName, currentDirectory, openedByClient, fileContent?, scriptKind?, hasMixedContent?, hostToQueryFileExistsOn?); + /** + * This gets the script info for the normalized path. If the path is not rooted disk path then the open script info with project root context is preferred + */ getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo; getScriptInfoForPath(fileName: Path): ScriptInfo; setHostConfiguration(args: protocol.ConfigureRequestArguments): void; From 163e40cde68d945dda726a93b748fa900e0b782e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 6 Nov 2017 10:56:52 -0800 Subject: [PATCH 3/3] Add testcase for non existent file without absolute path when opened with/without projectRoot --- .../unittests/tsserverProjectSystem.ts | 128 ++++++++++-------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index ae90ec7d741..a0a5995a1f0 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2789,65 +2789,85 @@ namespace ts.projectSystem { checkProjectRootFiles(project, [file1.path]); }); - it("when opening new file that doesnt exist on disk yet", () => { - const host = createServerHost([libFile]); - let hasError = false; - const errLogger: server.Logger = { - close: noop, - hasLevel: () => true, - loggingEnabled: () => true, - perftrc: noop, - info: noop, - msg: (_s, type) => { - if (type === server.Msg.Err) { - hasError = true; + describe("when opening new file that doesnt exist on disk yet", () => { + function verifyNonExistentFile(useProjectRoot: boolean) { + const host = createServerHost([libFile]); + let hasError = false; + const errLogger: server.Logger = { + close: noop, + hasLevel: () => true, + loggingEnabled: () => true, + perftrc: noop, + info: noop, + msg: (_s, type) => { + if (type === server.Msg.Err) { + hasError = true; + } + }, + startGroup: noop, + endGroup: noop, + getLogFileName: (): string => undefined + }; + const session = createSession(host, { canUseEvents: true, logger: errLogger, useInferredProjectPerProjectRoot: true }); + + const folderPath = "/user/someuser/projects/someFolder"; + const projectService = session.getProjectService(); + const untitledFile = "untitled:Untitled-1"; + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: untitledFile, + fileContent: "", + scriptKindName: "JS", + projectRootPath: useProjectRoot ? folderPath : undefined } - }, - startGroup: noop, - endGroup: noop, - getLogFileName: (): string => undefined - }; - const session = createSession(host, { canUseEvents: true, logger: errLogger, useInferredProjectPerProjectRoot: true }); - - const folderPath = "/user/someuser/projects/someFolder"; - const projectService = session.getProjectService(); - const untitledFile = "untitled:Untitled-1"; - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: untitledFile, - fileContent: "", - scriptKindName: "JS", - projectRootPath: folderPath + }); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const infoForUntitledAtProjectRoot = projectService.getScriptInfoForPath(`${folderPath.toLowerCase()}/${untitledFile.toLowerCase()}` as Path); + const infoForUnitiledAtRoot = projectService.getScriptInfoForPath(`/${untitledFile.toLowerCase()}` as Path); + if (useProjectRoot) { + assert.isDefined(infoForUntitledAtProjectRoot); + assert.isUndefined(infoForUnitiledAtRoot); } - }); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - host.checkTimeoutQueueLength(2); - - const newTimeoutId = host.getNextTimeoutId(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [untitledFile] + else { + assert.isDefined(infoForUnitiledAtRoot); + assert.isUndefined(infoForUntitledAtProjectRoot); } + host.checkTimeoutQueueLength(2); + + const newTimeoutId = host.getNextTimeoutId(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [untitledFile] + } + }); + host.checkTimeoutQueueLength(3); + + // Run the last one = get error request + host.runQueuedTimeoutCallbacks(newTimeoutId); + + assert.isFalse(hasError); + host.checkTimeoutQueueLength(2); + checkErrorMessage(host, "syntaxDiag", { file: untitledFile, diagnostics: [] }); + host.clearOutput(); + + host.runQueuedImmediateCallbacks(); + assert.isFalse(hasError); + checkErrorMessage(host, "semanticDiag", { file: untitledFile, diagnostics: [] }); + + checkCompleteEvent(host, 2, expectedSequenceId); + } + + it("has projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ true); }); - host.checkTimeoutQueueLength(3); - // Run the last one = get error request - host.runQueuedTimeoutCallbacks(newTimeoutId); - - assert.isFalse(hasError); - host.checkTimeoutQueueLength(2); - checkErrorMessage(host, "syntaxDiag", { file: untitledFile, diagnostics: [] }); - host.clearOutput(); - - host.runQueuedImmediateCallbacks(); - assert.isFalse(hasError); - checkErrorMessage(host, "semanticDiag", { file: untitledFile, diagnostics: [] }); - - checkCompleteEvent(host, 2, expectedSequenceId); + it("does not have projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ false); + }); }); });