@@ -467,6 +467,130 @@ namespace ts {
467
467
return classifiableNames ;
468
468
}
469
469
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
+
470
594
function tryReuseStructureFromOldProgram ( ) : boolean {
471
595
if ( ! oldProgram ) {
472
596
return false ;
@@ -494,7 +618,7 @@ namespace ts {
494
618
// check if program source files has changed in the way that can affect structure of the program
495
619
const newSourceFiles : SourceFile [ ] = [ ] ;
496
620
const filePaths : Path [ ] = [ ] ;
497
- const modifiedSourceFiles : SourceFile [ ] = [ ] ;
621
+ const modifiedSourceFiles : { oldFile : SourceFile , newFile : SourceFile } [ ] = [ ] ;
498
622
499
623
for ( const oldSourceFile of oldProgram . getSourceFiles ( ) ) {
500
624
let newSourceFile = host . getSourceFileByPath
@@ -537,29 +661,8 @@ namespace ts {
537
661
return false ;
538
662
}
539
663
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 } ) ;
563
666
}
564
667
else {
565
668
// file has no changes - use it as is
@@ -570,6 +673,33 @@ namespace ts {
570
673
newSourceFiles . push ( newSourceFile ) ;
571
674
}
572
675
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
+
573
703
// update fileName -> file mapping
574
704
for ( let i = 0 , len = newSourceFiles . length ; i < len ; i ++ ) {
575
705
filesByName . set ( filePaths [ i ] , newSourceFiles [ i ] ) ;
@@ -579,7 +709,7 @@ namespace ts {
579
709
fileProcessingDiagnostics = oldProgram . getFileProcessingDiagnostics ( ) ;
580
710
581
711
for ( const modifiedFile of modifiedSourceFiles ) {
582
- fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile ) ;
712
+ fileProcessingDiagnostics . reattachFileDiagnostics ( modifiedFile . newFile ) ;
583
713
}
584
714
resolvedTypeReferenceDirectives = oldProgram . getResolvedTypeReferenceDirectives ( ) ;
585
715
oldProgram . structureIsReused = true ;
@@ -999,9 +1129,11 @@ namespace ts {
999
1129
1000
1130
const isJavaScriptFile = isSourceFileJavaScript ( file ) ;
1001
1131
const isExternalModuleFile = isExternalModule ( file ) ;
1132
+ const isDtsFile = isDeclarationFile ( file ) ;
1002
1133
1003
1134
let imports : LiteralExpression [ ] ;
1004
1135
let moduleAugmentations : LiteralExpression [ ] ;
1136
+ let ambientModules : string [ ] ;
1005
1137
1006
1138
// If we are importing helpers, we need to add a synthetic reference to resolve the
1007
1139
// helpers library.
@@ -1023,6 +1155,7 @@ namespace ts {
1023
1155
1024
1156
file . imports = imports || emptyArray ;
1025
1157
file . moduleAugmentations = moduleAugmentations || emptyArray ;
1158
+ file . ambientModuleNames = ambientModules || emptyArray ;
1026
1159
1027
1160
return ;
1028
1161
@@ -1058,6 +1191,10 @@ namespace ts {
1058
1191
( moduleAugmentations || ( moduleAugmentations = [ ] ) ) . push ( moduleName ) ;
1059
1192
}
1060
1193
else if ( ! inAmbientModule ) {
1194
+ if ( isDtsFile ) {
1195
+ // for global .d.ts files record name of ambient module
1196
+ ( ambientModules || ( ambientModules = [ ] ) ) . push ( moduleName . text ) ;
1197
+ }
1061
1198
// An AmbientExternalModuleDeclaration declares an external module.
1062
1199
// This type of declaration is permitted only in the global module.
1063
1200
// The StringLiteral must specify a top - level external module name.
@@ -1303,7 +1440,7 @@ namespace ts {
1303
1440
if ( file . imports . length || file . moduleAugmentations . length ) {
1304
1441
file . resolvedModules = createMap < ResolvedModuleFull > ( ) ;
1305
1442
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 ) ;
1307
1444
Debug . assert ( resolutions . length === moduleNames . length ) ;
1308
1445
for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
1309
1446
const resolution = resolutions [ i ] ;
0 commit comments