@@ -116,6 +116,7 @@ namespace ts {
116
116
export interface UpstreamBlocked {
117
117
type : UpToDateStatusType . UpstreamBlocked ;
118
118
upstreamProjectName : string ;
119
+ upstreamProjectBlocked : boolean ;
119
120
}
120
121
121
122
/**
@@ -273,6 +274,26 @@ namespace ts {
273
274
export interface SolutionBuilderWithWatchHost < T extends BuilderProgram > extends SolutionBuilderHostBase < T > , WatchHost {
274
275
}
275
276
277
+ /*@internal */
278
+ export type BuildOrder = readonly ResolvedConfigFileName [ ] ;
279
+ /*@internal */
280
+ export interface CircularBuildOrder {
281
+ buildOrder : BuildOrder ;
282
+ circularDiagnostics : readonly Diagnostic [ ] ;
283
+ }
284
+ /*@internal */
285
+ export type AnyBuildOrder = BuildOrder | CircularBuildOrder ;
286
+
287
+ /*@internal */
288
+ export function isCircularBuildOrder ( buildOrder : AnyBuildOrder ) : buildOrder is CircularBuildOrder {
289
+ return ! ! buildOrder && ! ! ( buildOrder as CircularBuildOrder ) . buildOrder ;
290
+ }
291
+
292
+ /*@internal */
293
+ export function getBuildOrderFromAnyBuildOrder ( anyBuildOrder : AnyBuildOrder ) : BuildOrder {
294
+ return isCircularBuildOrder ( anyBuildOrder ) ? anyBuildOrder . buildOrder : anyBuildOrder ;
295
+ }
296
+
276
297
export interface SolutionBuilder < T extends BuilderProgram > {
277
298
build ( project ?: string , cancellationToken ?: CancellationToken ) : ExitStatus ;
278
299
clean ( project ?: string ) : ExitStatus ;
@@ -281,7 +302,7 @@ namespace ts {
281
302
getNextInvalidatedProject ( cancellationToken ?: CancellationToken ) : InvalidatedProject < T > | undefined ;
282
303
283
304
// Currently used for testing but can be made public if needed:
284
- /*@internal */ getBuildOrder ( ) : ReadonlyArray < ResolvedConfigFileName > ;
305
+ /*@internal */ getBuildOrder ( ) : AnyBuildOrder ;
285
306
286
307
// Testing only
287
308
/*@internal */ getUpToDateStatusOfProject ( project : string ) : UpToDateStatus ;
@@ -379,7 +400,7 @@ namespace ts {
379
400
readonly moduleResolutionCache : ModuleResolutionCache | undefined ;
380
401
381
402
// Mutable state
382
- buildOrder : readonly ResolvedConfigFileName [ ] | undefined ;
403
+ buildOrder : AnyBuildOrder | undefined ;
383
404
readFileWithCache : ( f : string ) => string | undefined ;
384
405
projectCompilerOptions : CompilerOptions ;
385
406
cache : SolutionBuilderStateCache | undefined ;
@@ -523,16 +544,19 @@ namespace ts {
523
544
return resolveConfigFileProjectName ( resolvePath ( state . currentDirectory , name ) ) ;
524
545
}
525
546
526
- function createBuildOrder ( state : SolutionBuilderState , roots : readonly ResolvedConfigFileName [ ] ) : readonly ResolvedConfigFileName [ ] {
547
+ function createBuildOrder ( state : SolutionBuilderState , roots : readonly ResolvedConfigFileName [ ] ) : AnyBuildOrder {
527
548
const temporaryMarks = createMap ( ) as ConfigFileMap < true > ;
528
549
const permanentMarks = createMap ( ) as ConfigFileMap < true > ;
529
550
const circularityReportStack : string [ ] = [ ] ;
530
551
let buildOrder : ResolvedConfigFileName [ ] | undefined ;
552
+ let circularDiagnostics : Diagnostic [ ] | undefined ;
531
553
for ( const root of roots ) {
532
554
visit ( root ) ;
533
555
}
534
556
535
- return buildOrder || emptyArray ;
557
+ return circularDiagnostics ?
558
+ { buildOrder : buildOrder || emptyArray , circularDiagnostics } :
559
+ buildOrder || emptyArray ;
536
560
537
561
function visit ( configFileName : ResolvedConfigFileName , inCircularContext ?: boolean ) {
538
562
const projPath = toResolvedConfigFilePath ( state , configFileName ) ;
@@ -541,8 +565,12 @@ namespace ts {
541
565
// Circular
542
566
if ( temporaryMarks . has ( projPath ) ) {
543
567
if ( ! inCircularContext ) {
544
- // TODO:: Do we report this as error?
545
- reportStatus ( state , Diagnostics . Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0 , circularityReportStack . join ( "\r\n" ) ) ;
568
+ ( circularDiagnostics || ( circularDiagnostics = [ ] ) ) . push (
569
+ createCompilerDiagnostic (
570
+ Diagnostics . Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0 ,
571
+ circularityReportStack . join ( "\r\n" )
572
+ )
573
+ ) ;
546
574
}
547
575
return ;
548
576
}
@@ -569,12 +597,11 @@ namespace ts {
569
597
570
598
function createStateBuildOrder ( state : SolutionBuilderState ) {
571
599
const buildOrder = createBuildOrder ( state , state . rootNames . map ( f => resolveProjectName ( state , f ) ) ) ;
572
- if ( arrayIsEqualTo ( state . buildOrder , buildOrder ) ) return state . buildOrder ! ;
573
600
574
601
// Clear all to ResolvedConfigFilePaths cache to start fresh
575
602
state . resolvedConfigFilePaths . clear ( ) ;
576
603
const currentProjects = arrayToSet (
577
- buildOrder ,
604
+ getBuildOrderFromAnyBuildOrder ( buildOrder ) ,
578
605
resolved => toResolvedConfigFilePath ( state , resolved )
579
606
) as ConfigFileMap < true > ;
580
607
@@ -611,9 +638,10 @@ namespace ts {
611
638
return state . buildOrder = buildOrder ;
612
639
}
613
640
614
- function getBuildOrderFor ( state : SolutionBuilderState , project : string | undefined , onlyReferences : boolean | undefined ) {
641
+ function getBuildOrderFor ( state : SolutionBuilderState , project : string | undefined , onlyReferences : boolean | undefined ) : AnyBuildOrder | undefined {
615
642
const resolvedProject = project && resolveProjectName ( state , project ) ;
616
643
const buildOrderFromState = getBuildOrder ( state ) ;
644
+ if ( isCircularBuildOrder ( buildOrderFromState ) ) return buildOrderFromState ;
617
645
if ( resolvedProject ) {
618
646
const projectPath = toResolvedConfigFilePath ( state , resolvedProject ) ;
619
647
const projectIndex = findIndex (
@@ -622,7 +650,8 @@ namespace ts {
622
650
) ;
623
651
if ( projectIndex === - 1 ) return undefined ;
624
652
}
625
- const buildOrder = resolvedProject ? createBuildOrder ( state , [ resolvedProject ] ) : buildOrderFromState ;
653
+ const buildOrder = resolvedProject ? createBuildOrder ( state , [ resolvedProject ] ) as BuildOrder : buildOrderFromState ;
654
+ Debug . assert ( ! isCircularBuildOrder ( buildOrder ) ) ;
626
655
Debug . assert ( ! onlyReferences || resolvedProject !== undefined ) ;
627
656
Debug . assert ( ! onlyReferences || buildOrder [ buildOrder . length - 1 ] === resolvedProject ) ;
628
657
return onlyReferences ? buildOrder . slice ( 0 , buildOrder . length - 1 ) : buildOrder ;
@@ -702,7 +731,7 @@ namespace ts {
702
731
state . allProjectBuildPending = false ;
703
732
if ( state . options . watch ) { reportWatchStatus ( state , Diagnostics . Starting_compilation_in_watch_mode ) ; }
704
733
enableCache ( state ) ;
705
- const buildOrder = getBuildOrder ( state ) ;
734
+ const buildOrder = getBuildOrderFromAnyBuildOrder ( getBuildOrder ( state ) ) ;
706
735
buildOrder . forEach ( configFileName =>
707
736
state . projectPendingBuild . set (
708
737
toResolvedConfigFilePath ( state , configFileName ) ,
@@ -1237,10 +1266,11 @@ namespace ts {
1237
1266
1238
1267
function getNextInvalidatedProject < T extends BuilderProgram > (
1239
1268
state : SolutionBuilderState < T > ,
1240
- buildOrder : readonly ResolvedConfigFileName [ ] ,
1269
+ buildOrder : AnyBuildOrder ,
1241
1270
reportQueue : boolean
1242
1271
) : InvalidatedProject < T > | undefined {
1243
1272
if ( ! state . projectPendingBuild . size ) return undefined ;
1273
+ if ( isCircularBuildOrder ( buildOrder ) ) return undefined ;
1244
1274
if ( state . currentInvalidatedProject ) {
1245
1275
// Only if same buildOrder the currentInvalidated project can be sent again
1246
1276
return arrayIsEqualTo ( state . currentInvalidatedProject . buildOrder , buildOrder ) ?
@@ -1309,7 +1339,16 @@ namespace ts {
1309
1339
if ( status . type === UpToDateStatusType . UpstreamBlocked ) {
1310
1340
reportAndStoreErrors ( state , projectPath , config . errors ) ;
1311
1341
projectPendingBuild . delete ( projectPath ) ;
1312
- if ( options . verbose ) reportStatus ( state , Diagnostics . Skipping_build_of_project_0_because_its_dependency_1_has_errors , project , status . upstreamProjectName ) ;
1342
+ if ( options . verbose ) {
1343
+ reportStatus (
1344
+ state ,
1345
+ status . upstreamProjectBlocked ?
1346
+ Diagnostics . Skipping_build_of_project_0_because_its_dependency_1_was_not_built :
1347
+ Diagnostics . Skipping_build_of_project_0_because_its_dependency_1_has_errors ,
1348
+ project ,
1349
+ status . upstreamProjectName
1350
+ ) ;
1351
+ }
1313
1352
continue ;
1314
1353
}
1315
1354
@@ -1511,10 +1550,12 @@ namespace ts {
1511
1550
}
1512
1551
1513
1552
// An upstream project is blocked
1514
- if ( refStatus . type === UpToDateStatusType . Unbuildable ) {
1553
+ if ( refStatus . type === UpToDateStatusType . Unbuildable ||
1554
+ refStatus . type === UpToDateStatusType . UpstreamBlocked ) {
1515
1555
return {
1516
1556
type : UpToDateStatusType . UpstreamBlocked ,
1517
- upstreamProjectName : ref . path
1557
+ upstreamProjectName : ref . path ,
1558
+ upstreamProjectBlocked : refStatus . type === UpToDateStatusType . UpstreamBlocked
1518
1559
} ;
1519
1560
}
1520
1561
@@ -1763,17 +1804,24 @@ namespace ts {
1763
1804
reportErrorSummary ( state , buildOrder ) ;
1764
1805
startWatching ( state , buildOrder ) ;
1765
1806
1766
- return errorProjects ?
1767
- successfulProjects ?
1768
- ExitStatus . DiagnosticsPresent_OutputsGenerated :
1769
- ExitStatus . DiagnosticsPresent_OutputsSkipped :
1770
- ExitStatus . Success ;
1807
+ return isCircularBuildOrder ( buildOrder ) ?
1808
+ ExitStatus . ProjectReferenceCycle_OutputsSkupped :
1809
+ errorProjects ?
1810
+ successfulProjects ?
1811
+ ExitStatus . DiagnosticsPresent_OutputsGenerated :
1812
+ ExitStatus . DiagnosticsPresent_OutputsSkipped :
1813
+ ExitStatus . Success ;
1771
1814
}
1772
1815
1773
1816
function clean ( state : SolutionBuilderState , project ?: string , onlyReferences ?: boolean ) {
1774
1817
const buildOrder = getBuildOrderFor ( state , project , onlyReferences ) ;
1775
1818
if ( ! buildOrder ) return ExitStatus . InvalidProject_OutputsSkipped ;
1776
1819
1820
+ if ( isCircularBuildOrder ( buildOrder ) ) {
1821
+ reportErrors ( state , buildOrder . circularDiagnostics ) ;
1822
+ return ExitStatus . ProjectReferenceCycle_OutputsSkupped ;
1823
+ }
1824
+
1777
1825
const { options, host } = state ;
1778
1826
const filesToDelete = options . dry ? [ ] as string [ ] : undefined ;
1779
1827
for ( const proj of buildOrder ) {
@@ -1955,10 +2003,10 @@ namespace ts {
1955
2003
) ;
1956
2004
}
1957
2005
1958
- function startWatching ( state : SolutionBuilderState , buildOrder : readonly ResolvedConfigFileName [ ] ) {
2006
+ function startWatching ( state : SolutionBuilderState , buildOrder : AnyBuildOrder ) {
1959
2007
if ( ! state . watchAllProjectsPending ) return ;
1960
2008
state . watchAllProjectsPending = false ;
1961
- for ( const resolved of buildOrder ) {
2009
+ for ( const resolved of getBuildOrderFromAnyBuildOrder ( buildOrder ) ) {
1962
2010
const resolvedPath = toResolvedConfigFilePath ( state , resolved ) ;
1963
2011
// Watch this file
1964
2012
watchConfigFile ( state , resolved , resolvedPath ) ;
@@ -2032,24 +2080,33 @@ namespace ts {
2032
2080
reportAndStoreErrors ( state , proj , [ state . configFileCache . get ( proj ) as Diagnostic ] ) ;
2033
2081
}
2034
2082
2035
- function reportErrorSummary ( state : SolutionBuilderState , buildOrder : readonly ResolvedConfigFileName [ ] ) {
2036
- if ( ! state . needsSummary || ( ! state . watch && ! state . host . reportErrorSummary ) ) return ;
2083
+ function reportErrorSummary ( state : SolutionBuilderState , buildOrder : AnyBuildOrder ) {
2084
+ if ( ! state . needsSummary ) return ;
2037
2085
state . needsSummary = false ;
2086
+ const canReportSummary = state . watch || ! ! state . host . reportErrorSummary ;
2038
2087
const { diagnostics } = state ;
2039
- // Report errors from the other projects
2040
- buildOrder . forEach ( project => {
2041
- const projectPath = toResolvedConfigFilePath ( state , project ) ;
2042
- if ( ! state . projectErrorsReported . has ( projectPath ) ) {
2043
- reportErrors ( state , diagnostics . get ( projectPath ) || emptyArray ) ;
2044
- }
2045
- } ) ;
2046
2088
let totalErrors = 0 ;
2047
- diagnostics . forEach ( singleProjectErrors => totalErrors += getErrorCountForSummary ( singleProjectErrors ) ) ;
2089
+ if ( isCircularBuildOrder ( buildOrder ) ) {
2090
+ reportBuildQueue ( state , buildOrder . buildOrder ) ;
2091
+ reportErrors ( state , buildOrder . circularDiagnostics ) ;
2092
+ if ( canReportSummary ) totalErrors += getErrorCountForSummary ( buildOrder . circularDiagnostics ) ;
2093
+ }
2094
+ else {
2095
+ // Report errors from the other projects
2096
+ buildOrder . forEach ( project => {
2097
+ const projectPath = toResolvedConfigFilePath ( state , project ) ;
2098
+ if ( ! state . projectErrorsReported . has ( projectPath ) ) {
2099
+ reportErrors ( state , diagnostics . get ( projectPath ) || emptyArray ) ;
2100
+ }
2101
+ } ) ;
2102
+ if ( canReportSummary ) diagnostics . forEach ( singleProjectErrors => totalErrors += getErrorCountForSummary ( singleProjectErrors ) ) ;
2103
+ }
2104
+
2048
2105
if ( state . watch ) {
2049
2106
reportWatchStatus ( state , getWatchErrorSummaryDiagnosticMessage ( totalErrors ) , totalErrors ) ;
2050
2107
}
2051
- else {
2052
- state . host . reportErrorSummary ! ( totalErrors ) ;
2108
+ else if ( state . host . reportErrorSummary ) {
2109
+ state . host . reportErrorSummary ( totalErrors ) ;
2053
2110
}
2054
2111
}
2055
2112
@@ -2122,7 +2179,9 @@ namespace ts {
2122
2179
case UpToDateStatusType . UpstreamBlocked :
2123
2180
return reportStatus (
2124
2181
state ,
2125
- Diagnostics . Project_0_can_t_be_built_because_its_dependency_1_has_errors ,
2182
+ status . upstreamProjectBlocked ?
2183
+ Diagnostics . Project_0_can_t_be_built_because_its_dependency_1_was_not_built :
2184
+ Diagnostics . Project_0_can_t_be_built_because_its_dependency_1_has_errors ,
2126
2185
relName ( state , configFileName ) ,
2127
2186
relName ( state , status . upstreamProjectName )
2128
2187
) ;
0 commit comments