@@ -72,6 +72,11 @@ namespace ts {
7272 return date1 ? date2 > date1 ? date2 : date1 : date2 ;
7373 }
7474
75+ /*@internal */
76+ export function getCurrentTime ( host : { now ?( ) : Date ; } ) {
77+ return host . now ? host . now ( ) : new Date ( ) ;
78+ }
79+
7580 export type ReportEmitErrorSummary = ( errorCount : number , filesInError : ( ReportFileInError | undefined ) [ ] ) => void ;
7681
7782 export interface ReportFileInError {
@@ -985,12 +990,12 @@ namespace ts {
985990 let newestDeclarationFileContentChangedTime : Date | undefined ;
986991 const emitterDiagnostics = createDiagnosticCollection ( ) ;
987992 const emittedOutputs = new Map < Path , string > ( ) ;
988- const buildInfoEntry = state . buildInfoCache . get ( projectPath ) ;
993+ const isOutFile = outFile ( config . options ) ;
989994 outputFiles . forEach ( ( { name, text, writeByteOrderMark, buildInfo } ) => {
990995 if ( resultFlags === BuildResultFlags . DeclarationOutputUnchanged && isDeclarationFileName ( name ) ) {
991996 // Check for unchanged .d.ts files
992997 if ( state . readFileWithCache ( name ) === text ) {
993- if ( config . options . composite ) {
998+ if ( config . options . composite && isOutFile ) {
994999 newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , ts . getModifiedTime ( host , name ) ) ;
9951000 }
9961001 }
@@ -1001,10 +1006,7 @@ namespace ts {
10011006
10021007 const path = toPath ( state , name ) ;
10031008 emittedOutputs . set ( path , name ) ;
1004- if ( buildInfoEntry ?. path === path ) {
1005- buildInfoEntry . buildInfo = buildInfo ! ;
1006- buildInfoEntry . modifiedTime = getCurrentTime ( state ) ;
1007- }
1009+ if ( buildInfo ) setBuildInfo ( state , buildInfo , projectPath , program ! . getCompilerOptions ( ) ) ;
10081010 writeFile ( writeFileCallback ? { writeFile : writeFileCallback } : compilerHost , emitterDiagnostics , name , text , writeByteOrderMark ) ;
10091011 } ) ;
10101012
@@ -1022,12 +1024,7 @@ namespace ts {
10221024 Debug . assertIsDefined ( program ) ;
10231025 Debug . assert ( step === BuildStep . EmitBuildInfo ) ;
10241026 const emitResult = program . emitBuildInfo ( ( name , text , writeByteOrderMark , onError , sourceFiles , data ) => {
1025- const path = toPath ( state , name ) ;
1026- const buildInfo = state . buildInfoCache . get ( projectPath ) ;
1027- if ( buildInfo ?. path === path ) {
1028- buildInfo . buildInfo = data ! . buildInfo ! ;
1029- buildInfo . modifiedTime = getCurrentTime ( state ) ;
1030- }
1027+ if ( data ?. buildInfo ) setBuildInfo ( state , data . buildInfo , projectPath , program ! . getCompilerOptions ( ) ) ;
10311028 if ( writeFileCallback ) writeFileCallback ( name , text , writeByteOrderMark , onError , sourceFiles , data ) ;
10321029 else state . compilerHost . writeFile ( name , text , writeByteOrderMark , onError , sourceFiles , data ) ;
10331030 } , cancellationToken ) ;
@@ -1076,7 +1073,7 @@ namespace ts {
10761073 state . diagnostics . delete ( projectPath ) ;
10771074 state . projectStatus . set ( projectPath , {
10781075 type : UpToDateStatusType . UpToDate ,
1079- newestDeclarationFileContentChangedTime : anyDtsChange ? undefined : newestDeclarationFileContentChangedTime ,
1076+ newestDeclarationFileContentChangedTime : newestDeclarationFileContentChangedTime || getDtsChangeTime ( state , config . options , projectPath ) ,
10801077 oldestOutputFileName
10811078 } ) ;
10821079 afterProgramDone ( state , program , config ) ;
@@ -1127,14 +1124,10 @@ namespace ts {
11271124 Debug . assert ( ! ! outputFiles . length ) ;
11281125 const emitterDiagnostics = createDiagnosticCollection ( ) ;
11291126 const emittedOutputs = new Map < Path , string > ( ) ;
1130- const buildInfoEntry = state . buildInfoCache . get ( projectPath ) ;
11311127 outputFiles . forEach ( ( { name, text, writeByteOrderMark, buildInfo } ) => {
11321128 const path = toPath ( state , name ) ;
11331129 emittedOutputs . set ( path , name ) ;
1134- if ( buildInfoEntry ?. path === path ) {
1135- buildInfoEntry . buildInfo = buildInfo ! ;
1136- buildInfoEntry . modifiedTime = getCurrentTime ( state ) ;
1137- }
1130+ if ( buildInfo ) setBuildInfo ( state , buildInfo , projectPath , config . options ) ;
11381131 writeFile ( writeFileCallback ? { writeFile : writeFileCallback } : compilerHost , emitterDiagnostics , name , text , writeByteOrderMark ) ;
11391132 } ) ;
11401133
@@ -1423,6 +1416,18 @@ namespace ts {
14231416 } ;
14241417 }
14251418
1419+ function setBuildInfo ( state : SolutionBuilderState , buildInfo : BuildInfo , resolvedConfigPath : ResolvedConfigFilePath , options : CompilerOptions ) {
1420+ const buildInfoPath = getTsBuildInfoEmitOutputFilePath ( options ) ! ;
1421+ const existing = getBuildInfoCacheEntry ( state , buildInfoPath , resolvedConfigPath ) ;
1422+ if ( existing ) {
1423+ existing . buildInfo = buildInfo ;
1424+ existing . modifiedTime = getCurrentTime ( state . host ) ;
1425+ }
1426+ else {
1427+ state . buildInfoCache . set ( resolvedConfigPath , { path : toPath ( state , buildInfoPath ) , buildInfo, modifiedTime : getCurrentTime ( state . host ) } ) ;
1428+ }
1429+ }
1430+
14261431 function getBuildInfoCacheEntry ( state : SolutionBuilderState , buildInfoPath : string , resolvedConfigPath : ResolvedConfigFilePath ) {
14271432 const path = toPath ( state , buildInfoPath ) ;
14281433 const existing = state . buildInfoCache . get ( resolvedConfigPath ) ;
@@ -1470,7 +1475,8 @@ namespace ts {
14701475 for ( const ref of project . projectReferences ) {
14711476 const resolvedRef = resolveProjectReferencePath ( ref ) ;
14721477 const resolvedRefPath = toResolvedConfigFilePath ( state , resolvedRef ) ;
1473- const refStatus = getUpToDateStatus ( state , parseConfigFile ( state , resolvedRef , resolvedRefPath ) , resolvedRefPath ) ;
1478+ const resolvedConfig = parseConfigFile ( state , resolvedRef , resolvedRefPath ) ! ;
1479+ const refStatus = getUpToDateStatus ( state , resolvedConfig , resolvedRefPath ) ;
14741480
14751481 // Its a circular reference ignore the status of this project
14761482 if ( refStatus . type === UpToDateStatusType . ComputingUpstream ||
@@ -1496,7 +1502,7 @@ namespace ts {
14961502 } ;
14971503 }
14981504
1499- if ( ! force ) ( referenceStatuses ||= [ ] ) . push ( { ref, refStatus } ) ;
1505+ if ( ! force ) ( referenceStatuses ||= [ ] ) . push ( { ref, refStatus, resolvedRefPath , resolvedConfig } ) ;
15001506 }
15011507 }
15021508 if ( force ) return { type : UpToDateStatusType . ForceBuild } ;
@@ -1507,6 +1513,7 @@ namespace ts {
15071513 let oldestOutputFileName = "(none)" ;
15081514 let oldestOutputFileTime = maximumDate ;
15091515 let buildInfoTime : Date | undefined ;
1516+ let newestDeclarationFileContentChangedTime ;
15101517 if ( buildInfoPath ) {
15111518 const buildInfoCacheEntry = getBuildInfoCacheEntry ( state , buildInfoPath , resolvedPath ) ;
15121519 buildInfoTime = buildInfoCacheEntry ?. modifiedTime || ts . getModifiedTime ( host , buildInfoPath ) ;
@@ -1540,6 +1547,7 @@ namespace ts {
15401547
15411548 oldestOutputFileTime = buildInfoTime ;
15421549 oldestOutputFileName = buildInfoPath ;
1550+ newestDeclarationFileContentChangedTime = buildInfo . program ?. dtsChangeTime ? new Date ( buildInfo . program . dtsChangeTime ) : undefined ;
15431551 }
15441552
15451553 // Check input files
@@ -1601,18 +1609,31 @@ namespace ts {
16011609 }
16021610 }
16031611
1612+ const seenRefs = buildInfoPath ? new Set < ResolvedConfigFilePath > ( ) : undefined ;
1613+ const buildInfoCacheEntry = state . buildInfoCache . get ( resolvedPath ) ! ;
1614+ seenRefs ?. add ( resolvedPath ) ;
1615+
16041616 let pseudoUpToDate = false ;
16051617 let usesPrepend = false ;
16061618 let upstreamChangedProject : string | undefined ;
16071619 if ( referenceStatuses ) {
1608- for ( const { ref, refStatus } of referenceStatuses ) {
1620+ for ( const { ref, refStatus, resolvedConfig , resolvedRefPath } of referenceStatuses ) {
16091621 usesPrepend = usesPrepend || ! ! ( ref . prepend ) ;
16101622 // If the upstream project's newest file is older than our oldest output, we
16111623 // can't be out of date because of it
16121624 if ( refStatus . newestInputFileTime && refStatus . newestInputFileTime <= oldestOutputFileTime ) {
16131625 continue ;
16141626 }
16151627
1628+ // Check if tsbuildinfo path is shared, then we need to rebuild
1629+ if ( buildInfoCacheEntry && hasSameBuildInfo ( state , buildInfoCacheEntry , seenRefs ! , resolvedConfig , resolvedRefPath ) ) {
1630+ return {
1631+ type : UpToDateStatusType . OutOfDateWithUpstream ,
1632+ outOfDateOutputFileName : buildInfoPath ! ,
1633+ newerProjectName : ref . path
1634+ } ;
1635+ }
1636+
16161637 // If the upstream project has only change .d.ts files, and we've built
16171638 // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild
16181639 if ( refStatus . newestDeclarationFileContentChangedTime && refStatus . newestDeclarationFileContentChangedTime <= oldestOutputFileTime ) {
@@ -1657,13 +1678,31 @@ namespace ts {
16571678 // Up to date
16581679 return {
16591680 type : pseudoUpToDate ? UpToDateStatusType . UpToDateWithUpstreamTypes : UpToDateStatusType . UpToDate ,
1660- newestDeclarationFileContentChangedTime : undefined ,
1681+ newestDeclarationFileContentChangedTime,
16611682 newestInputFileTime,
16621683 newestInputFileName,
16631684 oldestOutputFileName
16641685 } ;
16651686 }
16661687
1688+ function hasSameBuildInfo ( state : SolutionBuilderState , buildInfoCacheEntry : BuildInfoCacheEntry , seenRefs : Set < ResolvedConfigFilePath > , resolvedConfig : ParsedCommandLine , resolvedRefPath : ResolvedConfigFilePath ) {
1689+ if ( seenRefs . has ( resolvedRefPath ) ) return false ;
1690+ seenRefs . add ( resolvedRefPath ) ;
1691+ const refBuildInfo = state . buildInfoCache . get ( resolvedRefPath ) ! ;
1692+ if ( refBuildInfo . path === buildInfoCacheEntry . path ) return true ;
1693+
1694+ if ( resolvedConfig . projectReferences ) {
1695+ // Check references
1696+ for ( const ref of resolvedConfig . projectReferences ) {
1697+ const resolvedRef = resolveProjectReferencePath ( ref ) ;
1698+ const resolvedRefPath = toResolvedConfigFilePath ( state , resolvedRef ) ;
1699+ const resolvedConfig = parseConfigFile ( state , resolvedRef , resolvedRefPath ) ! ;
1700+ if ( hasSameBuildInfo ( state , buildInfoCacheEntry , seenRefs , resolvedConfig , resolvedRefPath ) ) return true ;
1701+ }
1702+ }
1703+ return false ;
1704+ }
1705+
16671706 function getUpToDateStatus ( state : SolutionBuilderState , project : ParsedCommandLine | undefined , resolvedPath : ResolvedConfigFilePath ) : UpToDateStatus {
16681707 if ( project === undefined ) {
16691708 return { type : UpToDateStatusType . Unbuildable , reason : "File deleted mid-build" } ;
@@ -1679,16 +1718,13 @@ namespace ts {
16791718 return actual ;
16801719 }
16811720
1682- function getCurrentTime ( state : SolutionBuilderState ) {
1683- return state . host . now ? state . host . now ( ) : new Date ( ) ;
1684- }
1685-
16861721 function updateOutputTimestampsWorker ( state : SolutionBuilderState , proj : ParsedCommandLine , anyDtsChange : boolean , verboseMessage : DiagnosticMessage , newestDeclarationFileContentChangedTime ?: Date , skipOutputs ?: ESMap < Path , string > ) {
16871722 if ( proj . options . noEmit ) return undefined ;
16881723
16891724 const buildInfoPath = getTsBuildInfoEmitOutputFilePath ( proj . options ) ;
16901725 const { host } = state ;
16911726 const outputs = getAllProjectOutputs ( proj , ! host . useCaseSensitiveFileNames ( ) ) ;
1727+ const isOutFile = outFile ( proj . options ) ;
16921728 if ( ! skipOutputs || outputs . length !== skipOutputs . size ) {
16931729 let reportVerbose = ! ! state . options . verbose ;
16941730 let now : Date | undefined ;
@@ -1697,7 +1733,7 @@ namespace ts {
16971733 continue ;
16981734 }
16991735
1700- if ( proj . options . composite && ! anyDtsChange && isDeclarationFileName ( file ) ) {
1736+ if ( proj . options . composite && isOutFile && ! anyDtsChange && isDeclarationFileName ( file ) ) {
17011737 newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , ts . getModifiedTime ( host , file ) ) ;
17021738 }
17031739
@@ -1707,22 +1743,29 @@ namespace ts {
17071743 reportStatus ( state , verboseMessage , proj . options . configFilePath ! ) ;
17081744 }
17091745
1710- host . setModifiedTime ( file , now ||= getCurrentTime ( state ) ) ;
1746+ host . setModifiedTime ( file , now ||= getCurrentTime ( state . host ) ) ;
17111747 }
17121748 }
17131749 }
17141750
17151751 return newestDeclarationFileContentChangedTime ;
17161752 }
17171753
1754+ function getDtsChangeTime ( state : SolutionBuilderState , options : CompilerOptions , resolvedConfigPath : ResolvedConfigFilePath ) {
1755+ if ( ! options . composite || outFile ( options ) ) return undefined ;
1756+ const buildInfoPath = getTsBuildInfoEmitOutputFilePath ( options ) ! ;
1757+ const buildInfo = getBuildInfo ( state , buildInfoPath , resolvedConfigPath , /*modifiedTime*/ undefined ) ;
1758+ return buildInfo ?. program ?. dtsChangeTime ? new Date ( buildInfo . program . dtsChangeTime ) : undefined ;
1759+ }
1760+
17181761 function updateOutputTimestamps ( state : SolutionBuilderState , proj : ParsedCommandLine , resolvedPath : ResolvedConfigFilePath ) {
17191762 if ( state . options . dry ) {
17201763 return reportStatus ( state , Diagnostics . A_non_dry_build_would_update_timestamps_for_output_of_project_0 , proj . options . configFilePath ! ) ;
17211764 }
17221765 const priorNewestUpdateTime = updateOutputTimestampsWorker ( state , proj , /*anyDtsChange*/ false , Diagnostics . Updating_output_timestamps_of_project_0 ) ;
17231766 state . projectStatus . set ( resolvedPath , {
17241767 type : UpToDateStatusType . UpToDate ,
1725- newestDeclarationFileContentChangedTime : priorNewestUpdateTime ,
1768+ newestDeclarationFileContentChangedTime : priorNewestUpdateTime || getDtsChangeTime ( state , proj . options , resolvedPath ) ,
17261769 oldestOutputFileName : getFirstProjectOutput ( proj , ! state . host . useCaseSensitiveFileNames ( ) )
17271770 } ) ;
17281771 }
0 commit comments