@@ -245,6 +245,7 @@ namespace ts {
245245 readonly projectStatus : ESMap < ResolvedConfigFilePath , UpToDateStatus > ;
246246 readonly extendedConfigCache : ESMap < string , ExtendedConfigCacheEntry > ;
247247 readonly buildInfoCache : ESMap < ResolvedConfigFilePath , BuildInfoCacheEntry > ;
248+ readonly outputTimeStamps : ESMap < ResolvedConfigFilePath , ESMap < Path , Date > > ;
248249
249250 readonly builderPrograms : ESMap < ResolvedConfigFilePath , T > ;
250251 readonly diagnostics : ESMap < ResolvedConfigFilePath , readonly Diagnostic [ ] > ;
@@ -330,6 +331,7 @@ namespace ts {
330331 projectStatus : new Map ( ) ,
331332 extendedConfigCache : new Map ( ) ,
332333 buildInfoCache : new Map ( ) ,
334+ outputTimeStamps : new Map ( ) ,
333335
334336 builderPrograms : new Map ( ) ,
335337 diagnostics : new Map ( ) ,
@@ -493,6 +495,7 @@ namespace ts {
493495 mutateMapSkippingNewValues ( state . projectPendingBuild , currentProjects , noopOnDelete ) ;
494496 mutateMapSkippingNewValues ( state . projectErrorsReported , currentProjects , noopOnDelete ) ;
495497 mutateMapSkippingNewValues ( state . buildInfoCache , currentProjects , noopOnDelete ) ;
498+ mutateMapSkippingNewValues ( state . outputTimeStamps , currentProjects , noopOnDelete ) ;
496499
497500 // Remove watches for the program no longer in the solution
498501 if ( state . watch ) {
@@ -983,15 +986,23 @@ namespace ts {
983986 const existingBuildInfo = state . buildInfoCache . get ( projectPath ) ?. buildInfo || undefined ;
984987 const emitterDiagnostics = createDiagnosticCollection ( ) ;
985988 const emittedOutputs = new Map < Path , string > ( ) ;
989+ const options = program . getCompilerOptions ( ) ;
990+ const isIncremental = isIncrementalCompilation ( options ) ;
991+ let outputTimeStampMap : ESMap < Path , Date > | undefined ;
992+ let now : Date | undefined ;
986993 outputFiles . forEach ( ( { name, text, writeByteOrderMark, buildInfo } ) => {
994+ const path = toPath ( state , name ) ;
987995 emittedOutputs . set ( toPath ( state , name ) , name ) ;
988996 if ( buildInfo ) {
989- setBuildInfo ( state , buildInfo , projectPath , program ! . getCompilerOptions ( ) ) ;
997+ setBuildInfo ( state , buildInfo , projectPath , options ) ;
990998 if ( buildInfo . program ?. dtsChangeTime !== existingBuildInfo ?. program ?. dtsChangeTime ) {
991999 resultFlags &= ~ BuildResultFlags . DeclarationOutputUnchanged ;
9921000 }
9931001 }
9941002 writeFile ( writeFileCallback ? { writeFile : writeFileCallback } : compilerHost , emitterDiagnostics , name , text , writeByteOrderMark ) ;
1003+ if ( ! isIncremental ) {
1004+ ( outputTimeStampMap ||= getOutputTimeStampMap ( state , projectPath ) ) . set ( path , now ||= getCurrentTime ( state . host ) ) ;
1005+ }
9951006 } ) ;
9961007
9971008 finishEmit (
@@ -1050,7 +1061,7 @@ namespace ts {
10501061 }
10511062
10521063 // Update time stamps for rest of the outputs
1053- updateOutputTimestampsWorker ( state , config , Diagnostics . Updating_unchanged_output_timestamps_of_project_0 , emittedOutputs ) ;
1064+ updateOutputTimestampsWorker ( state , config , projectPath , Diagnostics . Updating_unchanged_output_timestamps_of_project_0 , emittedOutputs ) ;
10541065 state . diagnostics . delete ( projectPath ) ;
10551066 state . projectStatus . set ( projectPath , {
10561067 type : UpToDateStatusType . UpToDate ,
@@ -1402,6 +1413,12 @@ namespace ts {
14021413 } ;
14031414 }
14041415
1416+ function getOutputTimeStampMap ( state : SolutionBuilderState , resolvedConfigFilePath : ResolvedConfigFilePath ) {
1417+ let result = state . outputTimeStamps . get ( resolvedConfigFilePath ) ;
1418+ if ( ! result ) state . outputTimeStamps . set ( resolvedConfigFilePath , result = new Map ( ) ) ;
1419+ return result ;
1420+ }
1421+
14051422 function setBuildInfo ( state : SolutionBuilderState , buildInfo : BuildInfo , resolvedConfigPath : ResolvedConfigFilePath , options : CompilerOptions ) {
14061423 const buildInfoPath = getTsBuildInfoEmitOutputFilePath ( options ) ! ;
14071424 const existing = getBuildInfoCacheEntry ( state , buildInfoPath , resolvedConfigPath ) ;
@@ -1573,9 +1590,12 @@ namespace ts {
15731590 if ( ! buildInfoPath ) {
15741591 // Collect the expected outputs of this project
15751592 const outputs = getAllProjectOutputs ( project , ! host . useCaseSensitiveFileNames ( ) ) ;
1593+ const outputTimeStampMap = getOutputTimeStampMap ( state , resolvedPath ) ;
15761594 for ( const output of outputs ) {
1595+ const path = toPath ( state , output ) ;
15771596 // Output is missing; can stop checking
1578- const outputTime = ts . getModifiedTime ( state . host , output ) ;
1597+ let outputTime = outputTimeStampMap . get ( path ) ;
1598+ if ( ! outputTime ) outputTimeStampMap . set ( path , outputTime = ts . getModifiedTime ( state . host , output ) ) ;
15791599 if ( outputTime === missingFileModifiedTime ) {
15801600 return {
15811601 type : UpToDateStatusType . OutputMissing ,
@@ -1711,37 +1731,46 @@ namespace ts {
17111731 function updateOutputTimestampsWorker (
17121732 state : SolutionBuilderState ,
17131733 proj : ParsedCommandLine ,
1734+ projectPath : ResolvedConfigFilePath ,
17141735 verboseMessage : DiagnosticMessage ,
17151736 skipOutputs ?: ESMap < Path , string >
17161737 ) {
17171738 if ( proj . options . noEmit ) return ;
1739+ let now : Date | undefined ;
17181740 const buildInfoPath = getTsBuildInfoEmitOutputFilePath ( proj . options ) ;
17191741 if ( buildInfoPath ) {
17201742 if ( ! skipOutputs ?. has ( toPath ( state , buildInfoPath ) ) ) {
17211743 if ( ! ! state . options . verbose ) reportStatus ( state , verboseMessage , proj . options . configFilePath ! ) ;
1722- state . host . setModifiedTime ( buildInfoPath , getCurrentTime ( state . host ) ) ;
1744+ state . host . setModifiedTime ( buildInfoPath , now = getCurrentTime ( state . host ) ) ;
1745+ getBuildInfoCacheEntry ( state , buildInfoPath , projectPath ) ! . modifiedTime = now ;
17231746 }
1747+ state . outputTimeStamps . delete ( projectPath ) ;
17241748 return ;
17251749 }
17261750
17271751 const { host } = state ;
17281752 const outputs = getAllProjectOutputs ( proj , ! host . useCaseSensitiveFileNames ( ) ) ;
1753+ const outputTimeStampMap = getOutputTimeStampMap ( state , projectPath ) ;
1754+ const modifiedOutputs = new Set < Path > ( ) ;
17291755 if ( ! skipOutputs || outputs . length !== skipOutputs . size ) {
17301756 let reportVerbose = ! ! state . options . verbose ;
1731- let now : Date | undefined ;
17321757 for ( const file of outputs ) {
1733- if ( skipOutputs && skipOutputs . has ( toPath ( state , file ) ) ) {
1734- continue ;
1735- }
1736-
1758+ const path = toPath ( state , file ) ;
1759+ if ( skipOutputs ?. has ( path ) ) continue ;
17371760 if ( reportVerbose ) {
17381761 reportVerbose = false ;
17391762 reportStatus ( state , verboseMessage , proj . options . configFilePath ! ) ;
17401763 }
1741-
17421764 host . setModifiedTime ( file , now ||= getCurrentTime ( state . host ) ) ;
1765+ outputTimeStampMap . set ( path , now ) ;
1766+ modifiedOutputs . add ( path ) ;
17431767 }
17441768 }
1769+
1770+ // Clear out timestamps not in output list any more
1771+ outputTimeStampMap . forEach ( ( _value , key ) => {
1772+ if ( ! skipOutputs ?. has ( key ) && ! modifiedOutputs . has ( key ) ) outputTimeStampMap . delete ( key ) ;
1773+ } ) ;
17451774 }
17461775
17471776 function getDtsChangeTime ( state : SolutionBuilderState , options : CompilerOptions , resolvedConfigPath : ResolvedConfigFilePath ) {
@@ -1755,7 +1784,7 @@ namespace ts {
17551784 if ( state . options . dry ) {
17561785 return reportStatus ( state , Diagnostics . A_non_dry_build_would_update_timestamps_for_output_of_project_0 , proj . options . configFilePath ! ) ;
17571786 }
1758- updateOutputTimestampsWorker ( state , proj , Diagnostics . Updating_output_timestamps_of_project_0 ) ;
1787+ updateOutputTimestampsWorker ( state , proj , resolvedPath , Diagnostics . Updating_output_timestamps_of_project_0 ) ;
17591788 state . projectStatus . set ( resolvedPath , {
17601789 type : UpToDateStatusType . UpToDate ,
17611790 newestDeclarationFileContentChangedTime : getDtsChangeTime ( state , proj . options , resolvedPath ) ,
0 commit comments