Skip to content

Commit

Permalink
If version of the input file does not change, dont mark as out of date
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat committed Apr 29, 2022
1 parent 7810c56 commit 28a9ff3
Show file tree
Hide file tree
Showing 57 changed files with 2,857 additions and 683 deletions.
108 changes: 76 additions & 32 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,13 +762,19 @@ namespace ts {
}

export interface ProgramBundleEmitBuildInfo {
fileNames: readonly string[];
fileInfos: readonly string[];
options: CompilerOptions | undefined;
outSignature?: string;
dtsChangeTime?: number;
}

export type ProgramBuildInfo = ProgramMultiFileEmitBuildInfo | ProgramBundleEmitBuildInfo;

export function isProgramBundleEmitBuildInfo(info: ProgramBuildInfo): info is ProgramBundleEmitBuildInfo {
return !!outFile(info.options || {});
}

/**
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
*/
Expand All @@ -779,7 +785,17 @@ namespace ts {
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
state.dtsChangeTime = state.hasChangedEmitSignature ? getCurrentTime(host).getTime() : state.dtsChangeTime;
if (outFilePath) {
const fileNames: string[] = [];
const fileInfos: string[] = [];
state.program!.getRootFileNames().forEach(f => {
const sourceFile = state.program!.getSourceFile(f);
if (!sourceFile) return;
fileNames.push(relativeToBuildInfo(sourceFile.resolvedPath));
fileInfos.push(sourceFile.version);
});
const result: ProgramBundleEmitBuildInfo = {
fileNames,
fileInfos,
options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions, "affectsBundleEmitBuildInfo"),
outSignature: state.outSignature,
dtsChangeTime: state.dtsChangeTime,
Expand Down Expand Up @@ -1370,40 +1386,52 @@ namespace ts {
{ version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat };
}

export function createBuilderProgramUsingProgramBuildInfo(programFromBuildInfo: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
export function createBuilderProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());

const program = programFromBuildInfo as ProgramMultiFileEmitBuildInfo & ProgramBundleEmitBuildInfo;
const filePaths = program.fileNames?.map(toPath);
const filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath)));
const fileInfos = new Map<Path, BuilderState.FileInfo>();
const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map<Path, string>() : undefined;
program.fileInfos?.forEach((fileInfo, index) => {
const path = toFilePath(index + 1 as ProgramBuildInfoFileId);
const stateFileInfo = toBuilderStateFileInfo(fileInfo);
fileInfos.set(path, stateFileInfo);
if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature);
});
program.emitSignatures?.forEach(value => {
if (isNumber(value)) emitSignatures!.delete(toFilePath(value));
else emitSignatures!.set(toFilePath(value[0]), value[1]);
});
const state: ReusableBuilderProgramState = {
fileInfos,
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
referencedMap: toManyToManyPathMap(program.referencedMap),
exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap),
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
hasReusableDiagnostic: true,
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]),
affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
changedFilesSet: new Set(map(program.changeFileSet, toFilePath)),
dtsChangeTime: program.dtsChangeTime,
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
outSignature: program.outSignature,
};
let state: ReusableBuilderProgramState;
let filePaths: Path[] | undefined;
let filePathsSetList: Set<Path>[] | undefined;
if (isProgramBundleEmitBuildInfo(program)) {
state = {
fileInfos: new Map(),
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
dtsChangeTime: program.dtsChangeTime,
outSignature: program.outSignature,
};
}
else {
filePaths = program.fileNames?.map(toPath);
filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath)));
const fileInfos = new Map<Path, BuilderState.FileInfo>();
const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map<Path, string>() : undefined;
program.fileInfos.forEach((fileInfo, index) => {
const path = toFilePath(index + 1 as ProgramBuildInfoFileId);
const stateFileInfo = toBuilderStateFileInfo(fileInfo);
fileInfos.set(path, stateFileInfo);
if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature);
});
program.emitSignatures?.forEach(value => {
if (isNumber(value)) emitSignatures!.delete(toFilePath(value));
else emitSignatures!.set(toFilePath(value[0]), value[1]);
});
state = {
fileInfos,
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
referencedMap: toManyToManyPathMap(program.referencedMap),
exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap),
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
hasReusableDiagnostic: true,
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]),
affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
changedFilesSet: new Set(map(program.changeFileSet, toFilePath)),
dtsChangeTime: program.dtsChangeTime,
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
};
}

return {
getState: () => state,
backupEmitState: noop,
Expand Down Expand Up @@ -1438,7 +1466,7 @@ namespace ts {
}

function toFilePath(fileId: ProgramBuildInfoFileId) {
return filePaths[fileId - 1];
return filePaths![fileId - 1];
}

function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) {
Expand All @@ -1458,6 +1486,22 @@ namespace ts {
}
}

export function getBuildInfoFileVersionMap(
program: ProgramBuildInfo,
buildInfoPath: string,
host: Pick<ReadBuildProgramHost, "useCaseSensitiveFileNames" | "getCurrentDirectory">
): ESMap<Path, string> {
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
const fileInfos = new Map<Path, string>();
program.fileInfos.forEach((fileInfo, index) => {
const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName);
const version = isString(fileInfo) ? fileInfo : (fileInfo as ProgramBuildInfoBuilderStateFileInfo).version;
fileInfos.set(path, version);
});
return fileInfos;
}

