Skip to content

Commit 09368bc

Browse files
authoredSep 26, 2022
Handle if project for open file will get recollected because of pending cleanup from closed script info (#50908)
* Handle if project for open file will get recollected because of pending cleanup from closed script info Fixes #50868 * Rename
1 parent c81bf4d commit 09368bc

File tree

6 files changed

+966
-46
lines changed

6 files changed

+966
-46
lines changed
 

‎src/server/editorServices.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -1167,20 +1167,22 @@ namespace ts.server {
11671167
}
11681168

11691169
/* @internal */
1170-
tryGetDefaultProjectForFile(fileName: NormalizedPath): Project | undefined {
1171-
const scriptInfo = this.getScriptInfoForNormalizedPath(fileName);
1170+
tryGetDefaultProjectForFile(fileNameOrScriptInfo: NormalizedPath | ScriptInfo): Project | undefined {
1171+
const scriptInfo = isString(fileNameOrScriptInfo) ? this.getScriptInfoForNormalizedPath(fileNameOrScriptInfo) : fileNameOrScriptInfo;
11721172
return scriptInfo && !scriptInfo.isOrphan() ? scriptInfo.getDefaultProject() : undefined;
11731173
}
11741174

11751175
/* @internal */
1176-
ensureDefaultProjectForFile(fileName: NormalizedPath): Project {
1177-
return this.tryGetDefaultProjectForFile(fileName) || this.doEnsureDefaultProjectForFile(fileName);
1176+
ensureDefaultProjectForFile(fileNameOrScriptInfo: NormalizedPath | ScriptInfo): Project {
1177+
return this.tryGetDefaultProjectForFile(fileNameOrScriptInfo) || this.doEnsureDefaultProjectForFile(fileNameOrScriptInfo);
11781178
}
11791179

1180-
private doEnsureDefaultProjectForFile(fileName: NormalizedPath): Project {
1180+
private doEnsureDefaultProjectForFile(fileNameOrScriptInfo: NormalizedPath | ScriptInfo): Project {
11811181
this.ensureProjectStructuresUptoDate();
1182-
const scriptInfo = this.getScriptInfoForNormalizedPath(fileName);
1183-
return scriptInfo ? scriptInfo.getDefaultProject() : (this.logErrorForScriptInfoNotFound(fileName), Errors.ThrowNoProject());
1182+
const scriptInfo = isString(fileNameOrScriptInfo) ? this.getScriptInfoForNormalizedPath(fileNameOrScriptInfo) : fileNameOrScriptInfo;
1183+
return scriptInfo ?
1184+
scriptInfo.getDefaultProject() :
1185+
(this.logErrorForScriptInfoNotFound(isString(fileNameOrScriptInfo) ? fileNameOrScriptInfo : fileNameOrScriptInfo.fileName), Errors.ThrowNoProject());
11841186
}
11851187

11861188
getScriptInfoEnsuringProjectsUptoDate(uncheckedFileName: string) {
@@ -3648,7 +3650,7 @@ namespace ts.server {
36483650
return;
36493651
}
36503652

3651-
const project = scriptInfo.getDefaultProject();
3653+
const project = this.ensureDefaultProjectForFile(scriptInfo);
36523654
if (!project.languageServiceEnabled) {
36533655
return;
36543656
}

‎src/server/session.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,10 @@ namespace ts.server {
17191719
this.projectService.logErrorForScriptInfoNotFound(args.file);
17201720
return Errors.ThrowNoProject();
17211721
}
1722+
else if (!getScriptInfoEnsuringProjectsUptoDate) {
1723+
// Ensure there are containing projects are present
1724+
this.projectService.ensureDefaultProjectForFile(scriptInfo);
1725+
}
17221726
projects = scriptInfo.containingProjects;
17231727
symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo);
17241728
}
@@ -1867,13 +1871,7 @@ namespace ts.server {
18671871
}
18681872

18691873
private getFileAndLanguageServiceForSyntacticOperation(args: protocol.FileRequestArgs) {
1870-
// Since this is syntactic operation, there should always be project for the file
1871-
// throw if we dont get project
1872-
const file = toNormalizedPath(args.file);
1873-
const project = this.getProject(args.projectFileName) || this.projectService.ensureDefaultProjectForFile(file);
1874-
if (!project) {
1875-
return Errors.ThrowNoProject();
1876-
}
1874+
const { file, project } = this.getFileAndProject(args);
18771875
return {
18781876
file,
18791877
languageService: project.getLanguageService(/*ensureSynchronized*/ false)

‎src/testRunner/unittests/tsserver/helpers.ts

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ namespace ts.projectSystem {
9595
function msg(s: string, type = server.Msg.Err, write: (s: string) => void) {
9696
s = `[${nowString()}] ${s}`;
9797
if (!inGroup || firstInGroup) s = padStringRight(type + " " + seq.toString(), " ") + s;
98+
if (Debug.isDebugging) console.log(s);
9899
write(s);
99100
if (!inGroup) seq++;
100101
}

‎src/testRunner/unittests/tsserver/projects.ts

+79-31
Original file line numberDiff line numberDiff line change
@@ -1610,39 +1610,87 @@ namespace ts.projectSystem {
16101610
checkNumberOfInferredProjects(projectService, 0);
16111611
});
16121612

1613-
it("file opened is in configured project that will be removed", () => {
1614-
const testsConfig: File = {
1615-
path: `${tscWatch.projectRoot}/playground/tsconfig.json`,
1616-
content: "{}"
1617-
};
1618-
const testsFile: File = {
1619-
path: `${tscWatch.projectRoot}/playground/tests.ts`,
1620-
content: `export function foo() {}`
1621-
};
1622-
const innerFile: File = {
1623-
path: `${tscWatch.projectRoot}/playground/tsconfig-json/tests/spec.ts`,
1624-
content: `export function bar() { }`
1625-
};
1626-
const innerConfig: File = {
1627-
path: `${tscWatch.projectRoot}/playground/tsconfig-json/tsconfig.json`,
1628-
content: JSON.stringify({
1629-
include: ["./src"]
1613+
describe("file opened is in configured project that will be removed", () => {
1614+
function runOnTs<T extends server.protocol.Request>(scenario: string, getRequest: (innerFile: File) => Partial<T>) {
1615+
it(scenario, () => {
1616+
const testsConfig: File = {
1617+
path: `${tscWatch.projectRoot}/playground/tsconfig.json`,
1618+
content: "{}"
1619+
};
1620+
const testsFile: File = {
1621+
path: `${tscWatch.projectRoot}/playground/tests.ts`,
1622+
content: `export function foo() {}`
1623+
};
1624+
const innerFile: File = {
1625+
path: `${tscWatch.projectRoot}/playground/tsconfig-json/tests/spec.ts`,
1626+
content: `export function bar() { }`
1627+
};
1628+
const innerConfig: File = {
1629+
path: `${tscWatch.projectRoot}/playground/tsconfig-json/tsconfig.json`,
1630+
content: JSON.stringify({
1631+
include: ["./src"]
1632+
})
1633+
};
1634+
const innerSrcFile: File = {
1635+
path: `${tscWatch.projectRoot}/playground/tsconfig-json/src/src.ts`,
1636+
content: `export function foobar() { }`
1637+
};
1638+
const host = createServerHost([testsConfig, testsFile, innerFile, innerConfig, innerSrcFile, libFile]);
1639+
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
1640+
openFilesForSession([testsFile], session);
1641+
closeFilesForSession([testsFile], session);
1642+
openFilesForSession([innerFile], session);
1643+
session.executeCommandSeq(getRequest(innerFile));
1644+
baselineTsserverLogs("projects", scenario, session);
1645+
});
1646+
}
1647+
runOnTs<protocol.OutliningSpansRequest>(
1648+
"file opened is in configured project that will be removed",
1649+
innerFile => ({
1650+
command: protocol.CommandTypes.GetOutliningSpans,
1651+
arguments: { file: innerFile.path }
16301652
})
1631-
};
1632-
const innerSrcFile: File = {
1633-
path: `${tscWatch.projectRoot}/playground/tsconfig-json/src/src.ts`,
1634-
content: `export function foobar() { }`
1635-
};
1636-
const host = createServerHost([testsConfig, testsFile, innerFile, innerConfig, innerSrcFile, libFile]);
1637-
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) });
1638-
openFilesForSession([testsFile], session);
1639-
closeFilesForSession([testsFile], session);
1640-
openFilesForSession([innerFile], session);
1641-
session.executeCommandSeq<protocol.OutliningSpansRequest>({
1642-
command: protocol.CommandTypes.GetOutliningSpans,
1643-
arguments: { file: innerFile.path }
1653+
);
1654+
1655+
runOnTs<protocol.ReferencesRequest>(
1656+
"references on file opened is in configured project that will be removed",
1657+
innerFile => ({
1658+
command: protocol.CommandTypes.References,
1659+
arguments: protocolFileLocationFromSubstring(innerFile, "bar")
1660+
})
1661+
);
1662+
1663+
it("js file opened is in configured project that will be removed", () => {
1664+
const rootConfig: File = {
1665+
path: `${tscWatch.projectRoot}/tsconfig.json`,
1666+
content: JSON.stringify({ compilerOptions: { allowJs: true } })
1667+
};
1668+
const mocksFile: File = {
1669+
path: `${tscWatch.projectRoot}/mocks/cssMock.js`,
1670+
content: `function foo() { }`
1671+
};
1672+
const innerFile: File = {
1673+
path: `${tscWatch.projectRoot}/apps/editor/scripts/createConfigVariable.js`,
1674+
content: `function bar() { }`
1675+
};
1676+
const innerConfig: File = {
1677+
path: `${tscWatch.projectRoot}/apps/editor/tsconfig.json`,
1678+
content: JSON.stringify({
1679+
extends: "../../tsconfig.json",
1680+
include: ["./src"],
1681+
})
1682+
};
1683+
const innerSrcFile: File = {
1684+
path: `${tscWatch.projectRoot}/apps/editor/src/src.js`,
1685+
content: `function fooBar() { }`
1686+
};
1687+
const host = createServerHost([rootConfig, mocksFile, innerFile, innerConfig, innerSrcFile, libFile]);
1688+
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
1689+
openFilesForSession([mocksFile], session);
1690+
closeFilesForSession([mocksFile], session);
1691+
openFilesForSession([innerFile], session);
1692+
baselineTsserverLogs("projects", "js file opened is in configured project that will be removed", session);
16441693
});
1645-
baselineTsserverLogs("projects", "file opened is in configured project that will be removed", session);
16461694
});
16471695
});
16481696
}

0 commit comments

Comments
 (0)