@@ -462,6 +462,130 @@ namespace ts {
462
462
return classifiableNames ;
463
463
}
464
464
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
+
465
589
function tryReuseStructureFromOldProgram ( ) : boolean {
466
590
if ( ! oldProgram ) {
467
591
return false ;
@@ -489,7 +613,7 @@ namespace ts {
489
613
// check if program source files has changed in the way that can affect structure of the program
490
614
const newSourceFiles : SourceFile [ ] = [ ] ;
491
615
const filePaths : Path [ ] = [ ] ;
492
- const modifiedSourceFiles : SourceFile [ ] = [ ] ;
616
+ const modifiedSourceFiles : { oldFile : SourceFile , newFile : SourceFile } [ ] = [ ] ;
493
617
494
618
for ( const oldSourceFile of oldProgram . getSourceFiles ( ) ) {
495
619
let newSourceFile = host . getSourceFileByPath
@@ -532,29 +656,8 @@ namespace ts {
532
656
return false ;
533
657
}
534
658
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 } ) ;
558
661
}
559
662
else {
560
663
// file has no changes - use it as is
@@ -565,6 +668,33 @@ namespace ts {
565
668
newSourceFiles . push ( newSourceFile ) ;
566
669
}
567
670
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
+
568
698
// update fileName -> file mapping
569
699
for ( let i = 0 , len = newSourceFiles . length ; i < len ; i ++ ) {
570
700
filesByName . set ( filePaths [ i ] , newSourceFiles [ i ] ) ;
@@ -574,7 +704,7 @@ namespace ts {
574
704
fileProcessingDiagnostics = oldProgram . getFileProcessingDiagnostics ( ) ;
575
705
576
706
for ( const modifiedFile of modifiedSourceFiles ) {
577
- fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile ) ;
707
+ fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile . newFile ) ;
578
708
}
579
709
resolvedTypeReferenceDirectives = oldProgram . getResolvedTypeReferenceDirectives ( ) ;
580
710
oldProgram . structureIsReused = true ;
@@ -994,9 +1124,11 @@ namespace ts {
994
1124
995
1125
const isJavaScriptFile = isSourceFileJavaScript ( file ) ;
996
1126
const isExternalModuleFile = isExternalModule ( file ) ;
1127
+ const isDtsFile = isDeclarationFile ( file ) ;
997
1128
998
1129
let imports : LiteralExpression [ ] ;
999
1130
let moduleAugmentations : LiteralExpression [ ] ;
1131
+ let ambientModules : string [ ] ;
1000
1132
1001
1133
// If we are importing helpers, we need to add a synthetic reference to resolve the
1002
1134
// helpers library.
@@ -1018,6 +1150,7 @@ namespace ts {
1018
1150
1019
1151
file . imports = imports || emptyArray ;
1020
1152
file . moduleAugmentations = moduleAugmentations || emptyArray ;
1153
+ file . ambientModuleNames = ambientModules || emptyArray ;
1021
1154
1022
1155
return ;
1023
1156
@@ -1053,6 +1186,10 @@ namespace ts {
1053
1186
( moduleAugmentations || ( moduleAugmentations = [ ] ) ) . push ( moduleName ) ;
1054
1187
}
1055
1188
else if ( ! inAmbientModule ) {
1189
+ if ( isDtsFile ) {
1190
+ // for global .d.ts files record name of ambient module
1191
+ ( ambientModules || ( ambientModules = [ ] ) ) . push ( moduleName . text ) ;
1192
+ }
1056
1193
// An AmbientExternalModuleDeclaration declares an external module.
1057
1194
// This type of declaration is permitted only in the global module.
1058
1195
// The StringLiteral must specify a top - level external module name.
@@ -1298,7 +1435,7 @@ namespace ts {
1298
1435
if ( file . imports . length || file . moduleAugmentations . length ) {
1299
1436
file . resolvedModules = createMap < ResolvedModuleFull > ( ) ;
1300
1437
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 ) ;
1302
1439
Debug . assert ( resolutions . length === moduleNames . length ) ;
1303
1440
for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
1304
1441
const resolution = resolutions [ i ] ;
0 commit comments