@@ -386,6 +386,114 @@ namespace ts {
386386 allDiagnostics ?: Diagnostic [ ] ;
387387 }
388388
389+ export function isProgramUptoDate ( program : Program , rootFileNames : string [ ] , newOptions : CompilerOptions , getSourceVersion : ( path : Path ) => string ) : boolean {
390+ // If we haven't create a program yet, then it is not up-to-date
391+ if ( ! program ) {
392+ return false ;
393+ }
394+
395+ // If number of files in the program do not match, it is not up-to-date
396+ if ( program . getRootFileNames ( ) . length !== rootFileNames . length ) {
397+ return false ;
398+ }
399+
400+ const fileNames = concatenate ( rootFileNames , map ( program . getSourceFiles ( ) , sourceFile => sourceFile . fileName ) ) ;
401+ // If any file is not up-to-date, then the whole program is not up-to-date
402+ for ( const fileName of fileNames ) {
403+ if ( ! sourceFileUpToDate ( program . getSourceFile ( fileName ) ) ) {
404+ return false ;
405+ }
406+ }
407+
408+ const currentOptions = program . getCompilerOptions ( ) ;
409+ // If the compilation settings do no match, then the program is not up-to-date
410+ if ( ! compareDataObjects ( currentOptions , newOptions ) ) {
411+ return false ;
412+ }
413+
414+ // If everything matches but the text of config file is changed,
415+ // error locations can change for program options, so update the program
416+ if ( currentOptions . configFile && newOptions . configFile ) {
417+ return currentOptions . configFile . text === newOptions . configFile . text ;
418+ }
419+
420+ return true ;
421+
422+ function sourceFileUpToDate ( sourceFile : SourceFile ) : boolean {
423+ if ( ! sourceFile ) {
424+ return false ;
425+ }
426+ return sourceFile . version === getSourceVersion ( sourceFile . path ) ;
427+ }
428+ }
429+
430+ function shouldProgramCreateNewSourceFiles ( program : Program , newOptions : CompilerOptions ) {
431+ // If any of these options change, we cant reuse old source file even if version match
432+ const oldOptions = program && program . getCompilerOptions ( ) ;
433+ return oldOptions &&
434+ ( oldOptions . target !== newOptions . target ||
435+ oldOptions . module !== newOptions . module ||
436+ oldOptions . moduleResolution !== newOptions . moduleResolution ||
437+ oldOptions . noResolve !== newOptions . noResolve ||
438+ oldOptions . jsx !== newOptions . jsx ||
439+ oldOptions . allowJs !== newOptions . allowJs ||
440+ oldOptions . disableSizeLimit !== newOptions . disableSizeLimit ||
441+ oldOptions . baseUrl !== newOptions . baseUrl ||
442+ ! equalOwnProperties ( oldOptions . paths , newOptions . paths ) ) ;
443+ }
444+
445+ /**
446+ * Updates the existing missing file watches with the new set of missing files after new program is created
447+ * @param program
448+ * @param existingMap
449+ * @param createMissingFileWatch
450+ * @param closeExistingFileWatcher
451+ */
452+ export function updateMissingFilePathsWatch ( program : Program , existingMap : Map < FileWatcher > ,
453+ createMissingFileWatch : ( missingFilePath : Path ) => FileWatcher ,
454+ closeExistingFileWatcher : ( missingFilePath : Path , fileWatcher : FileWatcher ) => void ) {
455+
456+ const missingFilePaths = program . getMissingFilePaths ( ) ;
457+ const newMissingFilePathMap = arrayToSet ( missingFilePaths ) ;
458+ // Update the missing file paths watcher
459+ return mutateExistingMapWithNewSet (
460+ existingMap , newMissingFilePathMap ,
461+ // Watch the missing files
462+ createMissingFileWatch ,
463+ // Files that are no longer missing (e.g. because they are no longer required)
464+ // should no longer be watched.
465+ closeExistingFileWatcher
466+ ) ;
467+ }
468+
469+ export type WildCardDirectoryWatchers = { watcher : FileWatcher , recursive : boolean } ;
470+
471+ export function updateWatchingWildcardDirectories ( existingWatchedForWildcards : Map < WildCardDirectoryWatchers > , wildcardDirectories : Map < WatchDirectoryFlags > ,
472+ watchDirectory : ( directory : string , recursive : boolean ) => FileWatcher ,
473+ closeDirectoryWatcher : ( directory : string , watcher : FileWatcher , recursive : boolean , recursiveChanged : boolean ) => void ) {
474+ return mutateExistingMap (
475+ existingWatchedForWildcards , wildcardDirectories ,
476+ // Create new watch and recursive info
477+ ( directory , flag ) => {
478+ const recursive = ( flag & WatchDirectoryFlags . Recursive ) !== 0 ;
479+ return {
480+ watcher : watchDirectory ( directory , recursive ) ,
481+ recursive
482+ } ;
483+ } ,
484+ // Close existing watch thats not needed any more
485+ ( directory , { watcher, recursive } ) => closeDirectoryWatcher ( directory , watcher , recursive , /*recursiveChanged*/ false ) ,
486+ // Watcher is same if the recursive flags match
487+ ( { recursive : existingRecursive } , flag ) => {
488+ // If the recursive dont match, it needs update
489+ const recursive = ( flag & WatchDirectoryFlags . Recursive ) !== 0 ;
490+ return existingRecursive !== recursive ;
491+ } ,
492+ // Close existing watch that doesnt match in recursive flag
493+ ( directory , { watcher, recursive } ) => closeDirectoryWatcher ( directory , watcher , recursive , /*recursiveChanged*/ true )
494+ ) ;
495+ }
496+
389497 /**
390498 * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
391499 * that represent a compilation unit.
@@ -478,6 +586,7 @@ namespace ts {
478586 // used to track cases when two file names differ only in casing
479587 const filesByNameIgnoreCase = host . useCaseSensitiveFileNames ( ) ? createMap < SourceFile > ( ) : undefined ;
480588
589+ const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles ( oldProgram , options ) ;
481590 const structuralIsReused = tryReuseStructureFromOldProgram ( ) ;
482591 if ( structuralIsReused !== StructureIsReused . Completely ) {
483592 forEach ( rootNames , name => processRootFile ( name , /*isDefaultLib*/ false ) ) ;
@@ -519,6 +628,17 @@ namespace ts {
519628 // unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
520629 moduleResolutionCache = undefined ;
521630
631+ // Release any files we have acquired in the old program but are
632+ // not part of the new program.
633+ if ( oldProgram && host . onReleaseOldSourceFile ) {
634+ const oldSourceFiles = oldProgram . getSourceFiles ( ) ;
635+ for ( const oldSourceFile of oldSourceFiles ) {
636+ if ( ! getSourceFile ( oldSourceFile . path ) || shouldCreateNewSourceFile ) {
637+ host . onReleaseOldSourceFile ( oldSourceFile , oldProgram . getCompilerOptions ( ) ) ;
638+ }
639+ }
640+ }
641+
522642 // unconditionally set oldProgram to undefined to prevent it from being captured in closure
523643 oldProgram = undefined ;
524644
@@ -783,8 +903,8 @@ namespace ts {
783903
784904 for ( const oldSourceFile of oldProgram . getSourceFiles ( ) ) {
785905 const newSourceFile = host . getSourceFileByPath
786- ? host . getSourceFileByPath ( oldSourceFile . fileName , oldSourceFile . path , options . target )
787- : host . getSourceFile ( oldSourceFile . fileName , options . target ) ;
906+ ? host . getSourceFileByPath ( oldSourceFile . fileName , oldSourceFile . path , options . target , /*onError*/ undefined , shouldCreateNewSourceFile )
907+ : host . getSourceFile ( oldSourceFile . fileName , options . target , /*onError*/ undefined , shouldCreateNewSourceFile ) ;
788908
789909 if ( ! newSourceFile ) {
790910 return oldProgram . structureIsReused = StructureIsReused . Not ;
@@ -1593,7 +1713,7 @@ namespace ts {
15931713 else {
15941714 fileProcessingDiagnostics . add ( createCompilerDiagnostic ( Diagnostics . Cannot_read_file_0_Colon_1 , fileName , hostErrorMessage ) ) ;
15951715 }
1596- } ) ;
1716+ } , shouldCreateNewSourceFile ) ;
15971717
15981718 filesByName . set ( path , file ) ;
15991719 if ( file ) {
0 commit comments