Skip to content

Commit 2451fff

Browse files
authored
Merge pull request #22820 from Microsoft/moduleResolutionEvents
[release-2.8] Invalidate resolutions from typeRoots watch event as a fallback mechanism
2 parents cc67601 + 477ee3d commit 2451fff

File tree

3 files changed

+80
-11
lines changed

3 files changed

+80
-11
lines changed

src/compiler/resolutionCache.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,13 @@ namespace ts {
361361
return { dir: rootDir, dirPath: rootPath };
362362
}
363363

364-
let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()));
365-
let dirPath = getDirectoryPath(failedLookupLocationPath);
364+
return getDirectoryToWatchFromFailedLookupLocationDirectory(
365+
getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())),
366+
getDirectoryPath(failedLookupLocationPath)
367+
);
368+
}
366369

370+
function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path) {
367371
// If directory path contains node module, get the most parent node_modules directory for watching
368372
while (stringContains(dirPath, "/node_modules/")) {
369373
dir = getDirectoryPath(dir);
@@ -621,7 +625,19 @@ namespace ts {
621625
clearMap(typeRootsWatches, closeFileWatcher);
622626
}
623627

624-
function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher {
628+
function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined {
629+
if (allFilesHaveInvalidatedResolution) {
630+
return undefined;
631+
}
632+
633+
if (isInDirectoryPath(rootPath, typeRootPath)) {
634+
return rootPath;
635+
}
636+
const { dirPath, ignore } = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath);
637+
return !ignore && directoryWatchesOfFailedLookups.has(dirPath) && dirPath;
638+
}
639+
640+
function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher {
625641
// Create new watch and recursive info
626642
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
627643
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
@@ -634,6 +650,13 @@ namespace ts {
634650
// We could potentially store more data here about whether it was/would be really be used or not
635651
// and with that determine to trigger compilation but for now this is enough
636652
resolutionHost.onChangedAutomaticTypeDirectiveNames();
653+
654+
// Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
655+
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
656+
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
657+
if (dirPath && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
658+
resolutionHost.onInvalidatedResolution();
659+
}
637660
}, WatchDirectoryFlags.Recursive);
638661
}
639662

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6211,6 +6211,44 @@ namespace ts.projectSystem {
62116211
verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false);
62126212
});
62136213
});
6214+
6215+
it("when node_modules dont receive event for the @types file addition", () => {
6216+
const projectLocation = "/user/username/folder/myproject";
6217+
const app: FileOrFolder = {
6218+
path: `${projectLocation}/app.ts`,
6219+
content: `import * as debug from "debug"`
6220+
};
6221+
const tsconfig: FileOrFolder = {
6222+
path: `${projectLocation}/tsconfig.json`,
6223+
content: ""
6224+
};
6225+
6226+
const files = [app, tsconfig, libFile];
6227+
const host = createServerHost(files);
6228+
const service = createProjectService(host);
6229+
service.openClientFile(app.path);
6230+
6231+
const project = service.configuredProjects.get(tsconfig.path);
6232+
checkProjectActualFiles(project, files.map(f => f.path));
6233+
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]);
6234+
6235+
const debugTypesFile: FileOrFolder = {
6236+
path: `${projectLocation}/node_modules/@types/debug/index.d.ts`,
6237+
content: "export {}"
6238+
};
6239+
files.push(debugTypesFile);
6240+
// Do not invoke recursive directory watcher for anything other than node_module/@types
6241+
const invoker = host.invokeWatchedDirectoriesRecursiveCallback;
6242+
host.invokeWatchedDirectoriesRecursiveCallback = (fullPath, relativePath) => {
6243+
if (fullPath.endsWith("@types")) {
6244+
invoker.call(host, fullPath, relativePath);
6245+
}
6246+
};
6247+
host.reloadFS(files);
6248+
host.runQueuedTimeoutCallbacks();
6249+
checkProjectActualFiles(project, files.map(f => f.path));
6250+
assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []);
6251+
});
62146252
});
62156253

62166254
describe("tsserverProjectSystem ProjectsChangedInBackground", () => {

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ interface Array<T> {}`
540540
// Invoke directory and recursive directory watcher for the folder
541541
// Here we arent invoking recursive directory watchers for the base folders
542542
// since that is something we would want to do for both file as well as folder we are deleting
543-
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
544-
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
543+
this.invokeWatchedDirectoriesCallback(fileOrDirectory.fullPath, relativePath);
544+
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, relativePath);
545545
}
546546

547547
if (basePath !== fileOrDirectory.path) {
@@ -554,9 +554,17 @@ interface Array<T> {}`
554554
}
555555
}
556556

557-
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) {
558-
const callbacks = this.watchedFiles.get(this.toPath(fileFullPath));
559-
invokeWatcherCallbacks(callbacks, ({ cb }) => cb(fileFullPath, eventKind));
557+
// For overriding the methods
558+
invokeWatchedDirectoriesCallback(folderFullPath: string, relativePath: string) {
559+
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
560+
}
561+
562+
invokeWatchedDirectoriesRecursiveCallback(folderFullPath: string, relativePath: string) {
563+
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
564+
}
565+
566+
invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, useFileNameInCallback?: boolean) {
567+
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind));
560568
}
561569

562570
private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) {
@@ -569,8 +577,8 @@ interface Array<T> {}`
569577
private invokeDirectoryWatcher(folderFullPath: string, fileName: string) {
570578
const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName);
571579
// Folder is changed when the directory watcher is invoked
572-
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(folderFullPath)), ({ cb, fileName }) => cb(fileName, FileWatcherEventKind.Changed));
573-
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
580+
this.invokeFileWatcher(folderFullPath, FileWatcherEventKind.Changed, /*useFileNameInCallback*/ true);
581+
this.invokeWatchedDirectoriesCallback(folderFullPath, relativePath);
574582
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
575583
}
576584

@@ -583,7 +591,7 @@ interface Array<T> {}`
583591
*/
584592
private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) {
585593
const relativePath = this.getRelativePathToDirectory(fullPath, fileName);
586-
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => this.directoryCallback(cb, relativePath));
594+
this.invokeWatchedDirectoriesRecursiveCallback(fullPath, relativePath);
587595
const basePath = getDirectoryPath(fullPath);
588596
if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) {
589597
this.invokeRecursiveDirectoryWatcher(basePath, fileName);

0 commit comments

Comments
 (0)