Skip to content

Commit 373510c

Browse files
committed
Handle the script infos that are opened with non rooted disk path
Fixes microsoft#19588
1 parent 668ac10 commit 373510c

5 files changed

Lines changed: 52 additions & 21 deletions

File tree

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2837,8 +2837,9 @@ namespace ts.projectSystem {
28372837

28382838
// Run the last one = get error request
28392839
host.runQueuedTimeoutCallbacks(newTimeoutId);
2840-
host.checkTimeoutQueueLength(2);
28412840

2841+
assert.isFalse(hasError);
2842+
host.checkTimeoutQueueLength(2);
28422843
checkErrorMessage(host, "syntaxDiag", { file: untitledFile, diagnostics: [] });
28432844
host.clearOutput();
28442845

src/server/editorServices.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ namespace ts.server {
353353
* Open files: with value being project root path, and key being Path of the file that is open
354354
*/
355355
readonly openFiles = createMap<NormalizedPath>();
356+
/**
357+
* Map of open files that are opened without complete path but have projectRoot as current directory
358+
*/
359+
private readonly openFilesWithNonRootedDiskPath = createMap<ScriptInfo>();
356360

357361
private compilerOptionsForInferredProjects: CompilerOptions;
358362
private compilerOptionsForInferredProjectsPerProjectRoot = createMap<CompilerOptions>();
@@ -930,12 +934,16 @@ namespace ts.server {
930934
// Closing file should trigger re-reading the file content from disk. This is
931935
// because the user may chose to discard the buffer content before saving
932936
// to the disk, and the server's version of the file can be out of sync.
933-
info.close();
937+
const fileExists = this.host.fileExists(info.fileName);
938+
info.close(fileExists);
934939
this.stopWatchingConfigFilesForClosedScriptInfo(info);
935940

936941
this.openFiles.delete(info.path);
942+
const canonicalFileName = this.toCanonicalFileName(info.fileName);
943+
if (this.openFilesWithNonRootedDiskPath.get(canonicalFileName) === info) {
944+
this.openFilesWithNonRootedDiskPath.delete(canonicalFileName);
945+
}
937946

938-
const fileExists = this.host.fileExists(info.fileName);
939947

940948
// collect all projects that should be removed
941949
let projectsToRemove: Project[];
@@ -1535,7 +1543,7 @@ namespace ts.server {
15351543
else {
15361544
const scriptKind = propertyReader.getScriptKind(f, this.hostConfiguration.extraFileExtensions);
15371545
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
1538-
scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, scriptKind, hasMixedContent, project.directoryStructureHost);
1546+
scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, project.currentDirectory, scriptKind, hasMixedContent, project.directoryStructureHost);
15391547
path = scriptInfo.path;
15401548
// If this script info is not already a root add it
15411549
if (!project.isRoot(scriptInfo)) {
@@ -1689,9 +1697,9 @@ namespace ts.server {
16891697
}
16901698

16911699
/*@internal*/
1692-
getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, hostToQueryFileExistsOn: DirectoryStructureHost) {
1700+
getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, currentDirectory: string, hostToQueryFileExistsOn: DirectoryStructureHost) {
16931701
return this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(
1694-
toNormalizedPath(uncheckedFileName), /*scriptKind*/ undefined,
1702+
toNormalizedPath(uncheckedFileName), currentDirectory, /*scriptKind*/ undefined,
16951703
/*hasMixedContent*/ undefined, hostToQueryFileExistsOn
16961704
);
16971705
}
@@ -1722,20 +1730,26 @@ namespace ts.server {
17221730
}
17231731

17241732
/*@internal*/
1725-
getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) {
1726-
return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
1733+
getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined, hostToQueryFileExistsOn: DirectoryStructureHost | undefined) {
1734+
return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
17271735
}
17281736

17291737
/*@internal*/
1730-
getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) {
1731-
return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
1738+
getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined) {
1739+
return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
17321740
}
17331741

17341742
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) {
1743+
return this.getOrCreateScriptInfoWorker(fileName, this.currentDirectory, openedByClient, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
1744+
}
1745+
1746+
private getOrCreateScriptInfoWorker(fileName: NormalizedPath, currentDirectory: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) {
17351747
Debug.assert(fileContent === undefined || openedByClient, "ScriptInfo needs to be opened by client to be able to set its user defined content");
1736-
const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName);
1748+
const path = normalizedPathToPath(fileName, currentDirectory, this.toCanonicalFileName);
17371749
let info = this.getScriptInfoForPath(path);
17381750
if (!info) {
1751+
Debug.assert(isRootedDiskPath(fileName) || openedByClient, "Script info with relative file name can only be open script info");
1752+
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");
17391753
const isDynamic = isDynamicFileName(fileName);
17401754
// If the file is not opened by client and the file doesnot exist on the disk, return
17411755
if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) {
@@ -1746,6 +1760,10 @@ namespace ts.server {
17461760
if (!openedByClient) {
17471761
this.watchClosedScriptInfo(info);
17481762
}
1763+
else if (!isRootedDiskPath(fileName) && currentDirectory !== this.currentDirectory) {
1764+
// File that is opened by user but isn't rooted disk path
1765+
this.openFilesWithNonRootedDiskPath.set(this.toCanonicalFileName(fileName), info);
1766+
}
17491767
}
17501768
if (openedByClient && !info.isScriptOpen()) {
17511769
// Opening closed script info
@@ -1762,8 +1780,12 @@ namespace ts.server {
17621780
return info;
17631781
}
17641782

1783+
/**
1784+
* 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
1785+
*/
17651786
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
1766-
return this.getScriptInfoForPath(normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName));
1787+
return !isRootedDiskPath(fileName) && this.openFilesWithNonRootedDiskPath.get(this.toCanonicalFileName(fileName)) ||
1788+
this.getScriptInfoForPath(normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName));
17671789
}
17681790

