@@ -34,9 +34,11 @@ namespace ts {
3434 * Map from config file name to up-to-date status
3535 */
3636 projectStatus : FileMap < UpToDateStatus > ;
37+ diagnostics ?: FileMap < number > ; // TODO(shkamat): this should be really be diagnostics but thats for later time
3738
38- invalidatedProjects : FileMap < true > ;
39- queuedProjects : FileMap < true > ;
39+ invalidateProject ( project : ResolvedConfigFileName , dependencyGraph : DependencyGraph | undefined ) : void ;
40+ getNextInvalidatedProject ( ) : ResolvedConfigFileName | undefined ;
41+ pendingInvalidatedProjects ( ) : boolean ;
4042 missingRoots : Map < true > ;
4143 }
4244
@@ -193,6 +195,7 @@ namespace ts {
193195 hasKey ( fileName : string ) : boolean ;
194196 removeKey ( fileName : string ) : void ;
195197 getKeys ( ) : string [ ] ;
198+ getSize ( ) : number ;
196199 }
197200
198201 /**
@@ -208,7 +211,8 @@ namespace ts {
208211 getValueOrUndefined,
209212 removeKey,
210213 getKeys,
211- hasKey
214+ hasKey,
215+ getSize
212216 } ;
213217
214218 function getKeys ( ) : string [ ] {
@@ -241,6 +245,10 @@ namespace ts {
241245 const f = normalizePath ( fileName ) ;
242246 return lookup . get ( f ) ;
243247 }
248+
249+ function getSize ( ) {
250+ return lookup . size ;
251+ }
244252 }
245253
246254 function createDependencyMapper ( ) {
@@ -374,18 +382,64 @@ namespace ts {
374382 }
375383
376384 export function createBuildContext ( options : BuildOptions ) : BuildContext {
377- const invalidatedProjects = createFileMap < true > ( ) ;
378- const queuedProjects = createFileMap < true > ( ) ;
385+ const invalidatedProjectQueue = [ ] as ResolvedConfigFileName [ ] ;
386+ let nextIndex = 0 ;
387+ const projectPendingBuild = createFileMap < true > ( ) ;
379388 const missingRoots = createMap < true > ( ) ;
389+ const diagnostics = options . watch ? createFileMap < number > ( ) : undefined ;
380390
381391 return {
382392 options,
383393 projectStatus : createFileMap ( ) ,
394+ diagnostics,
384395 unchangedOutputs : createFileMap ( ) ,
385- invalidatedProjects,
386- missingRoots,
387- queuedProjects
396+ invalidateProject,
397+ getNextInvalidatedProject,
398+ pendingInvalidatedProjects,
399+ missingRoots
388400 } ;
401+
402+ function invalidateProject ( proj : ResolvedConfigFileName , dependancyGraph : DependencyGraph | undefined ) {
403+ if ( ! projectPendingBuild . hasKey ( proj ) ) {
404+ addProjToQueue ( proj ) ;
405+ if ( dependancyGraph ) {
406+ queueBuildForDownstreamReferences ( proj , dependancyGraph ) ;
407+ }
408+ }
409+ }
410+
411+ function addProjToQueue ( proj : ResolvedConfigFileName ) {
412+ projectPendingBuild . setValue ( proj , true ) ;
413+ invalidatedProjectQueue . push ( proj ) ;
414+ }
415+
416+ function getNextInvalidatedProject ( ) {
417+ if ( nextIndex < invalidatedProjectQueue . length ) {
418+ const proj = invalidatedProjectQueue [ nextIndex ] ;
419+ nextIndex ++ ;
420+ projectPendingBuild . removeKey ( proj ) ;
421+ if ( ! projectPendingBuild . getSize ( ) ) {
422+ invalidatedProjectQueue . length = 0 ;
423+ }
424+ return proj ;
425+ }
426+ }
427+
428+ function pendingInvalidatedProjects ( ) {
429+ return ! ! projectPendingBuild . getSize ( ) ;
430+ }
431+
432+ // Mark all downstream projects of this one needing to be built "later"
433+ function queueBuildForDownstreamReferences ( root : ResolvedConfigFileName , dependancyGraph : DependencyGraph ) {
434+ const deps = dependancyGraph . dependencyMap . getReferencesTo ( root ) ;
435+ for ( const ref of deps ) {
436+ // Can skip circular references
437+ if ( ! projectPendingBuild . hasKey ( ref ) ) {
438+ addProjToQueue ( ref ) ;
439+ queueBuildForDownstreamReferences ( ref , dependancyGraph ) ;
440+ }
441+ }
442+ }
389443 }
390444
391445 export interface SolutionBuilderHost extends CompilerHost {
@@ -442,6 +496,7 @@ namespace ts {
442496 const hostWithWatch = host as SolutionBuilderWithWatchHost ;
443497 const configFileCache = createConfigFileCache ( host ) ;
444498 let context = createBuildContext ( defaultOptions ) ;
499+ let timerToBuildInvalidatedProject : any ;
445500
446501 const existingWatchersForWildcards = createMap < WildcardDirectoryWatcher > ( ) ;
447502 return {
@@ -453,8 +508,7 @@ namespace ts {
453508 getBuildGraph,
454509
455510 invalidateProject,
456- buildInvalidatedProjects,
457- buildDependentInvalidatedProjects,
511+ buildInvalidatedProject,
458512
459513 resolveProjectName,
460514
@@ -465,7 +519,19 @@ namespace ts {
465519 host . reportSolutionBuilderStatus ( createCompilerDiagnostic ( message , ...args ) ) ;
466520 }
467521
468- function reportWatchStatus ( message : DiagnosticMessage , ...args : string [ ] ) {
522+ function storeErrors ( proj : ResolvedConfigFileName , diagnostics : ReadonlyArray < Diagnostic > ) {
523+ if ( context . options . watch ) {
524+ storeErrorSummary ( proj , diagnostics . filter ( diagnostic => diagnostic . category === DiagnosticCategory . Error ) . length ) ;
525+ }
526+ }
527+
528+ function storeErrorSummary ( proj : ResolvedConfigFileName , errorCount : number ) {
529+ if ( context . options . watch ) {
530+ context . diagnostics ! . setValue ( proj , errorCount ) ;
531+ }
532+ }
533+
534+ function reportWatchStatus ( message : DiagnosticMessage , ...args : ( string | number | undefined ) [ ] ) {
469535 if ( hostWithWatch . onWatchStatusChange ) {
470536 hostWithWatch . onWatchStatusChange ( createCompilerDiagnostic ( message , ...args ) , host . getNewLine ( ) , { preserveWatchOutput : context . options . preserveWatchOutput } ) ;
471537 }
@@ -505,15 +571,12 @@ namespace ts {
505571 }
506572 }
507573
508- function invalidateProjectAndScheduleBuilds ( resolved : ResolvedConfigFileName ) {
509- reportWatchStatus ( Diagnostics . File_change_detected_Starting_incremental_compilation ) ;
510- invalidateProject ( resolved ) ;
511- if ( ! hostWithWatch . setTimeout ) {
512- return ;
513- }
514- hostWithWatch . setTimeout ( buildInvalidatedProjects , 100 ) ;
515- hostWithWatch . setTimeout ( buildDependentInvalidatedProjects , 3000 ) ;
516- }
574+ }
575+
576+ function invalidateProjectAndScheduleBuilds ( resolved : ResolvedConfigFileName ) {
577+ reportWatchStatus ( Diagnostics . File_change_detected_Starting_incremental_compilation ) ;
578+ invalidateProject ( resolved ) ;
579+ scheduleBuildInvalidatedProject ( ) ;
517580 }
518581
519582 function resetBuildContext ( opts = defaultOptions ) {
@@ -724,33 +787,44 @@ namespace ts {
724787 }
725788
726789 configFileCache . removeKey ( resolved ) ;
727- context . invalidatedProjects . setValue ( resolved , true ) ;
728790 context . projectStatus . removeKey ( resolved ) ;
729-
730- const graph = getGlobalDependencyGraph ( ) ! ;
731- if ( graph ) {
732- queueBuildForDownstreamReferences ( resolved ) ;
791+ if ( context . options . watch ) {
792+ context . diagnostics ! . removeKey ( resolved ) ;
733793 }
734794
735- // Mark all downstream projects of this one needing to be built "later"
736- function queueBuildForDownstreamReferences ( root : ResolvedConfigFileName ) {
737- const deps = graph . dependencyMap . getReferencesTo ( root ) ;
738- for ( const ref of deps ) {
739- // Can skip circular references
740- if ( ! context . queuedProjects . hasKey ( ref ) ) {
741- context . queuedProjects . setValue ( ref , true ) ;
742- queueBuildForDownstreamReferences ( ref ) ;
743- }
744- }
795+ context . invalidateProject ( resolved , getGlobalDependencyGraph ( ) ) ;
796+ }
797+
798+ function scheduleBuildInvalidatedProject ( ) {
799+ if ( ! hostWithWatch . setTimeout || ! hostWithWatch . clearTimeout ) {
800+ return ;
745801 }
802+ if ( timerToBuildInvalidatedProject ) {
803+ hostWithWatch . clearTimeout ( timerToBuildInvalidatedProject ) ;
804+ }
805+ timerToBuildInvalidatedProject = hostWithWatch . setTimeout ( buildInvalidatedProject , 250 ) ;
746806 }
747807
748- function buildInvalidatedProjects ( ) {
749- buildSomeProjects ( p => context . invalidatedProjects . hasKey ( p ) ) ;
808+ function buildInvalidatedProject ( ) {
809+ timerToBuildInvalidatedProject = undefined ;
810+ const buildProject = context . getNextInvalidatedProject ( ) ;
811+ buildSomeProjects ( p => p === buildProject ) ;
812+ if ( context . pendingInvalidatedProjects ( ) ) {
813+ if ( ! timerToBuildInvalidatedProject ) {
814+ scheduleBuildInvalidatedProject ( ) ;
815+ }
816+ }
817+ else {
818+ reportErrorSummary ( ) ;
819+ }
750820 }
751821
752- function buildDependentInvalidatedProjects ( ) {
753- buildSomeProjects ( p => context . queuedProjects . hasKey ( p ) ) ;
822+ function reportErrorSummary ( ) {
823+ if ( context . options . watch ) {
824+ let errorCount = 0 ;
825+ context . diagnostics ! . getKeys ( ) . forEach ( resolved => errorCount += context . diagnostics ! . getValue ( resolved ) ) ;
826+ reportWatchStatus ( errorCount === 1 ? Diagnostics . Found_1_error_Watching_for_file_changes : Diagnostics . Found_0_errors_Watching_for_file_changes , errorCount ) ;
827+ }
754828 }
755829
756830 function buildSomeProjects ( predicate : ( projName : ResolvedConfigFileName ) => boolean ) {
@@ -807,6 +881,7 @@ namespace ts {
807881 if ( temporaryMarks [ projPath ] ) {
808882 if ( ! inCircularContext ) {
809883 hadError = true ;
884+ // TODO(shkamat): Account for this error
810885 reportStatus ( Diagnostics . Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0 , circularityReportStack . join ( "\r\n" ) ) ;
811886 return ;
812887 }
@@ -852,6 +927,7 @@ namespace ts {
852927 if ( ! configFile ) {
853928 // Failed to read the config file
854929 resultFlags |= BuildResultFlags . ConfigFileErrors ;
930+ storeErrorSummary ( proj , 1 ) ;
855931 context . projectStatus . setValue ( proj , { type : UpToDateStatusType . Unbuildable , reason : "Config file errors" } ) ;
856932 return resultFlags ;
857933 }
@@ -879,6 +955,7 @@ namespace ts {
879955 for ( const diag of syntaxDiagnostics ) {
880956 host . reportDiagnostic ( diag ) ;
881957 }
958+ storeErrors ( proj , syntaxDiagnostics ) ;
882959 context . projectStatus . setValue ( proj , { type : UpToDateStatusType . Unbuildable , reason : "Syntactic errors" } ) ;
883960 return resultFlags ;
884961 }
@@ -891,6 +968,7 @@ namespace ts {
891968 for ( const diag of declDiagnostics ) {
892969 host . reportDiagnostic ( diag ) ;
893970 }
971+ storeErrors ( proj , declDiagnostics ) ;
894972 context . projectStatus . setValue ( proj , { type : UpToDateStatusType . Unbuildable , reason : "Declaration file errors" } ) ;
895973 return resultFlags ;
896974 }
@@ -903,6 +981,7 @@ namespace ts {
903981 for ( const diag of semanticDiagnostics ) {
904982 host . reportDiagnostic ( diag ) ;
905983 }
984+ storeErrors ( proj , semanticDiagnostics ) ;
906985 context . projectStatus . setValue ( proj , { type : UpToDateStatusType . Unbuildable , reason : "Semantic errors" } ) ;
907986 return resultFlags ;
908987 }
@@ -1028,6 +1107,7 @@ namespace ts {
10281107 if ( host . fileExists ( fullPathWithTsconfig ) ) {
10291108 return fullPathWithTsconfig as ResolvedConfigFileName ;
10301109 }
1110+ // TODO(shkamat): right now this is accounted as 1 error in config file, but we need to do better
10311111 host . reportDiagnostic ( createCompilerDiagnostic ( Diagnostics . File_0_not_found , relName ( fullPath ) ) ) ;
10321112 return undefined ;
10331113 }
@@ -1047,7 +1127,10 @@ namespace ts {
10471127 function buildAllProjects ( ) : ExitStatus {
10481128 if ( context . options . watch ) { reportWatchStatus ( Diagnostics . Starting_compilation_in_watch_mode ) ; }
10491129 const graph = getGlobalDependencyGraph ( ) ;
1050- if ( graph === undefined ) return ExitStatus . DiagnosticsPresent_OutputsSkipped ;
1130+ if ( graph === undefined ) {
1131+ reportErrorSummary ( ) ;
1132+ return ExitStatus . DiagnosticsPresent_OutputsSkipped ;
1133+ }
10511134
10521135 const queue = graph . buildQueue ;
10531136 reportBuildQueue ( graph ) ;
@@ -1091,6 +1174,7 @@ namespace ts {
10911174 const buildResult = buildSingleProject ( next ) ;
10921175 anyFailed = anyFailed || ! ! ( buildResult & BuildResultFlags . AnyErrors ) ;
10931176 }
1177+ reportErrorSummary ( ) ;
10941178 return anyFailed ? ExitStatus . DiagnosticsPresent_OutputsSkipped : ExitStatus . Success ;
10951179 }
10961180
0 commit comments