diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index d5f574762a..d05d835050 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -81,6 +81,7 @@ namespace ts { let filesWithInvalidatedResolutions: Map | undefined; let filesWithInvalidatedNonRelativeUnresolvedImports: Map> | undefined; let allFilesHaveInvalidatedResolution = false; + const nonRelativeExternalModuleResolutions = createMultiMap(); const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); @@ -154,6 +155,7 @@ namespace ts { function clear() { clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); customFailedLookupPaths.clear(); + nonRelativeExternalModuleResolutions.clear(); closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); @@ -199,19 +201,20 @@ namespace ts { perDirectoryResolvedModuleNames.clear(); nonRelaticeModuleNameCache.clear(); perDirectoryResolvedTypeReferenceDirectives.clear(); + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); } function finishCachingPerDirectoryResolution() { allFilesHaveInvalidatedResolution = false; filesWithInvalidatedNonRelativeUnresolvedImports = undefined; + clearPerDirectoryResolutions(); directoryWatchesOfFailedLookups.forEach((watcher, path) => { if (watcher.refCount === 0) { directoryWatchesOfFailedLookups.delete(path); watcher.watcher.close(); } }); - - clearPerDirectoryResolutions(); } function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): CachedResolvedModuleWithFailedLookupLocations { @@ -275,7 +278,7 @@ namespace ts { perDirectoryResolution.set(name, resolution); } resolutionsInFile.set(name, resolution); - watchFailedLookupLocationOfResolution(resolution); + watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution); if (existingResolution) { stopWatchFailedLookupLocationOfResolution(existingResolution); } @@ -441,18 +444,27 @@ namespace ts { return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); } - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + function watchFailedLookupLocationsOfExternalModuleResolutions(name: string, resolution: ResolutionWithFailedLookupLocations) { // No need to set the resolution refCount - if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) { - return; + if (resolution.failedLookupLocations && resolution.failedLookupLocations.length) { + if (resolution.refCount) { + resolution.refCount++; + } + else { + resolution.refCount = 1; + if (isExternalModuleNameRelative(name)) { + watchFailedLookupLocationOfResolution(resolution); + } + else { + nonRelativeExternalModuleResolutions.add(name, resolution); + } + } } + } - if (resolution.refCount !== undefined) { - resolution.refCount++; - return; - } + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + Debug.assert(!!resolution.refCount); - resolution.refCount = 1; const { failedLookupLocations } = resolution; let setAtRoot = false; for (const failedLookupLocation of failedLookupLocations) { @@ -480,6 +492,16 @@ namespace ts { } } + function setRefCountToUndefined(resolution: ResolutionWithFailedLookupLocations) { + resolution.refCount = undefined; + } + + function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { + const updateResolution = resolutionHost.getCurrentProgram().getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name) ? + setRefCountToUndefined : watchFailedLookupLocationOfResolution; + resolutions.forEach(updateResolution); + } + function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); if (dirWatcher) { @@ -492,11 +514,11 @@ namespace ts { } function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) { + if (!resolution.refCount) { return; } - resolution.refCount!--; + resolution.refCount--; if (resolution.refCount) { return; } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 3d128a6f2c..70f4a9e37c 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -8486,7 +8486,7 @@ new C();` }); }); - it("when watching directories for failed lookup locations in amd resolution", () => { + describe("when watching directories for failed lookup locations in amd resolution", () => { const projectRoot = "/user/username/projects/project"; const nodeFile: File = { path: `${projectRoot}/src/typings/node.d.ts`, @@ -8530,19 +8530,35 @@ export const x = 10;` } }) }; - const files = [nodeFile, electronFile, srcFile, moduleFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); - checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); - checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); - checkWatchedDirectoriesDetailed(host, [`${projectRoot}`], 1, /*recursive*/ false); // failed lookup for fs - const expectedWatchedDirectories = createMap(); - expectedWatchedDirectories.set(`${projectRoot}/src`, 2); // Wild card and failed lookup - expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs - expectedWatchedDirectories.set(`${projectRoot}/src/typings`, 1); // typeroot directory - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); + + function verifyModuleResolution(useNodeFile: boolean) { + const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); + checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); + checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); + if (useNodeFile) { + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup + } + else { + checkWatchedDirectoriesDetailed(host, [`${projectRoot}`], 1, /*recursive*/ false); // failed lookup for fs + } + const expectedWatchedDirectories = createMap(); + expectedWatchedDirectories.set(`${projectRoot}/src`, 2); // Wild card and failed lookup + expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 + expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs + expectedWatchedDirectories.set(`${projectRoot}/src/typings`, 1); // typeroot directory + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); + } + + it("when resolves to ambient module", () => { + verifyModuleResolution(/*useNodeFile*/ true); + }); + + it("when resolution fails", () => { + verifyModuleResolution(/*useNodeFile*/ false); + }); }); });