Skip to content

Commit c8e711c

Browse files
committed
Invalidate resolution of the failed lookup only if its one of the default extension or is one of the failed lookup location without that default extension
1 parent 680994e commit c8e711c

File tree

3 files changed

+139
-64
lines changed

3 files changed

+139
-64
lines changed

src/compiler/resolutionCache.ts

Lines changed: 126 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ namespace ts {
3030
isInvalidated?: boolean;
3131
}
3232

33+
interface ResolutionWithResolvedFileName {
34+
resolvedFileName: string | undefined;
35+
}
36+
3337
export interface ResolutionCacheHost extends ModuleResolutionHost {
3438
toPath(fileName: string): Path;
3539
getCompilationSettings(): CompilerOptions;
@@ -59,10 +63,15 @@ namespace ts {
5963
// The values are Map of resolutions with key being name lookedup.
6064
const resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
6165
const perDirectoryResolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
66+
6267
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
6368
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
69+
6470
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
6571

72+
const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json];
73+
const customFailedLookupPaths = createMap<number>();
74+
6675
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
6776
let rootDir: string;
6877
let rootPath: Path;
@@ -85,6 +94,14 @@ namespace ts {
8594
clear
8695
};
8796

97+
function getResolvedModule(resolution: ResolvedModuleWithFailedLookupLocations) {
98+
return resolution.resolvedModule;
99+
}
100+
101+
function getResolvedTypeReferenceDirective(resolution: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
102+
return resolution.resolvedTypeReferenceDirective;
103+
}
104+
88105
function setRootDirectory(dir: string) {
89106
Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size);
90107
rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory()));
@@ -100,6 +117,7 @@ namespace ts {
100117

101118
function clear() {
102119
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
120+
customFailedLookupPaths.clear();
103121
closeTypeRootsWatch();
104122
resolvedModuleNames.clear();
105123
resolvedTypeReferenceDirectives.clear();
@@ -160,14 +178,13 @@ namespace ts {
160178
return primaryResult;
161179
}
162180

163-
function resolveNamesWithLocalCache<T extends NameResolutionWithFailedLookupLocations, R>(
181+
function resolveNamesWithLocalCache<T extends NameResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
164182
names: string[],
165183
containingFile: string,
166184
cache: Map<Map<T>>,
167185
perDirectoryCache: Map<Map<T>>,
168186
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
169-
getResult: (s: T) => R,
170-
getResultFileName: (result: R) => string | undefined,
187+
getResolutionFromNameResolutionWithFailedLookupLocations: (s: T) => R,
171188
logChanges: boolean): R[] {
172189

173190
const path = resolutionHost.toPath(containingFile);
@@ -208,7 +225,7 @@ namespace ts {
208225
}
209226
Debug.assert(resolution !== undefined && !resolution.isInvalidated);
210227
seenNamesInFile.set(name, true);
211-
resolvedModules.push(getResult(resolution));
228+
resolvedModules.push(getResolutionFromNameResolutionWithFailedLookupLocations(resolution));
212229
}
213230

214231
// Stop watching and remove the unused name
@@ -228,23 +245,23 @@ namespace ts {
228245
if (!oldResolution || !newResolution || oldResolution.isInvalidated) {
229246
return false;
230247
}
231-
const oldResult = getResult(oldResolution);
232-
const newResult = getResult(newResolution);
248+
const oldResult = getResolutionFromNameResolutionWithFailedLookupLocations(oldResolution);
249+
const newResult = getResolutionFromNameResolutionWithFailedLookupLocations(newResolution);
233250
if (oldResult === newResult) {
234251
return true;
235252
}
236253
if (!oldResult || !newResult) {
237254
return false;
238255
}
239-
return getResultFileName(oldResult) === getResultFileName(newResult);
256+
return oldResult.resolvedFileName === newResult.resolvedFileName;
240257
}
241258
}
242259

243260
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
244261
return resolveNamesWithLocalCache(
245262
typeDirectiveNames, containingFile,
246263
resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives,
247-
resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName,
264+
resolveTypeReferenceDirective, getResolvedTypeReferenceDirective,
248265
/*logChanges*/ false
249266
);
250267
}
@@ -253,7 +270,7 @@ namespace ts {
253270
return resolveNamesWithLocalCache(
254271
moduleNames, containingFile,
255272
resolvedModuleNames, perDirectoryResolvedModuleNames,
256-
resolveModuleName, m => m.resolvedModule, r => r.resolvedFileName,
273+
resolveModuleName, getResolvedModule,
257274
logChanges
258275
);
259276
}
@@ -300,13 +317,24 @@ namespace ts {
300317
return { dir, dirPath };
301318
}
302319

