@@ -64,8 +64,8 @@ namespace ts {
6464 return existingValue || newValue ! ;
6565 }
6666
67- function getOrCreateValueMapFromConfigFileMap < T > ( configFileMap : ESMap < ResolvedConfigFilePath , ESMap < string , T > > , resolved : ResolvedConfigFilePath ) : ESMap < string , T > {
68- return getOrCreateValueFromConfigFileMap < ESMap < string , T > > ( configFileMap , resolved , ( ) => new Map ( ) ) ;
67+ function getOrCreateValueMapFromConfigFileMap < K extends string , V > ( configFileMap : ESMap < ResolvedConfigFilePath , ESMap < K , V > > , resolved : ResolvedConfigFilePath ) : ESMap < K , V > {
68+ return getOrCreateValueFromConfigFileMap ( configFileMap , resolved , ( ) => new Map ( ) ) ;
6969 }
7070
7171 function newer ( date1 : Date | undefined , date2 : Date ) : Date | undefined {
@@ -212,6 +212,12 @@ namespace ts {
212212 originalGetSourceFile : CompilerHost [ "getSourceFile" ] ;
213213 }
214214
215+ interface FileWatcherWithModifiedTime {
216+ callbacks : FileWatcherCallback [ ] ;
217+ watcher : FileWatcher ;
218+ modifiedTime : Date | undefined ;
219+ }
220+
215221 interface SolutionBuilderState < T extends BuilderProgram = BuilderProgram > extends WatchFactory < WatchType , ResolvedConfigFileName > {
216222 readonly host : SolutionBuilderHost < T > ;
217223 readonly hostWithWatch : SolutionBuilderWithWatchHost < T > ;
@@ -259,6 +265,8 @@ namespace ts {
259265 readonly allWatchedConfigFiles : ESMap < ResolvedConfigFilePath , FileWatcher > ;
260266 readonly allWatchedExtendedConfigFiles : ESMap < Path , SharedExtendedConfigFileWatcher < ResolvedConfigFilePath > > ;
261267 readonly allWatchedPackageJsonFiles : ESMap < ResolvedConfigFilePath , ESMap < Path , FileWatcher > > ;
268+ readonly filesWatched : ESMap < Path , FileWatcherWithModifiedTime | Date > ;
269+
262270 readonly lastCachedPackageJsonLookups : ESMap < ResolvedConfigFilePath , readonly ( readonly [ Path , object | boolean ] ) [ ] | undefined > ;
263271
264272 timerToBuildInvalidatedProject : any ;
@@ -341,6 +349,8 @@ namespace ts {
341349 allWatchedConfigFiles : new Map ( ) ,
342350 allWatchedExtendedConfigFiles : new Map ( ) ,
343351 allWatchedPackageJsonFiles : new Map ( ) ,
352+ filesWatched : new Map ( ) ,
353+
344354 lastCachedPackageJsonLookups : new Map ( ) ,
345355
346356 timerToBuildInvalidatedProject : undefined ,
@@ -969,7 +979,7 @@ namespace ts {
969979 if ( resultFlags === BuildResultFlags . DeclarationOutputUnchanged && isDeclarationFileName ( name ) ) {
970980 // Check for unchanged .d.ts files
971981 if ( state . readFileWithCache ( name ) === text ) {
972- newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , getModifiedTime ( host , name ) ) ;
982+ newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , ts . getModifiedTime ( host , name ) ) ;
973983 }
974984 else {
975985 resultFlags &= ~ BuildResultFlags . DeclarationOutputUnchanged ;
@@ -1323,9 +1333,66 @@ namespace ts {
13231333 return { buildResult, step : BuildStep . QueueReferencingProjects } ;
13241334 }
13251335
1336+ function isFileWatcherWithModifiedTime ( value : FileWatcherWithModifiedTime | Date ) : value is FileWatcherWithModifiedTime {
1337+ return ! ! ( value as FileWatcherWithModifiedTime ) . watcher ;
1338+ }
1339+
1340+ function getModifiedTime ( state : SolutionBuilderState , fileName : string ) : Date {
1341+ const path = toPath ( state , fileName ) ;
1342+ const existing = state . filesWatched . get ( path ) ;
1343+ if ( state . watch && ! ! existing ) {
1344+ if ( ! isFileWatcherWithModifiedTime ( existing ) ) return existing ;
1345+ if ( existing . modifiedTime ) return existing . modifiedTime ;
1346+ }
1347+ const result = ts . getModifiedTime ( state . host , fileName ) ;
1348+ if ( state . watch ) {
1349+ if ( existing ) ( existing as FileWatcherWithModifiedTime ) . modifiedTime = result ;
1350+ else state . filesWatched . set ( path , result ) ;
1351+ }
1352+ return result ;
1353+ }
1354+
1355+ function watchFile ( state : SolutionBuilderState , file : string , callback : FileWatcherCallback , pollingInterval : PollingInterval , options : WatchOptions | undefined , watchType : WatchType , project ?: ResolvedConfigFileName ) : FileWatcher {
1356+ const path = toPath ( state , file ) ;
1357+ const existing = state . filesWatched . get ( path ) ;
1358+ if ( existing && isFileWatcherWithModifiedTime ( existing ) ) {
1359+ existing . callbacks . push ( callback ) ;
1360+ }
1361+ else {
1362+ const watcher = state . watchFile (
1363+ file ,
1364+ ( fileName , eventKind , modifiedTime ) => {
1365+ const existing = Debug . checkDefined ( state . filesWatched . get ( path ) ) ;
1366+ Debug . assert ( isFileWatcherWithModifiedTime ( existing ) ) ;
1367+ existing . modifiedTime = modifiedTime ;
1368+ existing . callbacks . forEach ( cb => cb ( fileName , eventKind , modifiedTime ) ) ;
1369+ } ,
1370+ pollingInterval ,
1371+ options ,
1372+ watchType ,
1373+ project
1374+ ) ;
1375+ state . filesWatched . set ( path , { callbacks : [ callback ] , watcher, modifiedTime : existing } ) ;
1376+ }
1377+
1378+ return {
1379+ close : ( ) => {
1380+ const existing = Debug . checkDefined ( state . filesWatched . get ( path ) ) ;
1381+ Debug . assert ( isFileWatcherWithModifiedTime ( existing ) ) ;
1382+ if ( existing . callbacks . length === 1 ) {
1383+ state . filesWatched . delete ( path ) ;
1384+ closeFileWatcherOf ( existing ) ;
1385+ }
1386+ else {
1387+ unorderedRemoveItem ( existing . callbacks , callback ) ;
1388+ }
1389+ }
1390+ } ;
1391+ }
1392+
13261393 function checkConfigFileUpToDateStatus ( state : SolutionBuilderState , configFile : string , oldestOutputFileTime : Date , oldestOutputFileName : string ) : Status . OutOfDateWithSelf | undefined {
13271394 // Check tsconfig time
1328- const tsconfigTime = getModifiedTime ( state . host , configFile ) ;
1395+ const tsconfigTime = getModifiedTime ( state , configFile ) ;
13291396 if ( oldestOutputFileTime < tsconfigTime ) {
13301397 return {
13311398 type : UpToDateStatusType . OutOfDateWithSelf ,
@@ -1387,7 +1454,7 @@ namespace ts {
13871454 const { host } = state ;
13881455 // Get timestamps of input files
13891456 for ( const inputFile of project . fileNames ) {
1390- const inputTime = getModifiedTime ( host , inputFile ) ;
1457+ const inputTime = getModifiedTime ( state , inputFile ) ;
13911458 if ( inputTime === missingFileModifiedTime ) {
13921459 return {
13931460 type : UpToDateStatusType . Unbuildable ,
@@ -1413,7 +1480,7 @@ namespace ts {
14131480 if ( ! force ) {
14141481 for ( const output of outputs ) {
14151482 // Output is missing; can stop checking
1416- const outputTime = getModifiedTime ( host , output ) ;
1483+ const outputTime = ts . getModifiedTime ( host , output ) ;
14171484 if ( outputTime === missingFileModifiedTime ) {
14181485 return {
14191486 type : UpToDateStatusType . OutputMissing ,
@@ -1556,7 +1623,7 @@ namespace ts {
15561623 }
15571624
15581625 if ( ! anyDtsChange && isDeclarationFileName ( file ) ) {
1559- newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , getModifiedTime ( host , file ) ) ;
1626+ newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , ts . getModifiedTime ( host , file ) ) ;
15601627 }
15611628
15621629 host . setModifiedTime ( file , now ) ;
@@ -1778,11 +1845,10 @@ namespace ts {
17781845
17791846 function watchConfigFile ( state : SolutionBuilderState , resolved : ResolvedConfigFileName , resolvedPath : ResolvedConfigFilePath , parsed : ParsedCommandLine | undefined ) {
17801847 if ( ! state . watch || state . allWatchedConfigFiles . has ( resolvedPath ) ) return ;
1781- state . allWatchedConfigFiles . set ( resolvedPath , state . watchFile (
1848+ state . allWatchedConfigFiles . set ( resolvedPath , watchFile (
1849+ state ,
17821850 resolved ,
1783- ( ) => {
1784- invalidateProjectAndScheduleBuilds ( state , resolvedPath , ConfigFileProgramReloadLevel . Full ) ;
1785- } ,
1851+ ( ) => invalidateProjectAndScheduleBuilds ( state , resolvedPath , ConfigFileProgramReloadLevel . Full ) ,
17861852 PollingInterval . High ,
17871853 parsed ?. watchOptions ,
17881854 WatchType . ConfigFile ,
@@ -1795,11 +1861,11 @@ namespace ts {
17951861 resolvedPath ,
17961862 parsed ?. options ,
17971863 state . allWatchedExtendedConfigFiles ,
1798- ( extendedConfigFileName , extendedConfigFilePath ) => state . watchFile (
1864+ ( extendedConfigFileName , extendedConfigFilePath ) => watchFile (
1865+ state ,
17991866 extendedConfigFileName ,
18001867 ( ) => state . allWatchedExtendedConfigFiles . get ( extendedConfigFilePath ) ?. projects . forEach ( projectConfigFilePath =>
1801- invalidateProjectAndScheduleBuilds ( state , projectConfigFilePath , ConfigFileProgramReloadLevel . Full )
1802- ) ,
1868+ invalidateProjectAndScheduleBuilds ( state , projectConfigFilePath , ConfigFileProgramReloadLevel . Full ) ) ,
18031869 PollingInterval . High ,
18041870 parsed ?. watchOptions ,
18051871 WatchType . ExtendedConfigFile ,
@@ -1845,7 +1911,8 @@ namespace ts {
18451911 getOrCreateValueMapFromConfigFileMap ( state . allWatchedInputFiles , resolvedPath ) ,
18461912 arrayToMap ( parsed . fileNames , fileName => toPath ( state , fileName ) ) ,
18471913 {
1848- createNewValue : ( _path , input ) => state . watchFile (
1914+ createNewValue : ( _path , input ) => watchFile (
1915+ state ,
18491916 input ,
18501917 ( ) => invalidateProjectAndScheduleBuilds ( state , resolvedPath , ConfigFileProgramReloadLevel . None ) ,
18511918 PollingInterval . Low ,
@@ -1864,7 +1931,8 @@ namespace ts {
18641931 getOrCreateValueMapFromConfigFileMap ( state . allWatchedPackageJsonFiles , resolvedPath ) ,
18651932 new Map ( state . lastCachedPackageJsonLookups . get ( resolvedPath ) ) ,
18661933 {
1867- createNewValue : ( path , _input ) => state . watchFile (
1934+ createNewValue : ( path , _input ) => watchFile (
1935+ state ,
18681936 path ,
18691937 ( ) => invalidateProjectAndScheduleBuilds ( state , resolvedPath , ConfigFileProgramReloadLevel . Full ) ,
18701938 PollingInterval . High ,
0 commit comments