@@ -4,7 +4,7 @@ const fs = require("fs");
4
4
const log = require ( "fancy-log" ) ; // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
5
5
const ts = require ( "../../lib/typescript" ) ;
6
6
const { Duplex } = require ( "stream" ) ;
7
- const chalk = require ( "./ chalk" ) ;
7
+ const chalk = /** @type { * } */ ( require ( "chalk" ) ) ;
8
8
const Vinyl = require ( "vinyl" ) ;
9
9
10
10
/**
@@ -14,7 +14,7 @@ const Vinyl = require("vinyl");
14
14
* @param {UpToDateOptions } [options]
15
15
*
16
16
* @typedef UpToDateOptions
17
- * @property {boolean } [verbose]
17
+ * @property {boolean | "minimal" } [verbose]
18
18
* @property {(configFilePath: string) => ParsedCommandLine | undefined } [parseProject]
19
19
*/
20
20
function upToDate ( parsedProject , options ) {
@@ -47,9 +47,9 @@ function upToDate(parsedProject, options) {
47
47
cb ( ) ;
48
48
} ,
49
49
final ( cb ) {
50
- const status = ts . getUpToDateStatus ( upToDateHost , parsedProject ) ;
50
+ const status = getUpToDateStatus ( upToDateHost , parsedProject ) ;
51
51
reportStatus ( parsedProject , status , options ) ;
52
- if ( status . type !== ts . UpToDateStatusType . UpToDate ) {
52
+ if ( status . type !== UpToDateStatusType . UpToDate ) {
53
53
for ( const input of inputs ) duplex . push ( input ) ;
54
54
}
55
55
duplex . push ( null ) ;
@@ -88,11 +88,25 @@ function formatMessage(message, ...args) {
88
88
/**
89
89
* @param {ParsedCommandLine } project
90
90
* @param {UpToDateStatus } status
91
- * @param {{verbose?: boolean} } options
91
+ * @param {{verbose?: boolean | "minimal" } } options
92
92
*/
93
93
function reportStatus ( project , status , options ) {
94
+ switch ( options . verbose ) {
95
+ case "minimal" :
96
+ switch ( status . type ) {
97
+ case UpToDateStatusType . UpToDate :
98
+ log . info ( `Project '${ fileName ( project . options . configFilePath ) } ' is up to date.` ) ;
99
+ break ;
100
+ default :
101
+ log . info ( `Project '${ fileName ( project . options . configFilePath ) } ' is out of date, rebuilding...` ) ;
102
+ break ;
103
+ }
104
+ break ;
105
+ case true :
106
+ /**@type {* }*/ ( ts ) . formatUpToDateStatus ( project . options . configFilePath , status , fileName , formatMessage ) ;
107
+ break ;
108
+ }
94
109
if ( ! options . verbose ) return ;
95
- ts . formatUpToDateStatus ( project . options . configFilePath , status , fileName , formatMessage ) ;
96
110
}
97
111
98
112
/**
@@ -120,12 +134,302 @@ function formatStringFromArgs(text, args, baseIndex = 0) {
120
134
return text . replace ( / { ( \d + ) } / g, ( _match , index ) => args [ + index + baseIndex ] ) ;
121
135
}
122
136
137
+ const minimumDate = new Date ( - 8640000000000000 ) ;
138
+ const maximumDate = new Date ( 8640000000000000 ) ;
139
+ const missingFileModifiedTime = new Date ( 0 ) ;
140
+
141
+ /**
142
+ * @typedef {0 } UpToDateStatusType.Unbuildable
143
+ * @typedef {1 } UpToDateStatusType.UpToDate
144
+ * @typedef {2 } UpToDateStatusType.UpToDateWithUpstreamTypes
145
+ * @typedef {3 } UpToDateStatusType.OutputMissing
146
+ * @typedef {4 } UpToDateStatusType.OutOfDateWithSelf
147
+ * @typedef {5 } UpToDateStatusType.OutOfDateWithUpstream
148
+ * @typedef {6 } UpToDateStatusType.UpstreamOutOfDate
149
+ * @typedef {7 } UpToDateStatusType.UpstreamBlocked
150
+ * @typedef {8 } UpToDateStatusType.ComputingUpstream
151
+ * @typedef {9 } UpToDateStatusType.ContainerOnly
152
+ * @enum {UpToDateStatusType.Unbuildable | UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes | UpToDateStatusType.OutputMissing | UpToDateStatusType.OutOfDateWithSelf | UpToDateStatusType.OutOfDateWithUpstream | UpToDateStatusType.UpstreamOutOfDate | UpToDateStatusType.UpstreamBlocked | UpToDateStatusType.ComputingUpstream | UpToDateStatusType.ContainerOnly}
153
+ */
154
+ const UpToDateStatusType = {
155
+ Unbuildable : /** @type {0 } */ ( 0 ) ,
156
+ UpToDate : /** @type {1 } */ ( 1 ) ,
157
+ UpToDateWithUpstreamTypes : /** @type {2 } */ ( 2 ) ,
158
+ OutputMissing : /** @type {3 } */ ( 3 ) ,
159
+ OutOfDateWithSelf : /** @type {4 } */ ( 4 ) ,
160
+ OutOfDateWithUpstream : /** @type {5 } */ ( 5 ) ,
161
+ UpstreamOutOfDate : /** @type {6 } */ ( 6 ) ,
162
+ UpstreamBlocked : /** @type {7 } */ ( 7 ) ,
163
+ ComputingUpstream : /** @type {8 } */ ( 8 ) ,
164
+ ContainerOnly : /** @type {9 } */ ( 9 ) ,
165
+ } ;
166
+
167
+ /**
168
+ * @param {Date } date1
169
+ * @param {Date } date2
170
+ * @returns {Date }
171
+ */
172
+ function newer ( date1 , date2 ) {
173
+ return date2 > date1 ? date2 : date1 ;
174
+ }
175
+
176
+ /**
177
+ * @param {UpToDateHost } host
178
+ * @param {ParsedCommandLine | undefined } project
179
+ * @returns {UpToDateStatus }
180
+ */
181
+ function getUpToDateStatus ( host , project ) {
182
+ if ( project === undefined ) return { type : UpToDateStatusType . Unbuildable , reason : "File deleted mid-build" } ;
183
+ const prior = host . getLastStatus ? host . getLastStatus ( project . options . configFilePath ) : undefined ;
184
+ if ( prior !== undefined ) {
185
+ return prior ;
186
+ }
187
+ const actual = getUpToDateStatusWorker ( host , project ) ;
188
+ if ( host . setLastStatus ) {
189
+ host . setLastStatus ( project . options . configFilePath , actual ) ;
190
+ }
191
+ return actual ;
192
+ }
193
+
194
+ /**
195
+ * @param {UpToDateHost } host
196
+ * @param {ParsedCommandLine | undefined } project
197
+ * @returns {UpToDateStatus }
198
+ */
199
+ function getUpToDateStatusWorker ( host , project ) {
200
+ /** @type {string } */
201
+ let newestInputFileName = undefined ;
202
+ let newestInputFileTime = minimumDate ;
203
+ // Get timestamps of input files
204
+ for ( const inputFile of project . fileNames ) {
205
+ if ( ! host . fileExists ( inputFile ) ) {
206
+ return {
207
+ type : UpToDateStatusType . Unbuildable ,
208
+ reason : `${ inputFile } does not exist`
209
+ } ;
210
+ }
211
+
212
+ const inputTime = host . getModifiedTime ( inputFile ) || missingFileModifiedTime ;
213
+ if ( inputTime > newestInputFileTime ) {
214
+ newestInputFileName = inputFile ;
215
+ newestInputFileTime = inputTime ;
216
+ }
217
+ }
218
+
219
+ // Collect the expected outputs of this project
220
+ const outputs = /**@type {string[] }*/ ( /**@type {* }*/ ( ts ) . getAllProjectOutputs ( project ) ) ;
221
+
222
+ if ( outputs . length === 0 ) {
223
+ return {
224
+ type : UpToDateStatusType . ContainerOnly
225
+ } ;
226
+ }
227
+
228
+ // Now see if all outputs are newer than the newest input
229
+ let oldestOutputFileName = "(none)" ;
230
+ let oldestOutputFileTime = maximumDate ;
231
+ let newestOutputFileName = "(none)" ;
232
+ let newestOutputFileTime = minimumDate ;
233
+ /** @type {string | undefined } */
234
+ let missingOutputFileName ;
235
+ let newestDeclarationFileContentChangedTime = minimumDate ;
236
+ let isOutOfDateWithInputs = false ;
237
+ for ( const output of outputs ) {
238
+ // Output is missing; can stop checking
239
+ // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status
240
+ if ( ! host . fileExists ( output ) ) {
241
+ missingOutputFileName = output ;
242
+ break ;
243
+ }
244
+
245
+ const outputTime = host . getModifiedTime ( output ) || missingFileModifiedTime ;
246
+ if ( outputTime < oldestOutputFileTime ) {
247
+ oldestOutputFileTime = outputTime ;
248
+ oldestOutputFileName = output ;
249
+ }
250
+
251
+ // If an output is older than the newest input, we can stop checking
252
+ // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status
253
+ if ( outputTime < newestInputFileTime ) {
254
+ isOutOfDateWithInputs = true ;
255
+ break ;
256
+ }
257
+
258
+ if ( outputTime > newestOutputFileTime ) {
259
+ newestOutputFileTime = outputTime ;
260
+ newestOutputFileName = output ;
261
+ }
262
+
263
+ // Keep track of when the most recent time a .d.ts file was changed.
264
+ // In addition to file timestamps, we also keep track of when a .d.ts file
265
+ // had its file touched but not had its contents changed - this allows us
266
+ // to skip a downstream typecheck
267
+ if ( path . extname ( output ) === ".d.ts" ) {
268
+ const unchangedTime = host . getUnchangedTime ? host . getUnchangedTime ( output ) : undefined ;
269
+ if ( unchangedTime !== undefined ) {
270
+ newestDeclarationFileContentChangedTime = newer ( unchangedTime , newestDeclarationFileContentChangedTime ) ;
271
+ }
272
+ else {
273
+ const outputModifiedTime = host . getModifiedTime ( output ) || missingFileModifiedTime ;
274
+ newestDeclarationFileContentChangedTime = newer ( newestDeclarationFileContentChangedTime , outputModifiedTime ) ;
275
+ }
276
+ }
277
+ }
278
+
279
+ let pseudoUpToDate = false ;
280
+ let usesPrepend = false ;
281
+ /** @type {string | undefined } */
282
+ let upstreamChangedProject ;
283
+ if ( project . projectReferences ) {
284
+ if ( host . setLastStatus ) host . setLastStatus ( project . options . configFilePath , { type : UpToDateStatusType . ComputingUpstream } ) ;
285
+ for ( const ref of project . projectReferences ) {
286
+ usesPrepend = usesPrepend || ! ! ( ref . prepend ) ;
287
+ const resolvedRef = ts . resolveProjectReferencePath ( host , ref ) ;
288
+ const parsedRef = host . parseConfigFile ? host . parseConfigFile ( resolvedRef ) : ts . getParsedCommandLineOfConfigFile ( resolvedRef , { } , parseConfigHost ) ;
289
+ const refStatus = getUpToDateStatus ( host , parsedRef ) ;
290
+
291
+ // Its a circular reference ignore the status of this project
292
+ if ( refStatus . type === UpToDateStatusType . ComputingUpstream ) {
293
+ continue ;
294
+ }
295
+
296
+ // An upstream project is blocked
297
+ if ( refStatus . type === UpToDateStatusType . Unbuildable ) {
298
+ return {
299
+ type : UpToDateStatusType . UpstreamBlocked ,
300
+ upstreamProjectName : ref . path
301
+ } ;
302
+ }
303
+
304
+ // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?)
305
+ if ( refStatus . type !== UpToDateStatusType . UpToDate ) {
306
+ return {
307
+ type : UpToDateStatusType . UpstreamOutOfDate ,
308
+ upstreamProjectName : ref . path
309
+ } ;
310
+ }
311
+
312
+ // If the upstream project's newest file is older than our oldest output, we
313
+ // can't be out of date because of it
314
+ if ( refStatus . newestInputFileTime && refStatus . newestInputFileTime <= oldestOutputFileTime ) {
315
+ continue ;
316
+ }
317
+
318
+ // If the upstream project has only change .d.ts files, and we've built
319
+ // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild
320
+ if ( refStatus . newestDeclarationFileContentChangedTime && refStatus . newestDeclarationFileContentChangedTime <= oldestOutputFileTime ) {
321
+ pseudoUpToDate = true ;
322
+ upstreamChangedProject = ref . path ;
323
+ continue ;
324
+ }
325
+
326
+ // We have an output older than an upstream output - we are out of date
327
+ return {
328
+ type : UpToDateStatusType . OutOfDateWithUpstream ,
329
+ outOfDateOutputFileName : oldestOutputFileName ,
330
+ newerProjectName : ref . path
331
+ } ;
332
+ }
333
+ }
334
+
335
+ if ( missingOutputFileName !== undefined ) {
336
+ return {
337
+ type : UpToDateStatusType . OutputMissing ,
338
+ missingOutputFileName
339
+ } ;
340
+ }
341
+
342
+ if ( isOutOfDateWithInputs ) {
343
+ return {
344
+ type : UpToDateStatusType . OutOfDateWithSelf ,
345
+ outOfDateOutputFileName : oldestOutputFileName ,
346
+ newerInputFileName : newestInputFileName
347
+ } ;
348
+ }
349
+
350
+ if ( usesPrepend && pseudoUpToDate ) {
351
+ return {
352
+ type : UpToDateStatusType . OutOfDateWithUpstream ,
353
+ outOfDateOutputFileName : oldestOutputFileName ,
354
+ newerProjectName : upstreamChangedProject
355
+ } ;
356
+ }
357
+
358
+ // Up to date
359
+ return {
360
+ type : pseudoUpToDate ? UpToDateStatusType . UpToDateWithUpstreamTypes : UpToDateStatusType . UpToDate ,
361
+ newestDeclarationFileContentChangedTime,
362
+ newestInputFileTime,
363
+ newestOutputFileTime,
364
+ newestInputFileName,
365
+ newestOutputFileName,
366
+ oldestOutputFileName
367
+ } ;
368
+ }
369
+
370
+ const parseConfigHost = {
371
+ useCaseSensitiveFileNames : true ,
372
+ getCurrentDirectory : ( ) => process . cwd ( ) ,
373
+ readDirectory : ( file ) => fs . readdirSync ( file ) ,
374
+ fileExists : file => fs . existsSync ( file ) && fs . statSync ( file ) . isFile ( ) ,
375
+ readFile : file => fs . readFileSync ( file , "utf8" ) ,
376
+ onUnRecoverableConfigFileDiagnostic : ( ) => undefined
377
+ } ;
378
+
123
379
/**
124
380
* @typedef {import("vinyl") } File
125
381
* @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions } } ParsedCommandLine
126
382
* @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string } } CompilerOptions
127
- * @typedef {import("../../lib/typescript").UpToDateHost } UpToDateHost
128
- * @typedef {import("../../lib/typescript").UpToDateStatus } UpToDateStatus
129
383
* @typedef {import("../../lib/typescript").DiagnosticMessage } DiagnosticMessage
384
+ * @typedef UpToDateHost
385
+ * @property {(fileName: string) => boolean } fileExists
386
+ * @property {(fileName: string) => Date } getModifiedTime
387
+ * @property {(fileName: string) => Date } [getUnchangedTime]
388
+ * @property {(configFilePath: string) => ParsedCommandLine | undefined } parseConfigFile
389
+ * @property {(configFilePath: string) => UpToDateStatus } [getLastStatus]
390
+ * @property {(configFilePath: string, status: UpToDateStatus) => void } [setLastStatus]
391
+ *
392
+ * @typedef Status.Unbuildable
393
+ * @property {UpToDateStatusType.Unbuildable } type
394
+ * @property {string } reason
395
+ *
396
+ * @typedef Status.ContainerOnly
397
+ * @property {UpToDateStatusType.ContainerOnly } type
398
+ *
399
+ * @typedef Status.UpToDate
400
+ * @property {UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes } type
401
+ * @property {Date } [newestInputFileTime]
402
+ * @property {string } [newestInputFileName]
403
+ * @property {Date } [newestDeclarationFileContentChangedTime]
404
+ * @property {Date } [newestOutputFileTime]
405
+ * @property {string } [newestOutputFileName]
406
+ * @property {string } [oldestOutputFileName]
407
+ *
408
+ * @typedef Status.OutputMissing
409
+ * @property {UpToDateStatusType.OutputMissing } type
410
+ * @property {string } missingOutputFileName
411
+ *
412
+ * @typedef Status.OutOfDateWithSelf
413
+ * @property {UpToDateStatusType.OutOfDateWithSelf } type
414
+ * @property {string } outOfDateOutputFileName
415
+ * @property {string } newerInputFileName
416
+ *
417
+ * @typedef Status.UpstreamOutOfDate
418
+ * @property {UpToDateStatusType.UpstreamOutOfDate } type
419
+ * @property {string } upstreamProjectName
420
+ *
421
+ * @typedef Status.UpstreamBlocked
422
+ * @property {UpToDateStatusType.UpstreamBlocked } type
423
+ * @property {string } upstreamProjectName
424
+ *
425
+ * @typedef Status.ComputingUpstream
426
+ * @property {UpToDateStatusType.ComputingUpstream } type
427
+ *
428
+ * @typedef Status.OutOfDateWithUpstream
429
+ * @property {UpToDateStatusType.OutOfDateWithUpstream } type
430
+ * @property {string } outOfDateOutputFileName
431
+ * @property {string } newerProjectName
432
+
433
+ * @typedef {Status.Unbuildable | Status.ContainerOnly | Status.UpToDate | Status.OutputMissing | Status.OutOfDateWithSelf | Status.UpstreamOutOfDate | Status.UpstreamBlocked | Status.ComputingUpstream | Status.OutOfDateWithUpstream } UpToDateStatus
130
434
*/
131
435
void 0 ;
0 commit comments