Skip to content

Commit 28a9ff3

Browse files
committed
If version of the input file does not change, dont mark as out of date
1 parent 7810c56 commit 28a9ff3

File tree

57 files changed

+2857
-683
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2857
-683
lines changed

Diff for: src/compiler/builder.ts

+76-32
Original file line numberDiff line numberDiff line change
@@ -762,13 +762,19 @@ namespace ts {
762762
}
763763

764764
export interface ProgramBundleEmitBuildInfo {
765+
fileNames: readonly string[];
766+
fileInfos: readonly string[];
765767
options: CompilerOptions | undefined;
766768
outSignature?: string;
767769
dtsChangeTime?: number;
768770
}
769771

770772
export type ProgramBuildInfo = ProgramMultiFileEmitBuildInfo | ProgramBundleEmitBuildInfo;
771773

774+
export function isProgramBundleEmitBuildInfo(info: ProgramBuildInfo): info is ProgramBundleEmitBuildInfo {
775+
return !!outFile(info.options || {});
776+
}
777+
772778
/**
773779
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
774780
*/
@@ -779,7 +785,17 @@ namespace ts {
779785
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
780786
state.dtsChangeTime = state.hasChangedEmitSignature ? getCurrentTime(host).getTime() : state.dtsChangeTime;
781787
if (outFilePath) {
788+
const fileNames: string[] = [];
789+
const fileInfos: string[] = [];
790+
state.program!.getRootFileNames().forEach(f => {
791+
const sourceFile = state.program!.getSourceFile(f);
792+
if (!sourceFile) return;
793+
fileNames.push(relativeToBuildInfo(sourceFile.resolvedPath));
794+
fileInfos.push(sourceFile.version);
795+
});
782796
const result: ProgramBundleEmitBuildInfo = {
797+
fileNames,
798+
fileInfos,
783799
options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions, "affectsBundleEmitBuildInfo"),
784800
outSignature: state.outSignature,
785801
dtsChangeTime: state.dtsChangeTime,
@@ -1370,40 +1386,52 @@ namespace ts {
13701386
{ version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat };
13711387
}
13721388

1373-
export function createBuilderProgramUsingProgramBuildInfo(programFromBuildInfo: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
1389+
export function createBuilderProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
13741390
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
13751391
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
13761392

1377-
const program = programFromBuildInfo as ProgramMultiFileEmitBuildInfo & ProgramBundleEmitBuildInfo;
1378-
const filePaths = program.fileNames?.map(toPath);
1379-
const filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath)));
1380-
const fileInfos = new Map<Path, BuilderState.FileInfo>();
1381-
const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map<Path, string>() : undefined;
1382-
program.fileInfos?.forEach((fileInfo, index) => {
1383-
const path = toFilePath(index + 1 as ProgramBuildInfoFileId);
1384-
const stateFileInfo = toBuilderStateFileInfo(fileInfo);
1385-
fileInfos.set(path, stateFileInfo);
1386-
if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature);
1387-
});
1388-
program.emitSignatures?.forEach(value => {
1389-
if (isNumber(value)) emitSignatures!.delete(toFilePath(value));
1390-
else emitSignatures!.set(toFilePath(value[0]), value[1]);
1391-
});
1392-
const state: ReusableBuilderProgramState = {
1393-
fileInfos,
1394-
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
1395-
referencedMap: toManyToManyPathMap(program.referencedMap),
1396-
exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap),
1397-
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
1398-
hasReusableDiagnostic: true,
1399-
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
1400-
affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]),
1401-
affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
1402-
changedFilesSet: new Set(map(program.changeFileSet, toFilePath)),
1403-
dtsChangeTime: program.dtsChangeTime,
1404-
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
1405-
outSignature: program.outSignature,
1406-
};
1393+
let state: ReusableBuilderProgramState;
1394+
let filePaths: Path[] | undefined;
1395+
let filePathsSetList: Set<Path>[] | undefined;
1396+
if (isProgramBundleEmitBuildInfo(program)) {
1397+
state = {
1398+
fileInfos: new Map(),
1399+
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
1400+
dtsChangeTime: program.dtsChangeTime,
1401+
outSignature: program.outSignature,
1402+
};
1403+
}
1404+
else {
1405+
filePaths = program.fileNames?.map(toPath);
1406+
filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath)));
1407+
const fileInfos = new Map<Path, BuilderState.FileInfo>();
1408+
const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map<Path, string>() : undefined;
1409+
program.fileInfos.forEach((fileInfo, index) => {
1410+
const path = toFilePath(index + 1 as ProgramBuildInfoFileId);
1411+
const stateFileInfo = toBuilderStateFileInfo(fileInfo);
1412+
fileInfos.set(path, stateFileInfo);
1413+
if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature);
1414+
});
1415+
program.emitSignatures?.forEach(value => {
1416+
if (isNumber(value)) emitSignatures!.delete(toFilePath(value));
1417+
else emitSignatures!.set(toFilePath(value[0]), value[1]);
1418+
});
1419+
state = {
1420+
fileInfos,
1421+
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
1422+
referencedMap: toManyToManyPathMap(program.referencedMap),
1423+
exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap),
1424+
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
1425+
hasReusableDiagnostic: true,
1426+
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
1427+
affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]),
1428+
affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
1429+
changedFilesSet: new Set(map(program.changeFileSet, toFilePath)),
1430+
dtsChangeTime: program.dtsChangeTime,
1431+
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
1432+
};
1433+
}
1434+
14071435
return {
14081436
getState: () => state,
14091437
backupEmitState: noop,
@@ -1438,7 +1466,7 @@ namespace ts {
14381466
}
14391467

14401468
function toFilePath(fileId: ProgramBuildInfoFileId) {
1441-
return filePaths[fileId - 1];
1469+
return filePaths![fileId - 1];
14421470
}
14431471

14441472
function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) {
@@ -1458,6 +1486,22 @@ namespace ts {
14581486
}
14591487
}
14601488

