11namespace ts {
2- /*
2+ const MinimumDate = new Date ( - 8640000000000000 ) ;
3+ const MaximumDate = new Date ( 8640000000000000 ) ;
4+
5+ /**
6+ * A BuildContext tracks what's going on during the course of a build.
7+ * The primary thing we track here is which files were written to,
8+ * but unchanged, because this enables fast downstream updates
9+ */
310 interface BuildContext {
4- unchangedOutputs: FileMap<number>;
11+ /**
12+ * Map from output file name to its pre-build timestamp
13+ */
14+ unchangedOutputs : FileMap < Date > ;
15+
16+ /**
17+ * Map from config file name to up-to-date status
18+ */
19+ projectStatus : FileMap < UpToDateStatus > ;
20+ }
21+
22+ enum BuildResultFlags {
23+ None = 0 ,
24+
25+ /**
26+ * No errors of any kind occurred during build
27+ */
28+ Success = 1 << 0 ,
29+ /**
30+ * None of the .d.ts files emitted by this build were
31+ * different from the existing files on disk
32+ */
33+ DeclarationOutputUnchanged = 1 << 1 ,
34+
35+ ConfigFileErrors = 1 << 2 ,
36+ SyntaxErrors = 1 << 3 ,
37+ TypeErrors = 1 << 4 ,
38+ DeclarationEmitErrors = 1 << 5 ,
39+
40+ AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors
41+ }
42+
43+ enum UpToDateStatusType {
44+ Unbuildable ,
45+ UpToDate ,
46+ /**
47+ * The project appears out of date because its upstream inputs are newer than its outputs,
48+ * but all of its outputs are actually newer than the previous identical outputs of its inputs.
49+ * This means we can Pseudo-build (just touch timestamps), as if we had actually built this project.
50+ */
51+ UpToDateWithUpstreamTypes ,
52+ OutputMissing ,
53+ OutOfDateWithSelf ,
54+ OutOfDateWithUpstream ,
55+ UpstreamOutOfDate
56+ }
57+
58+ type UpToDateStatus =
59+ | StatusUnbuildable
60+ | StatusUpToDate
61+ | StatusOutputMissing
62+ | StatusOutOfDateWithSelf
63+ | StatusOutOfDateWithUpstream
64+ | StatusUpstreamOutOfDate ;
65+
66+ /**
67+ * The project can't be built at all in its current state. For example,
68+ * its config file cannot be parsed, or it has a syntax error or missing file
69+ */
70+ interface StatusUnbuildable {
71+ type : UpToDateStatusType . Unbuildable ;
72+ reason : string ;
73+ }
74+
75+ /**
76+ * The project is up to date with respect to its inputs.
77+ * We track what the newest input file is.
78+ */
79+ interface StatusUpToDate {
80+ type : UpToDateStatusType . UpToDate | UpToDateStatusType . UpToDateWithUpstreamTypes ;
81+ newestInputFileTime : Date ;
82+ newestDeclarationFileContentChangedTime : Date ;
83+ newestOutputFileTime : Date ;
84+ }
85+
86+ /**
87+ * One or more of the outputs of the project does not exist.
88+ */
89+ interface StatusOutputMissing {
90+ type : UpToDateStatusType . OutputMissing ;
91+ /**
92+ * The name of the first output file that didn't exist
93+ */
94+ missingOutputFileName : string ;
95+ }
96+
97+ /**
98+ * One or more of the project's outputs is older than its newest input.
99+ */
100+ interface StatusOutOfDateWithSelf {
101+ type : UpToDateStatusType . OutOfDateWithSelf ;
102+ outOfDateOutputFileName : string ;
103+ newerInputFileName : string ;
5104 }
6105
106+ /**
107+ * This project depends on an out-of-date project, so shouldn't be built yet
108+ */
109+ interface StatusUpstreamOutOfDate {
110+ type : UpToDateStatusType . UpstreamOutOfDate ;
111+ upstreamProjectName : string ;
112+ }
7113
114+ /**
115+ * One or more of the project's outputs is older than the newest output of
116+ * an upstream project.
117+ */
118+ interface StatusOutOfDateWithUpstream {
119+ type : UpToDateStatusType . OutOfDateWithUpstream ;
120+ outOfDateOutputFileName : string ;
121+ newerProjectName : string ;
122+ }
8123
9124 interface FileMap < T > {
10125 setValue ( fileName : string , value : T ) : void ;
@@ -14,6 +129,9 @@ namespace ts {
14129 tryGetValue ( fileName : string ) : [ false , undefined ] | [ true , T ] ;
15130 }
16131
132+ /**
133+ * A FileMap maintains a normalized-key to value relationship
134+ */
17135 function createFileMap < T > ( ) : FileMap < T > {
18136 const lookup : { [ key : string ] : T } = Object . create ( null ) ;
19137
@@ -65,5 +183,204 @@ namespace ts {
65183 }
66184 }
67185 }
68- */
186+
187+ function getOutputDeclarationFileName ( inputFileName : string , configFile : ts . ParsedCommandLine ) {
188+ const relativePath = getRelativePathFromDirectory ( rootDirOfOptions ( configFile . options , configFile . options . configFilePath ) , inputFileName , true ) ;
189+ const outputPath = resolvePath ( configFile . options . declarationDir || configFile . options . outDir || getDirectoryPath ( configFile . options . configFilePath ) , relativePath ) ;
190+ return changeExtension ( outputPath , ".d.ts" ) ;
191+ }
192+
193+ function getOutputJavaScriptFileName ( inputFileName : string , configFile : ts . ParsedCommandLine ) {
194+ // TODO handle JSX: Preserve
195+ const relativePath = getRelativePathFromDirectory ( rootDirOfOptions ( configFile . options , configFile . options . configFilePath ) , inputFileName , true ) ;
196+ const outputPath = resolvePath ( configFile . options . outDir || getDirectoryPath ( configFile . options . configFilePath ) , relativePath ) ;
197+ return changeExtension ( outputPath , ( fileExtensionIs ( inputFileName , ".tsx" ) && configFile . options . jsx === JsxEmit . Preserve ) ? ".jsx" : ".js" ) ;
198+ }
199+
200+ function getOutputFileNames ( inputFileName : string , configFile : ts . ParsedCommandLine ) : ReadonlyArray < string > {
201+ if ( configFile . options . outFile ) {
202+ return emptyArray ;
203+ }
204+
205+ const outputs : string [ ] = [ ] ;
206+ outputs . push ( getOutputJavaScriptFileName ( inputFileName , configFile ) ) ;
207+ if ( configFile . options . declaration ) {
208+ const dts = outputs . push ( getOutputDeclarationFileName ( inputFileName , configFile ) ) ;
209+ if ( configFile . options . declarationMap ) {
210+ outputs . push ( dts + ".map" ) ;
211+ }
212+ }
213+ return outputs ;
214+ }
215+
216+ function getOutFileOutputs ( project : ts . ParsedCommandLine ) : ReadonlyArray < string > {
217+ Debug . assert ( ! ! project . options . outFile , "outFile must be set" ) ;
218+ const outputs : string [ ] = [ ] ;
219+ outputs . push ( project . options . outFile ) ;
220+ if ( project . options . declaration ) {
221+ const dts = outputs . push ( changeExtension ( project . options . outFile , ".d.ts" ) ) ;
222+ if ( project . options . declarationMap ) {
223+ outputs . push ( dts + ".map" ) ;
224+ }
225+ }
226+ return outputs ;
227+ }
228+
229+ function rootDirOfOptions ( opts : ts . CompilerOptions , configFileName : string ) {
230+ return opts . rootDir || path . dirname ( configFileName ) ;
231+ }
232+
233+ function createConfigFileCache ( host : CompilerHost ) {
234+ const cache = createFileMap < ParsedCommandLine > ( ) ;
235+ const configParseHost = parseConfigHostFromCompilerHost ( host ) ;
236+
237+ // TODO: Cache invalidation!
238+
239+ function parseConfigFile ( configFilePath : string ) {
240+ const sourceFile = host . getSourceFile ( configFilePath , ScriptTarget . JSON ) as JsonSourceFile ;
241+ const parsed = parseJsonSourceFileConfigFileContent ( sourceFile , configParseHost , configFilePath ) ;
242+ cache . setValue ( configFilePath , parsed ) ;
243+ return parsed ;
244+ }
245+
246+ return {
247+ parseConfigFile
248+ }
249+ }
250+
251+ function newer ( date1 : Date , date2 : Date ) : Date {
252+ return date2 > date1 ? date2 : date1 ;
253+ }
254+
255+ function older ( date1 : Date , date2 : Date ) : Date {
256+ return date2 < date1 ? date2 : date1 ;
257+ }
258+
259+ function createSolutionBuilder ( host : CompilerHost ) {
260+ const configFileCache = createConfigFileCache ( host ) ;
261+
262+ function getUpToDateStatus ( project : ParsedCommandLine , context : BuildContext ) : UpToDateStatus {
263+ let newestInputFileName : string = '???' ;
264+ let newestInputFileTime = MinimumDate ;
265+ // Get timestamps of input files
266+ for ( const inputFile of project . fileNames ) {
267+ if ( ! host . fileExists ( inputFile ) ) {
268+ return {
269+ type : UpToDateStatusType . Unbuildable ,
270+ reason : `${ inputFile } does not exist`
271+ } ;
272+ }
273+
274+ const inputTime = sys . getModifiedTime ( inputFile ) ;
275+ if ( inputTime > newestInputFileTime ) {
276+ newestInputFileName = inputFile ;
277+ newestInputFileTime = inputTime ;
278+ }
279+ }
280+
281+ // Collect the expected outputs of this project
282+ let outputs : ReadonlyArray < string > ;
283+ if ( project . options . outFile ) {
284+ outputs = getOutFileOutputs ( project ) ;
285+ }
286+ else {
287+ outputs = [ ] ;
288+ for ( const inputFile of project . fileNames ) {
289+ ( outputs as string [ ] ) . push ( ...getOutputFileNames ( inputFile , project ) ) ;
290+ }
291+ }
292+
293+ // Now see if all outputs are newer than the newest input
294+ let oldestOutputFileName : string = "n/a" ;
295+ let oldestOutputFileTime : Date = MinimumDate ;
296+ let newestOutputFileTime : Date = MaximumDate ;
297+ let newestDeclarationFileContentChangedTime : Date = MinimumDate ;
298+ for ( const output of outputs ) {
299+ // Output is missing
300+ if ( ! host . fileExists ( output ) ) {
301+ return {
302+ type : UpToDateStatusType . OutputMissing ,
303+ missingOutputFileName : output
304+ } ;
305+ }
306+
307+ const outputTime = sys . getModifiedTime ( output ) ;
308+ // If an output is older than the newest input, we can stop checking
309+ if ( outputTime < newestInputFileTime ) {
310+ return {
311+ type : UpToDateStatusType . OutOfDateWithSelf ,
312+ outOfDateOutputFileName : output ,
313+ newerInputFileName : newestInputFileName
314+ } ;
315+ }
316+
317+ if ( outputTime < oldestOutputFileTime ) {
318+ oldestOutputFileTime = outputTime ;
319+ oldestOutputFileName = output ;
320+ }
321+ newestOutputFileTime = older ( newestOutputFileTime , outputTime ) ;
322+
323+ // Keep track of when the most recent time a .d.ts file was changed.
324+ // In addition to file timestamps, we also keep track of when a .d.ts file
325+ // had its file touched but not had its contents changed - this allows us
326+ // to skip a downstream typecheck
327+ if ( fileExtensionIs ( output , ".d.ts" ) ) {
328+ const unchangedTime = context . unchangedOutputs . getValueOrUndefined ( output ) ;
329+ if ( unchangedTime !== undefined ) {
330+ newestDeclarationFileContentChangedTime = newer ( unchangedTime , newestDeclarationFileContentChangedTime ) ;
331+ }
332+ else {
333+ newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , sys . getModifiedTime ( output ) ) ;
334+ }
335+ }
336+ }
337+
338+ let pseudoUpToDate = false ;
339+ // By here, we know the project is at least up-to-date with its own inputs.
340+ // See if any of its upstream projects are newer than it
341+ for ( const ref of project . projectReferences ) {
342+ const refStatus = getUpToDateStatus ( configFileCache . parseConfigFile ( ref . path ) , context ) ;
343+
344+ // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?)
345+ if ( refStatus . type !== UpToDateStatusType . UpToDate ) {
346+ return {
347+ type : UpToDateStatusType . UpstreamOutOfDate ,
348+ upstreamProjectName : ref . path
349+ }
350+ }
351+
352+ // If the upstream project's newest file is older than our oldest output, we
353+ // can't be out of date because of it
354+ if ( refStatus . newestInputFileTime < oldestOutputFileTime ) {
355+ continue ;
356+ }
357+
358+ // If the upstream project has only change .d.ts files, and we've built
359+ // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild
360+ if ( refStatus . newestDeclarationFileContentChangedTime < oldestOutputFileTime ) {
361+ pseudoUpToDate = true ;
362+ continue ;
363+ }
364+
365+ // We have an output older than an upstream output - we are out of date
366+ return {
367+ type : UpToDateStatusType . OutOfDateWithUpstream ,
368+ outOfDateOutputFileName : oldestOutputFileName ,
369+ newerProjectName : ref . path
370+ } ;
371+ }
372+
373+ // Up to date
374+ return {
375+ type : pseudoUpToDate ? UpToDateStatusType . UpToDateWithUpstreamTypes : UpToDateStatusType . UpToDate ,
376+ newestDeclarationFileContentChangedTime,
377+ newestInputFileTime,
378+ newestOutputFileTime
379+ } ;
380+ }
381+
382+ return {
383+ getUpToDateStatus
384+ }
385+ }
69386}
0 commit comments