@@ -467,6 +467,130 @@ namespace ts {
467467 return classifiableNames ;
468468 }
469469
470+ interface OldProgramState {
471+ program : Program ;
472+ file : SourceFile ;
473+ modifiedFilePaths : Path [ ] ;
474+ }
475+
476+ function resolveModuleNamesReusingOldState ( moduleNames : string [ ] , containingFile : string , file : SourceFile , oldProgramState ?: OldProgramState ) {
477+ if ( ! oldProgramState && ! file . ambientModuleNames . length ) {
478+ // if old program state is not supplied and file does not contain locally defined ambient modules
479+ // then the best we can do is fallback to the default logic
480+ return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
481+ }
482+
483+ // at this point we know that either
484+ // - file has local declarations for ambient modules
485+ // OR
486+ // - old program state is available
487+ // OR
488+ // - both of items above
489+ // With this it is possible that we can tell how some module names from the initial list will be resolved
490+ // without doing actual resolution (in particular if some name was resolved to ambient module).
491+ // Such names should be excluded from the list of module names that will be provided to `resolveModuleNamesWorker`
492+ // since we don't want to resolve them again.
493+
494+ // this is a list of modules for which we cannot predict resolution so they should be actually resolved
495+ let unknownModuleNames : string [ ] ;
496+ // this is a list of combined results assembles from predicted and resolved results.
497+ // Order in this list matches the order in the original list of module names `moduleNames` which is important
498+ // so later we can split results to resolutions of modules and resolutions of module augmentations.
499+ let result : ResolvedModuleFull [ ] ;
500+ // a transient placeholder that is used to mark predicted resolution in the result list
501+ const predictedToResolveToAmbientModuleMarker : ResolvedModuleFull = < any > { } ;
502+
503+ for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
504+ const moduleName = moduleNames [ i ] ;
505+ // module name is known to be resolved to ambient module if
506+ // - module name is contained in the list of ambient modules that are locally declared in the file
507+ // - in the old program module name was resolved to ambient module whose declaration is in non-modified file
508+ // (so the same module declaration will land in the new program)
509+ let isKnownToResolveToAmbientModule = false ;
510+ if ( contains ( file . ambientModuleNames , moduleName ) ) {
511+ isKnownToResolveToAmbientModule = true ;
512+ if ( isTraceEnabled ( options , host ) ) {
513+ trace ( host , Diagnostics . Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1 , moduleName , containingFile ) ;
514+ }
515+ }
516+ else {
517+ isKnownToResolveToAmbientModule = checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName , oldProgramState ) ;
518+ }
519+
520+ if ( isKnownToResolveToAmbientModule ) {
521+ if ( ! unknownModuleNames ) {
522+ // found a first module name for which result can be prediced
523+ // this means that this module name should not be passed to `resolveModuleNamesWorker`.
524+ // We'll use a separate list for module names that are definitely unknown.
525+ result = new Array ( moduleNames . length ) ;
526+ // copy all module names that appear before the current one in the list
527+ // since they are known to be unknown
528+ unknownModuleNames = moduleNames . slice ( 0 , i ) ;
529+ }
530+ // mark prediced resolution in the result list
531+ result [ i ] = predictedToResolveToAmbientModuleMarker ;
532+ }
533+ else if ( unknownModuleNames ) {
534+ // found unknown module name and we are already using separate list for those - add it to the list
535+ unknownModuleNames . push ( moduleName ) ;
536+ }
537+ }
538+
539+ if ( ! unknownModuleNames ) {
540+ // we've looked throught the list but have not seen any predicted resolution
541+ // use default logic
542+ return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
543+ }
544+
545+ const resolutions = unknownModuleNames . length
546+ ? resolveModuleNamesWorker ( unknownModuleNames , containingFile )
547+ : emptyArray ;
548+
549+ // combine results of resolutions and predicted results
550+ let j = 0 ;
551+ for ( let i = 0 ; i < result . length ; i ++ ) {
552+ if ( result [ i ] == predictedToResolveToAmbientModuleMarker ) {
553+ result [ i ] = undefined ;
554+ }
555+ else {
556+ result [ i ] = resolutions [ j ] ;
557+ j ++ ;
558+ }
559+ }
560+ Debug . assert ( j === resolutions . length ) ;
561+ return result ;
562+
563+ function checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName : string , oldProgramState ?: OldProgramState ) : boolean {
564+ if ( ! oldProgramState ) {
565+ return false ;
566+ }
567+ const resolutionToFile = getResolvedModule ( oldProgramState . file , moduleName ) ;
568+ if ( resolutionToFile ) {
569+ // module used to be resolved to file - ignore it
570+ return false ;
571+ }
572+ const ambientModule = oldProgram . getTypeChecker ( ) . tryFindAmbientModuleWithoutAugmentations ( moduleName ) ;
573+ if ( ! ( ambientModule && ambientModule . declarations ) ) {
574+ return false ;
575+ }
576+
577+ // at least one of declarations should come from non-modified source file
578+ const firstUnmodifiedFile = forEach ( ambientModule . declarations , d => {
579+ const f = getSourceFileOfNode ( d ) ;
580+ return ! contains ( oldProgramState . modifiedFilePaths , f . path ) && f ;
581+ } ) ;
582+
583+ if ( ! firstUnmodifiedFile ) {
584+ return false ;
585+ }
586+
587+ if ( isTraceEnabled ( options , host ) ) {
588+ trace ( host , Diagnostics . Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified , moduleName , firstUnmodifiedFile . fileName ) ;
589+ }
590+ return true ;
591+ }
592+ }
593+
470594 function tryReuseStructureFromOldProgram ( ) : boolean {
471595 if ( ! oldProgram ) {
472596 return false ;
@@ -494,7 +618,7 @@ namespace ts {
494618 // check if program source files has changed in the way that can affect structure of the program
495619 const newSourceFiles : SourceFile [ ] = [ ] ;
496620 const filePaths : Path [ ] = [ ] ;
497- const modifiedSourceFiles : SourceFile [ ] = [ ] ;
621+ const modifiedSourceFiles : { oldFile : SourceFile , newFile : SourceFile } [ ] = [ ] ;
498622
499623 for ( const oldSourceFile of oldProgram . getSourceFiles ( ) ) {
500624 let newSourceFile = host . getSourceFileByPath
@@ -537,29 +661,8 @@ namespace ts {
537661 return false ;
538662 }
539663
540- const newSourceFilePath = getNormalizedAbsolutePath ( newSourceFile . fileName , currentDirectory ) ;
541- if ( resolveModuleNamesWorker ) {
542- const moduleNames = map ( concatenate ( newSourceFile . imports , newSourceFile . moduleAugmentations ) , getTextOfLiteral ) ;
543- const resolutions = resolveModuleNamesWorker ( moduleNames , newSourceFilePath ) ;
544- // ensure that module resolution results are still correct
545- const resolutionsChanged = hasChangesInResolutions ( moduleNames , resolutions , oldSourceFile . resolvedModules , moduleResolutionIsEqualTo ) ;
546- if ( resolutionsChanged ) {
547- return false ;
548- }
549- }
550- if ( resolveTypeReferenceDirectiveNamesWorker ) {
551- const typesReferenceDirectives = map ( newSourceFile . typeReferenceDirectives , x => x . fileName ) ;
552- const resolutions = resolveTypeReferenceDirectiveNamesWorker ( typesReferenceDirectives , newSourceFilePath ) ;
553- // ensure that types resolutions are still correct
554- const resolutionsChanged = hasChangesInResolutions ( typesReferenceDirectives , resolutions , oldSourceFile . resolvedTypeReferenceDirectiveNames , typeDirectiveIsEqualTo ) ;
555- if ( resolutionsChanged ) {
556- return false ;
557- }
558- }
559- // pass the cache of module/types resolutions from the old source file
560- newSourceFile . resolvedModules = oldSourceFile . resolvedModules ;
561- newSourceFile . resolvedTypeReferenceDirectiveNames = oldSourceFile . resolvedTypeReferenceDirectiveNames ;
562- modifiedSourceFiles . push ( newSourceFile ) ;
664+ // tentatively approve the file
665+ modifiedSourceFiles . push ( { oldFile : oldSourceFile , newFile : newSourceFile } ) ;
563666 }
564667 else {
565668 // file has no changes - use it as is
@@ -570,6 +673,33 @@ namespace ts {
570673 newSourceFiles . push ( newSourceFile ) ;
571674 }
572675
676+ const modifiedFilePaths = modifiedSourceFiles . map ( f => f . newFile . path ) ;
677+ // try to verify results of module resolution
678+ for ( const { oldFile : oldSourceFile , newFile : newSourceFile } of modifiedSourceFiles ) {
679+ const newSourceFilePath = getNormalizedAbsolutePath ( newSourceFile . fileName , currentDirectory ) ;
680+ if ( resolveModuleNamesWorker ) {
681+ const moduleNames = map ( concatenate ( newSourceFile . imports , newSourceFile . moduleAugmentations ) , getTextOfLiteral ) ;
682+ const resolutions = resolveModuleNamesReusingOldState ( moduleNames , newSourceFilePath , newSourceFile , { file : oldSourceFile , program : oldProgram , modifiedFilePaths } ) ;
683+ // ensure that module resolution results are still correct
684+ const resolutionsChanged = hasChangesInResolutions ( moduleNames , resolutions , oldSourceFile . resolvedModules , moduleResolutionIsEqualTo ) ;
685+ if ( resolutionsChanged ) {
686+ return false ;
687+ }
688+ }
689+ if ( resolveTypeReferenceDirectiveNamesWorker ) {
690+ const typesReferenceDirectives = map ( newSourceFile . typeReferenceDirectives , x => x . fileName ) ;
691+ const resolutions = resolveTypeReferenceDirectiveNamesWorker ( typesReferenceDirectives , newSourceFilePath ) ;
692+ // ensure that types resolutions are still correct
693+ const resolutionsChanged = hasChangesInResolutions ( typesReferenceDirectives , resolutions , oldSourceFile . resolvedTypeReferenceDirectiveNames , typeDirectiveIsEqualTo ) ;
694+ if ( resolutionsChanged ) {
695+ return false ;
696+ }
697+ }
698+ // pass the cache of module/types resolutions from the old source file
699+ newSourceFile . resolvedModules = oldSourceFile . resolvedModules ;
700+ newSourceFile . resolvedTypeReferenceDirectiveNames = oldSourceFile . resolvedTypeReferenceDirectiveNames ;
701+ }
702+
573703 // update fileName -> file mapping
574704 for ( let i = 0 , len = newSourceFiles . length ; i < len ; i ++ ) {
575705 filesByName . set ( filePaths [ i ] , newSourceFiles [ i ] ) ;
@@ -579,7 +709,7 @@ namespace ts {
579709 fileProcessingDiagnostics = oldProgram . getFileProcessingDiagnostics ( ) ;
580710
581711 for ( const modifiedFile of modifiedSourceFiles ) {
582- fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile ) ;
712+ fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile . newFile ) ;
583713 }
584714 resolvedTypeReferenceDirectives = oldProgram . getResolvedTypeReferenceDirectives ( ) ;
585715 oldProgram . structureIsReused = true ;
@@ -999,9 +1129,11 @@ namespace ts {
9991129
10001130 const isJavaScriptFile = isSourceFileJavaScript ( file ) ;
10011131 const isExternalModuleFile = isExternalModule ( file ) ;
1132+ const isDtsFile = isDeclarationFile ( file ) ;
10021133
10031134 let imports : LiteralExpression [ ] ;
10041135 let moduleAugmentations : LiteralExpression [ ] ;
1136+ let ambientModules : string [ ] ;
10051137
10061138 // If we are importing helpers, we need to add a synthetic reference to resolve the
10071139 // helpers library.
@@ -1023,6 +1155,7 @@ namespace ts {
10231155
10241156 file . imports = imports || emptyArray ;
10251157 file . moduleAugmentations = moduleAugmentations || emptyArray ;
1158+ file . ambientModuleNames = ambientModules || emptyArray ;
10261159
10271160 return ;
10281161
@@ -1058,6 +1191,10 @@ namespace ts {
10581191 ( moduleAugmentations || ( moduleAugmentations = [ ] ) ) . push ( moduleName ) ;
10591192 }
10601193 else if ( ! inAmbientModule ) {
1194+ if ( isDtsFile ) {
1195+ // for global .d.ts files record name of ambient module
1196+ ( ambientModules || ( ambientModules = [ ] ) ) . push ( moduleName . text ) ;
1197+ }
10611198 // An AmbientExternalModuleDeclaration declares an external module.
10621199 // This type of declaration is permitted only in the global module.
10631200 // The StringLiteral must specify a top - level external module name.
@@ -1303,7 +1440,7 @@ namespace ts {
13031440 if ( file . imports . length || file . moduleAugmentations . length ) {
13041441 file . resolvedModules = createMap < ResolvedModuleFull > ( ) ;
13051442 const moduleNames = map ( concatenate ( file . imports , file . moduleAugmentations ) , getTextOfLiteral ) ;
1306- const resolutions = resolveModuleNamesWorker ( moduleNames , getNormalizedAbsolutePath ( file . fileName , currentDirectory ) ) ;
1443+ const resolutions = resolveModuleNamesReusingOldState ( moduleNames , getNormalizedAbsolutePath ( file . fileName , currentDirectory ) , file ) ;
13071444 Debug . assert ( resolutions . length === moduleNames . length ) ;
13081445 for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
13091446 const resolution = resolutions [ i ] ;
0 commit comments