@@ -6,6 +6,23 @@ namespace ts {
66 const emptyArray : any [ ] = [ ] ;
77 const ignoreDiagnosticCommentRegEx = / ( ^ \s * $ ) | ( ^ \s * \/ \/ \/ ? \s * ( @ t s - i g n o r e ) ? ) / ;
88
9+
10+ const enum StructuralChangesFromOldProgram {
11+ None = 0 ,
12+ NoOldProgram = 1 ,
13+ ModuleResolutionOptions = 2 ,
14+ RootNames = 3 ,
15+ TypesOptions = 4 ,
16+ SourceFileRemoved = 5 ,
17+ HasNoDefaultLib = 6 ,
18+ TripleSlashReferences = 7 ,
19+ Imports = 8 ,
20+ ModuleAugmentations = 9 ,
21+ TripleSlashTypesReferences = 10 ,
22+
23+ CannotReuseResolution = NoOldProgram | ModuleResolutionOptions | RootNames | TypesOptions | SourceFileRemoved
24+ }
25+
926 export function findConfigFile ( searchPath : string , fileExists : ( fileName : string ) => boolean , configName = "tsconfig.json" ) : string {
1027 while ( true ) {
1128 const fileName = combinePaths ( searchPath , configName ) ;
@@ -298,6 +315,7 @@ namespace ts {
298315 let diagnosticsProducingTypeChecker : TypeChecker ;
299316 let noDiagnosticsTypeChecker : TypeChecker ;
300317 let classifiableNames : Map < string > ;
318+ let modifiedFilePaths : Path [ ] | undefined ;
301319
302320 const cachedSemanticDiagnosticsForFile : DiagnosticCache = { } ;
303321 const cachedDeclarationDiagnosticsForFile : DiagnosticCache = { } ;
@@ -367,7 +385,11 @@ namespace ts {
367385 // used to track cases when two file names differ only in casing
368386 const filesByNameIgnoreCase = host . useCaseSensitiveFileNames ( ) ? createFileMap < SourceFile > ( fileName => fileName . toLowerCase ( ) ) : undefined ;
369387
370- if ( ! tryReuseStructureFromOldProgram ( ) ) {
388+ const structuralChanges = tryReuseStructureFromOldProgram ( ) ;
389+ if ( structuralChanges === StructuralChangesFromOldProgram . None ) {
390+ Debug . assert ( oldProgram . structureIsReused === true ) ;
391+ }
392+ else {
371393 forEach ( rootNames , name => processRootFile ( name , /*isDefaultLib*/ false ) ) ;
372394
373395 // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders
@@ -476,18 +498,36 @@ namespace ts {
476498 }
477499
478500 interface OldProgramState {
479- program : Program ;
501+ program : Program | undefined ;
480502 file : SourceFile ;
481503 modifiedFilePaths : Path [ ] ;
482504 }
483505
484- function resolveModuleNamesReusingOldState ( moduleNames : string [ ] , containingFile : string , file : SourceFile , oldProgramState ?: OldProgramState ) {
485- if ( ! oldProgramState && ! file . ambientModuleNames . length ) {
486- // if old program state is not supplied and file does not contain locally defined ambient modules
487- // then the best we can do is fallback to the default logic
506+ function createOldProgramState (
507+ program : Program | undefined ,
508+ file : SourceFile ,
509+ modifiedFilePaths : Path [ ] ) : OldProgramState {
510+ return { program, file, modifiedFilePaths } ;
511+ }
512+
513+ function resolveModuleNamesReusingOldState ( moduleNames : string [ ] , containingFile : string , file : SourceFile , oldProgramState : OldProgramState ) {
514+ if ( structuralChanges & StructuralChangesFromOldProgram . CannotReuseResolution && ! file . ambientModuleNames . length ) {
515+ // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
516+ // the best we can do is fallback to the default logic.
488517 return resolveModuleNamesWorker ( moduleNames , containingFile ) ;
489518 }
490519
520+ const oldSourceFile = oldProgramState . program && oldProgramState . program . getSourceFile ( containingFile ) ;
521+ if ( oldSourceFile === file ) {
522+ // `file` is unchanged from the old program, so we can reuse old module resolutions.
523+ const oldSourceFileResult : ResolvedModuleFull [ ] = [ ] ;
524+ for ( const moduleName of moduleNames ) {
525+ const resolvedModule = oldSourceFile . resolvedModules . get ( moduleName ) ;
526+ oldSourceFileResult . push ( resolvedModule ) ;
527+ }
528+ return oldSourceFileResult ;
529+ }
530+
491531 // at this point we know that either
492532 // - file has local declarations for ambient modules
493533 // OR
@@ -535,7 +575,7 @@ namespace ts {
535575 // since they are known to be unknown
536576 unknownModuleNames = moduleNames . slice ( 0 , i ) ;
537577 }
538- // mark prediced resolution in the result list
578+ // Mark predicted resolution in the result list.
539579 result [ i ] = predictedToResolveToAmbientModuleMarker ;
540580 }
541581 else if ( unknownModuleNames ) {
@@ -568,16 +608,13 @@ namespace ts {
568608 Debug . assert ( j === resolutions . length ) ;
569609 return result ;
570610
571- function checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName : string , oldProgramState ?: OldProgramState ) : boolean {
572- if ( ! oldProgramState ) {
573- return false ;
574- }
611+ function checkModuleNameResolvedToAmbientModuleInNonModifiedFile ( moduleName : string , oldProgramState : OldProgramState ) : boolean {
575612 const resolutionToFile = getResolvedModule ( oldProgramState . file , moduleName ) ;
576613 if ( resolutionToFile ) {
577614 // module used to be resolved to file - ignore it
578615 return false ;
579616 }
580- const ambientModule = oldProgram . getTypeChecker ( ) . tryFindAmbientModuleWithoutAugmentations ( moduleName ) ;
617+ const ambientModule = oldProgramState . program && oldProgramState . program . getTypeChecker ( ) . tryFindAmbientModuleWithoutAugmentations ( moduleName ) ;
581618 if ( ! ( ambientModule && ambientModule . declarations ) ) {
582619 return false ;
583620 }
@@ -599,99 +636,106 @@ namespace ts {
599636 }
600637 }
601638
602- function tryReuseStructureFromOldProgram ( ) : boolean {
639+ function tryReuseStructureFromOldProgram ( ) : StructuralChangesFromOldProgram {
603640 if ( ! oldProgram ) {
604- return false ;
641+ return StructuralChangesFromOldProgram . NoOldProgram ;
605642 }
606643
607644 // check properties that can affect structure of the program or module resolution strategy
608645 // if any of these properties has changed - structure cannot be reused
609646 const oldOptions = oldProgram . getCompilerOptions ( ) ;
610647 if ( changesAffectModuleResolution ( oldOptions , options ) ) {
611- return false ;
648+ return StructuralChangesFromOldProgram . ModuleResolutionOptions ;
612649 }
613650
614651 Debug . assert ( ! oldProgram . structureIsReused ) ;
615652
616653 // there is an old program, check if we can reuse its structure
617654 const oldRootNames = oldProgram . getRootFileNames ( ) ;
618655 if ( ! arrayIsEqualTo ( oldRootNames , rootNames ) ) {
619- return false ;
656+ return StructuralChangesFromOldProgram . RootNames ;
620657 }
621658
622659 if ( ! arrayIsEqualTo ( options . types , oldOptions . types ) ) {
623- return false ;
660+ return StructuralChangesFromOldProgram . TypesOptions ;
624661 }
625662
626663 // check if program source files has changed in the way that can affect structure of the program
627664 const newSourceFiles : SourceFile [ ] = [ ] ;
628665 const filePaths : Path [ ] = [ ] ;
629666 const modifiedSourceFiles : { oldFile : SourceFile , newFile : SourceFile } [ ] = [ ] ;
667+ let structuralChanges = StructuralChangesFromOldProgram . None ;
630668
631669 for ( const oldSourceFile of oldProgram . getSourceFiles ( ) ) {
632670 let newSourceFile = host . getSourceFileByPath
633671 ? host . getSourceFileByPath ( oldSourceFile . fileName , oldSourceFile . path , options . target )
634672 : host . getSourceFile ( oldSourceFile . fileName , options . target ) ;
635673
636674 if ( ! newSourceFile ) {
637- return false ;
675+ structuralChanges |= StructuralChangesFromOldProgram . SourceFileRemoved ;
638676 }
677+ else {
678+ newSourceFile . path = oldSourceFile . path ;
679+ filePaths . push ( newSourceFile . path ) ;
680+
681+ if ( oldSourceFile !== newSourceFile ) {
682+ if ( oldSourceFile . hasNoDefaultLib !== newSourceFile . hasNoDefaultLib ) {
683+ // value of no-default-lib has changed
684+ // this will affect if default library is injected into the list of files
685+ structuralChanges |= StructuralChangesFromOldProgram . HasNoDefaultLib ;
686+ }
639687
640- newSourceFile . path = oldSourceFile . path ;
641- filePaths . push ( newSourceFile . path ) ;
688+ // check tripleslash references
689+ if ( ! arrayIsEqualTo ( oldSourceFile . referencedFiles , newSourceFile . referencedFiles , fileReferenceIsEqualTo ) ) {
690+ // tripleslash references has changed
691+ structuralChanges |= StructuralChangesFromOldProgram . TripleSlashReferences ;
692+ }
642693
643- if ( oldSourceFile !== newSourceFile ) {
644- if ( oldSourceFile . hasNoDefaultLib !== newSourceFile . hasNoDefaultLib ) {
645- // value of no-default-lib has changed
646- // this will affect if default library is injected into the list of files
647- return false ;
648- }
694+ // check imports and module augmentations
695+ collectExternalModuleReferences ( newSourceFile ) ;
696+ if ( ! arrayIsEqualTo ( oldSourceFile . imports , newSourceFile . imports , moduleNameIsEqualTo ) ) {
697+ // imports has changed
698+ structuralChanges |= StructuralChangesFromOldProgram . Imports ;
699+ }
700+ if ( ! arrayIsEqualTo ( oldSourceFile . moduleAugmentations , newSourceFile . moduleAugmentations , moduleNameIsEqualTo ) ) {
701+ // moduleAugmentations has changed
702+ structuralChanges |= StructuralChangesFromOldProgram . ModuleAugmentations ;
703+ }
649704
650- // check tripleslash references
651- if ( ! arrayIsEqualTo ( oldSourceFile . referencedFiles , newSourceFile . referencedFiles , fileReferenceIsEqualTo ) ) {
652- // tripleslash references has changed
653- return false ;
654- }
705+ if ( ! arrayIsEqualTo ( oldSourceFile . typeReferenceDirectives , newSourceFile . typeReferenceDirectives , fileReferenceIsEqualTo ) ) {
706+ // 'types' references has changed
707+ structuralChanges |= StructuralChangesFromOldProgram . TripleSlashTypesReferences ;
708+ }
655709
656- // check imports and module augmentations
657- collectExternalModuleReferences ( newSourceFile ) ;
658- if ( ! arrayIsEqualTo ( oldSourceFile . imports , newSourceFile . imports , moduleNameIsEqualTo ) ) {
659- // imports has changed
660- return false ;
661- }
662- if ( ! arrayIsEqualTo ( oldSourceFile . moduleAugmentations , newSourceFile . moduleAugmentations , moduleNameIsEqualTo ) ) {
663- // moduleAugmentations has changed
664- return false ;
710+ // tentatively approve the file
711+ modifiedSourceFiles . push ( { oldFile : oldSourceFile , newFile : newSourceFile } ) ;
665712 }
666-
667- if ( ! arrayIsEqualTo ( oldSourceFile . typeReferenceDirectives , newSourceFile . typeReferenceDirectives , fileReferenceIsEqualTo ) ) {
668- // 'types' references has changed
669- return false ;
713+ else {
714+ // file has no changes - use it as is
715+ newSourceFile = oldSourceFile ;
670716 }
671717
672- // tentatively approve the file
673- modifiedSourceFiles . push ( { oldFile : oldSourceFile , newFile : newSourceFile } ) ;
674- }
675- else {
676- // file has no changes - use it as is
677- newSourceFile = oldSourceFile ;
718+ // if file has passed all checks it should be safe to reuse it
719+ newSourceFiles . push ( newSourceFile ) ;
678720 }
721+ }
679722
680- // if file has passed all checks it should be safe to reuse it
681- newSourceFiles . push ( newSourceFile ) ;
723+ if ( structuralChanges !== StructuralChangesFromOldProgram . None ) {
724+ return structuralChanges ;
682725 }
683726
684- const modifiedFilePaths = modifiedSourceFiles . map ( f => f . newFile . path ) ;
727+ modifiedFilePaths = modifiedSourceFiles . map ( f => f . newFile . path ) ;
685728 // try to verify results of module resolution
686729 for ( const { oldFile : oldSourceFile , newFile : newSourceFile } of modifiedSourceFiles ) {
687730 const newSourceFilePath = getNormalizedAbsolutePath ( newSourceFile . fileName , currentDirectory ) ;
688731 if ( resolveModuleNamesWorker ) {
689732 const moduleNames = map ( concatenate ( newSourceFile . imports , newSourceFile . moduleAugmentations ) , getTextOfLiteral ) ;
690- const resolutions = resolveModuleNamesReusingOldState ( moduleNames , newSourceFilePath , newSourceFile , { file : oldSourceFile , program : oldProgram , modifiedFilePaths } ) ;
733+ const oldProgramState = createOldProgramState ( oldProgram , oldSourceFile , modifiedFilePaths ) ;
734+ const resolutions = resolveModuleNamesReusingOldState ( moduleNames , newSourceFilePath , newSourceFile , oldProgramState ) ;
691735 // ensure that module resolution results are still correct
692736 const resolutionsChanged = hasChangesInResolutions ( moduleNames , resolutions , oldSourceFile . resolvedModules , moduleResolutionIsEqualTo ) ;
693737 if ( resolutionsChanged ) {
694- return false ;
738+ return StructuralChangesFromOldProgram . Imports | StructuralChangesFromOldProgram . ModuleAugmentations ;
695739 }
696740 }
697741 if ( resolveTypeReferenceDirectiveNamesWorker ) {
@@ -700,7 +744,7 @@ namespace ts {
700744 // ensure that types resolutions are still correct
701745 const resolutionsChanged = hasChangesInResolutions ( typesReferenceDirectives , resolutions , oldSourceFile . resolvedTypeReferenceDirectiveNames , typeDirectiveIsEqualTo ) ;
702746 if ( resolutionsChanged ) {
703- return false ;
747+ return StructuralChangesFromOldProgram . TripleSlashTypesReferences ;
704748 }
705749 }
706750 // pass the cache of module/types resolutions from the old source file
@@ -722,7 +766,7 @@ namespace ts {
722766 resolvedTypeReferenceDirectives = oldProgram . getResolvedTypeReferenceDirectives ( ) ;
723767 oldProgram . structureIsReused = true ;
724768
725- return true ;
769+ return StructuralChangesFromOldProgram . None ;
726770 }
727771
728772 function getEmitHost ( writeFileCallback ?: WriteFileCallback ) : EmitHost {
@@ -1521,11 +1565,11 @@ namespace ts {
15211565 function processImportedModules ( file : SourceFile ) {
15221566 collectExternalModuleReferences ( file ) ;
15231567 if ( file . imports . length || file . moduleAugmentations . length ) {
1524- file . resolvedModules = createMap < ResolvedModuleFull > ( ) ;
15251568 // Because global augmentation doesn't have string literal name, we can check for global augmentation as such.
15261569 const nonGlobalAugmentation = filter ( file . moduleAugmentations , ( moduleAugmentation ) => moduleAugmentation . kind === SyntaxKind . StringLiteral ) ;
15271570 const moduleNames = map ( concatenate ( file . imports , nonGlobalAugmentation ) , getTextOfLiteral ) ;
1528- const resolutions = resolveModuleNamesReusingOldState ( moduleNames , getNormalizedAbsolutePath ( file . fileName , currentDirectory ) , file ) ;
1571+ const oldProgramState = createOldProgramState ( oldProgram , file , modifiedFilePaths ) ;
1572+ const resolutions = resolveModuleNamesReusingOldState ( moduleNames , getNormalizedAbsolutePath ( file . fileName , currentDirectory ) , file , oldProgramState ) ;
15291573 Debug . assert ( resolutions . length === moduleNames . length ) ;
15301574 for ( let i = 0 ; i < moduleNames . length ; i ++ ) {
15311575 const resolution = resolutions [ i ] ;
0 commit comments