export function createRedirectedBuilderProgram(getState: () => { program?: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram {
return {
getState: notImplemented,
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/builderState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ namespace ts {
* Otherwise undefined
* Thus non undefined value indicates, module emit
*/
readonly referencedMap: BuilderState.ReadonlyManyToManyPathMap | undefined;
readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined;
/**
* Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled
* Otherwise undefined
*
* This is equivalent to referencedMap, but for the emitted .d.ts file.
*/
readonly exportedModulesMap: BuilderState.ManyToManyPathMap | undefined;
readonly exportedModulesMap?: BuilderState.ManyToManyPathMap | undefined;

/**
* true if file version is used as signature
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5276,6 +5276,10 @@
"category": "Message",
"code": 6399
},
"Project '{0}' is up to date but needs update to timestamps of output files that are older than input files": {
"category": "Message",
"code": 6400
},

"The expected type comes from property '{0}' which is declared here on type '{1}'": {
"category": "Message",
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/tsbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace ts {
UpstreamBlocked,
ComputingUpstream,
TsVersionOutputOfDate,
UpToDateWithInputFileText,

/**
* Projects with no outputs (i.e. "solution" files)
Expand Down Expand Up @@ -68,7 +69,7 @@ namespace ts {
* We track what the newest input file is.
*/
export interface UpToDate {
type: UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes;
type: UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes | UpToDateStatusType.UpToDateWithInputFileText;
newestInputFileTime?: Date;
newestInputFileName?: string;
newestDeclarationFileContentChangedTime: Date | undefined;
Expand Down
42 changes: 35 additions & 7 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1256,7 +1256,7 @@ namespace ts {
continue;
}

if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) {
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.UpToDateWithInputFileText) {
reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config));
return createUpdateOutputFileStampsProject(
state,
Expand Down Expand Up @@ -1517,6 +1517,8 @@ namespace ts {
let oldestOutputFileName = "(none)";
let oldestOutputFileTime = maximumDate;
let buildInfoTime: Date | undefined;
let buildInfoProgram: ProgramBuildInfo | undefined;
let buildInfoVersionMap: ESMap<Path, string> | undefined;
let newestDeclarationFileContentChangedTime;
if (buildInfoPath) {
const buildInfoCacheEntry = getBuildInfoCacheEntry(state, buildInfoPath, resolvedPath);
Expand Down Expand Up @@ -1551,6 +1553,7 @@ namespace ts {
buildInfoFile: buildInfoPath
};
}
buildInfoProgram = buildInfo.program;
}

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

// If an buildInfo is older than the newest input, we can stop checking
if (buildInfoTime && buildInfoTime < inputTime) {
return {
type: UpToDateStatusType.OutOfDateWithSelf,
outOfDateOutputFileName: buildInfoPath!,
newerInputFileName: inputFile
};
let version: string | undefined;
let currentVersion: string | undefined;
if (buildInfoProgram) {
// Read files and see if they are same, read is anyways cached
if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host);
version = buildInfoVersionMap.get(toPath(state, inputFile));
const text = version ? state.readFileWithCache(inputFile) : undefined;
currentVersion = text && (host.createHash || generateDjb2Hash)(text);
if (version && version === currentVersion) pseudoInputUptodate = true;
}

if (!version || version !== currentVersion) {
return {
type: UpToDateStatusType.OutOfDateWithSelf,
outOfDateOutputFileName: buildInfoPath!,
newerInputFileName: inputFile
};
}
}

if (inputTime > newestInputFileTime) {
Expand Down Expand Up @@ -1692,7 +1709,11 @@ namespace ts {

// Up to date
return {
type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate,
type: pseudoUpToDate ?
UpToDateStatusType.UpToDateWithUpstreamTypes :
pseudoInputUptodate ?
UpToDateStatusType.UpToDateWithInputFileText :
UpToDateStatusType.UpToDate,
newestDeclarationFileContentChangedTime,
newestInputFileTime,
newestInputFileName,
Expand Down Expand Up @@ -1845,6 +1866,7 @@ namespace ts {
}
// falls through

case UpToDateStatusType.UpToDateWithInputFileText:
case UpToDateStatusType.UpToDateWithUpstreamTypes:
case UpToDateStatusType.OutOfDateWithPrepend:
if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) {
Expand Down Expand Up @@ -2289,6 +2311,12 @@ namespace ts {
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
relName(state, configFileName)
);
case UpToDateStatusType.UpToDateWithInputFileText:
return reportStatus(
state,
Diagnostics.Project_0_is_up_to_date_but_needs_update_to_timestamps_of_output_files_that_are_older_than_input_files,
relName(state, configFileName)
);
case UpToDateStatusType.UpstreamOutOfDate:
return reportStatus(
state,
Expand Down
Loading

0 comments on commit 28a9ff3

Please sign in to comment.