Handle dts emit time errors for incremental scenarios and buildInfo (#56295)

This commit is contained in:
Sheetal Nandi
2023-11-02 18:35:28 -07:00
committed by GitHub
parent d86bddec5e
commit e58d8fd635
15 changed files with 1477 additions and 194 deletions
+138 -87
View File
@@ -133,6 +133,8 @@ export interface ReusableBuilderProgramState extends BuilderState {
* Cache of bind and check diagnostics for files with their Path being the key
*/
semanticDiagnosticsPerFile?: Map<Path, readonly ReusableDiagnostic[] | readonly Diagnostic[]> | undefined;
/** Cache of dts emit diagnostics for files with their Path being the key */
emitDiagnosticsPerFile?: Map<Path, readonly ReusableDiagnostic[] | readonly Diagnostic[]> | undefined;
/**
* The map has key by source file's path that has been changed
*/
@@ -202,6 +204,8 @@ export interface BuilderProgramState extends BuilderState, ReusableBuilderProgra
* Cache of bind and check diagnostics for files with their Path being the key
*/
semanticDiagnosticsPerFile: Map<Path, readonly Diagnostic[]> | undefined;
/** Cache of dts emit diagnostics for files with their Path being the key */
emitDiagnosticsPerFile?: Map<Path, readonly Diagnostic[]> | undefined;
/**
* The map has key by source file's path that has been changed
*/
@@ -261,6 +265,8 @@ export type SavedBuildProgramEmitState =
| "outSignature"
| "latestChangedDtsFile"
| "hasChangedEmitSignature"
| "buildInfoEmitPending"
| "emitDiagnosticsPerFile"
>
& { changedFilesSet: BuilderProgramState["changedFilesSet"] | undefined; };
@@ -376,25 +382,33 @@ function createBuilderProgramState(newProgram: Program, oldState: Readonly<Reusa
// Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated
addFileToChangeSet(state, sourceFilePath);
}
else if (canCopySemanticDiagnostics) {
else {
const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!;
if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return;
if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return;
// Unchanged file copy diagnostics
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
if (diagnostics) {
state.semanticDiagnosticsPerFile!.set(
const emitDiagnostics = oldState!.emitDiagnosticsPerFile?.get(sourceFilePath);
if (emitDiagnostics) {
(state.emitDiagnosticsPerFile ??= new Map()).set(
sourceFilePath,
oldState!.hasReusableDiagnostic ?
convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) :
repopulateDiagnostics(diagnostics as readonly Diagnostic[], newProgram),
convertToDiagnostics(emitDiagnostics as readonly ReusableDiagnostic[], newProgram) :
repopulateDiagnostics(emitDiagnostics as readonly Diagnostic[], newProgram),
);
if (!state.semanticDiagnosticsFromOldState) {
state.semanticDiagnosticsFromOldState = new Set();
}
if (canCopySemanticDiagnostics) {
if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return;
if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return;
// Unchanged file copy diagnostics
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
if (diagnostics) {
state.semanticDiagnosticsPerFile!.set(
sourceFilePath,
oldState!.hasReusableDiagnostic ?
convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) :
repopulateDiagnostics(diagnostics as readonly Diagnostic[], newProgram),
);
(state.semanticDiagnosticsFromOldState ??= new Set()).add(sourceFilePath);
}
state.semanticDiagnosticsFromOldState.add(sourceFilePath);
}
}
if (canCopyEmitSignatures) {
@@ -570,6 +584,8 @@ function backupBuilderProgramEmitState(state: Readonly<BuilderProgramState>): Sa
latestChangedDtsFile: state.latestChangedDtsFile,
hasChangedEmitSignature: state.hasChangedEmitSignature,
changedFilesSet: outFilePath ? new Set(state.changedFilesSet) : undefined,
buildInfoEmitPending: state.buildInfoEmitPending,
emitDiagnosticsPerFile: state.emitDiagnosticsPerFile && new Map(state.emitDiagnosticsPerFile),
};
}
@@ -581,6 +597,8 @@ function restoreBuilderProgramEmitState(state: BuilderProgramState, savedEmitSta
state.outSignature = savedEmitState.outSignature;
state.latestChangedDtsFile = savedEmitState.latestChangedDtsFile;
state.hasChangedEmitSignature = savedEmitState.hasChangedEmitSignature;
state.buildInfoEmitPending = savedEmitState.buildInfoEmitPending;
state.emitDiagnosticsPerFile = savedEmitState.emitDiagnosticsPerFile;
if (savedEmitState.changedFilesSet) state.changedFilesSet = savedEmitState.changedFilesSet;
}
@@ -692,6 +710,19 @@ function getNextAffectedFilePendingEmit(state: BuilderProgramState, emitOnlyDtsF
});
}
function getNextPendingEmitDiagnosticsFile(state: BuilderProgramState) {
if (!state.emitDiagnosticsPerFile?.size) return undefined;
return forEachEntry(state.emitDiagnosticsPerFile, (diagnostics, path) => {
const affectedFile = state.program!.getSourceFileByPath(path);
if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program!)) {
state.emitDiagnosticsPerFile!.delete(path);
return undefined;
}
const seenKind = state.seenEmittedFiles?.get(affectedFile.resolvedPath) || BuilderFileEmit.None;
if (!(seenKind & BuilderFileEmit.AllDts)) return { affectedFile, diagnostics, seenKind };
});
}
function removeDiagnosticsOfLibraryFiles(state: BuilderProgramState) {
if (!state.cleanedDiagnosticsOfLibFiles) {
state.cleanedDiagnosticsOfLibFiles = true;
@@ -997,6 +1028,7 @@ export interface ProgramMultiFileEmitBuildInfo {
referencedMap: ProgramBuildInfoReferencedMap | undefined;
exportedModulesMap: ProgramBuildInfoReferencedMap | undefined;
semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined;
emitDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined;
affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined;
changeFileSet: readonly ProgramBuildInfoFileId[] | undefined;
emitSignatures: readonly ProgramBuildInfoEmitSignature[] | undefined;
@@ -1140,21 +1172,7 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde
});
}
let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined;
if (state.semanticDiagnosticsPerFile) {
for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) {
const value = state.semanticDiagnosticsPerFile.get(key)!;
(semanticDiagnosticsPerFile ||= []).push(
value.length ?
[
toFileId(key),
convertToReusableDiagnostics(value, relativeToBuildInfo),
] :
toFileId(key),
);
}
}
const semanticDiagnosticsPerFile = convertToProgramBuildInfoDiagnostics(state.semanticDiagnosticsPerFile);
let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined;
if (state.affectedFilesPendingEmit?.size) {
const fullEmitForOptions = getBuilderFileEmit(state.compilerOptions);
@@ -1181,7 +1199,7 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde
(changeFileSet ||= []).push(toFileId(path));
}
}
const emitDiagnosticsPerFile = convertToProgramBuildInfoDiagnostics(state.emitDiagnosticsPerFile);
const program: ProgramMultiFileEmitBuildInfo = {
fileNames,
fileInfos,
@@ -1191,6 +1209,7 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde
referencedMap,
exportedModulesMap,
semanticDiagnosticsPerFile,
emitDiagnosticsPerFile,
affectedFilesPendingEmit,
changeFileSet,
emitSignatures,
@@ -1257,80 +1276,97 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde
(result ||= {})[name] = convertToReusableCompilerOptionValue(
optionInfo,
options[name] as CompilerOptionsValue,
relativeToBuildInfoEnsuringAbsolutePath,
);
}
}
return result;
}
}
function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) {
if (option) {
Debug.assert(option.type !== "listOrElement");
if (option.type === "list") {
const values = value as readonly string[];
if (option.element.isFilePath && values.length) {
return values.map(relativeToBuildInfo);
function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue) {
if (option) {
Debug.assert(option.type !== "listOrElement");
if (option.type === "list") {
const values = value as readonly string[];
if (option.element.isFilePath && values.length) {
return values.map(relativeToBuildInfoEnsuringAbsolutePath);
}
}
else if (option.isFilePath) {
return relativeToBuildInfoEnsuringAbsolutePath(value as string);
}
}
else if (option.isFilePath) {
return relativeToBuildInfo(value as string);
}
return value;
}
return value;
}
function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] {
Debug.assert(!!diagnostics.length);
return diagnostics.map(diagnostic => {
const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo);
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
result.reportDeprecated = diagnostic.reportsDeprecated;
result.source = diagnostic.source;
result.skippedOn = diagnostic.skippedOn;
const { relatedInformation } = diagnostic;
result.relatedInformation = relatedInformation ?
relatedInformation.length ?
relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) :
[] :
undefined;
function convertToProgramBuildInfoDiagnostics(diagnostics: Map<Path, readonly Diagnostic[]> | undefined) {
let result: ProgramBuildInfoDiagnostic[] | undefined;
if (diagnostics) {
for (const key of arrayFrom(diagnostics.keys()).sort(compareStringsCaseSensitive)) {
const value = diagnostics.get(key)!;
(result ||= []).push(
value.length ?
[
toFileId(key),
convertToReusableDiagnostics(value),
] :
toFileId(key),
);
}
}
return result;
});
}
}
function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation {
const { file } = diagnostic;
return {
...diagnostic,
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined,
messageText: isString(diagnostic.messageText) ? diagnostic.messageText : convertToReusableDiagnosticMessageChain(diagnostic.messageText),
};
}
function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[]): readonly ReusableDiagnostic[] {
Debug.assert(!!diagnostics.length);
return diagnostics.map(diagnostic => {
const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic);
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
result.reportDeprecated = diagnostic.reportsDeprecated;
result.source = diagnostic.source;
result.skippedOn = diagnostic.skippedOn;
const { relatedInformation } = diagnostic;
result.relatedInformation = relatedInformation ?
relatedInformation.length ?
relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r)) :
[] :
undefined;
return result;
});
}
function convertToReusableDiagnosticMessageChain(chain: DiagnosticMessageChain): ReusableDiagnosticMessageChain {
if (chain.repopulateInfo) {
function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation): ReusableDiagnosticRelatedInformation {
const { file } = diagnostic;
return {
info: chain.repopulateInfo(),
next: convertToReusableDiagnosticMessageChainArray(chain.next),
...diagnostic,
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined,
messageText: isString(diagnostic.messageText) ? diagnostic.messageText : convertToReusableDiagnosticMessageChain(diagnostic.messageText),
};
}
const next = convertToReusableDiagnosticMessageChainArray(chain.next);
return next === chain.next ? chain : { ...chain, next };
}
function convertToReusableDiagnosticMessageChainArray(array: DiagnosticMessageChain[] | undefined): ReusableDiagnosticMessageChain[] | undefined {
if (!array) return array;
return forEach(array, (chain, index) => {
const reusable = convertToReusableDiagnosticMessageChain(chain);
if (chain === reusable) return undefined;
const result: ReusableDiagnosticMessageChain[] = index > 0 ? array.slice(0, index - 1) : [];
result.push(reusable);
for (let i = index + 1; i < array.length; i++) {
result.push(convertToReusableDiagnosticMessageChain(array[i]));
function convertToReusableDiagnosticMessageChain(chain: DiagnosticMessageChain): ReusableDiagnosticMessageChain {
if (chain.repopulateInfo) {
return {
info: chain.repopulateInfo(),
next: convertToReusableDiagnosticMessageChainArray(chain.next),
};
}
return result;
}) || array;
const next = convertToReusableDiagnosticMessageChainArray(chain.next);
return next === chain.next ? chain : { ...chain, next };
}
function convertToReusableDiagnosticMessageChainArray(array: DiagnosticMessageChain[] | undefined): ReusableDiagnosticMessageChain[] | undefined {
if (!array) return array;
return forEach(array, (chain, index) => {
const reusable = convertToReusableDiagnosticMessageChain(chain);
if (chain === reusable) return undefined;
const result: ReusableDiagnosticMessageChain[] = index > 0 ? array.slice(0, index - 1) : [];
result.push(reusable);
for (let i = index + 1; i < array.length; i++) {
result.push(convertToReusableDiagnosticMessageChain(array[i]));
}
return result;
}) || array;
}
}
/** @internal */
@@ -1497,6 +1533,14 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos
if (!outFile(state.compilerOptions)) {
const pendingAffectedFile = getNextAffectedFilePendingEmit(state, emitOnlyDtsFiles);
if (!pendingAffectedFile) {
const pendingForDiagnostics = getNextPendingEmitDiagnosticsFile(state);
if (pendingForDiagnostics) {
(state.seenEmittedFiles ??= new Map()).set(pendingForDiagnostics.affectedFile.resolvedPath, pendingForDiagnostics.seenKind | BuilderFileEmit.AllDts);
return {
result: { emitSkipped: true, diagnostics: pendingForDiagnostics.diagnostics },
affected: pendingForDiagnostics.affectedFile,
};
}
// Emit buildinfo if pending
if (!state.buildInfoEmitPending) return undefined;
const affected = state.program!;
@@ -1550,6 +1594,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos
const pendingKind = getPendingEmitKind(existingPending, emitKind | existing);
if (pendingKind) (state.affectedFilesPendingEmit ??= new Map()).set(affectedSourceFile.resolvedPath, pendingKind);
else state.affectedFilesPendingEmit?.delete(affectedSourceFile.resolvedPath);
if (result.diagnostics.length) (state.emitDiagnosticsPerFile ??= new Map()).set(affectedSourceFile.resolvedPath, result.diagnostics);
}
else {
// In program clear our changed files since any emit handles all changes
@@ -1765,6 +1810,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos
function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) {
const existingKind = state.affectedFilesPendingEmit?.get(affectedFilePendingEmit) || BuilderFileEmit.None;
(state.affectedFilesPendingEmit ??= new Map()).set(affectedFilePendingEmit, existingKind | kind);
state.emitDiagnosticsPerFile?.delete(affectedFilePendingEmit);
}
/** @internal */
@@ -1840,7 +1886,8 @@ export function createBuilderProgramUsingProgramBuildInfo(buildInfo: BuildInfo,
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]),
semanticDiagnosticsPerFile: toPerFileDiagnostics(program.semanticDiagnosticsPerFile),
emitDiagnosticsPerFile: toPerFileDiagnostics(program.emitDiagnosticsPerFile),
hasReusableDiagnostic: true,
affectedFilesPendingEmit: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(isNumber(value) ? value : value[0]), value => toBuilderFileEmit(value, fullEmitForOptions!)),
changedFilesSet: new Set(map(program.changeFileSet, toFilePath)),
@@ -1900,6 +1947,10 @@ export function createBuilderProgramUsingProgramBuildInfo(buildInfo: BuildInfo,
referenceMap.forEach(([fileId, fileIdListId]) => map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)));
return map;
}
function toPerFileDiagnostics(diagnostics: readonly ProgramBuildInfoDiagnostic[] | undefined): Map<Path, readonly ReusableDiagnostic[]> | undefined {
return diagnostics && arrayToMap(diagnostics, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]);
}
}
/** @internal */
+40 -48
View File
@@ -800,28 +800,6 @@ export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFi
emitBuildInfo(bundleBuildInfo, buildInfoPath);
tracing?.pop();
if (!emitSkipped && emittedFilesList) {
if (!emitOnly) {
if (jsFilePath) {
emittedFilesList.push(jsFilePath);
}
if (sourceMapFilePath) {
emittedFilesList.push(sourceMapFilePath);
}
if (buildInfoPath) {
emittedFilesList.push(buildInfoPath);
}
}
if (emitOnly !== EmitOnly.Js) {
if (declarationFilePath) {
emittedFilesList.push(declarationFilePath);
}
if (declarationMapPath) {
emittedFilesList.push(declarationMapPath);
}
}
}
function relativeToBuildInfo(path: string) {
return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName));
}
@@ -837,6 +815,7 @@ export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFi
const buildInfo = host.getBuildInfo(bundle) || createBuildInfo(/*program*/ undefined, bundle);
// Pass buildinfo as additional data to avoid having to reparse
writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText(buildInfo), /*writeByteOrderMark*/ false, /*sourceFiles*/ undefined, { buildInfo });
emittedFilesList?.push(buildInfoPath);
}
function emitJsFileOrBundle(
@@ -888,6 +867,13 @@ export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFi
// Clean up emit nodes on parse tree
transform.dispose();
if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo;
if (emittedFilesList) {
emittedFilesList.push(jsFilePath);
if (sourceMapFilePath) {
emittedFilesList.push(sourceMapFilePath);
}
}
}
function emitDeclarationFileOrBundle(
@@ -917,35 +903,35 @@ export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFi
}
}
const printerOptions: PrinterOptions = {
removeComments: compilerOptions.removeComments,
newLine: compilerOptions.newLine,
noEmitHelpers: true,
module: compilerOptions.module,
target: compilerOptions.target,
sourceMap: !forceDtsEmit && compilerOptions.declarationMap,
inlineSourceMap: compilerOptions.inlineSourceMap,
extendedDiagnostics: compilerOptions.extendedDiagnostics,
onlyPrintJsDocStyle: true,
omitBraceSourceMapPositions: true,
writeBundleFileInfo: !!bundleBuildInfo,
recordInternalSection: !!bundleBuildInfo,
relativeToBuildInfo,
};
const declarationPrinter = createPrinter(printerOptions, {
// resolver hooks
hasGlobalName: resolver.hasGlobalName,
// transform hooks
onEmitNode: declarationTransform.emitNodeWithNotification,
isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled,
substituteNode: declarationTransform.substituteNode,
});
const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit;
emitSkipped = emitSkipped || declBlocked;
if (!declBlocked || forceDtsEmit) {
Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform");
const printerOptions: PrinterOptions = {
removeComments: compilerOptions.removeComments,
newLine: compilerOptions.newLine,
noEmitHelpers: true,
module: compilerOptions.module,
target: compilerOptions.target,
sourceMap: !forceDtsEmit && compilerOptions.declarationMap,
inlineSourceMap: compilerOptions.inlineSourceMap,
extendedDiagnostics: compilerOptions.extendedDiagnostics,
onlyPrintJsDocStyle: true,
omitBraceSourceMapPositions: true,
writeBundleFileInfo: !!bundleBuildInfo,
recordInternalSection: !!bundleBuildInfo,
relativeToBuildInfo,
};
const declarationPrinter = createPrinter(printerOptions, {
// resolver hooks
hasGlobalName: resolver.hasGlobalName,
// transform hooks
onEmitNode: declarationTransform.emitNodeWithNotification,
isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled,
substituteNode: declarationTransform.substituteNode,
});
printSourceFileOrBundle(
declarationFilePath,
declarationMapPath,
@@ -959,9 +945,15 @@ export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFi
// Explicitly do not passthru either `inline` option
},
);
if (emittedFilesList) {
emittedFilesList.push(declarationFilePath);
if (declarationMapPath) {
emittedFilesList.push(declarationMapPath);
}
}
if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo;
}
declarationTransform.dispose();
if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo;
}
function collectLinkedAliases(node: Node) {
+2 -1
View File
@@ -1809,7 +1809,8 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
if (
(buildInfo.program as ProgramMultiFileEmitBuildInfo).changeFileSet?.length ||
(!project.options.noEmit ?
(buildInfo.program as ProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit?.length :
(buildInfo.program as ProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit?.length ||
(buildInfo.program as ProgramMultiFileEmitBuildInfo).emitDiagnosticsPerFile?.length :
some((buildInfo.program as ProgramMultiFileEmitBuildInfo).semanticDiagnosticsPerFile, isArray))
) {
return {
+12 -6
View File
@@ -180,13 +180,14 @@ export type ReadableProgramBuildInfoFileInfo<T> = Omit<ts.BuilderState.FileInfo,
export type ReadableProgramBuildInfoRoot =
| [original: ts.ProgramBuildInfoFileId, readable: string]
| [original: ts.ProgramBuildInfoRootStartEnd, readable: readonly string[]];
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo, "fileIdsList" | "fileInfos" | "root" | "referencedMap" | "exportedModulesMap" | "semanticDiagnosticsPerFile" | "affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"> & {
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo, "fileIdsList" | "fileInfos" | "root" | "referencedMap" | "exportedModulesMap" | "semanticDiagnosticsPerFile" | "emitDiagnosticsPerFile" | "affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"> & {
fileNamesList: readonly (readonly string[])[] | undefined;
fileInfos: ts.MapLike<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo>>;
root: readonly ReadableProgramBuildInfoRoot[];
referencedMap: ts.MapLike<string[]> | undefined;
exportedModulesMap: ts.MapLike<string[]> | undefined;
semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
emitDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
affectedFilesPendingEmit: readonly ReadableProgramBuilderInfoFilePendingEmit[] | undefined;
changeFileSet: readonly string[] | undefined;
emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined;
@@ -240,11 +241,8 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
options: buildInfo.program.options,
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap),
semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d =>
ts.isNumber(d) ?
toFileName(d) :
[toFileName(d[0]), d[1]]
),
semanticDiagnosticsPerFile: toReadableProgramBuildInfoDiagnosticsPerFile(buildInfo.program.semanticDiagnosticsPerFile),
emitDiagnosticsPerFile: toReadableProgramBuildInfoDiagnosticsPerFile(buildInfo.program.emitDiagnosticsPerFile),
affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(value => toReadableProgramBuilderInfoFilePendingEmit(value, fullEmitForOptions!)),
changeFileSet: buildInfo.program.changeFileSet?.map(toFileName),
emitSignatures: buildInfo.program.emitSignatures?.map(s =>
@@ -334,6 +332,14 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
result = result ? `${result} | ${flag}` : flag;
}
}
function toReadableProgramBuildInfoDiagnosticsPerFile(diagnostics: ts.ProgramBuildInfoDiagnostic[] | undefined): readonly ReadableProgramBuildInfoDiagnostic[] | undefined {
return diagnostics?.map(d =>
ts.isNumber(d) ?
toFileName(d) :
[toFileName(d[0]), d[1]]
);
}
}
export function toPathWithSystem(sys: ts.System, fileName: string): ts.Path {
+21
View File
@@ -364,6 +364,26 @@ function verifyTscEditDiscrepancies({
expectedIndex++;
});
}
if (incrementalReadableBuildInfo?.program?.emitDiagnosticsPerFile) {
incrementalReadableBuildInfo.program.emitDiagnosticsPerFile.forEach(([actualFileOrArray]) => {
const actualFile = ts.isString(actualFileOrArray) ? actualFileOrArray : actualFileOrArray[0];
if (
!ts.find(
(cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).emitDiagnosticsPerFile,
([expectedFileOrArray]) => actualFile === (ts.isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]),
) && !ts.find(
(cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit,
([expectedFileOrArray]) => actualFile === (ts.isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]),
)
) {
addBaseline(
`Incremental build contains ${actualFile} file has errors, clean build does not have errors or does not mark is as pending emit: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`,
);
}
});
}
}
}
}
@@ -453,6 +473,7 @@ function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): {
options: { ...readableBuildInfo.program.options, noEmit: undefined },
exportedModulesMap: undefined,
affectedFilesPendingEmit: undefined,
emitDiagnosticsPerFile: undefined,
latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined,
},
size: undefined, // Size doesnt need to be equal
@@ -8,6 +8,10 @@ import {
jsonToReadableText,
} from "../helpers";
import {
libContent,
} from "../helpers/contents";
import {
noChangeOnlyRuns,
verifyTsc,
} from "../helpers/tsc";
import {
@@ -132,3 +136,43 @@ export function fn4() {
commandLineArgs: ["--b", "/src/packages/pkg2/tsconfig.json", "--verbose"],
});
});
verifyTsc({
scenario: "declarationEmit",
subScenario: "reports dts generation errors with incremental",
commandLineArgs: ["-b", `/src/project`, "--explainFiles", "--listEmittedFiles", "--v"],
fs: () =>
loadProjectFromFiles({
"/src/project/tsconfig.json": jsonToReadableText({
compilerOptions: {
module: "NodeNext",
moduleResolution: "NodeNext",
incremental: true,
declaration: true,
skipLibCheck: true,
skipDefaultLibCheck: true,
},
}),
"/src/project/index.ts": dedent`
import ky from 'ky';
export const api = ky.extend({});
`,
"/src/project/package.json": jsonToReadableText({
type: "module",
}),
"/src/project/node_modules/ky/distribution/index.d.ts": dedent`
type KyInstance = {
extend(options: Record<string,unknown>): KyInstance;
}
declare const ky: KyInstance;
export default ky;
`,
"/src/project/node_modules/ky/package.json": jsonToReadableText({
name: "ky",
type: "module",
main: "./distribution/index.js",
}),
"/lib/lib.esnext.full.d.ts": libContent,
}),
edits: noChangeOnlyRuns,
});
@@ -618,14 +618,17 @@ console.log(a);`,
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;`,
),
edits: [
noChangeRun,
{
caption: "modify public to protected",
edit: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "public", "protected"),
},
noChangeRun,
{
caption: "modify protected to public",
edit: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "protected", "public"),
},
noChangeRun,
],
});
}
@@ -959,4 +962,49 @@ console.log(a);`,
edit: fs => appendText(fs, "/src/project/src/bug.js", `export const something = 1;`),
}],
});
verifyTsc({
scenario: "incremental",
subScenario: "reports dts generation errors",
commandLineArgs: ["-p", `/src/project`, "--explainFiles", "--listEmittedFiles"],
fs: () =>
loadProjectFromFiles({
"/src/project/tsconfig.json": jsonToReadableText({
compilerOptions: {
module: "NodeNext",
moduleResolution: "NodeNext",
composite: true,
skipLibCheck: true,
skipDefaultLibCheck: true,
},
}),
"/src/project/index.ts": Utils.dedent`
import ky from 'ky';
export const api = ky.extend({});
`,
"/src/project/package.json": jsonToReadableText({
type: "module",
}),
"/src/project/node_modules/ky/distribution/index.d.ts": Utils.dedent`
type KyInstance = {
extend(options: Record<string,unknown>): KyInstance;
}
declare const ky: KyInstance;
export default ky;
`,
"/src/project/node_modules/ky/package.json": jsonToReadableText({
name: "ky",
type: "module",
main: "./distribution/index.js",
}),
"/lib/lib.esnext.full.d.ts": libContent,
}),
edits: [
noChangeRun,
{
...noChangeRun,
commandLineArgs: ["-b", `/src/project`, "--explainFiles", "--listEmittedFiles", "-v"],
},
],
});
});