@@ -480,6 +480,7 @@ namespace ts {
480480 interface OldProgramState {
481481 program : Program | undefined ;
482482 file : SourceFile ;
483+ /** The collection of paths modified *since* the old program. */
483484 modifiedFilePaths : Path [ ] ;
484485 }
485486
@@ -497,101 +498,96 @@ namespace ts {
497498 return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
498499 }
499500
500- const oldSourceFile = oldProgramState . program && oldProgramState . program . getSourceFile ( containingFile ) ;
501- if ( oldSourceFile === file ) {
502- // `file` is unchanged from the old program, so we can reuse old module resolutions.
503- if ( isTraceEnabled ( options , host ) ) {
504- trace ( host , Diagnostics . Reusing_module_resolutions_originating_in_0_since_this_file_was_not_modified , file . path ) ;
505- }
506- const oldSourceFileResult : ResolvedModuleFull [ ] = [ ] ;
507- for ( const moduleName of moduleNames ) {
508- const resolvedModule = oldSourceFile . resolvedModules . get ( moduleName ) ;
509- oldSourceFileResult . push ( resolvedModule ) ;
510- }
511- return oldSourceFileResult ;
512- }
513-
514- // at this point we know that either
501+ // at this point we know at least one of the following hold:
515502 // - file has local declarations for ambient modules
516- // OR
517503 // - old program state is available
518- // OR
519- // - both of items above
520- // With this it is possible that we can tell how some module names from the initial list will be resolved
521- // without doing actual resolution (in particular if some name was resolved to ambient module).
522- // Such names should be excluded from the list of module names that will be provided to `resolveModuleNamesWorker`
523- // since we don't want to resolve them again.
524-
525- // this is a list of modules for which we cannot predict resolution so they should be actually resolved
504+ // With this information, we can infer some module resolutions without performing resolution.
505+
506+ /** An ordered list of module names for which we cannot recover the resolution. */
526507 let unknownModuleNames : string [ ] ;
527- // this is a list of combined results assembles from predicted and resolved results.
528- // Order in this list matches the order in the original list of module names `moduleNames` which is important
529- // so later we can split results to resolutions of modules and resolutions of module augmentations.
508+ /**
509+ * The indexing of elements in this list matches that of `moduleNames`.
510+ *
511+ * Before combining results, result[i] is in one of the following states:
512+ * * undefined: needs to be recomputed,
513+ * * predictedToResolveToAmbientModuleMarker: known to be an ambient module.
514+ * Needs to be reset to undefined before returning,
515+ * * ResolvedModuleFull instance: can be reused.
516+ */
530517 let result : ResolvedModuleFull [ ] ;
531- // a transient placeholder that is used to mark predicted resolution in the result list
518+ /** A transient placeholder used to mark predicted resolution in the result list. */
532519 const predictedToResolveToAmbientModuleMarker : ResolvedModuleFull = < any > { } ;
533520
521+ const oldSourceFile = oldProgramState . program && oldProgramState . program . getSourceFile ( containingFile ) ;
522+
534523 for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
535524 const moduleName = moduleNames [ i ] ;
536- // module name is known to be resolved to ambient module if
537- // - module name is contained in the list of ambient modules that are locally declared in the file
538- // - in the old program module name was resolved to ambient module whose declaration is in non-modified file
525+ // TODO: if we want to reuse resolutions more aggressively, refine this to check for whether the
526+ // text of the corresponding modulenames has changed.
527+ if ( file === oldSourceFile ) {
528+ const oldResolvedModule = oldSourceFile && oldSourceFile . resolvedModules . get ( moduleName ) ;
529+ if ( oldResolvedModule ) {
530+ if ( isTraceEnabled ( options , host ) ) {
531+ trace ( host , Diagnostics . Reusing_resolution_of_module_0_to_file_1_from_old_program , moduleName , containingFile ) ;
532+ }
533+ ( result || ( result = new Array ( moduleNames . length ) ) ) [ i ] = oldResolvedModule ;
534+ continue ;
535+ }
536+ }
537+ // We know moduleName resolves to an ambient module provided that moduleName:
538+ // - is in the list of ambient modules locally declared in the current source file.
539+ // - resolved to an ambient module in the old program whose declaration is in an unmodified file
539540 // (so the same module declaration will land in the new program)
540- let isKnownToResolveToAmbientModule = false ;
541+ let resolvesToAmbientModuleInNonModifiedFile = false ;
541542 if ( contains ( file . ambientModuleNames , moduleName ) ) {
542- isKnownToResolveToAmbientModule = true ;
543+ resolvesToAmbientModuleInNonModifiedFile = true ;
543544 if ( isTraceEnabled ( options , host ) ) {
544545 trace ( host , Diagnostics . Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1 , moduleName , containingFile ) ;
545546 }
546547 }
547548 else {
548- isKnownToResolveToAmbientModule = checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName , oldProgramState ) ;
549- }
550-
551- if ( isKnownToResolveToAmbientModule ) {
552- if ( ! unknownModuleNames ) {
553- // found a first module name for which result can be prediced
554- // this means that this module name should not be passed to `resolveModuleNamesWorker`.
555- // We'll use a separate list for module names that are definitely unknown.
556- result = new Array ( moduleNames . length ) ;
557- // copy all module names that appear before the current one in the list
558- // since they are known to be unknown
559- unknownModuleNames = moduleNames . slice ( 0 , i ) ;
560- }
561- // Mark predicted resolution in the result list.
562- result [ i ] = predictedToResolveToAmbientModuleMarker ;
549+ resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile ( moduleName , oldProgramState ) ;
563550 }
564- else if ( unknownModuleNames ) {
565- // found unknown module name and we are already using separate list for those - add it to the list
566- unknownModuleNames . push ( moduleName ) ;
567- }
568- }
569551
570- if ( ! unknownModuleNames ) {
571- // we've looked throught the list but have not seen any predicted resolution
572- // use default logic
573- return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
552+ if ( resolvesToAmbientModuleInNonModifiedFile ) {
553+ ( result || ( result = new Array ( moduleNames . length ) ) ) [ i ] = predictedToResolveToAmbientModuleMarker ;
554+ }
555+ else {
556+ // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
557+ ( unknownModuleNames || ( unknownModuleNames = [ ] ) ) . push ( moduleName ) ;
558+ }
574559 }
575560
576- const resolutions = unknownModuleNames . length
561+ const resolutions = unknownModuleNames && unknownModuleNames . length
577562 ? resolveModuleNamesWorker ( unknownModuleNames , containingFile )
578563 : emptyArray ;
579564
580- // combine results of resolutions and predicted results
565+ // Combine results of resolutions and predicted results
566+ if ( ! result ) {
567+ // There were no unresolved/ambient resolutions.
568+ Debug . assert ( resolutions . length === moduleNames . length ) ;
569+ return < ResolvedModuleFull [ ] > resolutions ;
570+ }
571+
581572 let j = 0 ;
582573 for ( let i = 0 ; i < result . length ; i ++ ) {
583- if ( result [ i ] === predictedToResolveToAmbientModuleMarker ) {
584- result [ i ] = undefined ;
574+ if ( result [ i ] ) {
575+ if ( result [ i ] === predictedToResolveToAmbientModuleMarker ) {
576+ result [ i ] = undefined ;
577+ }
585578 }
586579 else {
587580 result [ i ] = resolutions [ j ] ;
588581 j ++ ;
589582 }
590583 }
591584 Debug . assert ( j === resolutions . length ) ;
585+
592586 return result ;
593587
594- function checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName : string , oldProgramState : OldProgramState ) : boolean {
588+ // TODO: if we change our policy of rechecking failed lookups on each program create,
589+ // we should adjust the value returned here.
590+ function moduleNameResolvesToAmbientModuleInNonModifiedFile ( moduleName : string , oldProgramState : OldProgramState ) : boolean {
595591 const resolutionToFile = getResolvedModule ( oldProgramState . file , moduleName ) ;
596592 if ( resolutionToFile ) {
597593 // module used to be resolved to file - ignore it
@@ -706,7 +702,7 @@ namespace ts {
706702
707703 modifiedFilePaths = modifiedSourceFiles . map ( f => f . newFile . path ) ;
708704 // try to verify results of module resolution
709- for ( const { oldFile : oldSourceFile , newFile : newSourceFile } of modifiedSourceFiles ) {
705+ modifiedSourceFilesLoop: for ( const { oldFile : oldSourceFile , newFile : newSourceFile } of modifiedSourceFiles ) {
710706 const newSourceFilePath = getNormalizedAbsolutePath ( newSourceFile . fileName , currentDirectory ) ;
711707 if ( resolveModuleNamesWorker ) {
712708 const moduleNames = map ( concatenate ( newSourceFile . imports , newSourceFile . moduleAugmentations ) , getTextOfLiteral ) ;
@@ -715,7 +711,8 @@ namespace ts {
715711 // ensure that module resolution results are still correct
716712 const resolutionsChanged = hasChangesInResolutions ( moduleNames , resolutions , oldSourceFile . resolvedModules , moduleResolutionIsEqualTo ) ;
717713 if ( resolutionsChanged ) {
718- return oldProgram . structureIsReused = StructureIsReused . ModulesInUneditedFiles ;
714+ oldProgram . structureIsReused = StructureIsReused . ModulesInUneditedFiles ;
715+ continue modifiedSourceFilesLoop;
719716 }
720717 }
721718 if ( resolveTypeReferenceDirectiveNamesWorker ) {
@@ -724,14 +721,19 @@ namespace ts {
724721 // ensure that types resolutions are still correct
725722 const resolutionsChanged = hasChangesInResolutions ( typesReferenceDirectives , resolutions , oldSourceFile . resolvedTypeReferenceDirectiveNames , typeDirectiveIsEqualTo ) ;
726723 if ( resolutionsChanged ) {
727- return oldProgram . structureIsReused = StructureIsReused . ModulesInUneditedFiles ;
724+ oldProgram . structureIsReused = StructureIsReused . ModulesInUneditedFiles ;
725+ continue modifiedSourceFilesLoop;
728726 }
729727 }
730728 // pass the cache of module/types resolutions from the old source file
731729 newSourceFile . resolvedModules = oldSourceFile . resolvedModules ;
732730 newSourceFile . resolvedTypeReferenceDirectiveNames = oldSourceFile . resolvedTypeReferenceDirectiveNames ;
733731 }
734732
733+ if ( oldProgram . structureIsReused !== StructureIsReused . Completely ) {
734+ return oldProgram . structureIsReused ;
735+ }
736+
735737 // update fileName -> file mapping
736738 for ( let i = 0 ; i < newSourceFiles . length ; i ++ ) {
737739 filesByName . set ( filePaths [ i ] , newSourceFiles [ i ] ) ;
0 commit comments