Skip to content

Commit daceaa2

Browse files
committed
Use declaration diagnostics in the d.ts signature for the file so it can be more accurate for detecting changes to file that could affect other files
Fixes #49527
1 parent d024e27 commit daceaa2

9 files changed

+173
-216
lines changed

src/compiler/builder.ts

+83-16
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,13 @@ namespace ts {
333333
* This is to allow the callers to be able to actually remove affected file only when the operation is complete
334334
* eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained
335335
*/
336-
function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash, host: BuilderProgramHost): SourceFile | Program | undefined {
336+
function getNextAffectedFile(
337+
state: BuilderProgramState,
338+
cancellationToken: CancellationToken | undefined,
339+
computeHash: BuilderState.ComputeHash,
340+
getCanonicalFileName: GetCanonicalFileName,
341+
host: BuilderProgramHost
342+
): SourceFile | Program | undefined {
337343
while (true) {
338344
const { affectedFiles } = state;
339345
if (affectedFiles) {
@@ -344,7 +350,14 @@ namespace ts {
344350
if (!seenAffectedFiles.has(affectedFile.resolvedPath)) {
345351
// Set the next affected file as seen and remove the cached semantic diagnostics
346352
state.affectedFilesIndex = affectedFilesIndex;
347-
handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash, host);
353+
handleDtsMayChangeOfAffectedFile(
354+
state,
355+
affectedFile,
356+
cancellationToken,
357+
computeHash,
358+
getCanonicalFileName,
359+
host
360+
);
348361
return affectedFile;
349362
}
350363
affectedFilesIndex++;
@@ -376,7 +389,14 @@ namespace ts {
376389
}
377390

378391
// Get next batch of affected files
379-
state.affectedFiles = BuilderState.getFilesAffectedByWithOldState(state, program, nextKey.value, cancellationToken, computeHash);
392+
state.affectedFiles = BuilderState.getFilesAffectedByWithOldState(
393+
state,
394+
program,
395+
nextKey.value,
396+
cancellationToken,
397+
computeHash,
398+
getCanonicalFileName,
399+
);
380400
state.currentChangedFilePath = nextKey.value;
381401
state.affectedFilesIndex = 0;
382402
if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set();
@@ -435,6 +455,7 @@ namespace ts {
435455
affectedFile: SourceFile,
436456
cancellationToken: CancellationToken | undefined,
437457
computeHash: BuilderState.ComputeHash,
458+
getCanonicalFileName: GetCanonicalFileName,
438459
host: BuilderProgramHost,
439460
) {
440461
removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath);
@@ -451,11 +472,19 @@ namespace ts {
451472
affectedFile,
452473
cancellationToken,
453474
computeHash,
475+
getCanonicalFileName,
454476
);
455477
return;
456478
}
457479
if (state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) return;
458-
handleDtsMayChangeOfReferencingExportOfAffectedFile(state, affectedFile, cancellationToken, computeHash, host);
480+
handleDtsMayChangeOfReferencingExportOfAffectedFile(
481+
state,
482+
affectedFile,
483+
cancellationToken,
484+
computeHash,
485+
getCanonicalFileName,
486+
host,
487+
);
459488
}
460489

461490
/**
@@ -467,6 +496,7 @@ namespace ts {
467496
path: Path,
468497
cancellationToken: CancellationToken | undefined,
469498
computeHash: BuilderState.ComputeHash,
499+
getCanonicalFileName: GetCanonicalFileName,
470500
host: BuilderProgramHost
471501
): void {
472502
removeSemanticDiagnosticsOf(state, path);
@@ -486,6 +516,7 @@ namespace ts {
486516
sourceFile,
487517
cancellationToken,
488518
computeHash,
519+
getCanonicalFileName,
489520
!host.disableUseFileVersionAsSignature
490521
);
491522
// If not dts emit, nothing more to do
@@ -520,6 +551,7 @@ namespace ts {
520551
filePath: Path,
521552
cancellationToken: CancellationToken | undefined,
522553
computeHash: BuilderState.ComputeHash,
554+
getCanonicalFileName: GetCanonicalFileName,
523555
host: BuilderProgramHost,
524556
): boolean {
525557
if (!state.fileInfos.get(filePath)?.affectsGlobalScope) return false;
@@ -530,6 +562,7 @@ namespace ts {
530562
file.resolvedPath,
531563
cancellationToken,
532564
computeHash,
565+
getCanonicalFileName,
533566
host,
534567
));
535568
removeDiagnosticsOfLibraryFiles(state);
@@ -544,6 +577,7 @@ namespace ts {
544577
affectedFile: SourceFile,
545578
cancellationToken: CancellationToken | undefined,
546579
computeHash: BuilderState.ComputeHash,
580+
getCanonicalFileName: GetCanonicalFileName,
547581
host: BuilderProgramHost
548582
) {
549583
// If there was change in signature (dts output) for the changed file,
@@ -561,8 +595,8 @@ namespace ts {
561595
const currentPath = queue.pop()!;
562596
if (!seenFileNamesMap.has(currentPath)) {
563597
seenFileNamesMap.set(currentPath, true);
564-
if (handleDtsMayChangeOfGlobalScope(state, currentPath, cancellationToken, computeHash, host)) return;
565-
handleDtsMayChangeOf(state, currentPath, cancellationToken, computeHash, host);
598+
if (handleDtsMayChangeOfGlobalScope(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host)) return;
599+
handleDtsMayChangeOf(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host);
566600
if (isChangedSignature(state, currentPath)) {
567601
const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!;
568602
queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath));
@@ -575,7 +609,7 @@ namespace ts {
575609
// Go through exported modules from cache first
576610
// If exported modules has path, all files referencing file exported from are affected
577611
state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => {
578-
if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, cancellationToken, computeHash, host)) return true;
612+
if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, cancellationToken, computeHash, getCanonicalFileName, host)) return true;
579613
const references = state.referencedMap!.getKeys(exportedFromPath);
580614
return references && forEachKey(references, filePath =>
581615
handleDtsMayChangeOfFileAndExportsOfFile(
@@ -584,6 +618,7 @@ namespace ts {
584618
seenFileAndExportsOfFile,
585619
cancellationToken,
586620
computeHash,
621+
getCanonicalFileName,
587622
host,
588623
)
589624
);
@@ -600,12 +635,13 @@ namespace ts {
600635
seenFileAndExportsOfFile: Set<string>,
601636
cancellationToken: CancellationToken | undefined,
602637
computeHash: BuilderState.ComputeHash,
638+
getCanonicalFileName: GetCanonicalFileName,
603639
host: BuilderProgramHost,
604640
): boolean | undefined {
605641
if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) return undefined;
606642

607-
if (handleDtsMayChangeOfGlobalScope(state, filePath, cancellationToken, computeHash, host)) return true;
608-
handleDtsMayChangeOf(state, filePath, cancellationToken, computeHash, host);
643+
if (handleDtsMayChangeOfGlobalScope(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host)) return true;
644+
handleDtsMayChangeOf(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host);
609645

610646
// If exported modules has path, all files referencing file exported from are affected
611647
state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath =>
@@ -615,6 +651,7 @@ namespace ts {
615651
seenFileAndExportsOfFile,
616652
cancellationToken,
617653
computeHash,
654+
getCanonicalFileName,
618655
host,
619656
)
620657
);
@@ -627,6 +664,7 @@ namespace ts {
627664
referencingFilePath,
628665
cancellationToken,
629666
computeHash,
667+
getCanonicalFileName,
630668
host,
631669
)
632670
);
@@ -1038,8 +1076,30 @@ namespace ts {
10381076
return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray };
10391077
}
10401078

1079+
function getTextHandlingSourceMapForSignature(text: string, data: WriteFileCallbackData | undefined) {
1080+
return data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text;
1081+
}
1082+
1083+
export function computeSignatureWithDiagnostics(
1084+
program: Program,
1085+
text: string,
1086+
computeHash: BuilderState.ComputeHash | undefined,
1087+
getCanonicalFileName: GetCanonicalFileName,
1088+
data: WriteFileCallbackData | undefined
1089+
) {
1090+
text = getTextHandlingSourceMapForSignature(text, data);
1091+
if (data?.diagnostics?.length) {
1092+
text += formatDiagnostics(data.diagnostics, {
1093+
getCurrentDirectory: () => program.getCommonSourceDirectory(),
1094+
getCanonicalFileName,
1095+
getNewLine: () => "\n",
1096+
});
1097+
}
1098+
return (computeHash || generateDjb2Hash)(text);
1099+
}
1100+
10411101
export function computeSignature(text: string, computeHash: BuilderState.ComputeHash | undefined, data?: WriteFileCallbackData) {
1042-
return (computeHash || generateDjb2Hash)(data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text);
1102+
return (computeHash || generateDjb2Hash)(getTextHandlingSourceMapForSignature(text, data));
10431103
}
10441104

10451105
export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram;
@@ -1108,7 +1168,7 @@ namespace ts {
11081168
* in that order would be used to write the files
11091169
*/
11101170
function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
1111-
let affected = getNextAffectedFile(state, cancellationToken, computeHash, host);
1171+
let affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host);
11121172
let emitKind = BuilderFileEmit.Full;
11131173
let isPendingEmitFile = false;
11141174
if (!affected) {
@@ -1170,19 +1230,26 @@ namespace ts {
11701230
const file = sourceFiles[0];
11711231
const info = state.fileInfos.get(file.resolvedPath)!;
11721232
if (info.signature === file.version) {
1173-
newSignature = computeSignature(text, computeHash, data);
1174-
if (newSignature !== file.version) { // Update it
1233+
const signature = computeSignatureWithDiagnostics(
1234+
state.program!,
1235+
text,
1236+
computeHash,
1237+
getCanonicalFileName,
1238+
data,
1239+
);
1240+
if (!data?.diagnostics?.length) newSignature = signature;
1241+
if (signature !== file.version) { // Update it
11751242
if (host.storeFilesChangingSignatureDuringEmit) (state.filesChangingSignature ||= new Set()).add(file.resolvedPath);
11761243
if (state.exportedModulesMap) BuilderState.updateExportedModules(state, file, file.exportedModulesFromDeclarationEmit);
11771244
if (state.affectedFiles) {
11781245
// Keep old signature so we know what to undo if cancellation happens
11791246
const existing = state.oldSignatures?.get(file.resolvedPath);
11801247
if (existing === undefined) (state.oldSignatures ||= new Map()).set(file.resolvedPath, info.signature || false);
1181-
info.signature = newSignature;
1248+
info.signature = signature;
11821249
}
11831250
else {
11841251
// These are directly commited
1185-
info.signature = newSignature;
1252+
info.signature = signature;
11861253
state.oldExportedModulesMap?.clear();
11871254
}
11881255
}
@@ -1287,7 +1354,7 @@ namespace ts {
12871354
*/
12881355
function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult<readonly Diagnostic[]> {
12891356
while (true) {
1290-
const affected = getNextAffectedFile(state, cancellationToken, computeHash, host);
1357+
const affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host);
12911358
if (!affected) {
12921359
// Done
12931360
return undefined;

src/compiler/builderState.ts

+45-9
Original file line numberDiff line numberDiff line change
@@ -321,24 +321,45 @@ namespace ts {
321321
/**
322322
* Gets the files affected by the path from the program
323323
*/
324-
export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash): readonly SourceFile[] {
325-
const result = getFilesAffectedByWithOldState(state, programOfThisState, path, cancellationToken, computeHash);
324+
export function getFilesAffectedBy(
325+
state: BuilderState,
326+
programOfThisState: Program,
327+
path: Path,
328+
cancellationToken: CancellationToken | undefined,
329+
computeHash: ComputeHash,
330+
getCanonicalFileName: GetCanonicalFileName,
331+
): readonly SourceFile[] {
332+
const result = getFilesAffectedByWithOldState(
333+
state,
334+
programOfThisState,
335+
path,
336+
cancellationToken,
337+
computeHash,
338+
getCanonicalFileName,
339+
);
326340
state.oldSignatures?.clear();
327341
state.oldExportedModulesMap?.clear();
328342
return result;
329343
}
330344

331-
export function getFilesAffectedByWithOldState(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash): readonly SourceFile[] {
345+
export function getFilesAffectedByWithOldState(
346+
state: BuilderState,
347+
programOfThisState: Program,
348+
path: Path,
349+
cancellationToken: CancellationToken | undefined,
350+
computeHash: ComputeHash,
351+
getCanonicalFileName: GetCanonicalFileName,
352+
): readonly SourceFile[] {
332353
const sourceFile = programOfThisState.getSourceFileByPath(path);
333354
if (!sourceFile) {
334355
return emptyArray;
335356
}
336357

337-
if (!updateShapeSignature(state, programOfThisState, sourceFile, cancellationToken, computeHash)) {
358+
if (!updateShapeSignature(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName)) {
338359
return [sourceFile];
339360
}
340361

341-
return (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, cancellationToken, computeHash);
362+
return (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName);
342363
}
343364

344365
export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) {
@@ -349,7 +370,15 @@ namespace ts {
349370
/**
350371
* Returns if the shape of the signature has changed since last emit
351372
*/
352-
export function updateShapeSignature(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, useFileVersionAsSignature = state.useFileVersionAsSignature) {
373+
export function updateShapeSignature(
374+
state: BuilderState,
375+
programOfThisState: Program,
376+
sourceFile: SourceFile,
377+
cancellationToken: CancellationToken | undefined,
378+
computeHash: ComputeHash,
379+
getCanonicalFileName: GetCanonicalFileName,
380+
useFileVersionAsSignature = state.useFileVersionAsSignature
381+
) {
353382
// If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
354383
if (state.hasCalledUpdateShapeSignature?.has(sourceFile.resolvedPath)) return false;
355384

@@ -361,7 +390,7 @@ namespace ts {
361390
sourceFile,
362391
(fileName, text, _writeByteOrderMark, _onError, sourceFiles, data) => {
363392
Debug.assert(isDeclarationFileName(fileName), `File extension for signature expected to be dts: Got:: ${fileName}`);
364-
latestSignature = computeSignature(text, computeHash, data);
393+
latestSignature = computeSignatureWithDiagnostics(programOfThisState, text, computeHash, getCanonicalFileName, data);
365394
if (latestSignature !== prevSignature) {
366395
updateExportedModules(state, sourceFile, sourceFiles![0].exportedModulesFromDeclarationEmit);
367396
}
@@ -550,7 +579,14 @@ namespace ts {
550579
/**
551580
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
552581
*/
553-
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash) {
582+
function getFilesAffectedByUpdatedShapeWhenModuleEmit(
583+
state: BuilderState,
584+
programOfThisState: Program,
585+
sourceFileWithUpdatedShape: SourceFile,
586+
cancellationToken: CancellationToken | undefined,
587+
computeHash: ComputeHash,
588+
getCanonicalFileName: GetCanonicalFileName,
589+
) {
554590
if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) {
555591
return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
556592
}
@@ -573,7 +609,7 @@ namespace ts {
573609
if (!seenFileNamesMap.has(currentPath)) {
574610
const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!;
575611
seenFileNamesMap.set(currentPath, currentSourceFile);
576-
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash)) {
612+
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash, getCanonicalFileName)) {
577613
queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath));
578614
}
579615
}

0 commit comments

Comments
 (0)