Skip to content

Commit 496939d

Browse files
committed
Handle reloadProjects to reload the project from scratch
1 parent eaa55a7 commit 496939d

File tree

4 files changed

+131
-6
lines changed

4 files changed

+131
-6
lines changed

src/server/editorServices.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,9 +2196,10 @@ namespace ts.server {
21962196
* Read the config file of the project again by clearing the cache and update the project graph
21972197
*/
21982198
/* @internal */
2199-
reloadConfiguredProject(project: ConfiguredProject, reason: string, isInitialLoad: boolean) {
2199+
reloadConfiguredProject(project: ConfiguredProject, reason: string, isInitialLoad: boolean, clearSemanticCache: boolean) {
22002200
// At this point, there is no reason to not have configFile in the host
22012201
const host = project.getCachedDirectoryStructureHost();
2202+
if (clearSemanticCache) this.clearSemanticCache(project);
22022203

22032204
// Clear the cache since we are reloading the project from disk
22042205
host.clearCache();
@@ -2212,6 +2213,13 @@ namespace ts.server {
22122213
this.sendConfigFileDiagEvent(project, configFileName);
22132214
}
22142215

2216+
/* @internal */
2217+
private clearSemanticCache(project: Project) {
2218+
project.resolutionCache.clear();
2219+
project.getLanguageService(/*ensureSynchronized*/ false).cleanupSemanticCache();
2220+
project.markAsDirty();
2221+
}
2222+
22152223
private sendConfigFileDiagEvent(project: ConfiguredProject, triggerFile: NormalizedPath) {
22162224
if (!this.eventHandler || this.suppressDiagnosticEvents) {
22172225
return;
@@ -2789,14 +2797,20 @@ namespace ts.server {
27892797
// as there is no need to load contents of the files from the disk
27902798

27912799
// Reload Projects
2792-
this.reloadConfiguredProjectForFiles(this.openFiles as ESMap<Path, NormalizedPath | undefined>, /*delayReload*/ false, returnTrue, "User requested reload projects");
2800+
this.reloadConfiguredProjectForFiles(this.openFiles as ESMap<Path, NormalizedPath | undefined>, /*clearSemanticCache*/ true, /*delayReload*/ false, returnTrue, "User requested reload projects");
2801+
this.externalProjects.forEach(project => {
2802+
this.clearSemanticCache(project);
2803+
project.updateGraph();
2804+
});
2805+
this.inferredProjects.forEach(project => this.clearSemanticCache(project));
27932806
this.ensureProjectForOpenFiles();
27942807
}
27952808

27962809
private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) {
27972810
// Get open files to reload projects for
27982811
this.reloadConfiguredProjectForFiles(
27992812
configFileExistenceInfo.openFilesImpactedByConfigFile,
2813+
/*clearSemanticCache*/ false,
28002814
/*delayReload*/ true,
28012815
ignoreIfNotRootOfInferredProject ?
28022816
isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project
@@ -2813,12 +2827,12 @@ namespace ts.server {
28132827
* If the there is no existing project it just opens the configured project for the config file
28142828
* reloadForInfo provides a way to filter out files to reload configured project for
28152829
*/
2816-
private reloadConfiguredProjectForFiles<T>(openFiles: ESMap<Path, T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) {
2830+
private reloadConfiguredProjectForFiles<T>(openFiles: ESMap<Path, T>, clearSemanticCache: boolean, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) {
28172831
const updatedProjects = new Map<string, true>();
28182832
const reloadChildProject = (child: ConfiguredProject) => {
28192833
if (!updatedProjects.has(child.canonicalConfigFilePath)) {
28202834
updatedProjects.set(child.canonicalConfigFilePath, true);
2821-
this.reloadConfiguredProject(child, reason, /*isInitialLoad*/ false);
2835+
this.reloadConfiguredProject(child, reason, /*isInitialLoad*/ false, clearSemanticCache);
28222836
}
28232837
};
28242838
// try to reload config file for all open files
@@ -2844,11 +2858,12 @@ namespace ts.server {
28442858
if (delayReload) {
28452859
project.pendingReload = ConfigFileProgramReloadLevel.Full;
28462860
project.pendingReloadReason = reason;
2861+
if (clearSemanticCache) this.clearSemanticCache(project);
28472862
this.delayUpdateProjectGraph(project);
28482863
}
28492864
else {
28502865
// reload from the disk
2851-
this.reloadConfiguredProject(project, reason, /*isInitialLoad*/ false);
2866+
this.reloadConfiguredProject(project, reason, /*isInitialLoad*/ false, clearSemanticCache);
28522867
// If this project does not contain this file directly, reload the project till the reloaded project contains the script info directly
28532868
if (!projectContainsInfoDirectly(project, info)) {
28542869
const referencedProject = forEachResolvedProjectReferenceProject(

src/server/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2131,7 +2131,7 @@ namespace ts.server {
21312131
this.openFileWatchTriggered.clear();
21322132
const reason = Debug.checkDefined(this.pendingReloadReason);
21332133
this.pendingReloadReason = undefined;
2134-
this.projectService.reloadConfiguredProject(this, reason, isInitialLoad);
2134+
this.projectService.reloadConfiguredProject(this, reason, isInitialLoad, /*clearSemanticCache*/ false);
21352135
result = true;
21362136
break;
21372137
default:

src/testRunner/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@
184184
"unittests/tsserver/projects.ts",
185185
"unittests/tsserver/refactors.ts",
186186
"unittests/tsserver/reload.ts",
187+
"unittests/tsserver/reloadProjects.ts",
187188
"unittests/tsserver/rename.ts",
188189
"unittests/tsserver/resolutionCache.ts",
189190
"unittests/tsserver/session.ts",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
namespace ts.projectSystem {
2+
describe("unittests:: tsserver:: reloadProjects", () => {
3+
const configFile: File = {
4+
path: `${tscWatch.projectRoot}/tsconfig.json`,
5+
content: JSON.stringify({
6+
watchOptions: { excludeDirectories: ["node_modules"] }
7+
})
8+
};
9+
const file1: File = {
10+
path: `${tscWatch.projectRoot}/file1.ts`,
11+
content: `import { foo } from "module1";
12+
foo();`
13+
};
14+
const file2: File = {
15+
path: `${tscWatch.projectRoot}/file2.ts`,
16+
content: `${file1.content}
17+
export function bar(){}`
18+
};
19+
const moduleFile: File = {
20+
path: `${tscWatch.projectRoot}/node_modules/module1/index.d.ts`,
21+
content: `export function foo(): string;`
22+
};
23+
24+
it("configured project", () => {
25+
const host = createServerHost([configFile, libFile, file1, file2]);
26+
const service = createProjectService(host);
27+
service.openClientFile(file1.path);
28+
checkNumberOfProjects(service, { configuredProjects: 1 });
29+
const project = service.configuredProjects.get(configFile.path)!;
30+
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path]);
31+
32+
// Install module1
33+
host.ensureFileOrFolder(moduleFile);
34+
host.checkTimeoutQueueLength(0);
35+
36+
service.reloadProjects();
37+
checkNumberOfProjects(service, { configuredProjects: 1 });
38+
assert.strictEqual(service.configuredProjects.get(configFile.path), project);
39+
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path, moduleFile.path]);
40+
});
41+
42+
it("inferred project", () => {
43+
const host = createServerHost([libFile, file1, file2]);
44+
const service = createProjectService(host, /*parameters*/ undefined, { useInferredProjectPerProjectRoot: true, });
45+
const timeoutId = host.getNextTimeoutId();
46+
service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["node_modules"] }, tscWatch.projectRoot);
47+
host.clearTimeout(timeoutId);
48+
service.openClientFile(file1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, tscWatch.projectRoot);
49+
checkNumberOfProjects(service, { inferredProjects: 1 });
50+
const project = service.inferredProjects[0];
51+
checkProjectActualFiles(project, [libFile.path, file1.path]);
52+
53+
// Install module1
54+
host.ensureFileOrFolder(moduleFile);
55+
host.checkTimeoutQueueLength(0);
56+
57+
service.reloadProjects();
58+
checkNumberOfProjects(service, { inferredProjects: 1 });
59+
assert.strictEqual(service.inferredProjects[0], project);
60+
checkProjectActualFiles(project, [libFile.path, file1.path, moduleFile.path]);
61+
});
62+
63+
it("external project", () => {
64+
const host = createServerHost([libFile, file1, file2]);
65+
const service = createProjectService(host);
66+
service.openExternalProject({
67+
projectFileName: `${tscWatch.projectRoot}/project.sln`,
68+
options: { excludeDirectories: ["node_modules"] },
69+
rootFiles: [{ fileName: file1.path }, { fileName: file2.path }]
70+
});
71+
service.openClientFile(file1.path);
72+
checkNumberOfProjects(service, { externalProjects: 1 });
73+
const project = service.externalProjects[0];
74+
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]);
75+
76+
// Install module1
77+
host.ensureFileOrFolder(moduleFile);
78+
host.checkTimeoutQueueLength(0);
79+
80+
service.reloadProjects();
81+
checkNumberOfProjects(service, { externalProjects: 1 });
82+
assert.strictEqual(service.externalProjects[0], project);
83+
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, moduleFile.path]);
84+
});
85+
86+
it("external project with config file", () => {
87+
const host = createServerHost([libFile, file1, file2, configFile]);
88+
const service = createProjectService(host);
89+
service.openExternalProject({
90+
projectFileName: `${tscWatch.projectRoot}/project.sln`,
91+
options: { excludeDirectories: ["node_modules"] },
92+
rootFiles: [{ fileName: file1.path }, { fileName: file2.path }, { fileName: configFile.path }]
93+
});
94+
service.openClientFile(file1.path);
95+
checkNumberOfProjects(service, { configuredProjects: 1 });
96+
const project = service.configuredProjects.get(configFile.path)!;
97+
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path]);
98+
99+
// Install module1
100+
host.ensureFileOrFolder(moduleFile);
101+
host.checkTimeoutQueueLength(0);
102+
103+
service.reloadProjects();
104+
checkNumberOfProjects(service, { configuredProjects: 1 });
105+
assert.strictEqual(service.configuredProjects.get(configFile.path), project);
106+
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path, moduleFile.path]);
107+
});
108+
});
109+
}

0 commit comments

Comments
 (0)