17691791
getScriptInfoForPath(fileName: Path) {
@@ -1948,7 +1970,7 @@ namespace ts.server {
19481970
let sendConfigFileDiagEvent = false;
19491971
let configFileErrors: ReadonlyArray<Diagnostic>;
19501972

1951-
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, fileContent, scriptKind, hasMixedContent);
1973+
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent);
19521974
let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName);
19531975
if (!project) {
19541976
configFileName = this.getConfigFileNameForFile(info, projectRootPath);

src/server/project.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ namespace ts.server {
285285
}
286286

287287
private getOrCreateScriptInfoAndAttachToProject(fileName: string) {
288-
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.directoryStructureHost);
288+
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost);
289289
if (scriptInfo) {
290290
const existingValue = this.rootFilesMap.get(scriptInfo.path);
291291
if (existingValue !== scriptInfo && existingValue !== undefined) {
@@ -365,7 +365,7 @@ namespace ts.server {
365365

366366
/*@internal*/
367367
toPath(fileName: string) {
368-
return this.projectService.toPath(fileName);
368+
return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName);
369369
}
370370

371371
/*@internal*/
@@ -658,7 +658,7 @@ namespace ts.server {
658658
}
659659

660660
containsFile(filename: NormalizedPath, requireOpen?: boolean) {
661-
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
661+
const info = this.projectService.getScriptInfoForPath(this.toPath(filename));
662662
if (info && (info.isScriptOpen() || !requireOpen)) {
663663
return this.containsScriptInfo(info);
664664
}
@@ -855,7 +855,7 @@ namespace ts.server {
855855
// by the LSHost for files in the program when the program is retrieved above but
856856
// the program doesn't contain external files so this must be done explicitly.
857857
inserted => {
858-
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.directoryStructureHost);
858+
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.currentDirectory, this.directoryStructureHost);
859859
scriptInfo.attachToProject(this);
860860
},
861861
removed => this.detachScriptInfoFromProject(removed)
@@ -901,7 +901,7 @@ namespace ts.server {
901901
}
902902

903903
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
904-
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(fileName);
904+
const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName));
905905
if (scriptInfo && !scriptInfo.isAttached(this)) {
906906
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
907907
}

src/server/scriptInfo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,9 @@ namespace ts.server {
248248
}
249249
}
250250

251-
public close() {
251+
public close(fileExists = true) {
252252
this.textStorage.isOpen = false;
253-
if (this.isDynamicOrHasMixedContent()) {
253+
if (this.isDynamicOrHasMixedContent() || !fileExists) {
254254
if (this.textStorage.reload("")) {
255255
this.markContainingProjectsAsDirty();
256256
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7058,7 +7058,7 @@ declare namespace ts.server {
70587058
constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent: boolean, path: Path);
70597059
isScriptOpen(): boolean;
70607060
open(newText: string): void;
7061-
close(): void;
7061+
close(fileExists?: boolean): void;
70627062
getSnapshot(): IScriptSnapshot;
70637063
getFormatCodeSettings(): FormatCodeSettings;
70647064
attachToProject(project: Project): boolean;
@@ -7482,6 +7482,10 @@ declare namespace ts.server {
74827482
* Open files: with value being project root path, and key being Path of the file that is open
74837483
*/
74847484
readonly openFiles: Map<NormalizedPath>;
7485+
/**
7486+
* Map of open files that are opened without complete path but have projectRoot as current directory
7487+
*/
7488+
private readonly openFilesWithNonRootedDiskPath;
74857489
private compilerOptionsForInferredProjects;
74867490
private compilerOptionsForInferredProjectsPerProjectRoot;
74877491
/**
@@ -7621,6 +7625,10 @@ declare namespace ts.server {
76217625
private watchClosedScriptInfo(info);
76227626
private stopWatchingScriptInfo(info);
76237627
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost): ScriptInfo;
7628+
private getOrCreateScriptInfoWorker(fileName, currentDirectory, openedByClient, fileContent?, scriptKind?, hasMixedContent?, hostToQueryFileExistsOn?);
7629+
/**
7630+
* 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
7631+
*/
76247632
getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo;
76257633
getScriptInfoForPath(fileName: Path): ScriptInfo;
76267634
setHostConfiguration(args: protocol.ConfigureRequestArguments): void;

0 commit comments

Comments
 (0)