320+
function isPathWithDefaultFailedLookupExtension(path: Path) {
321+
return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
322+
}
323+
303324
function watchFailedLookupLocationOfResolution<T extends NameResolutionWithFailedLookupLocations>(
304325
resolution: T, startIndex?: number
305326
) {
306327
if (resolution && resolution.failedLookupLocations) {
307328
for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) {
308329
const failedLookupLocation = resolution.failedLookupLocations[i];
309-
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
330+
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
331+
// If the failed lookup location path is not one of the supported extensions,
332+
// store it in the custom path
333+
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
334+
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
335+
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
336+
}
337+
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
310338
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
311339
if (dirWatcher) {
312340
dirWatcher.refCount++;
@@ -330,7 +358,17 @@ namespace ts {
330358
if (resolution && resolution.failedLookupLocations) {
331359
for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) {
332360
const failedLookupLocation = resolution.failedLookupLocations[i];
333-
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
361+
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
362+
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
363+
if (refCount) {
364+
if (refCount === 1) {
365+
customFailedLookupPaths.delete(failedLookupLocationPath);
366+
}
367+
else {
368+
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
369+
}
370+
}
371+
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
334372
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
335373
// Do not close the watcher yet since it might be needed by other failed lookup locations.
336374
dirWatcher.refCount--;
@@ -349,72 +387,106 @@ namespace ts {
349387
// If the files are added to project root or node_modules directory, always run through the invalidation process
350388
// Otherwise run through invalidation only if adding to the immediate directory
351389
if (dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) {
352-
const isChangedFailedLookupLocation: (location: string) => boolean = dirPath === fileOrFolderPath ?
353-
// If the file watched directory is created/deleted invalidate any resolution has failed lookup in this directory
354-
location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)) :
355-
// Otherwise only the resolutions referencing the file or folder added
356-
location => resolutionHost.toPath(location) === fileOrFolderPath;
357-
if (invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation)) {
390+
let isChangedFailedLookupLocation: (location: string) => boolean;
391+
if (dirPath === fileOrFolderPath) {
392+
// Watching directory is created
393+
// Invalidate any resolution has failed lookup in this directory
394+
isChangedFailedLookupLocation = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location));
395+
}
396+
else {
397+
// Some file or folder in the watching directory is created
398+
// Return early if it does not have any of the watching extension or not the custom failed lookup path
399+
if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) {
400+
return;
401+
}
402+
// Resolution need to be invalidated if failed lookup location is same as the file or folder getting created
403+
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath;
404+
}
405+
const hasChangedFailedLookupLocation = (resolution: NameResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation);
406+
if (invalidateResolutionOfFailedLookupLocation(hasChangedFailedLookupLocation)) {
358407
resolutionHost.onInvalidatedResolution();
359408
}
360409
}
361410
}, WatchDirectoryFlags.Recursive);
362411
}
363412

