Skip to content

Commit f592419

Browse files
authored
Merge pull request #19138 from Microsoft/configuredProjectRef
Handle the configured project lifetime to account for files added to the project after config file gets reloaded
2 parents 89e19ff + b68a636 commit f592419

File tree

4 files changed

+299
-62
lines changed

4 files changed

+299
-62
lines changed

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 232 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,10 @@ namespace ts.projectSystem {
355355
return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator);
356356
}
357357

358+
function checkOpenFiles(projectService: server.ProjectService, expectedFiles: FileOrFolder[]) {
359+
checkFileNames("Open files", projectService.openFiles.map(info => info.fileName), expectedFiles.map(file => file.path));
360+
}
361+
358362
/**
359363
* Test server cancellation token used to mock host token cancellation requests.
360364
* The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls
@@ -1060,16 +1064,19 @@ namespace ts.projectSystem {
10601064
projectService.openClientFile(file1.path);
10611065
checkNumberOfConfiguredProjects(projectService, 1);
10621066
const project = projectService.configuredProjects.get(configFile.path);
1067+
assert.isTrue(project.hasOpenRef()); // file1
10631068

10641069
projectService.closeClientFile(file1.path);
10651070
checkNumberOfConfiguredProjects(projectService, 1);
10661071
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
1067-
assert.equal(project.openRefCount, 0);
1072+
assert.isFalse(project.hasOpenRef()); // No open files
1073+
assert.isFalse(project.isClosed());
10681074

10691075
projectService.openClientFile(file2.path);
10701076
checkNumberOfConfiguredProjects(projectService, 1);
10711077
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
1072-
assert.equal(project.openRefCount, 1);
1078+
assert.isTrue(project.hasOpenRef()); // file2
1079+
assert.isFalse(project.isClosed());
10731080
});
10741081

10751082
it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => {
@@ -1091,14 +1098,18 @@ namespace ts.projectSystem {
10911098
projectService.openClientFile(file1.path);
10921099
checkNumberOfConfiguredProjects(projectService, 1);
10931100
const project = projectService.configuredProjects.get(configFile.path);
1101+
assert.isTrue(project.hasOpenRef()); // file1
10941102

10951103
projectService.closeClientFile(file1.path);
10961104
checkNumberOfConfiguredProjects(projectService, 1);
10971105
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
1098-
assert.equal(project.openRefCount, 0);
1106+
assert.isFalse(project.hasOpenRef()); // No files
1107+
assert.isFalse(project.isClosed());
10991108

11001109
projectService.openClientFile(libFile.path);
11011110
checkNumberOfConfiguredProjects(projectService, 0);
1111+
assert.isFalse(project.hasOpenRef()); // No files + project closed
1112+
assert.isTrue(project.isClosed());
11021113
});
11031114

11041115
it("should not close external project with no open files", () => {
@@ -2085,39 +2096,224 @@ namespace ts.projectSystem {
20852096
projectService.openClientFile(file2.path);
20862097
checkNumberOfProjects(projectService, { configuredProjects: 1 });
20872098
const project1 = projectService.configuredProjects.get(tsconfig1.path);
2088-
assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 1");
2099+
assert.isTrue(project1.hasOpenRef(), "Has open ref count in project1 - 1"); // file2
20892100
assert.equal(project1.getScriptInfo(file2.path).containingProjects.length, 1, "containing projects count");
2101+
assert.isFalse(project1.isClosed());
20902102

20912103
projectService.openClientFile(file1.path);
20922104
checkNumberOfProjects(projectService, { configuredProjects: 2 });
2093-
assert.equal(project1.openRefCount, 2, "Open ref count in project1 - 2");
2105+
assert.isTrue(project1.hasOpenRef(), "Has open ref count in project1 - 2"); // file2
20942106
assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1);
2107+
assert.isFalse(project1.isClosed());
20952108

20962109
const project2 = projectService.configuredProjects.get(tsconfig2.path);
2097-
assert.equal(project2.openRefCount, 1, "Open ref count in project2 - 2");
2110+
assert.isTrue(project2.hasOpenRef(), "Has open ref count in project2 - 2"); // file1
2111+
assert.isFalse(project2.isClosed());
20982112

20992113
assert.equal(project1.getScriptInfo(file1.path).containingProjects.length, 2, `${file1.path} containing projects count`);
21002114
assert.equal(project1.getScriptInfo(file2.path).containingProjects.length, 1, `${file2.path} containing projects count`);
21012115

21022116
projectService.closeClientFile(file2.path);
21032117
checkNumberOfProjects(projectService, { configuredProjects: 2 });
2104-
assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 3");
2105-
assert.equal(project2.openRefCount, 1, "Open ref count in project2 - 3");
2118+
assert.isFalse(project1.hasOpenRef(), "Has open ref count in project1 - 3"); // No files
2119+
assert.isTrue(project2.hasOpenRef(), "Has open ref count in project2 - 3"); // file1
21062120
assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1);
21072121
assert.strictEqual(projectService.configuredProjects.get(tsconfig2.path), project2);
2122+
assert.isFalse(project1.isClosed());
2123+
assert.isFalse(project2.isClosed());
21082124

21092125
projectService.closeClientFile(file1.path);
21102126
checkNumberOfProjects(projectService, { configuredProjects: 2 });
2111-
assert.equal(project1.openRefCount, 0, "Open ref count in project1 - 4");
2112-
assert.equal(project2.openRefCount, 0, "Open ref count in project2 - 4");
2127+
assert.isFalse(project1.hasOpenRef(), "Has open ref count in project1 - 4"); // No files
2128+
assert.isFalse(project2.hasOpenRef(), "Has open ref count in project2 - 4"); // No files
21132129
assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1);
21142130
assert.strictEqual(projectService.configuredProjects.get(tsconfig2.path), project2);
2131+
assert.isFalse(project1.isClosed());
2132+
assert.isFalse(project2.isClosed());
21152133

21162134
projectService.openClientFile(file2.path);
21172135
checkNumberOfProjects(projectService, { configuredProjects: 1 });
21182136
assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1);
21192137
assert.isUndefined(projectService.configuredProjects.get(tsconfig2.path));
2120-
assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 5");
2138+
assert.isTrue(project1.hasOpenRef(), "Has open ref count in project1 - 5"); // file2
2139+
assert.isFalse(project1.isClosed());
2140+
assert.isTrue(project2.isClosed());
2141+
});
2142+
2143+
it("Open ref of configured project when open file gets added to the project as part of configured file update", () => {
2144+
const file1: FileOrFolder = {
2145+
path: "/a/b/src/file1.ts",
2146+
content: "let x = 1;"
2147+
};
2148+
const file2: FileOrFolder = {
2149+
path: "/a/b/src/file2.ts",
2150+
content: "let y = 1;"
2151+
};
2152+
const file3: FileOrFolder = {
2153+
path: "/a/b/file3.ts",
2154+
content: "let z = 1;"
2155+
};
2156+
const file4: FileOrFolder = {
2157+
path: "/a/file4.ts",
2158+
content: "let z = 1;"
2159+
};
2160+
const configFile = {
2161+
path: "/a/b/tsconfig.json",
2162+
content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] })
2163+
};
2164+
2165+
const files = [file1, file2, file3, file4];
2166+
const host = createServerHost(files.concat(configFile));
2167+
const projectService = createProjectService(host);
2168+
2169+
projectService.openClientFile(file1.path);
2170+
projectService.openClientFile(file2.path);
2171+
projectService.openClientFile(file3.path);
2172+
projectService.openClientFile(file4.path);
2173+
2174+
const infos = files.map(file => projectService.getScriptInfoForPath(file.path as Path));
2175+
checkOpenFiles(projectService, files);
2176+
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 });
2177+
const configProject1 = projectService.configuredProjects.get(configFile.path);
2178+
assert.isTrue(configProject1.hasOpenRef()); // file1 and file3
2179+
checkProjectActualFiles(configProject1, [file1.path, file3.path, configFile.path]);
2180+
const inferredProject1 = projectService.inferredProjects[0];
2181+
checkProjectActualFiles(inferredProject1, [file2.path]);
2182+
const inferredProject2 = projectService.inferredProjects[1];
2183+
checkProjectActualFiles(inferredProject2, [file4.path]);
2184+
2185+
configFile.content = "{}";
2186+
host.reloadFS(files.concat(configFile));
2187+
host.runQueuedTimeoutCallbacks();
2188+
2189+
verifyScriptInfos();
2190+
checkOpenFiles(projectService, files);
2191+
verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true); // file1, file2, file3
2192+
checkNumberOfInferredProjects(projectService, 1);
2193+
const inferredProject3 = projectService.inferredProjects[0];
2194+
checkProjectActualFiles(inferredProject3, [file4.path]);
2195+
assert.strictEqual(inferredProject3, inferredProject2);
2196+
2197+
projectService.closeClientFile(file1.path);
2198+
projectService.closeClientFile(file2.path);
2199+
projectService.closeClientFile(file4.path);
2200+
2201+
verifyScriptInfos();
2202+
checkOpenFiles(projectService, [file3]);
2203+
verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true); // file3
2204+
checkNumberOfInferredProjects(projectService, 0);
2205+
2206+
projectService.openClientFile(file4.path);
2207+
verifyScriptInfos();
2208+
checkOpenFiles(projectService, [file3, file4]);
2209+
verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true); // file3
2210+
checkNumberOfInferredProjects(projectService, 1);
2211+
const inferredProject4 = projectService.inferredProjects[0];
2212+
checkProjectActualFiles(inferredProject4, [file4.path]);
2213+
2214+
projectService.closeClientFile(file3.path);
2215+
verifyScriptInfos();
2216+
checkOpenFiles(projectService, [file4]);
2217+
verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ false); // No open files
2218+
checkNumberOfInferredProjects(projectService, 1);
2219+
const inferredProject5 = projectService.inferredProjects[0];
2220+
checkProjectActualFiles(inferredProject4, [file4.path]);
2221+
assert.strictEqual(inferredProject5, inferredProject4);
2222+
2223+
const file5: FileOrFolder = {
2224+
path: "/file5.ts",
2225+
content: "let zz = 1;"
2226+
};
2227+
host.reloadFS(files.concat(configFile, file5));
2228+
projectService.openClientFile(file5.path);
2229+
verifyScriptInfosAreUndefined([file1, file2, file3]);
2230+
assert.strictEqual(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path));
2231+
assert.isDefined(projectService.getScriptInfoForPath(file5.path as Path));
2232+
checkOpenFiles(projectService, [file4, file5]);
2233+
checkNumberOfConfiguredProjects(projectService, 0);
2234+
2235+
function verifyScriptInfos() {
2236+
infos.forEach(info => assert.strictEqual(projectService.getScriptInfoForPath(info.path), info));
2237+
}
2238+
2239+
function verifyScriptInfosAreUndefined(files: FileOrFolder[]) {
2240+
for (const file of files) {
2241+
assert.isUndefined(projectService.getScriptInfoForPath(file.path as Path));
2242+
}
2243+
}
2244+
2245+
function verifyConfiguredProjectStateAfterUpdate(hasOpenRef: boolean) {
2246+
checkNumberOfConfiguredProjects(projectService, 1);
2247+
const configProject2 = projectService.configuredProjects.get(configFile.path);
2248+
assert.strictEqual(configProject2, configProject1);
2249+
checkProjectActualFiles(configProject2, [file1.path, file2.path, file3.path, configFile.path]);
2250+
assert.equal(configProject2.hasOpenRef(), hasOpenRef);
2251+
}
2252+
});
2253+
2254+
it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => {
2255+
const file1: FileOrFolder = {
2256+
path: "/a/b/src/file1.ts",
2257+
content: "let x = 1;"
2258+
};
2259+
const file2: FileOrFolder = {
2260+
path: "/a/b/src/file2.ts",
2261+
content: "let y = 1;"
2262+
};
2263+
const file3: FileOrFolder = {
2264+
path: "/a/b/file3.ts",
2265+
content: "let z = 1;"
2266+
};
2267+
const file4: FileOrFolder = {
2268+
path: "/a/file4.ts",
2269+
content: "let z = 1;"
2270+
};
2271+
const configFile = {
2272+
path: "/a/b/tsconfig.json",
2273+
content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] })
2274+
};
2275+
2276+
const files = [file1, file2, file3];
2277+
const hostFiles = files.concat(file4, configFile);
2278+
const host = createServerHost(hostFiles);
2279+
const projectService = createProjectService(host);
2280+
2281+
projectService.openClientFile(file1.path);
2282+
projectService.openClientFile(file2.path);
2283+
projectService.openClientFile(file3.path);
2284+
2285+
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
2286+
const configuredProject = projectService.configuredProjects.get(configFile.path);
2287+
assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3
2288+
checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]);
2289+
const inferredProject1 = projectService.inferredProjects[0];
2290+
checkProjectActualFiles(inferredProject1, [file2.path]);
2291+
2292+
projectService.closeClientFile(file1.path);
2293+
projectService.closeClientFile(file3.path);
2294+
assert.isFalse(configuredProject.hasOpenRef()); // No files
2295+
2296+
configFile.content = "{}";
2297+
host.reloadFS(files.concat(configFile));
2298+
// Time out is not yet run so there is project update pending
2299+
assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project
2300+
2301+
projectService.openClientFile(file4.path);
2302+
2303+
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 });
2304+
assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject);
2305+
assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project
2306+
assert.strictEqual(projectService.inferredProjects[0], inferredProject1);
2307+
const inferredProject2 = projectService.inferredProjects[1];
2308+
checkProjectActualFiles(inferredProject2, [file4.path]);
2309+
2310+
host.runQueuedTimeoutCallbacks();
2311+
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 });
2312+
assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject);
2313+
assert.isTrue(configuredProject.hasOpenRef()); // file2
2314+
checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]);
2315+
assert.strictEqual(projectService.inferredProjects[0], inferredProject2);
2316+
checkProjectActualFiles(inferredProject2, [file4.path]);
21212317
});
21222318

21232319
it("language service disabled state is updated in external projects", () => {
@@ -2188,18 +2384,36 @@ namespace ts.projectSystem {
21882384
projectService.openClientFile(f1.path);
21892385
projectService.checkNumberOfProjects({ configuredProjects: 1 });
21902386
const project = projectService.configuredProjects.get(config.path);
2387+
assert.isTrue(project.hasOpenRef()); // f1
2388+
assert.isFalse(project.isClosed());
21912389

21922390
projectService.closeClientFile(f1.path);
21932391
projectService.checkNumberOfProjects({ configuredProjects: 1 });
21942392
assert.strictEqual(projectService.configuredProjects.get(config.path), project);
2195-
assert.equal(project.openRefCount, 0);
2393+
assert.isFalse(project.hasOpenRef()); // No files
2394+
assert.isFalse(project.isClosed());
21962395

21972396
for (const f of [f1, f2, f3]) {
2198-
// There shouldnt be any script info as we closed the file that resulted in creation of it
2397+
// All the script infos should be present and contain the project since it is still alive.
21992398
const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path));
22002399
assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`);
22012400
assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`);
22022401
}
2402+
2403+
const f4 = {
2404+
path: "/aa.js",
2405+
content: "var x = 1"
2406+
};
2407+
host.reloadFS([f1, f2, f3, config, f4]);
2408+
projectService.openClientFile(f4.path);
2409+
projectService.checkNumberOfProjects({ inferredProjects: 1 });
2410+
assert.isFalse(project.hasOpenRef()); // No files
2411+
assert.isTrue(project.isClosed());
2412+
2413+
for (const f of [f1, f2, f3]) {
2414+
// All the script infos should not be present since the project is closed and orphan script infos are collected
2415+
assert.isUndefined(projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path)));
2416+
}
22032417
});
22042418

22052419
it("language service disabled events are triggered", () => {
@@ -2836,17 +3050,19 @@ namespace ts.projectSystem {
28363050
projectService.openClientFile(f.path);
28373051
projectService.checkNumberOfProjects({ configuredProjects: 1 });
28383052
const project = projectService.configuredProjects.get(config.path);
2839-
assert.equal(project.openRefCount, 1);
3053+
assert.isTrue(project.hasOpenRef()); // f
28403054

28413055
projectService.closeClientFile(f.path);
28423056
projectService.checkNumberOfProjects({ configuredProjects: 1 });
28433057
assert.strictEqual(projectService.configuredProjects.get(config.path), project);
2844-
assert.equal(project.openRefCount, 0);
3058+
assert.isFalse(project.hasOpenRef()); // No files
3059+
assert.isFalse(project.isClosed());
28453060

28463061
projectService.openClientFile(f.path);
28473062
projectService.checkNumberOfProjects({ configuredProjects: 1 });
28483063
assert.strictEqual(projectService.configuredProjects.get(config.path), project);
2849-
assert.equal(project.openRefCount, 1);
3064+
assert.isTrue(project.hasOpenRef()); // f
3065+
assert.isFalse(project.isClosed());
28503066
});
28513067
});
28523068

0 commit comments

Comments
 (0)