Skip to content

Commit 2671668

Browse files
authored
Remove cached resolvedModule result if the target file was deleted (#11460)
* Add test first for debugging * Remove cached resolvedModule if the file was deleted * Move the fix to lsHost * Properly clean lastDeletedFile * Remove unused builder API * move the delete file check into module name valid check
1 parent 4fa2e5e commit 2671668

File tree

3 files changed

+144
-5
lines changed

3 files changed

+144
-5
lines changed

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2097,7 +2097,7 @@ namespace ts.projectSystem {
20972097
const projectFileName = "externalProject";
20982098
const host = createServerHost([f]);
20992099
const projectService = createProjectService(host);
2100-
// create a project
2100+
// create a project
21012101
projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} });
21022102
projectService.checkNumberOfProjects({ externalProjects: 1 });
21032103

@@ -2135,6 +2135,137 @@ namespace ts.projectSystem {
21352135
});
21362136
});
21372137

2138+
describe("rename a module file and rename back", () => {
2139+
it("should restore the states for inferred projects", () => {
2140+
const moduleFile = {
2141+
path: "/a/b/moduleFile.ts",
2142+
content: "export function bar() { };"
2143+
};
2144+
const file1 = {
2145+
path: "/a/b/file1.ts",
2146+
content: "import * as T from './moduleFile'; T.bar();"
2147+
};
2148+
const host = createServerHost([moduleFile, file1]);
2149+
const session = createSession(host);
2150+
2151+
openFilesForSession([file1], session);
2152+
const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
2153+
server.CommandNames.SemanticDiagnosticsSync,
2154+
{ file: file1.path }
2155+
);
2156+
let diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2157+
assert.equal(diags.length, 0);
2158+
2159+
const moduleFileOldPath = moduleFile.path;
2160+
const moduleFileNewPath = "/a/b/moduleFile1.ts";
2161+
moduleFile.path = moduleFileNewPath;
2162+
host.reloadFS([moduleFile, file1]);
2163+
host.triggerFileWatcherCallback(moduleFileOldPath);
2164+
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
2165+
host.runQueuedTimeoutCallbacks();
2166+
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2167+
assert.equal(diags.length, 1);
2168+
2169+
moduleFile.path = moduleFileOldPath;
2170+
host.reloadFS([moduleFile, file1]);
2171+
host.triggerFileWatcherCallback(moduleFileNewPath);
2172+
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
2173+
host.runQueuedTimeoutCallbacks();
2174+
2175+
// Make a change to trigger the program rebuild
2176+
const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(
2177+
server.CommandNames.Change,
2178+
{ file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" }
2179+
);
2180+
session.executeCommand(changeRequest);
2181+
host.runQueuedTimeoutCallbacks();
2182+
2183+
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2184+
assert.equal(diags.length, 0);
2185+
});
2186+
2187+
it("should restore the states for configured projects", () => {
2188+
const moduleFile = {
2189+
path: "/a/b/moduleFile.ts",
2190+
content: "export function bar() { };"
2191+
};
2192+
const file1 = {
2193+
path: "/a/b/file1.ts",
2194+
content: "import * as T from './moduleFile'; T.bar();"
2195+
};
2196+
const configFile = {
2197+
path: "/a/b/tsconfig.json",
2198+
content: `{}`
2199+
};
2200+
const host = createServerHost([moduleFile, file1, configFile]);
2201+
const session = createSession(host);
2202+
2203+
openFilesForSession([file1], session);
2204+
const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
2205+
server.CommandNames.SemanticDiagnosticsSync,
2206+
{ file: file1.path }
2207+
);
2208+
let diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2209+
assert.equal(diags.length, 0);
2210+
2211+
const moduleFileOldPath = moduleFile.path;
2212+
const moduleFileNewPath = "/a/b/moduleFile1.ts";
2213+
moduleFile.path = moduleFileNewPath;
2214+
host.reloadFS([moduleFile, file1, configFile]);
2215+
host.triggerFileWatcherCallback(moduleFileOldPath);
2216+
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
2217+
host.runQueuedTimeoutCallbacks();
2218+
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2219+
assert.equal(diags.length, 1);
2220+
2221+
moduleFile.path = moduleFileOldPath;
2222+
host.reloadFS([moduleFile, file1, configFile]);
2223+
host.triggerFileWatcherCallback(moduleFileNewPath);
2224+
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
2225+
host.runQueuedTimeoutCallbacks();
2226+
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2227+
assert.equal(diags.length, 0);
2228+
});
2229+
2230+
});
2231+
2232+
describe("add the missing module file for inferred project", () => {
2233+
it("should remove the `module not found` error", () => {
2234+
const moduleFile = {
2235+
path: "/a/b/moduleFile.ts",
2236+
content: "export function bar() { };"
2237+
};
2238+
const file1 = {
2239+
path: "/a/b/file1.ts",
2240+
content: "import * as T from './moduleFile'; T.bar();"
2241+
};
2242+
const host = createServerHost([file1]);
2243+
const session = createSession(host);
2244+
openFilesForSession([file1], session);
2245+
const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
2246+
server.CommandNames.SemanticDiagnosticsSync,
2247+
{ file: file1.path }
2248+
);
2249+
let diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2250+
assert.equal(diags.length, 1);
2251+
2252+
host.reloadFS([file1, moduleFile]);
2253+
host.triggerDirectoryWatcherCallback(getDirectoryPath(file1.path), moduleFile.path);
2254+
host.runQueuedTimeoutCallbacks();
2255+
2256+
// Make a change to trigger the program rebuild
2257+
const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(
2258+
server.CommandNames.Change,
2259+
{ file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" }
2260+
);
2261+
session.executeCommand(changeRequest);
2262+
2263+
// Recheck
2264+
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
2265+
assert.equal(diags.length, 0);
2266+
});
2267+
});
2268+
21382269
describe("Configure file diagnostics events", () => {
21392270

21402271
it("are generated when the config file has errors", () => {

src/server/editorServices.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ namespace ts.server {
180180

181181
private toCanonicalFileName: (f: string) => string;
182182

183+
public lastDeletedFile: ScriptInfo;
184+
183185
constructor(public readonly host: ServerHost,
184186
public readonly logger: Logger,
185187
public readonly cancellationToken: HostCancellationToken,
@@ -272,7 +274,7 @@ namespace ts.server {
272274
else {
273275
projectsToUpdate = [];
274276
for (const f of this.changedFiles) {
275-
projectsToUpdate = projectsToUpdate.concat(f.containingProjects);
277+
projectsToUpdate = projectsToUpdate.concat(f.containingProjects);
276278
}
277279
}
278280
this.updateProjectGraphs(projectsToUpdate);
@@ -342,6 +344,7 @@ namespace ts.server {
342344

343345
if (!info.isOpen) {
344346
this.filenameToScriptInfo.remove(info.path);
347+
this.lastDeletedFile = info;
345348

346349
// capture list of projects since detachAllProjects will wipe out original list
347350
const containingProjects = info.containingProjects.slice();
@@ -350,6 +353,7 @@ namespace ts.server {
350353

351354
// update projects to make sure that set of referenced files is correct
352355
this.updateProjectGraphs(containingProjects);
356+
this.lastDeletedFile = undefined;
353357

354358
if (!this.eventHandler) {
355359
return;

src/server/lsHost.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ namespace ts.server {
5252
};
5353
}
5454

55-
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
55+
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R extends { resolvedFileName?: string }>(
5656
names: string[],
5757
containingFile: string,
5858
cache: ts.FileMap<Map<T>>,
@@ -65,6 +65,7 @@ namespace ts.server {
6565
const newResolutions: Map<T> = createMap<T>();
6666
const resolvedModules: R[] = [];
6767
const compilerOptions = this.getCompilationSettings();
68+
const lastDeletedFileName = this.project.projectService.lastDeletedFile && this.project.projectService.lastDeletedFile.fileName;
6869

6970
for (const name of names) {
7071
// check if this is a duplicate entry in the list
@@ -94,8 +95,11 @@ namespace ts.server {
9495
return false;
9596
}
9697

97-
if (getResult(resolution)) {
98-
// TODO: consider checking failedLookupLocations
98+
const result = getResult(resolution);
99+
if (result) {
100+
if (result.resolvedFileName && result.resolvedFileName === lastDeletedFileName) {
101+
return false;
102+
}
99103
return true;
100104
}
101105

0 commit comments

Comments
 (0)