364-
function invalidateResolutionCacheOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R>(
365-
deletedFilePath: Path,
413+
function invalidateResolutionCache<T extends NameResolutionWithFailedLookupLocations>(
366414
cache: Map<Map<T>>,
367-
getResult: (s: T) => R,
368-
getResultFileName: (result: R) => string | undefined) {
369-
cache.forEach((value, path) => {
370-
if (path === deletedFilePath) {
371-
cache.delete(path);
372-
if (value) {
373-
value.forEach(stopWatchFailedLookupLocationOfResolution);
415+
ignoreFile: (resolutions: Map<T>, containingFilePath: Path) => boolean,
416+
isInvalidatedResolution: (resolution: T) => boolean
417+
) {
418+
const seen = createMap<Map<true>>();
419+
cache.forEach((resolutions, containingFilePath) => {
420+
if (!ignoreFile(resolutions, containingFilePath as Path) && resolutions) {
421+
const dirPath = getDirectoryPath(containingFilePath);
422+
let seenInDir = seen.get(dirPath);
423+
if (!seenInDir) {
424+
seenInDir = createMap<true>();
425+
seen.set(dirPath, seenInDir);
374426
}
375-
}
376-
else if (value) {
377-
value.forEach(resolution => {
378-
if (resolution && !resolution.isInvalidated) {
379-
const result = getResult(resolution);
380-
if (result) {
381-
if (resolutionHost.toPath(getResultFileName(result)) === deletedFilePath) {
382-
resolution.isInvalidated = true;
383-
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(path, true);
384-
}
385-
}
427+
resolutions.forEach((resolution, name) => {
428+
if (seenInDir.has(name)) {
429+
return;
430+
}
431+
seenInDir.set(name, true);
432+
if (!resolution.isInvalidated && isInvalidatedResolution(resolution)) {
433+
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
434+
resolution.isInvalidated = true;
435+
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFilePath, true);
386436
}
387437
});
388438
}
389439
});
390440
}
391441

442+
function invalidateResolutionCacheOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
443+
deletedFilePath: Path,
444+
cache: Map<Map<T>>,
445+
getResolutionFromNameResolutionWithFailedLookupLocations: (s: T) => R,
446+
) {
447+
invalidateResolutionCache(
448+
cache,
449+
// Ignore file thats same as deleted file path, and handle it here
450+
(resolutions, containingFilePath) => {
451+
if (containingFilePath !== deletedFilePath) {
452+
return false;
453+
}
454+
455+
// Deleted file, stop watching failed lookups for all the resolutions in the file
456+
cache.delete(containingFilePath);
457+
resolutions.forEach(stopWatchFailedLookupLocationOfResolution);
458+
return true;
459+
},
460+
// Resolution is invalidated if the resulting file name is same as the deleted file path
461+
resolution => {
462+
const result = getResolutionFromNameResolutionWithFailedLookupLocations(resolution);
463+
return result && resolutionHost.toPath(result.resolvedFileName) === deletedFilePath;
464+
}
465+
);
466+
}
467+
392468
function invalidateResolutionOfFile(filePath: Path) {
393-
invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName);
394-
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
469+
invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, getResolvedModule);
470+
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, getResolvedTypeReferenceDirective);
395471
}
396472

397473
function invalidateResolutionCacheOfFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
398474
cache: Map<Map<T>>,
399-
isChangedFailedLookupLocation: (location: string) => boolean
475+
hasChangedFailedLookupLocation: (resolution: T) => boolean
400476
) {
401-
cache.forEach((value, containingFile) => {
402-
if (value) {
403-
value.forEach(resolution => {
404-
if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, isChangedFailedLookupLocation)) {
405-
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
406-
resolution.isInvalidated = true;
407-
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFile, true);
408-
}
409-
});
410-
}
411-
});
477+
invalidateResolutionCache(
478+
cache,
479+
// Do not ignore any file
480+
returnFalse,
481+
// Resolution is invalidated if the resulting file name is same as the deleted file path
482+
hasChangedFailedLookupLocation
483+
);
412484
}
413485

414-
function invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation: (location: string) => boolean) {
486+
function invalidateResolutionOfFailedLookupLocation(hasChangedFailedLookupLocation: (resolution: NameResolutionWithFailedLookupLocations) => boolean) {
415487
const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;
416-
invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, isChangedFailedLookupLocation);
417-
invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, isChangedFailedLookupLocation);
488+
invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, hasChangedFailedLookupLocation);
489+
invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, hasChangedFailedLookupLocation);
418490
return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount;
419491
}
420492

src/compiler/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4010,7 +4010,8 @@ namespace ts {
40104010
Tsx = ".tsx",
40114011
Dts = ".d.ts",
40124012
Js = ".js",
4013-
Jsx = ".jsx"
4013+
Jsx = ".jsx",
4014+
Json = ".json"
40144015
}
40154016

40164017
export interface ResolvedModuleWithFailedLookupLocations {
@@ -4025,7 +4026,7 @@ namespace ts {
40254026
// True if the type declaration file was found in a primary lookup location
40264027
primary: boolean;
40274028
// The location of the .d.ts file we located, or undefined if resolution failed
4028-
resolvedFileName?: string;
4029+
resolvedFileName: string | undefined;
40294030
}
40304031

40314032
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {

0 commit comments

Comments
 (0)