1489+
export function getBuildInfoFileVersionMap(
1490+
program: ProgramBuildInfo,
1491+
buildInfoPath: string,
1492+
host: Pick<ReadBuildProgramHost, "useCaseSensitiveFileNames" | "getCurrentDirectory">
1493+
): ESMap<Path, string> {
1494+
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
1495+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
1496+
const fileInfos = new Map<Path, string>();
1497+
program.fileInfos.forEach((fileInfo, index) => {
1498+
const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName);
1499+
const version = isString(fileInfo) ? fileInfo : (fileInfo as ProgramBuildInfoBuilderStateFileInfo).version;
1500+
fileInfos.set(path, version);
1501+
});
1502+
return fileInfos;
1503+
}
1504+
14611505
export function createRedirectedBuilderProgram(getState: () => { program?: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram {
14621506
return {
14631507
getState: notImplemented,

Diff for: src/compiler/builderState.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ namespace ts {
2020
* Otherwise undefined
2121
* Thus non undefined value indicates, module emit
2222
*/
23-
readonly referencedMap: BuilderState.ReadonlyManyToManyPathMap | undefined;
23+
readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined;
2424
/**
2525
* Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled
2626
* Otherwise undefined
2727
*
2828
* This is equivalent to referencedMap, but for the emitted .d.ts file.
2929
*/
30-
readonly exportedModulesMap: BuilderState.ManyToManyPathMap | undefined;
30+
readonly exportedModulesMap?: BuilderState.ManyToManyPathMap | undefined;
3131

3232
/**
3333
* true if file version is used as signature

Diff for: src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -5276,6 +5276,10 @@
52765276
"category": "Message",
52775277
"code": 6399
52785278
},
5279+
"Project '{0}' is up to date but needs update to timestamps of output files that are older than input files": {
5280+
"category": "Message",
5281+
"code": 6400
5282+
},
52795283

52805284
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
52815285
"category": "Message",

Diff for: src/compiler/tsbuild.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace ts {
2323
UpstreamBlocked,
2424
ComputingUpstream,
2525
TsVersionOutputOfDate,
26+
UpToDateWithInputFileText,
2627

2728
/**
2829
* Projects with no outputs (i.e. "solution" files)
@@ -68,7 +69,7 @@ namespace ts {
6869
* We track what the newest input file is.
6970
*/
7071
export interface UpToDate {
71-
type: UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes;
72+
type: UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes | UpToDateStatusType.UpToDateWithInputFileText;
7273
newestInputFileTime?: Date;
7374
newestInputFileName?: string;
7475
newestDeclarationFileContentChangedTime: Date | undefined;

Diff for: src/compiler/tsbuildPublic.ts

+35-7
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,7 @@ namespace ts {
12561256
continue;
12571257
}
12581258

1259-
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) {
1259+
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.UpToDateWithInputFileText) {
12601260
reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config));
12611261
return createUpdateOutputFileStampsProject(
12621262
state,
@@ -1517,6 +1517,8 @@ namespace ts {
15171517
let oldestOutputFileName = "(none)";
15181518
let oldestOutputFileTime = maximumDate;
15191519
let buildInfoTime: Date | undefined;
1520+
let buildInfoProgram: ProgramBuildInfo | undefined;
1521+
let buildInfoVersionMap: ESMap<Path, string> | undefined;
15201522
let newestDeclarationFileContentChangedTime;
15211523
if (buildInfoPath) {
15221524
const buildInfoCacheEntry = getBuildInfoCacheEntry(state, buildInfoPath, resolvedPath);
@@ -1551,6 +1553,7 @@ namespace ts {
15511553
buildInfoFile: buildInfoPath
15521554
};
15531555
}
1556+
buildInfoProgram = buildInfo.program;
15541557
}
15551558

15561559
oldestOutputFileTime = buildInfoTime;
@@ -1561,6 +1564,7 @@ namespace ts {
15611564
// Check input files
15621565
let newestInputFileName: string = undefined!;
15631566
let newestInputFileTime = minimumDate;
1567+
let pseudoInputUptodate = false;
15641568
// Get timestamps of input files
15651569
for (const inputFile of project.fileNames) {
15661570
const inputTime = getModifiedTime(state, inputFile);
@@ -1573,11 +1577,24 @@ namespace ts {
15731577

15741578
// If an buildInfo is older than the newest input, we can stop checking
15751579
if (buildInfoTime && buildInfoTime < inputTime) {
1576-
return {
1577-
type: UpToDateStatusType.OutOfDateWithSelf,
1578-
outOfDateOutputFileName: buildInfoPath!,
1579-
newerInputFileName: inputFile
1580-
};
1580+
let version: string | undefined;
1581+
let currentVersion: string | undefined;
1582+
if (buildInfoProgram) {
1583+
// Read files and see if they are same, read is anyways cached
1584+
if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host);
1585+
version = buildInfoVersionMap.get(toPath(state, inputFile));
1586+
const text = version ? state.readFileWithCache(inputFile) : undefined;
1587+
currentVersion = text && (host.createHash || generateDjb2Hash)(text);
1588+
if (version && version === currentVersion) pseudoInputUptodate = true;
1589+
}
1590+
1591+
if (!version || version !== currentVersion) {
1592+
return {
1593+
type: UpToDateStatusType.OutOfDateWithSelf,
1594+
outOfDateOutputFileName: buildInfoPath!,
1595+
newerInputFileName: inputFile
1596+
};
1597+
}
15811598
}
15821599

15831600
if (inputTime > newestInputFileTime) {
@@ -1692,7 +1709,11 @@ namespace ts {
16921709

16931710
// Up to date
16941711
return {
1695-
type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate,
1712+
type: pseudoUpToDate ?
1713+
UpToDateStatusType.UpToDateWithUpstreamTypes :
1714+
pseudoInputUptodate ?
1715+
UpToDateStatusType.UpToDateWithInputFileText :
1716+
UpToDateStatusType.UpToDate,
16961717
newestDeclarationFileContentChangedTime,
16971718
newestInputFileTime,
16981719
newestInputFileName,
@@ -1845,6 +1866,7 @@ namespace ts {
18451866
}
18461867
// falls through
18471868

1869+
case UpToDateStatusType.UpToDateWithInputFileText:
18481870
case UpToDateStatusType.UpToDateWithUpstreamTypes:
18491871
case UpToDateStatusType.OutOfDateWithPrepend:
18501872
if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) {
@@ -2289,6 +2311,12 @@ namespace ts {
22892311
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
22902312
relName(state, configFileName)
22912313
);
2314+
case UpToDateStatusType.UpToDateWithInputFileText:
2315+
return reportStatus(
2316+
state,
2317+
Diagnostics.Project_0_is_up_to_date_but_needs_update_to_timestamps_of_output_files_that_are_older_than_input_files,
2318+
relName(state, configFileName)
2319+
);
22922320
case UpToDateStatusType.UpstreamOutOfDate:
22932321
return reportStatus(
22942322
state,

0 commit comments

Comments
 (0)