Skip to content

Commit d224ee0

Browse files
committed
Free up space in the TransformFlags enum
1 parent 9cf201c commit d224ee0

File tree

7 files changed

+377
-76
lines changed

7 files changed

+377
-76
lines changed

scripts/build/upToDate.js

+312-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const fs = require("fs");
44
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
55
const ts = require("../../lib/typescript");
66
const { Duplex } = require("stream");
7-
const chalk = require("./chalk");
7+
const chalk = /**@type {*} */(require("chalk"));
88
const 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
*/
2020
function 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
*/
9393
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+
}
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
*/
131435
void 0;

0 commit comments

Comments
 (0)