@@ -12,6 +12,7 @@ namespace ts {
1212 resolveTypeReferenceDirectives ( typeDirectiveNames : string [ ] , containingFile : string ) : ResolvedTypeReferenceDirective [ ] ;
1313
1414 invalidateResolutionOfFile ( filePath : Path ) : void ;
15+ removeResolutionsOfFile ( filePath : Path ) : void ;
1516 createHasInvalidatedResolution ( ) : HasInvalidatedResolution ;
1617
1718 startCachingPerDirectoryResolution ( ) : void ;
@@ -25,7 +26,7 @@ namespace ts {
2526 clear ( ) : void ;
2627 }
2728
28- interface NameResolutionWithFailedLookupLocations {
29+ interface ResolutionWithFailedLookupLocations {
2930 readonly failedLookupLocations : ReadonlyArray < string > ;
3031 isInvalidated ?: boolean ;
3132 }
@@ -45,6 +46,7 @@ namespace ts {
4546 projectName ?: string ;
4647 getGlobalCache ?( ) : string | undefined ;
4748 writeLog ( s : string ) : void ;
49+ maxNumberOfFilesToIterateForInvalidation ?: number ;
4850 }
4951
5052 interface DirectoryWatchesOfFailedLookup {
@@ -54,9 +56,17 @@ namespace ts {
5456 refCount : number ;
5557 }
5658
59+ /*@internal */
60+ export const maxNumberOfFilesToIterateForInvalidation = 256 ;
61+
62+ interface GetResolutionWithResolvedFileName < T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations , R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName > {
63+ ( resolution : T ) : R ;
64+ }
65+
5766 export function createResolutionCache ( resolutionHost : ResolutionCacheHost ) : ResolutionCache {
5867 let filesWithChangedSetOfUnresolvedImports : Path [ ] | undefined ;
5968 let filesWithInvalidatedResolutions : Map < true > | undefined ;
69+ let allFilesHaveInvalidatedResolution = false ;
6070
6171 // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file.
6272 // The key in the map is source file's path.
@@ -86,6 +96,7 @@ namespace ts {
8696 finishCachingPerDirectoryResolution,
8797 resolveModuleNames,
8898 resolveTypeReferenceDirectives,
99+ removeResolutionsOfFile,
89100 invalidateResolutionOfFile,
90101 createHasInvalidatedResolution,
91102 setRootDirectory,
@@ -121,6 +132,7 @@ namespace ts {
121132 closeTypeRootsWatch ( ) ;
122133 resolvedModuleNames . clear ( ) ;
123134 resolvedTypeReferenceDirectives . clear ( ) ;
135+ allFilesHaveInvalidatedResolution = false ;
124136 Debug . assert ( perDirectoryResolvedModuleNames . size === 0 && perDirectoryResolvedTypeReferenceDirectives . size === 0 ) ;
125137 }
126138
@@ -135,6 +147,11 @@ namespace ts {
135147 }
136148
137149 function createHasInvalidatedResolution ( ) : HasInvalidatedResolution {
150+ if ( allFilesHaveInvalidatedResolution ) {
151+ // Any file asked would have invalidated resolution
152+ filesWithInvalidatedResolutions = undefined ;
153+ return returnTrue ;
154+ }
138155 const collected = filesWithInvalidatedResolutions ;
139156 filesWithInvalidatedResolutions = undefined ;
140157 return path => collected && collected . has ( path ) ;
@@ -145,6 +162,7 @@ namespace ts {
145162 }
146163
147164 function finishCachingPerDirectoryResolution ( ) {
165+ allFilesHaveInvalidatedResolution = false ;
148166 directoryWatchesOfFailedLookups . forEach ( ( watcher , path ) => {
149167 if ( watcher . refCount === 0 ) {
150168 directoryWatchesOfFailedLookups . delete ( path ) ;
@@ -178,13 +196,13 @@ namespace ts {
178196 return primaryResult ;
179197 }
180198
181- function resolveNamesWithLocalCache < T extends NameResolutionWithFailedLookupLocations , R extends ResolutionWithResolvedFileName > (
199+ function resolveNamesWithLocalCache < T extends ResolutionWithFailedLookupLocations , R extends ResolutionWithResolvedFileName > (
182200 names : string [ ] ,
183201 containingFile : string ,
184202 cache : Map < Map < T > > ,
185203 perDirectoryCache : Map < Map < T > > ,
186204 loader : ( name : string , containingFile : string , options : CompilerOptions , host : ModuleResolutionHost ) => T ,
187- getResolutionFromNameResolutionWithFailedLookupLocations : ( s : T ) => R ,
205+ getResolutionWithResolvedFileName : GetResolutionWithResolvedFileName < T , R > ,
188206 logChanges : boolean ) : R [ ] {
189207
190208 const path = resolutionHost . toPath ( containingFile ) ;
@@ -203,7 +221,7 @@ namespace ts {
203221 for ( const name of names ) {
204222 let resolution = resolutionsInFile . get ( name ) ;
205223 // Resolution is valid if it is present and not invalidated
206- if ( ! resolution || resolution . isInvalidated ) {
224+ if ( allFilesHaveInvalidatedResolution || ! resolution || resolution . isInvalidated ) {
207225 const existingResolution = resolution ;
208226 const resolutionInDirectory = perDirectoryResolution . get ( name ) ;
209227 if ( resolutionInDirectory ) {
@@ -225,7 +243,7 @@ namespace ts {
225243 }
226244 Debug . assert ( resolution !== undefined && ! resolution . isInvalidated ) ;
227245 seenNamesInFile . set ( name , true ) ;
228- resolvedModules . push ( getResolutionFromNameResolutionWithFailedLookupLocations ( resolution ) ) ;
246+ resolvedModules . push ( getResolutionWithResolvedFileName ( resolution ) ) ;
229247 }
230248
231249 // Stop watching and remove the unused name
@@ -245,8 +263,8 @@ namespace ts {
245263 if ( ! oldResolution || ! newResolution || oldResolution . isInvalidated ) {
246264 return false ;
247265 }
248- const oldResult = getResolutionFromNameResolutionWithFailedLookupLocations ( oldResolution ) ;
249- const newResult = getResolutionFromNameResolutionWithFailedLookupLocations ( newResolution ) ;
266+ const oldResult = getResolutionWithResolvedFileName ( oldResolution ) ;
267+ const newResult = getResolutionWithResolvedFileName ( newResolution ) ;
250268 if ( oldResult === newResult ) {
251269 return true ;
252270 }
@@ -321,9 +339,7 @@ namespace ts {
321339 return fileExtensionIsOneOf ( path , failedLookupDefaultExtensions ) ;
322340 }
323341
324- function watchFailedLookupLocationOfResolution < T extends NameResolutionWithFailedLookupLocations > (
325- resolution : T , startIndex ?: number
326- ) {
342+ function watchFailedLookupLocationOfResolution ( resolution : ResolutionWithFailedLookupLocations , startIndex ?: number ) {
327343 if ( resolution && resolution . failedLookupLocations ) {
328344 for ( let i = startIndex || 0 ; i < resolution . failedLookupLocations . length ; i ++ ) {
329345 const failedLookupLocation = resolution . failedLookupLocations [ i ] ;
@@ -346,15 +362,11 @@ namespace ts {
346362 }
347363 }
348364
349- function stopWatchFailedLookupLocationOfResolution < T extends NameResolutionWithFailedLookupLocations > (
350- resolution : T
351- ) {
365+ function stopWatchFailedLookupLocationOfResolution ( resolution : ResolutionWithFailedLookupLocations ) {
352366 stopWatchFailedLookupLocationOfResolutionFrom ( resolution , 0 ) ;
353367 }
354368
355- function stopWatchFailedLookupLocationOfResolutionFrom < T extends NameResolutionWithFailedLookupLocations > (
356- resolution : T , startIndex : number
357- ) {
369+ function stopWatchFailedLookupLocationOfResolutionFrom ( resolution : ResolutionWithFailedLookupLocations , startIndex : number ) {
358370 if ( resolution && resolution . failedLookupLocations ) {
359371 for ( let i = startIndex ; i < resolution . failedLookupLocations . length ; i ++ ) {
360372 const failedLookupLocation = resolution . failedLookupLocations [ i ] ;
@@ -387,107 +399,106 @@ namespace ts {
387399 // If the files are added to project root or node_modules directory, always run through the invalidation process
388400 // Otherwise run through invalidation only if adding to the immediate directory
389401 if ( dirPath === rootPath || isNodeModulesDirectory ( dirPath ) || getDirectoryPath ( fileOrFolderPath ) === dirPath ) {
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 ) ) {
402+ if ( invalidateResolutionOfFailedLookupLocation ( fileOrFolderPath , dirPath === fileOrFolderPath ) ) {
407403 resolutionHost . onInvalidatedResolution ( ) ;
408404 }
409405 }
410406 } , WatchDirectoryFlags . Recursive ) ;
411407 }
412408
413- function invalidateResolutionCache < T extends NameResolutionWithFailedLookupLocations > (
409+ function removeResolutionsOfFileFromCache ( cache : Map < Map < ResolutionWithFailedLookupLocations > > , filePath : Path ) {
410+ // Deleted file, stop watching failed lookups for all the resolutions in the file
411+ const resolutions = cache . get ( filePath ) ;
412+ if ( resolutions ) {
413+ resolutions . forEach ( stopWatchFailedLookupLocationOfResolution ) ;
414+ cache . delete ( filePath ) ;
415+ }
416+ }
417+
418+ function removeResolutionsOfFile ( filePath : Path ) {
419+ removeResolutionsOfFileFromCache ( resolvedModuleNames , filePath ) ;
420+ removeResolutionsOfFileFromCache ( resolvedTypeReferenceDirectives , filePath ) ;
421+ }
422+
423+ function invalidateResolutionCache < T extends ResolutionWithFailedLookupLocations , R extends ResolutionWithResolvedFileName > (
414424 cache : Map < Map < T > > ,
415- ignoreFile : ( resolutions : Map < T > , containingFilePath : Path ) => boolean ,
416- isInvalidatedResolution : ( resolution : T ) => boolean
425+ isInvalidatedResolution : ( resolution : T , getResolutionWithResolvedFileName : GetResolutionWithResolvedFileName < T , R > ) => boolean ,
426+ getResolutionWithResolvedFileName : GetResolutionWithResolvedFileName < T , R >
417427 ) {
418428 const seen = createMap < Map < true > > ( ) ;
419429 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 ) ;
426- }
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 ) ;
436- }
437- } ) ;
430+ const dirPath = getDirectoryPath ( containingFilePath ) ;
431+ let seenInDir = seen . get ( dirPath ) ;
432+ if ( ! seenInDir ) {
433+ seenInDir = createMap < true > ( ) ;
434+ seen . set ( dirPath , seenInDir ) ;
438435 }
436+ resolutions . forEach ( ( resolution , name ) => {
437+ if ( seenInDir . has ( name ) ) {
438+ return ;
439+ }
440+ seenInDir . set ( name , true ) ;
441+ if ( ! resolution . isInvalidated && isInvalidatedResolution ( resolution , getResolutionWithResolvedFileName ) ) {
442+ // Mark the file as needing re-evaluation of module resolution instead of using it blindly.
443+ resolution . isInvalidated = true ;
444+ ( filesWithInvalidatedResolutions || ( filesWithInvalidatedResolutions = createMap < true > ( ) ) ) . set ( containingFilePath , true ) ;
445+ }
446+ } ) ;
439447 } ) ;
440448 }
441449
442- function invalidateResolutionCacheOfDeletedFile < T extends NameResolutionWithFailedLookupLocations , R extends ResolutionWithResolvedFileName > (
443- deletedFilePath : Path ,
444- cache : Map < Map < T > > ,
445- getResolutionFromNameResolutionWithFailedLookupLocations : ( s : T ) => R ,
450+ function hasReachedResolutionIterationLimit ( ) {
451+ const maxSize = resolutionHost . maxNumberOfFilesToIterateForInvalidation || maxNumberOfFilesToIterateForInvalidation ;
452+ return resolvedModuleNames . size > maxSize || resolvedTypeReferenceDirectives . size > maxSize ;
453+ }
454+
455+ function invalidateResolutions (
456+ isInvalidatedResolution : ( resolution : ResolutionWithFailedLookupLocations , getResolutionWithResolvedFileName : GetResolutionWithResolvedFileName ) => boolean ,
446457 ) {
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- }
458+ // If more than maxNumberOfFilesToIterateForInvalidation present,
459+ // just invalidated all files and recalculate the resolutions for files instead
460+ if ( hasReachedResolutionIterationLimit ( ) ) {
461+ allFilesHaveInvalidatedResolution = true ;
462+ return ;
463+ }
464+ invalidateResolutionCache ( resolvedModuleNames , isInvalidatedResolution , getResolvedModule ) ;
465+ invalidateResolutionCache ( resolvedTypeReferenceDirectives , isInvalidatedResolution , getResolvedTypeReferenceDirective ) ;
466+ }
454467
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- } ,
468+ function invalidateResolutionOfFile ( filePath : Path ) {
469+ removeResolutionsOfFile ( filePath ) ;
470+ invalidateResolutions (
460471 // 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 ;
472+ ( resolution , getResolutionWithResolvedFileName ) => {
473+ const result = getResolutionWithResolvedFileName ( resolution ) ;
474+ return result && resolutionHost . toPath ( result . resolvedFileName ) === filePath ;
464475 }
465476 ) ;
466477 }
467478
468- function invalidateResolutionOfFile ( filePath : Path ) {
469- invalidateResolutionCacheOfDeletedFile ( filePath , resolvedModuleNames , getResolvedModule ) ;
470- invalidateResolutionCacheOfDeletedFile ( filePath , resolvedTypeReferenceDirectives , getResolvedTypeReferenceDirective ) ;
471- }
472-
473- function invalidateResolutionCacheOfFailedLookupLocation < T extends NameResolutionWithFailedLookupLocations > (
474- cache : Map < Map < T > > ,
475- hasChangedFailedLookupLocation : ( resolution : T ) => boolean
476- ) {
477- invalidateResolutionCache (
478- cache ,
479- // Do not ignore any file
480- returnFalse ,
479+ function invalidateResolutionOfFailedLookupLocation ( fileOrFolderPath : Path , isCreatingWatchedDirectory : boolean ) {
480+ let isChangedFailedLookupLocation : ( location : string ) => boolean ;
481+ if ( isCreatingWatchedDirectory ) {
482+ // Watching directory is created
483+ // Invalidate any resolution has failed lookup in this directory
484+ isChangedFailedLookupLocation = location => isInDirectoryPath ( fileOrFolderPath , resolutionHost . toPath ( location ) ) ;
485+ }
486+ else {
487+ // Some file or folder in the watching directory is created
488+ // Return early if it does not have any of the watching extension or not the custom failed lookup path
489+ if ( ! isPathWithDefaultFailedLookupExtension ( fileOrFolderPath ) && ! customFailedLookupPaths . has ( fileOrFolderPath ) ) {
490+ return false ;
491+ }
492+ // Resolution need to be invalidated if failed lookup location is same as the file or folder getting created
493+ isChangedFailedLookupLocation = location => resolutionHost . toPath ( location ) === fileOrFolderPath ;
494+ }
495+ const hasChangedFailedLookupLocation = ( resolution : ResolutionWithFailedLookupLocations ) => some ( resolution . failedLookupLocations , isChangedFailedLookupLocation ) ;
496+ const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions . size ;
497+ invalidateResolutions (
481498 // Resolution is invalidated if the resulting file name is same as the deleted file path
482499 hasChangedFailedLookupLocation
483500 ) ;
484- }
485-
486- function invalidateResolutionOfFailedLookupLocation ( hasChangedFailedLookupLocation : ( resolution : NameResolutionWithFailedLookupLocations ) => boolean ) {
487- const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions . size ;
488- invalidateResolutionCacheOfFailedLookupLocation ( resolvedModuleNames , hasChangedFailedLookupLocation ) ;
489- invalidateResolutionCacheOfFailedLookupLocation ( resolvedTypeReferenceDirectives , hasChangedFailedLookupLocation ) ;
490- return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions . size !== invalidatedFilesCount ;
501+ return allFilesHaveInvalidatedResolution || filesWithInvalidatedResolutions && filesWithInvalidatedResolutions . size !== invalidatedFilesCount ;
491502 }
492503
493504 function closeTypeRootsWatch ( ) {
0 commit comments