@@ -9,11 +9,13 @@ namespace ts {
99 getResolvedModuleWithFailedLookupLocationsFromCache ( moduleName : string , containingFile : string ) : CachedResolvedModuleWithFailedLookupLocations | undefined ;
1010 resolveTypeReferenceDirectives ( typeDirectiveNames : string [ ] , containingFile : string , redirectedReference ?: ResolvedProjectReference ) : ( ResolvedTypeReferenceDirective | undefined ) [ ] ;
1111
12+ invalidateResolutionsOfFailedLookupLocations ( ) : boolean ;
1213 invalidateResolutionOfFile ( filePath : Path ) : void ;
1314 removeResolutionsOfFile ( filePath : Path ) : void ;
1415 removeResolutionsFromProjectReferenceRedirects ( filePath : Path ) : void ;
1516 setFilesWithInvalidatedNonRelativeUnresolvedImports ( filesWithUnresolvedImports : Map < readonly string [ ] > ) : void ;
1617 createHasInvalidatedResolution ( forceAllFilesAsInvalidated ?: boolean ) : HasInvalidatedResolution ;
18+ hasChangedAutomaticTypeDirectiveNames ( ) : boolean ;
1719
1820 startCachingPerDirectoryResolution ( ) : void ;
1921 finishCachingPerDirectoryResolution ( ) : void ;
@@ -50,6 +52,7 @@ namespace ts {
5052 onInvalidatedResolution ( ) : void ;
5153 watchTypeRootsDirectory ( directory : string , cb : DirectoryWatcherCallback , flags : WatchDirectoryFlags ) : FileWatcher ;
5254 onChangedAutomaticTypeDirectiveNames ( ) : void ;
55+ scheduleInvalidateResolutionsOfFailedLookupLocations ( ) : void ;
5356 getCachedDirectoryStructureHost ( ) : CachedDirectoryStructureHost | undefined ;
5457 projectName ?: string ;
5558 getGlobalCache ?( ) : string | undefined ;
@@ -147,6 +150,11 @@ namespace ts {
147150 const resolutionsWithFailedLookups : ResolutionWithFailedLookupLocations [ ] = [ ] ;
148151 const resolvedFileToResolution = createMultiMap < ResolutionWithFailedLookupLocations > ( ) ;
149152
153+ let hasChangedAutomaticTypeDirectiveNames = false ;
154+ const failedLookupChecks : Path [ ] = [ ] ;
155+ const startsWithPathChecks : Path [ ] = [ ] ;
156+ const isInDirectoryChecks : Path [ ] = [ ] ;
157+
150158 const getCurrentDirectory = memoize ( ( ) => resolutionHost . getCurrentDirectory ! ( ) ) ; // TODO: GH#18217
151159 const cachedDirectoryStructureHost = resolutionHost . getCachedDirectoryStructureHost ( ) ;
152160
@@ -195,7 +203,9 @@ namespace ts {
195203 resolveTypeReferenceDirectives,
196204 removeResolutionsFromProjectReferenceRedirects,
197205 removeResolutionsOfFile,
206+ hasChangedAutomaticTypeDirectiveNames : ( ) => hasChangedAutomaticTypeDirectiveNames ,
198207 invalidateResolutionOfFile,
208+ invalidateResolutionsOfFailedLookupLocations,
199209 setFilesWithInvalidatedNonRelativeUnresolvedImports,
200210 createHasInvalidatedResolution,
201211 updateTypeRootsWatch,
@@ -227,9 +237,13 @@ namespace ts {
227237 resolvedTypeReferenceDirectives . clear ( ) ;
228238 resolvedFileToResolution . clear ( ) ;
229239 resolutionsWithFailedLookups . length = 0 ;
240+ failedLookupChecks . length = 0 ;
241+ startsWithPathChecks . length = 0 ;
242+ isInDirectoryChecks . length = 0 ;
230243 // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
231244 // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
232245 clearPerDirectoryResolutions ( ) ;
246+ hasChangedAutomaticTypeDirectiveNames = false ;
233247 }
234248
235249 function startRecordingFilesWithChangedResolutions ( ) {
@@ -253,6 +267,8 @@ namespace ts {
253267 }
254268
255269 function createHasInvalidatedResolution ( forceAllFilesAsInvalidated ?: boolean ) : HasInvalidatedResolution {
270+ // Ensure pending resolutions are applied
271+ invalidateResolutionsOfFailedLookupLocations ( ) ;
256272 if ( forceAllFilesAsInvalidated ) {
257273 // Any file asked would have invalidated resolution
258274 filesWithInvalidatedResolutions = undefined ;
@@ -281,6 +297,7 @@ namespace ts {
281297 watcher . watcher . close ( ) ;
282298 }
283299 } ) ;
300+ hasChangedAutomaticTypeDirectiveNames = false ;
284301 }
285302
286303 function resolveModuleName ( moduleName : string , containingFile : string , compilerOptions : CompilerOptions , host : ModuleResolutionHost , redirectedReference ?: ResolvedProjectReference ) : CachedResolvedModuleWithFailedLookupLocations {
@@ -662,9 +679,7 @@ namespace ts {
662679 cachedDirectoryStructureHost . addOrDeleteFileOrDirectory ( fileOrDirectory , fileOrDirectoryPath ) ;
663680 }
664681
665- if ( invalidateResolutionOfFailedLookupLocation ( fileOrDirectoryPath , dirPath === fileOrDirectoryPath ) ) {
666- resolutionHost . onInvalidatedResolution ( ) ;
667- }
682+ scheduleInvalidateResolutionOfFailedLookupLocation ( fileOrDirectoryPath , dirPath === fileOrDirectoryPath ) ;
668683 } , nonRecursive ? WatchDirectoryFlags . None : WatchDirectoryFlags . Recursive ) ;
669684 }
670685
@@ -700,36 +715,42 @@ namespace ts {
700715 removeResolutionsOfFileFromCache ( resolvedTypeReferenceDirectives , filePath , getResolvedTypeReferenceDirective ) ;
701716 }
702717
703- function invalidateResolution ( resolution : ResolutionWithFailedLookupLocations ) {
704- resolution . isInvalidated = true ;
705- let changedInAutoTypeReferenced = false ;
706- for ( const containingFilePath of Debug . assertDefined ( resolution . files ) ) {
707- ( filesWithInvalidatedResolutions || ( filesWithInvalidatedResolutions = createMap < true > ( ) ) ) . set ( containingFilePath , true ) ;
708- // When its a file with inferred types resolution, invalidate type reference directive resolution
709- changedInAutoTypeReferenced = changedInAutoTypeReferenced || containingFilePath . endsWith ( inferredTypesContainingFile ) ;
710- }
711- if ( changedInAutoTypeReferenced ) {
712- resolutionHost . onChangedAutomaticTypeDirectiveNames ( ) ;
718+ function invalidateResolutions ( resolutions : ResolutionWithFailedLookupLocations [ ] | undefined , canInvalidate : ( resolution : ResolutionWithFailedLookupLocations ) => boolean ) {
719+ if ( ! resolutions ) return false ;
720+ let invalidated = false ;
721+ for ( const resolution of resolutions ) {
722+ if ( resolution . isInvalidated || ! canInvalidate ( resolution ) ) continue ;
723+ resolution . isInvalidated = invalidated = true ;
724+ for ( const containingFilePath of Debug . assertDefined ( resolution . files ) ) {
725+ ( filesWithInvalidatedResolutions || ( filesWithInvalidatedResolutions = createMap < true > ( ) ) ) . set ( containingFilePath , true ) ;
726+ // When its a file with inferred types resolution, invalidate type reference directive resolution
727+ hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || containingFilePath . endsWith ( inferredTypesContainingFile ) ;
728+ }
713729 }
730+ return invalidated ;
714731 }
715732
716733 function invalidateResolutionOfFile ( filePath : Path ) {
717734 removeResolutionsOfFile ( filePath ) ;
718735 // Resolution is invalidated if the resulting file name is same as the deleted file path
719- forEach ( resolvedFileToResolution . get ( filePath ) , invalidateResolution ) ;
736+ const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames ;
737+ if ( invalidateResolutions ( resolvedFileToResolution . get ( filePath ) , returnTrue ) &&
738+ hasChangedAutomaticTypeDirectiveNames &&
739+ ! prevHasChangedAutomaticTypeDirectiveNames ) {
740+ resolutionHost . onChangedAutomaticTypeDirectiveNames ( ) ;
741+ }
720742 }
721743
722744 function setFilesWithInvalidatedNonRelativeUnresolvedImports ( filesMap : ReadonlyMap < readonly string [ ] > ) {
723745 Debug . assert ( filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined ) ;
724746 filesWithInvalidatedNonRelativeUnresolvedImports = filesMap ;
725747 }
726748
727- function invalidateResolutionOfFailedLookupLocation ( fileOrDirectoryPath : Path , isCreatingWatchedDirectory : boolean ) {
728- let isChangedFailedLookupLocation : ( location : string ) => boolean ;
749+ function scheduleInvalidateResolutionOfFailedLookupLocation ( fileOrDirectoryPath : Path , isCreatingWatchedDirectory : boolean ) {
729750 if ( isCreatingWatchedDirectory ) {
730751 // Watching directory is created
731752 // Invalidate any resolution has failed lookup in this directory
732- isChangedFailedLookupLocation = location => isInDirectoryPath ( fileOrDirectoryPath , resolutionHost . toPath ( location ) ) ;
753+ isInDirectoryChecks . push ( fileOrDirectoryPath ) ;
733754 }
734755 else {
735756 // If something to do with folder/file starting with "." in node_modules folder, skip it
@@ -748,10 +769,8 @@ namespace ts {
748769 if ( isNodeModulesAtTypesDirectory ( fileOrDirectoryPath ) || isNodeModulesDirectory ( fileOrDirectoryPath ) ||
749770 isNodeModulesAtTypesDirectory ( dirOfFileOrDirectory ) || isNodeModulesDirectory ( dirOfFileOrDirectory ) ) {
750771 // Invalidate any resolution from this directory
751- isChangedFailedLookupLocation = location => {
752- const locationPath = resolutionHost . toPath ( location ) ;
753- return locationPath === fileOrDirectoryPath || startsWith ( resolutionHost . toPath ( location ) , fileOrDirectoryPath ) ;
754- } ;
772+ failedLookupChecks . push ( fileOrDirectoryPath ) ;
773+ startsWithPathChecks . push ( fileOrDirectoryPath ) ;
755774 }
756775 else {
757776 if ( ! isPathWithDefaultFailedLookupExtension ( fileOrDirectoryPath ) && ! customFailedLookupPaths . has ( fileOrDirectoryPath ) ) {
@@ -762,20 +781,33 @@ namespace ts {
762781 return false ;
763782 }
764783 // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
765- isChangedFailedLookupLocation = location => resolutionHost . toPath ( location ) === fileOrDirectoryPath ;
784+ failedLookupChecks . push ( fileOrDirectoryPath ) ;
766785 }
767786 }
768- let invalidated = false ;
769- // Resolution is invalidated if the resulting file name is same as the deleted file path
770- for ( const resolution of resolutionsWithFailedLookups ) {
771- if ( resolution . failedLookupLocations . some ( isChangedFailedLookupLocation ) ) {
772- invalidateResolution ( resolution ) ;
773- invalidated = true ;
774- }
787+ resolutionHost . scheduleInvalidateResolutionsOfFailedLookupLocations ( ) ;
788+ }
789+
790+ function invalidateResolutionsOfFailedLookupLocations ( ) {
791+ if ( ! failedLookupChecks . length && ! startsWithPathChecks . length && ! isInDirectoryChecks . length ) {
792+ return false ;
775793 }
794+
795+ const invalidated = invalidateResolutions ( resolutionsWithFailedLookups , canInvalidateFailedLookupResolution ) ;
796+ failedLookupChecks . length = 0 ;
797+ startsWithPathChecks . length = 0 ;
798+ isInDirectoryChecks . length = 0 ;
776799 return invalidated ;
777800 }
778801
802+ function canInvalidateFailedLookupResolution ( resolution : ResolutionWithFailedLookupLocations ) {
803+ return resolution . failedLookupLocations . some ( location => {
804+ const locationPath = resolutionHost . toPath ( location ) ;
805+ return contains ( failedLookupChecks , locationPath ) ||
806+ startsWithPathChecks . some ( fileOrDirectoryPath => startsWith ( locationPath , fileOrDirectoryPath ) ) ||
807+ isInDirectoryChecks . some ( fileOrDirectoryPath => isInDirectoryPath ( fileOrDirectoryPath , locationPath ) ) ;
808+ } ) ;
809+ }
810+
779811 function closeTypeRootsWatch ( ) {
780812 clearMap ( typeRootsWatches , closeFileWatcher ) ;
781813 }
@@ -800,13 +832,14 @@ namespace ts {
800832 // For now just recompile
801833 // We could potentially store more data here about whether it was/would be really be used or not
802834 // and with that determine to trigger compilation but for now this is enough
835+ hasChangedAutomaticTypeDirectiveNames = true ;
803836 resolutionHost . onChangedAutomaticTypeDirectiveNames ( ) ;
804837
805838 // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
806839 // So handle to failed lookup locations here as well to ensure we are invalidating resolutions
807840 const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot ( typeRoot , typeRootPath ) ;
808- if ( dirPath && invalidateResolutionOfFailedLookupLocation ( fileOrDirectoryPath , dirPath === fileOrDirectoryPath ) ) {
809- resolutionHost . onInvalidatedResolution ( ) ;
841+ if ( dirPath ) {
842+ scheduleInvalidateResolutionOfFailedLookupLocation ( fileOrDirectoryPath , dirPath === fileOrDirectoryPath ) ;
810843 }
811844 } , WatchDirectoryFlags . Recursive ) ;
812845 }
0 commit comments