@@ -4,7 +4,7 @@ const fs = require("fs");
44const log = require ( "fancy-log" ) ; // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
55const ts = require ( "../../lib/typescript" ) ;
66const { Duplex } = require ( "stream" ) ;
7- const chalk = require ( "./ chalk" ) ;
7+ const chalk = /** @type { * } */ ( require ( "chalk" ) ) ;
88const Vinyl = require ( "vinyl" ) ;
99
1010/**
@@ -14,7 +14,7 @@ const Vinyl = require("vinyl");
1414 * @param {UpToDateOptions } [options]
1515 *
1616 * @typedef UpToDateOptions
17- * @property {boolean } [verbose]
17+ * @property {boolean | "minimal" } [verbose]
1818 * @property {(configFilePath: string) => ParsedCommandLine | undefined } [parseProject]
1919 */
2020function upToDate ( parsedProject , options ) {
@@ -47,9 +47,9 @@ function upToDate(parsedProject, options) {
4747 cb ( ) ;
4848 } ,
4949 final ( cb ) {
50- const status = ts . getUpToDateStatus ( upToDateHost , parsedProject ) ;
50+ const status = getUpToDateStatus ( upToDateHost , parsedProject ) ;
5151 reportStatus ( parsedProject , status , options ) ;
52- if ( status . type !== ts . UpToDateStatusType . UpToDate ) {
52+ if ( status . type !== UpToDateStatusType . UpToDate ) {
5353 for ( const input of inputs ) duplex . push ( input ) ;
5454 }
5555 duplex . push ( null ) ;
@@ -88,11 +88,25 @@ function formatMessage(message, ...args) {
8888/**
8989 * @param {ParsedCommandLine } project
9090 * @param {UpToDateStatus } status
91- * @param {{verbose?: boolean} } options
91+ * @param {{verbose?: boolean | "minimal" } } options
9292 */
9393function 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+ }
94109 if ( ! options . verbose ) return ;
95- ts . formatUpToDateStatus ( project . options . configFilePath , status , fileName , formatMessage ) ;
96110}
97111
98112/**
@@ -120,12 +134,302 @@ function formatStringFromArgs(text, args, baseIndex = 0) {
120134 return text . replace ( / { ( \d + ) } / g, ( _match , index ) => args [ + index + baseIndex ] ) ;
121135}
122136
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+
123379/**
124380 * @typedef {import("vinyl") } File
125381 * @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions } } ParsedCommandLine
126382 * @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string } } CompilerOptions
127- * @typedef {import("../../lib/typescript").UpToDateHost } UpToDateHost
128- * @typedef {import("../../lib/typescript").UpToDateStatus } UpToDateStatus
129383 * @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
130434 */
131435void 0 ;
0 commit comments