Skip to content

Commit a2cd10b

Browse files
uniqueiniquityBenjamin Lichtman
authored and
Benjamin Lichtman
committed
Merge pull request #31685 from uniqueiniquity/stopInvalidatingOnOpenFileSave
Stop invalidating module resolution cache when saving an open file
1 parent a1a2bd6 commit a2cd10b

File tree

7 files changed

+70
-2
lines changed

7 files changed

+70
-2
lines changed

src/compiler/resolutionCache.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ namespace ts {
5454
writeLog(s: string): void;
5555
maxNumberOfFilesToIterateForInvalidation?: number;
5656
getCurrentProgram(): Program | undefined;
57+
fileIsOpen(filePath: Path): boolean;
5758
}
5859

5960
interface DirectoryWatchesOfFailedLookup {
@@ -698,6 +699,11 @@ namespace ts {
698699
// If something to do with folder/file starting with "." in node_modules folder, skip it
699700
if (isPathIgnored(fileOrDirectoryPath)) return false;
700701

702+
// prevent saving an open file from over-eagerly triggering invalidation
703+
if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) {
704+
return false;
705+
}
706+
701707
// Some file or directory in the watching directory is created
702708
// Return early if it does not have any of the watching extension or not the custom failed lookup path
703709
const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath);

src/compiler/watch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ namespace ts {
691691
hasChangedAutomaticTypeDirectiveNames = true;
692692
scheduleProgramUpdate();
693693
};
694+
compilerHost.fileIsOpen = returnFalse;
694695
compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation;
695696
compilerHost.getCurrentProgram = getCurrentProgram;
696697
compilerHost.writeLog = writeLog;

src/server/editorServices.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,12 @@ namespace ts.server {
10031003
fileOrDirectory => {
10041004
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
10051005
project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
1006+
1007+
// don't trigger callback on open, existing files
1008+
if (project.fileIsOpen(fileOrDirectoryPath)) {
1009+
return;
1010+
}
1011+
10061012
if (isPathIgnored(fileOrDirectoryPath)) return;
10071013
const configFilename = project.getConfigFilePath();
10081014

src/server/project.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,11 @@ namespace ts.server {
457457
return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined;
458458
}
459459

460+
/*@internal*/
461+
fileIsOpen(filePath: Path) {
462+
return this.projectService.openFiles.has(filePath);
463+
}
464+
460465
/*@internal*/
461466
writeLog(s: string) {
462467
this.projectService.logger.info(s);

src/testRunner/unittests/tsserver/projects.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,26 @@ var x = 10;`
13411341
}
13421342
});
13431343

1344+
it("no project structure update on directory watch invoke on open file save", () => {
1345+
const projectRootPath = "/users/username/projects/project";
1346+
const file1: File = {
1347+
path: `${projectRootPath}/a.ts`,
1348+
content: "export const a = 10;"
1349+
};
1350+
const config: File = {
1351+
path: `${projectRootPath}/tsconfig.json`,
1352+
content: "{}"
1353+
};
1354+
const files = [file1, config];
1355+
const host = createServerHost(files);
1356+
const service = createProjectService(host);
1357+
service.openClientFile(file1.path);
1358+
checkNumberOfProjects(service, { configuredProjects: 1 });
1359+
1360+
host.modifyFile(file1.path, file1.content, { invokeFileDeleteCreateAsPartInsteadOfChange: true });
1361+
host.checkTimeoutQueueLength(0);
1362+
});
1363+
13441364
it("handles delayed directory watch invoke on file creation", () => {
13451365
const projectRootPath = "/users/username/projects/project";
13461366
const fileB: File = {

src/testRunner/unittests/tsserver/resolutionCache.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,5 +975,34 @@ export const x = 10;`
975975
host.checkTimeoutQueueLength(0);
976976
});
977977
});
978+
979+
describe("avoid unnecessary invalidation", () => {
980+
it("unnecessary lookup invalidation on save", () => {
981+
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`];
982+
const module1Name = "module1";
983+
const module2Name = "module2";
984+
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
985+
const file1: File = {
986+
path: `${projectLocation}/src/file1.ts`,
987+
content: fileContent
988+
};
989+
const { module1, module2 } = getModules(`${projectLocation}/src/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`);
990+
const files = [module1, module2, file1, configFile, libFile];
991+
const host = createServerHost(files);
992+
const resolutionTrace = createHostModuleResolutionTrace(host);
993+
const service = createProjectService(host);
994+
service.openClientFile(file1.path);
995+
const project = service.configuredProjects.get(configFile.path)!;
996+
(project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1;
997+
const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
998+
getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
999+
verifyTrace(resolutionTrace, expectedTrace);
1000+
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
1001+
1002+
// invoke callback to simulate saving
1003+
host.modifyFile(file1.path, file1.content, { invokeFileDeleteCreateAsPartInsteadOfChange: true });
1004+
host.checkTimeoutQueueLengthAndRun(0);
1005+
});
1006+
});
9781007
});
9791008
}

src/testRunner/unittests/tsserver/syntaxOperations.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,14 @@ describe("Test Suite 1", () => {
6060

6161
const navBarResultUnitTest1 = navBarFull(session, unitTest1);
6262
host.deleteFile(unitTest1.path);
63-
host.checkTimeoutQueueLengthAndRun(2);
64-
checkProjectActualFiles(project, expectedFilesWithoutUnitTest1);
63+
host.checkTimeoutQueueLengthAndRun(0);
64+
checkProjectActualFiles(project, expectedFilesWithUnitTest1);
6565

6666
session.executeCommandSeq<protocol.CloseRequest>({
6767
command: protocol.CommandTypes.Close,
6868
arguments: { file: unitTest1.path }
6969
});
70+
host.checkTimeoutQueueLengthAndRun(2);
7071
checkProjectActualFiles(project, expectedFilesWithoutUnitTest1);
7172

7273
const unitTest1WithChangedContent: File = {

0 commit comments

Comments
 (0)