@@ -19,6 +19,12 @@ namespace ts {
1919 projectStatus : FileMap < UpToDateStatus > ;
2020 }
2121
22+ type Mapper = ReturnType < typeof createDependencyMapper > ;
23+ interface DependencyGraph {
24+ buildQueue : string [ ] [ ] ;
25+ dependencyMap : Mapper ;
26+ }
27+
2228 enum BuildResultFlags {
2329 None = 0 ,
2430
@@ -189,6 +195,47 @@ namespace ts {
189195 }
190196 }
191197
198+ export function createDependencyMapper ( ) {
199+ const childToParents : { [ key : string ] : string [ ] } = { } ;
200+ const parentToChildren : { [ key : string ] : string [ ] } = { } ;
201+ const allKeys : string [ ] = [ ] ;
202+
203+ function addReference ( childConfigFileName : string , parentConfigFileName : string ) : void {
204+ addEntry ( childToParents , childConfigFileName , parentConfigFileName ) ;
205+ addEntry ( parentToChildren , parentConfigFileName , childConfigFileName ) ;
206+ }
207+
208+ function getReferencesTo ( parentConfigFileName : string ) : string [ ] {
209+ return parentToChildren [ normalizePath ( parentConfigFileName ) ] || [ ] ;
210+ }
211+
212+ function getReferencesOf ( childConfigFileName : string ) : string [ ] {
213+ return childToParents [ normalizePath ( childConfigFileName ) ] || [ ] ;
214+ }
215+
216+ function getKeys ( ) : ReadonlyArray < string > {
217+ return allKeys ;
218+ }
219+
220+ function addEntry ( mapToAddTo : typeof childToParents | typeof parentToChildren , key : string , element : string ) {
221+ key = normalizePath ( key ) ;
222+ element = normalizePath ( element ) ;
223+ const arr = ( mapToAddTo [ key ] = mapToAddTo [ key ] || [ ] ) ;
224+ if ( arr . indexOf ( element ) < 0 ) {
225+ arr . push ( element ) ;
226+ }
227+ if ( allKeys . indexOf ( key ) < 0 ) allKeys . push ( key ) ;
228+ if ( allKeys . indexOf ( element ) < 0 ) allKeys . push ( element ) ;
229+ }
230+
231+ return {
232+ addReference,
233+ getReferencesTo,
234+ getReferencesOf,
235+ getKeys
236+ } ;
237+ }
238+
192239 function getOutputDeclarationFileName ( inputFileName : string , configFile : ParsedCommandLine ) {
193240 const relativePath = getRelativePathFromDirectory ( rootDirOfOptions ( configFile . options , configFile . options . configFilePath ) , inputFileName , /*ignoreCase*/ true ) ;
194241 const outputPath = resolvePath ( configFile . options . declarationDir || configFile . options . outDir || getDirectoryPath ( configFile . options . configFilePath ) , relativePath ) ;
@@ -232,7 +279,7 @@ namespace ts {
232279 }
233280
234281 function rootDirOfOptions ( opts : CompilerOptions , configFileName : string ) {
235- return opts . rootDir || path . dirname ( configFileName ) ;
282+ return opts . rootDir || getDirectoryPath ( configFileName ) ;
236283 }
237284
238285 function createConfigFileCache ( host : CompilerHost ) {
@@ -261,7 +308,12 @@ namespace ts {
261308 return date2 < date1 ? date2 : date1 ;
262309 }
263310
311+ function isDeclarationFile ( fileName : string ) {
312+ return fileExtensionIs ( fileName , ".d.ts" ) ;
313+ }
314+
264315 function createSolutionBuilder ( host : CompilerHost ) {
316+ const diagReporter = createDiagnosticReporter ( sys , /*pretty*/ true ) ;
265317 const configFileCache = createConfigFileCache ( host ) ;
266318
267319 function getUpToDateStatus ( project : ParsedCommandLine , context : BuildContext ) : UpToDateStatus {
@@ -394,8 +446,163 @@ namespace ts {
394446 } ;
395447 }
396448
449+ function createDependencyGraph ( roots : string [ ] ) : DependencyGraph {
450+ // This is a list of list of projects that need to be built.
451+ // The ordering here is "backwards", i.e. the first entry in the array is the last set of projects that need to be built;
452+ // and the last entry is the first set of projects to be built.
453+ // Each subarray is effectively unordered.
454+ // We traverse the reference graph from each root, then "clean" the list by removing
455+ // any entry that is duplicated to its right.
456+ const buildQueue : string [ ] [ ] = [ ] ;
457+ const dependencyMap = createDependencyMapper ( ) ;
458+ let buildQueuePosition = 0 ;
459+ for ( const root of roots ) {
460+ const config = configFileCache . parseConfigFile ( root ) ;
461+ if ( config === undefined ) {
462+ throw new Error ( `Could not parse ${ root } ` ) ;
463+ }
464+ enumerateReferences ( normalizePath ( root ) , config ) ;
465+ }
466+ removeDuplicatesFromBuildQueue ( buildQueue ) ;
467+
468+ return {
469+ buildQueue,
470+ dependencyMap
471+ } ;
472+
473+ function enumerateReferences ( fileName : string , root : ts . ParsedCommandLine ) : void {
474+ const myBuildLevel = buildQueue [ buildQueuePosition ] = buildQueue [ buildQueuePosition ] || [ ] ;
475+ if ( myBuildLevel . indexOf ( fileName ) < 0 ) {
476+ myBuildLevel . push ( fileName ) ;
477+ }
478+
479+ const refs = root . projectReferences ;
480+ if ( refs === undefined ) return ;
481+ buildQueuePosition ++ ;
482+ for ( const ref of refs ) {
483+ dependencyMap . addReference ( fileName , ref . path ) ;
484+ const resolvedRef = configFileCache . parseConfigFile ( ref . path ) ;
485+ if ( resolvedRef === undefined ) continue ;
486+ enumerateReferences ( normalizePath ( ref . path ) , resolvedRef ) ;
487+ }
488+ buildQueuePosition -- ;
489+ }
490+
491+ /**
492+ * Removes entries from arrays which appear in later arrays.
493+ * TODO: Use a lookup object to optimize this a bit?
494+ */
495+ function removeDuplicatesFromBuildQueue ( queue : string [ ] [ ] ) : void {
496+ // No need to check the last array
497+ for ( let i = 0 ; i < queue . length - 1 ; i ++ ) {
498+ queue [ i ] = queue [ i ] . filter ( fn => ! occursAfter ( fn , i + 1 ) ) ;
499+ }
500+
501+ function occursAfter ( s : string , start : number ) {
502+ for ( let i = start ; i < queue . length ; i ++ ) {
503+ if ( queue [ i ] . indexOf ( s ) >= 0 ) return true ;
504+ }
505+ return false ;
506+ }
507+ }
508+ }
509+
510+ function buildSingleProject ( proj : string , context : BuildContext ) {
511+ let resultFlags = BuildResultFlags . None ;
512+ resultFlags |= BuildResultFlags . DeclarationOutputUnchanged ;
513+
514+ const configFile = configFileCache . parseConfigFile ( proj ) ;
515+ if ( ! configFile ) {
516+ // Failed to read the config file
517+ resultFlags |= BuildResultFlags . ConfigFileErrors ;
518+ return resultFlags ;
519+ }
520+
521+ if ( configFile . fileNames . length === 0 ) {
522+ // Nothing to build - must be a solution file, basically
523+ return BuildResultFlags . None ;
524+ }
525+
526+ const programOptions : CreateProgramOptions = {
527+ projectReferences : configFile . projectReferences ,
528+ host : host ,
529+ rootNames : configFile . fileNames ,
530+ options : configFile . options
531+ } ;
532+ const program = ts . createProgram ( programOptions ) ;
533+
534+ // Don't emit anything in the presence of syntactic errors or options diagnostics
535+ const syntaxDiagnostics = [ ...program . getOptionsDiagnostics ( ) , ...program . getSyntacticDiagnostics ( ) ] ;
536+ if ( syntaxDiagnostics . length ) {
537+ resultFlags |= BuildResultFlags . SyntaxErrors ;
538+ for ( const diag of syntaxDiagnostics ) {
539+ diagReporter ( diag ) ;
540+ }
541+ return resultFlags ;
542+ }
543+
544+ // Don't emit .d.ts if there are decl file errors
545+ if ( program . getCompilerOptions ( ) . declaration ) {
546+ const declDiagnostics = program . getDeclarationDiagnostics ( ) ;
547+ if ( declDiagnostics . length ) {
548+ resultFlags |= BuildResultFlags . DeclarationEmitErrors ;
549+ for ( const diag of declDiagnostics ) {
550+ diagReporter ( diag ) ;
551+ }
552+ }
553+ return resultFlags ;
554+ }
555+
556+ const semanticDiagnostics = [ ...program . getSemanticDiagnostics ( ) ] ;
557+ if ( semanticDiagnostics . length ) {
558+ resultFlags |= BuildResultFlags . TypeErrors ;
559+ for ( const diag of semanticDiagnostics ) {
560+ diagReporter ( diag ) ;
561+ }
562+ return resultFlags ;
563+ }
564+
565+ program . emit ( undefined , ( fileName , content , writeBom , onError ) => {
566+ let priorChangeTime : Date | undefined ;
567+
568+ if ( isDeclarationFile ( fileName ) && host . fileExists ( fileName ) ) {
569+ if ( host . readFile ( fileName ) === content ) {
570+ resultFlags &= ~ BuildResultFlags . DeclarationOutputUnchanged ;
571+ priorChangeTime = host . getLastWriteTime && host . getLastWriteTime ( fileName ) ;
572+ }
573+ }
574+
575+ host . writeFile ( fileName , content , writeBom , onError , emptyArray ) ;
576+ if ( priorChangeTime !== undefined ) {
577+ context . unchangedOutputs . setValue ( fileName , priorChangeTime ) ;
578+ }
579+ } ) ;
580+
581+ return resultFlags ;
582+ }
583+
584+ function buildProjects ( configFileNames : string [ ] , context : BuildContext ) {
585+ // Establish what needs to be built
586+ const graph = createDependencyGraph ( configFileNames ) ;
587+
588+ const queue = graph . buildQueue ;
589+ while ( queue . length > 0 ) {
590+ const next = queue [ 0 ] . pop ( ) ! ;
591+
592+ const result = buildSingleProject ( next , context ) ;
593+ if ( result & BuildResultFlags . AnyErrors ) {
594+ break ;
595+ }
596+
597+ if ( queue [ 0 ] . length === 0 ) {
598+ queue . pop ( ) ;
599+ }
600+ }
601+ }
602+
397603 return {
398- getUpToDateStatus
604+ getUpToDateStatus,
605+ buildProjects
399606 } ;
400607 }
401608}
0 commit comments