@@ -462,6 +462,130 @@ namespace ts {
462462 return classifiableNames ;
463463 }
464464
465+ interface OldProgramState {
466+ program : Program ;
467+ file : SourceFile ;
468+ modifiedFilePaths : Path [ ] ;
469+ }
470+
471+ function resolveModuleNamesReusingOldState ( moduleNames : string [ ] , containingFile : string , file : SourceFile , oldProgramState ?: OldProgramState ) {
472+ if ( ! oldProgramState && ! file . ambientModuleNames . length ) {
473+ // if old program state is not supplied and file does not contain locally defined ambient modules
474+ // then the best we can do is fallback to the default logic
475+ return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
476+ }
477+
478+ // at this point we know that either
479+ // - file has local declarations for ambient modules
480+ // OR
481+ // - old program state is available
482+ // OR
483+ // - both of items above
484+ // With this it is possible that we can tell how some module names from the initial list will be resolved
485+ // without doing actual resolution (in particular if some name was resolved to ambient module).
486+ // Such names should be excluded from the list of module names that will be provided to `resolveModuleNamesWorker`
487+ // since we don't want to resolve them again.
488+
489+ // this is a list of modules for which we cannot predict resolution so they should be actually resolved
490+ let unknownModuleNames : string [ ] ;
491+ // this is a list of combined results assembles from predicted and resolved results.
492+ // Order in this list matches the order in the original list of module names `moduleNames` which is important
493+ // so later we can split results to resolutions of modules and resolutions of module augmentations.
494+ let result : ResolvedModuleFull [ ] ;
495+ // a transient placeholder that is used to mark predicted resolution in the result list
496+ const predictedToResolveToAmbientModuleMarker : ResolvedModuleFull = < any > { } ;
497+
498+ for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
499+ const moduleName = moduleNames [ i ] ;
500+ // module name is known to be resolved to ambient module if
501+ // - module name is contained in the list of ambient modules that are locally declared in the file
502+ // - in the old program module name was resolved to ambient module whose declaration is in non-modified file
503+ // (so the same module declaration will land in the new program)
504+ let isKnownToResolveToAmbientModule = false ;
505+ if ( contains ( file . ambientModuleNames , moduleName ) ) {
506+ isKnownToResolveToAmbientModule = true ;
507+ if ( isTraceEnabled ( options , host ) ) {
508+ trace ( host , Diagnostics . Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1 , moduleName , containingFile ) ;
509+ }
510+ }
511+ else {
512+ isKnownToResolveToAmbientModule = checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName , oldProgramState ) ;
513+ }
514+
515+ if ( isKnownToResolveToAmbientModule ) {
516+ if ( ! unknownModuleNames ) {
517+ // found a first module name for which result can be prediced
518+ // this means that this module name should not be passed to `resolveModuleNamesWorker`.
519+ // We'll use a separate list for module names that are definitely unknown.
520+ result = new Array ( moduleNames . length ) ;
521+ // copy all module names that appear before the current one in the list
522+ // since they are known to be unknown
523+ unknownModuleNames = moduleNames . slice ( 0 , i ) ;
524+ }
525+ // mark prediced resolution in the result list
526+ result [ i ] = predictedToResolveToAmbientModuleMarker ;
527+ }
528+ else if ( unknownModuleNames ) {
529+ // found unknown module name and we are already using separate list for those - add it to the list
530+ unknownModuleNames . push ( moduleName ) ;
531+ }
532+ }
533+
534+ if ( ! unknownModuleNames ) {
535+ // we've looked throught the list but have not seen any predicted resolution
536+ // use default logic
537+ return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
538+ }
539+
540+ const resolutions = unknownModuleNames . length
541+ ? resolveModuleNamesWorker ( unknownModuleNames , containingFile )
542+ : emptyArray ;
543+
544+ // combine results of resolutions and predicted results
545+ let j = 0 ;
546+ for ( let i = 0 ; i < result . length ; i ++ ) {
547+ if ( result [ i ] == predictedToResolveToAmbientModuleMarker ) {
548+ result [ i ] = undefined ;
549+ }
550+ else {
551+ result [ i ] = resolutions [ j ] ;
552+ j ++ ;
553+ }
554+ }
555+ Debug . assert ( j === resolutions . length ) ;
556+ return result ;
557+
558+ function checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName : string , oldProgramState ?: OldProgramState ) : boolean {
559+ if ( ! oldProgramState ) {
560+ return false ;
561+ }
562+ const resolutionToFile = getResolvedModule ( oldProgramState . file , moduleName ) ;
563+ if ( resolutionToFile ) {
564+ // module used to be resolved to file - ignore it
565+ return false ;
566+ }
567+ const ambientModule = oldProgram . getTypeChecker ( ) . tryFindAmbientModuleWithoutAugmentations ( moduleName ) ;
568+ if ( ! ( ambientModule && ambientModule . declarations ) ) {
569+ return false ;
570+ }
571+
572+ // at least one of declarations should come from non-modified source file
573+ const firstUnmodifiedFile = forEach ( ambientModule . declarations , d => {
574+ const f = getSourceFileOfNode ( d ) ;
575+ return ! contains ( oldProgramState . modifiedFilePaths , f . path ) && f ;
576+ } ) ;
577+
578+ if ( ! firstUnmodifiedFile ) {
579+ return false ;
580+ }
581+
582+ if ( isTraceEnabled ( options , host ) ) {
583+ trace ( host , Diagnostics . Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified , moduleName , firstUnmodifiedFile . fileName ) ;
584+ }
585+ return true ;
586+ }
587+ }
588+
465589 function tryReuseStructureFromOldProgram ( ) : boolean {
466590 if ( ! oldProgram ) {
467591 return false ;
@@ -489,7 +613,7 @@ namespace ts {
489613 // check if program source files has changed in the way that can affect structure of the program
490614 const newSourceFiles : SourceFile [ ] = [ ] ;
491615 const filePaths : Path [ ] = [ ] ;
492- const modifiedSourceFiles : SourceFile [ ] = [ ] ;
616+ const modifiedSourceFiles : { oldFile : SourceFile , newFile : SourceFile } [ ] = [ ] ;
493617
494618 for ( const oldSourceFile of oldProgram . getSourceFiles ( ) ) {
495619 let newSourceFile = host . getSourceFileByPath
@@ -532,29 +656,8 @@ namespace ts {
532656 return false ;
533657 }
534658
535- const newSourceFilePath = getNormalizedAbsolutePath ( newSourceFile . fileName , currentDirectory ) ;
536- if ( resolveModuleNamesWorker ) {
537- const moduleNames = map ( concatenate ( newSourceFile . imports , newSourceFile . moduleAugmentations ) , getTextOfLiteral ) ;
538- const resolutions = resolveModuleNamesWorker ( moduleNames , newSourceFilePath ) ;
539- // ensure that module resolution results are still correct
540- const resolutionsChanged = hasChangesInResolutions ( moduleNames , resolutions , oldSourceFile . resolvedModules , moduleResolutionIsEqualTo ) ;
541- if ( resolutionsChanged ) {
542- return false ;
543- }
544- }
545- if ( resolveTypeReferenceDirectiveNamesWorker ) {
546- const typesReferenceDirectives = map ( newSourceFile . typeReferenceDirectives , x => x . fileName ) ;
547- const resolutions = resolveTypeReferenceDirectiveNamesWorker ( typesReferenceDirectives , newSourceFilePath ) ;
548- // ensure that types resolutions are still correct
549- const resolutionsChanged = hasChangesInResolutions ( typesReferenceDirectives , resolutions , oldSourceFile . resolvedTypeReferenceDirectiveNames , typeDirectiveIsEqualTo ) ;
550- if ( resolutionsChanged ) {
551- return false ;
552- }
553- }
554- // pass the cache of module/types resolutions from the old source file
555- newSourceFile . resolvedModules = oldSourceFile . resolvedModules ;
556- newSourceFile . resolvedTypeReferenceDirectiveNames = oldSourceFile . resolvedTypeReferenceDirectiveNames ;
557- modifiedSourceFiles . push ( newSourceFile ) ;
659+ // tentatively approve the file
660+ modifiedSourceFiles . push ( { oldFile : oldSourceFile , newFile : newSourceFile } ) ;
558661 }
559662 else {
560663 // file has no changes - use it as is
@@ -565,6 +668,33 @@ namespace ts {
565668 newSourceFiles . push ( newSourceFile ) ;
566669 }
567670
671+ const modifiedFilePaths = modifiedSourceFiles . map ( f => f . newFile . path ) ;
672+ // try to verify results of module resolution
673+ for ( const { oldFile : oldSourceFile , newFile : newSourceFile } of modifiedSourceFiles ) {
674+ const newSourceFilePath = getNormalizedAbsolutePath ( newSourceFile . fileName , currentDirectory ) ;
675+ if ( resolveModuleNamesWorker ) {
676+ const moduleNames = map ( concatenate ( newSourceFile . imports , newSourceFile . moduleAugmentations ) , getTextOfLiteral ) ;
677+ const resolutions = resolveModuleNamesReusingOldState ( moduleNames , newSourceFilePath , newSourceFile , { file : oldSourceFile , program : oldProgram , modifiedFilePaths } ) ;
678+ // ensure that module resolution results are still correct
679+ const resolutionsChanged = hasChangesInResolutions ( moduleNames , resolutions , oldSourceFile . resolvedModules , moduleResolutionIsEqualTo ) ;
680+ if ( resolutionsChanged ) {
681+ return false ;
682+ }
683+ }
684+ if ( resolveTypeReferenceDirectiveNamesWorker ) {
685+ const typesReferenceDirectives = map ( newSourceFile . typeReferenceDirectives , x => x . fileName ) ;
686+ const resolutions = resolveTypeReferenceDirectiveNamesWorker ( typesReferenceDirectives , newSourceFilePath ) ;
687+ // ensure that types resolutions are still correct
688+ const resolutionsChanged = hasChangesInResolutions ( typesReferenceDirectives , resolutions , oldSourceFile . resolvedTypeReferenceDirectiveNames , typeDirectiveIsEqualTo ) ;
689+ if ( resolutionsChanged ) {
690+ return false ;
691+ }
692+ }
693+ // pass the cache of module/types resolutions from the old source file
694+ newSourceFile . resolvedModules = oldSourceFile . resolvedModules ;
695+ newSourceFile . resolvedTypeReferenceDirectiveNames = oldSourceFile . resolvedTypeReferenceDirectiveNames ;
696+ }
697+
568698 // update fileName -> file mapping
569699 for ( let i = 0 , len = newSourceFiles . length ; i < len ; i ++ ) {
570700 filesByName . set ( filePaths [ i ] , newSourceFiles [ i ] ) ;
@@ -574,7 +704,7 @@ namespace ts {
574704 fileProcessingDiagnostics = oldProgram . getFileProcessingDiagnostics ( ) ;
575705
576706 for ( const modifiedFile of modifiedSourceFiles ) {
577- fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile ) ;
707+ fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile . newFile ) ;
578708 }
579709 resolvedTypeReferenceDirectives = oldProgram . getResolvedTypeReferenceDirectives ( ) ;
580710 oldProgram . structureIsReused = true ;
@@ -994,9 +1124,11 @@ namespace ts {
9941124
9951125 const isJavaScriptFile = isSourceFileJavaScript ( file ) ;
9961126 const isExternalModuleFile = isExternalModule ( file ) ;
1127+ const isDtsFile = isDeclarationFile ( file ) ;
9971128
9981129 let imports : LiteralExpression [ ] ;
9991130 let moduleAugmentations : LiteralExpression [ ] ;
1131+ let ambientModules : string [ ] ;
10001132
10011133 // If we are importing helpers, we need to add a synthetic reference to resolve the
10021134 // helpers library.
@@ -1018,6 +1150,7 @@ namespace ts {
10181150
10191151 file . imports = imports || emptyArray ;
10201152 file . moduleAugmentations = moduleAugmentations || emptyArray ;
1153+ file . ambientModuleNames = ambientModules || emptyArray ;
10211154
10221155 return ;
10231156
@@ -1053,6 +1186,10 @@ namespace ts {
10531186 ( moduleAugmentations || ( moduleAugmentations = [ ] ) ) . push ( moduleName ) ;
10541187 }
10551188 else if ( ! inAmbientModule ) {
1189+ if ( isDtsFile ) {
1190+ // for global .d.ts files record name of ambient module
1191+ ( ambientModules || ( ambientModules = [ ] ) ) . push ( moduleName . text ) ;
1192+ }
10561193 // An AmbientExternalModuleDeclaration declares an external module.
10571194 // This type of declaration is permitted only in the global module.
10581195 // The StringLiteral must specify a top - level external module name.
@@ -1298,7 +1435,7 @@ namespace ts {
12981435 if ( file . imports . length || file . moduleAugmentations . length ) {
12991436 file . resolvedModules = createMap < ResolvedModuleFull > ( ) ;
13001437 const moduleNames = map ( concatenate ( file . imports , file . moduleAugmentations ) , getTextOfLiteral ) ;
1301- const resolutions = resolveModuleNamesWorker ( moduleNames , getNormalizedAbsolutePath ( file . fileName , currentDirectory ) ) ;
1438+ const resolutions = resolveModuleNamesReusingOldState ( moduleNames , getNormalizedAbsolutePath ( file . fileName , currentDirectory ) , file ) ;
13021439 Debug . assert ( resolutions . length === moduleNames . length ) ;
13031440 for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
13041441 const resolution = resolutions [ i ] ;
0 commit comments