@@ -273,6 +273,26 @@ namespace ts {
273
273
export interface SolutionBuilderWithWatchHost < T extends BuilderProgram > extends SolutionBuilderHostBase < T > , WatchHost {
274
274
}
275
275
276
+ /*@internal */
277
+ export type BuildOrder = readonly ResolvedConfigFileName [ ] ;
278
+ /*@internal */
279
+ export interface CircularBuildOrder {
280
+ buildOrder : BuildOrder ;
281
+ circularDiagnostics : readonly Diagnostic [ ] ;
282
+ }
283
+ /*@internal */
284
+ export type AnyBuildOrder = BuildOrder | CircularBuildOrder ;
285
+
286
+ /*@internal */
287
+ export function isCircularBuildOrder ( buildOrder : AnyBuildOrder ) : buildOrder is CircularBuildOrder {
288
+ return ! ! buildOrder && ! ! ( buildOrder as CircularBuildOrder ) . buildOrder ;
289
+ }
290
+
291
+ /*@internal */
292
+ export function getBuildOrderFromAnyBuildOrder ( anyBuildOrder : AnyBuildOrder ) : BuildOrder {
293
+ return isCircularBuildOrder ( anyBuildOrder ) ? anyBuildOrder . buildOrder : anyBuildOrder ;
294
+ }
295
+
276
296
export interface SolutionBuilder < T extends BuilderProgram > {
277
297
build ( project ?: string , cancellationToken ?: CancellationToken ) : ExitStatus ;
278
298
clean ( project ?: string ) : ExitStatus ;
@@ -281,7 +301,7 @@ namespace ts {
281
301
getNextInvalidatedProject ( cancellationToken ?: CancellationToken ) : InvalidatedProject < T > | undefined ;
282
302
283
303
// Currently used for testing but can be made public if needed:
284
- /*@internal */ getBuildOrder ( ) : ReadonlyArray < ResolvedConfigFileName > ;
304
+ /*@internal */ getBuildOrder ( ) : AnyBuildOrder ;
285
305
286
306
// Testing only
287
307
/*@internal */ getUpToDateStatusOfProject ( project : string ) : UpToDateStatus ;
@@ -379,7 +399,7 @@ namespace ts {
379
399
readonly moduleResolutionCache : ModuleResolutionCache | undefined ;
380
400
381
401
// Mutable state
382
- buildOrder : readonly ResolvedConfigFileName [ ] | undefined ;
402
+ buildOrder : AnyBuildOrder | undefined ;
383
403
readFileWithCache : ( f : string ) => string | undefined ;
384
404
projectCompilerOptions : CompilerOptions ;
385
405
cache : SolutionBuilderStateCache | undefined ;
@@ -523,16 +543,19 @@ namespace ts {
523
543
return resolveConfigFileProjectName ( resolvePath ( state . currentDirectory , name ) ) ;
524
544
}
525
545
526
- function createBuildOrder ( state : SolutionBuilderState , roots : readonly ResolvedConfigFileName [ ] ) : readonly ResolvedConfigFileName [ ] {
546
+ function createBuildOrder ( state : SolutionBuilderState , roots : readonly ResolvedConfigFileName [ ] ) : AnyBuildOrder {
527
547
const temporaryMarks = createMap ( ) as ConfigFileMap < true > ;
528
548
const permanentMarks = createMap ( ) as ConfigFileMap < true > ;
529
549
const circularityReportStack : string [ ] = [ ] ;
530
550
let buildOrder : ResolvedConfigFileName [ ] | undefined ;
551
+ let circularDiagnostics : Diagnostic [ ] | undefined ;
531
552
for ( const root of roots ) {
532
553
visit ( root ) ;
533
554
}
534
555
535
- return buildOrder || emptyArray ;
556
+ return circularDiagnostics ?
557
+ { buildOrder : buildOrder || emptyArray , circularDiagnostics } :
558
+ buildOrder || emptyArray ;
536
559
537
560
function visit ( configFileName : ResolvedConfigFileName , inCircularContext ?: boolean ) {
538
561
const projPath = toResolvedConfigFilePath ( state , configFileName ) ;
@@ -541,8 +564,12 @@ namespace ts {
541
564
// Circular
542
565
if ( temporaryMarks . has ( projPath ) ) {
543
566
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" ) ) ;
567
+ ( circularDiagnostics || ( circularDiagnostics = [ ] ) ) . push (
568
+ createCompilerDiagnostic (
569
+ Diagnostics . Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0 ,
570
+ circularityReportStack . join ( "\r\n" )
571
+ )
572
+ ) ;
546
573
}
547
574
return ;
548
575
}
@@ -569,12 +596,11 @@ namespace ts {
569
596
570
597
function createStateBuildOrder ( state : SolutionBuilderState ) {
571
598
const buildOrder = createBuildOrder ( state , state . rootNames . map ( f => resolveProjectName ( state , f ) ) ) ;
572
- if ( arrayIsEqualTo ( state . buildOrder , buildOrder ) ) return state . buildOrder ! ;
573
599
574
600
// Clear all to ResolvedConfigFilePaths cache to start fresh
575
601
state . resolvedConfigFilePaths . clear ( ) ;
576
602
const currentProjects = arrayToSet (
577
- buildOrder ,
603
+ getBuildOrderFromAnyBuildOrder ( buildOrder ) ,
578
604
resolved => toResolvedConfigFilePath ( state , resolved )
579
605
) as ConfigFileMap < true > ;
580
606
@@ -611,9 +637,10 @@ namespace ts {
611
637
return state . buildOrder = buildOrder ;
612
638
}
613
639
614
- function getBuildOrderFor ( state : SolutionBuilderState , project : string | undefined , onlyReferences : boolean | undefined ) {
640
+ function getBuildOrderFor ( state : SolutionBuilderState , project : string | undefined , onlyReferences : boolean | undefined ) : AnyBuildOrder | undefined {
615
641
const resolvedProject = project && resolveProjectName ( state , project ) ;
616
642
const buildOrderFromState = getBuildOrder ( state ) ;
643
+ if ( isCircularBuildOrder ( buildOrderFromState ) ) return buildOrderFromState ;
617
644
if ( resolvedProject ) {
618
645
const projectPath = toResolvedConfigFilePath ( state , resolvedProject ) ;
619
646
const projectIndex = findIndex (
@@ -622,7 +649,8 @@ namespace ts {
622
649
) ;
623
650
if ( projectIndex === - 1 ) return undefined ;
624
651
}
625
- const buildOrder = resolvedProject ? createBuildOrder ( state , [ resolvedProject ] ) : buildOrderFromState ;
652
+ const buildOrder = resolvedProject ? createBuildOrder ( state , [ resolvedProject ] ) as BuildOrder : buildOrderFromState ;
653
+ Debug . assert ( ! isCircularBuildOrder ( buildOrder ) ) ;
626
654
Debug . assert ( ! onlyReferences || resolvedProject !== undefined ) ;
627
655
Debug . assert ( ! onlyReferences || buildOrder [ buildOrder . length - 1 ] === resolvedProject ) ;
628
656
return onlyReferences ? buildOrder . slice ( 0 , buildOrder . length - 1 ) : buildOrder ;
@@ -702,7 +730,7 @@ namespace ts {
702
730
state . allProjectBuildPending = false ;
703
731
if ( state . options . watch ) { reportWatchStatus ( state , Diagnostics . Starting_compilation_in_watch_mode ) ; }
704
732
enableCache ( state ) ;
705
- const buildOrder = getBuildOrder ( state ) ;
733
+ const buildOrder = getBuildOrderFromAnyBuildOrder ( getBuildOrder ( state ) ) ;
706
734
buildOrder . forEach ( configFileName =>
707
735
state . projectPendingBuild . set (
708
736
toResolvedConfigFilePath ( state , configFileName ) ,
@@ -1237,10 +1265,11 @@ namespace ts {
1237
1265
1238
1266
function getNextInvalidatedProject < T extends BuilderProgram > (
1239
1267
state : SolutionBuilderState < T > ,
1240
- buildOrder : readonly ResolvedConfigFileName [ ] ,
1268
+ buildOrder : AnyBuildOrder ,
1241
1269
reportQueue : boolean
1242
1270
) : InvalidatedProject < T > | undefined {
1243
1271
if ( ! state . projectPendingBuild . size ) return undefined ;
1272
+ if ( isCircularBuildOrder ( buildOrder ) ) return undefined ;
1244
1273
if ( state . currentInvalidatedProject ) {
1245
1274
// Only if same buildOrder the currentInvalidated project can be sent again
1246
1275
return arrayIsEqualTo ( state . currentInvalidatedProject . buildOrder , buildOrder ) ?
@@ -1763,17 +1792,24 @@ namespace ts {
1763
1792
reportErrorSummary ( state , buildOrder ) ;
1764
1793
startWatching ( state , buildOrder ) ;
1765
1794
1766
- return errorProjects ?
1767
- successfulProjects ?
1768
- ExitStatus . DiagnosticsPresent_OutputsGenerated :
1769
- ExitStatus . DiagnosticsPresent_OutputsSkipped :
1770
- ExitStatus . Success ;
1795
+ return isCircularBuildOrder ( buildOrder ) ?
1796
+ ExitStatus . ProjectReferenceCycle_OutputsSkupped :
1797
+ errorProjects ?
1798
+ successfulProjects ?
1799
+ ExitStatus . DiagnosticsPresent_OutputsGenerated :
1800
+ ExitStatus . DiagnosticsPresent_OutputsSkipped :
1801
+ ExitStatus . Success ;
1771
1802
}
1772
1803
1773
1804
function clean ( state : SolutionBuilderState , project ?: string , onlyReferences ?: boolean ) {
1774
1805
const buildOrder = getBuildOrderFor ( state , project , onlyReferences ) ;
1775
1806
if ( ! buildOrder ) return ExitStatus . InvalidProject_OutputsSkipped ;
1776
1807
1808
+ if ( isCircularBuildOrder ( buildOrder ) ) {
1809
+ reportErrors ( state , buildOrder . circularDiagnostics ) ;
1810
+ return ExitStatus . ProjectReferenceCycle_OutputsSkupped ;
1811
+ }
1812
+
1777
1813
const { options, host } = state ;
1778
1814
const filesToDelete = options . dry ? [ ] as string [ ] : undefined ;
1779
1815
for ( const proj of buildOrder ) {
@@ -1955,10 +1991,10 @@ namespace ts {
1955
1991
) ;
1956
1992
}
1957
1993
1958
- function startWatching ( state : SolutionBuilderState , buildOrder : readonly ResolvedConfigFileName [ ] ) {
1994
+ function startWatching ( state : SolutionBuilderState , buildOrder : AnyBuildOrder ) {
1959
1995
if ( ! state . watchAllProjectsPending ) return ;
1960
1996
state . watchAllProjectsPending = false ;
1961
- for ( const resolved of buildOrder ) {
1997
+ for ( const resolved of getBuildOrderFromAnyBuildOrder ( buildOrder ) ) {
1962
1998
const resolvedPath = toResolvedConfigFilePath ( state , resolved ) ;
1963
1999
// Watch this file
1964
2000
watchConfigFile ( state , resolved , resolvedPath ) ;
@@ -2032,24 +2068,33 @@ namespace ts {
2032
2068
reportAndStoreErrors ( state , proj , [ state . configFileCache . get ( proj ) as Diagnostic ] ) ;
2033
2069
}
2034
2070
2035
- function reportErrorSummary ( state : SolutionBuilderState , buildOrder : readonly ResolvedConfigFileName [ ] ) {
2036
- if ( ! state . needsSummary || ( ! state . watch && ! state . host . reportErrorSummary ) ) return ;
2071
+ function reportErrorSummary ( state : SolutionBuilderState , buildOrder : AnyBuildOrder ) {
2072
+ if ( ! state . needsSummary ) return ;
2037
2073
state . needsSummary = false ;
2074
+ const canReportSummary = state . watch || ! ! state . host . reportErrorSummary ;
2038
2075
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
2076
let totalErrors = 0 ;
2047
- diagnostics . forEach ( singleProjectErrors => totalErrors += getErrorCountForSummary ( singleProjectErrors ) ) ;
2077
+ if ( isCircularBuildOrder ( buildOrder ) ) {
2078
+ reportBuildQueue ( state , buildOrder . buildOrder ) ;
2079
+ reportErrors ( state , buildOrder . circularDiagnostics ) ;
2080
+ if ( canReportSummary ) totalErrors += getErrorCountForSummary ( buildOrder . circularDiagnostics ) ;
2081
+ }
2082
+ else {
2083
+ // Report errors from the other projects
2084
+ buildOrder . forEach ( project => {
2085
+ const projectPath = toResolvedConfigFilePath ( state , project ) ;
2086
+ if ( ! state . projectErrorsReported . has ( projectPath ) ) {
2087
+ reportErrors ( state , diagnostics . get ( projectPath ) || emptyArray ) ;
2088
+ }
2089
+ } ) ;
2090
+ if ( canReportSummary ) diagnostics . forEach ( singleProjectErrors => totalErrors += getErrorCountForSummary ( singleProjectErrors ) ) ;
2091
+ }
2092
+
2048
2093
if ( state . watch ) {
2049
2094
reportWatchStatus ( state , getWatchErrorSummaryDiagnosticMessage ( totalErrors ) , totalErrors ) ;
2050
2095
}
2051
- else {
2052
- state . host . reportErrorSummary ! ( totalErrors ) ;
2096
+ else if ( state . host . reportErrorSummary ) {
2097
+ state . host . reportErrorSummary ( totalErrors ) ;
2053
2098
}
2054
2099
}
2055
2100
0 commit comments