Skip to content

Commit 27e3d6d

Browse files
committed
Fix the incorrect resolution watching ref management for symlinks
1 parent 137cec1 commit 27e3d6d

File tree

2 files changed

+52
-40
lines changed

2 files changed

+52
-40
lines changed

src/compiler/resolutionCache.ts

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
normalizePath,
5555
PackageId,
5656
packageIdToString,
57+
PackageJsonInfo,
5758
parseNodeModuleFromPath,
5859
Path,
5960
PathPathComponents,
@@ -213,7 +214,7 @@ export interface FileWatcherOfAffectingLocation {
213214
watcher: FileWatcher;
214215
resolutions: number;
215216
files: number;
216-
paths: Set<string>;
217+
symlinks: Set<string> | undefined;
217218
}
218219

219220
/** @internal */
@@ -723,12 +724,11 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
723724
}
724725
});
725726
fileWatchesOfAffectingLocations.forEach((watcher, path) => {
726-
if (watcher.files === 0 && watcher.resolutions === 0) {
727+
if (watcher.files === 0 && watcher.resolutions === 0 && !watcher.symlinks?.size) {
727728
fileWatchesOfAffectingLocations.delete(path);
728729
watcher.watcher.close();
729730
}
730731
});
731-
732732
hasChangedAutomaticTypeDirectiveNames = false;
733733
}
734734

@@ -1057,51 +1057,63 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
10571057
return;
10581058
}
10591059
let locationToWatch = affectingLocation;
1060+
let isSymlink = false;
1061+
let symlinkWatcher: FileWatcherOfAffectingLocation | undefined;
10601062
if (resolutionHost.realpath) {
10611063
locationToWatch = resolutionHost.realpath(affectingLocation);
10621064
if (affectingLocation !== locationToWatch) {
1063-
const fileWatcher = fileWatchesOfAffectingLocations.get(locationToWatch);
1064-
if (fileWatcher) {
1065-
if (forResolution) fileWatcher.resolutions++;
1066-
else fileWatcher.files++;
1067-
fileWatcher.paths.add(affectingLocation);
1068-
fileWatchesOfAffectingLocations.set(affectingLocation, fileWatcher);
1069-
return;
1070-
}
1065+
isSymlink = true;
1066+
symlinkWatcher = fileWatchesOfAffectingLocations.get(locationToWatch);
10711067
}
10721068
}
1073-
const paths = new Set<string>();
1074-
paths.add(locationToWatch);
1075-
let actualWatcher = canWatchAffectingLocation(resolutionHost.toPath(locationToWatch)) ?
1076-
resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => {
1077-
cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind);
1078-
const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap();
1079-
paths.forEach(path => {
1080-
if (watcher.resolutions) (affectingPathChecks ??= new Set()).add(path);
1081-
if (watcher.files) (affectingPathChecksForFile ??= new Set()).add(path);
1082-
packageJsonMap?.delete(resolutionHost.toPath(path));
1083-
});
1084-
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
1085-
}) : noopFileWatcher;
1086-
const watcher: FileWatcherOfAffectingLocation = {
1087-
watcher: actualWatcher !== noopFileWatcher ? {
1088-
close: () => {
1089-
actualWatcher.close();
1090-
// Ensure when watching symlinked package.json, we can close the actual file watcher only once
1091-
actualWatcher = noopFileWatcher;
1092-
}
1093-
} : actualWatcher,
1094-
resolutions: forResolution ? 1 : 0,
1095-
files: forResolution ? 0 : 1,
1096-
paths,
1097-
};
1098-
fileWatchesOfAffectingLocations.set(locationToWatch, watcher);
1099-
if (affectingLocation !== locationToWatch) {
1069+
1070+
const resolutions = forResolution ? 1 : 0;
1071+
const files = forResolution ? 0 : 1;
1072+
if (!isSymlink || !symlinkWatcher) {
1073+
const watcher: FileWatcherOfAffectingLocation = {
1074+
watcher: canWatchAffectingLocation(resolutionHost.toPath(locationToWatch)) ?
1075+
resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => {
1076+
cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind);
1077+
invalidateAffectingFileWatcher(locationToWatch, moduleResolutionCache.getPackageJsonInfoCache().getInternalMap());
1078+
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
1079+
}) : noopFileWatcher,
1080+
resolutions: isSymlink ? 0 : resolutions,
1081+
files: isSymlink ? 0 : files,
1082+
symlinks: undefined,
1083+
};
1084+
fileWatchesOfAffectingLocations.set(locationToWatch, watcher);
1085+
if (isSymlink) symlinkWatcher = watcher;
1086+
}
1087+
if (isSymlink) {
1088+
Debug.assert(!!symlinkWatcher);
1089+
const watcher: FileWatcherOfAffectingLocation = {
1090+
watcher: {
1091+
close: () => {
1092+
const symlinkWatcher = fileWatchesOfAffectingLocations.get(locationToWatch);
1093+
// Close symlink watcher if no ref
1094+
if (symlinkWatcher?.symlinks?.delete(affectingLocation) && !symlinkWatcher.symlinks.size && !symlinkWatcher.resolutions && !symlinkWatcher.files) {
1095+
fileWatchesOfAffectingLocations.delete(locationToWatch);
1096+
symlinkWatcher.watcher.close();
1097+
}
1098+
},
1099+
},
1100+
resolutions,
1101+
files,
1102+
symlinks: undefined,
1103+
};
11001104
fileWatchesOfAffectingLocations.set(affectingLocation, watcher);
1101-
paths.add(affectingLocation);
1105+
(symlinkWatcher.symlinks ??= new Set()).add(affectingLocation);
11021106
}
11031107
}
11041108

1109+
function invalidateAffectingFileWatcher(path: string, packageJsonMap: Map<Path, PackageJsonInfo | boolean> | undefined) {
1110+
const watcher = fileWatchesOfAffectingLocations.get(path);
1111+
if (watcher?.resolutions) (affectingPathChecks ??= new Set()).add(path);
1112+
if (watcher?.files) (affectingPathChecksForFile ??= new Set()).add(path);
1113+
watcher?.symlinks?.forEach(path => invalidateAffectingFileWatcher(path, packageJsonMap));
1114+
packageJsonMap?.delete(resolutionHost.toPath(path));
1115+
}
1116+
11051117
function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) {
11061118
const program = resolutionHost.getCurrentProgram();
11071119
if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) {

src/harness/incrementalUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ function verifyResolutionCache(
385385
) {
386386
ts.Debug.assert(expected?.resolutions === actual?.resolutions, `${projectName}:: ${caption}:: resolutions`);
387387
ts.Debug.assert(expected?.files === actual?.files, `${projectName}:: ${caption}:: files`);
388-
verifySet(expected?.paths, actual?.paths, `${projectName}:: ${caption}:: paths`);
388+
verifySet(expected?.symlinks, actual?.symlinks, `${projectName}:: ${caption}:: symlinks`);
389389
}
390390
}
391391

0 commit comments

Comments
 (0)