Skip to content

Commit df21926

Browse files
authored
To handle d.ts emit errors that could affect other files, in incremental mode use d.ts emit text + diagnostics as signature of the file (#49543)
* Add test when declaration emit has errors and d.ts emit doesnt change which results in incorrect incremental behaviour * Refactor * 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 * Renames and clarifications * Simplify serialize declaration diagnostics for signature purpose Do not serialize file name if error is in same file we are emitting. this should avoid having to do file path computation in most cases. Locations are start and length instead of line and character. Do not use any indents * Fix baselines
1 parent 8ed846c commit df21926

11 files changed

+862
-118
lines changed

src/compiler/builder.ts

+107-24
Large diffs are not rendered by default.

src/compiler/builderState.ts

+61-25
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ namespace ts {
33
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
44
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput {
55
const outputFiles: OutputFile[] = [];
6-
const { emitSkipped, diagnostics, exportedModulesFromDeclarationEmit } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit);
7-
return { outputFiles, emitSkipped, diagnostics, exportedModulesFromDeclarationEmit };
6+
const { emitSkipped, diagnostics } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit);
7+
return { outputFiles, emitSkipped, diagnostics };
88

99
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
1010
outputFiles.push({ name: fileName, writeByteOrderMark, text });
@@ -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,30 +370,42 @@ 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

356385
const info = state.fileInfos.get(sourceFile.resolvedPath)!;
357386
const prevSignature = info.signature;
358387
let latestSignature: string | undefined;
359388
if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) {
360-
const emitOutput = getFileEmitOutput(
361-
programOfThisState,
389+
programOfThisState.emit(
362390
sourceFile,
363-
/*emitOnlyDtsFiles*/ true,
391+
(fileName, text, _writeByteOrderMark, _onError, sourceFiles, data) => {
392+
Debug.assert(isDeclarationFileName(fileName), `File extension for signature expected to be dts: Got:: ${fileName}`);
393+
latestSignature = computeSignatureWithDiagnostics(
394+
sourceFile,
395+
text,
396+
computeHash,
397+
getCanonicalFileName,
398+
data,
399+
);
400+
if (latestSignature !== prevSignature) {
401+
updateExportedModules(state, sourceFile, sourceFiles![0].exportedModulesFromDeclarationEmit);
402+
}
403+
},
364404
cancellationToken,
405+
/*emitOnlyDtsFiles*/ true,
365406
/*customTransformers*/ undefined,
366407
/*forceDtsEmit*/ true
367408
);
368-
const firstDts = firstOrUndefined(emitOutput.outputFiles);
369-
if (firstDts) {
370-
Debug.assert(isDeclarationFileName(firstDts.name), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`);
371-
latestSignature = computeSignature(firstDts.text, computeHash);
372-
if (latestSignature !== prevSignature) {
373-
updateExportedModules(state, sourceFile, emitOutput.exportedModulesFromDeclarationEmit);
374-
}
375-
}
376409
}
377410
// Default is to use file version as signature
378411
if (latestSignature === undefined) {
@@ -395,10 +428,6 @@ namespace ts {
395428
return latestSignature !== prevSignature;
396429
}
397430

398-
export function computeSignature(text: string, computeHash: ComputeHash | undefined) {
399-
return (computeHash || generateDjb2Hash)(text);
400-
}
401-
402431
/**
403432
* Coverts the declaration emit result into exported modules map
404433
*/
@@ -556,7 +585,14 @@ namespace ts {
556585
/**
557586
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
558587
*/
559-
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash) {
588+
function getFilesAffectedByUpdatedShapeWhenModuleEmit(
589+
state: BuilderState,
590+
programOfThisState: Program,
591+
sourceFileWithUpdatedShape: SourceFile,
592+
cancellationToken: CancellationToken | undefined,
593+
computeHash: ComputeHash,
594+
getCanonicalFileName: GetCanonicalFileName,
595+
) {
560596
if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) {
561597
return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
562598
}
@@ -579,7 +615,7 @@ namespace ts {
579615
if (!seenFileNamesMap.has(currentPath)) {
580616
const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!;
581617
seenFileNamesMap.set(currentPath, currentSourceFile);
582-
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash)) {
618+
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash, getCanonicalFileName)) {
583619
queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath));
584620
}
585621
}

src/compiler/builderStatePublic.ts

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ namespace ts {
33
outputFiles: OutputFile[];
44
emitSkipped: boolean;
55
/* @internal */ diagnostics: readonly Diagnostic[];
6-
/* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
76
}
87

98
export interface OutputFile {

src/compiler/emitter.ts

+14-19
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ namespace ts {
288288
const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint");
289289
let bundleBuildInfo: BundleBuildInfo | undefined;
290290
let emitSkipped = false;
291-
let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined;
292291

293292
// Emit each output file
294293
enter();
@@ -308,7 +307,6 @@ namespace ts {
308307
diagnostics: emitterDiagnostics.getDiagnostics(),
309308
emittedFiles: emittedFilesList,
310309
sourceMaps: sourceMapDataList,
311-
exportedModulesFromDeclarationEmit
312310
};
313311

314312
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) {
@@ -414,7 +412,7 @@ namespace ts {
414412
});
415413

416414
Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform");
417-
printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions);
415+
printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform, printer, compilerOptions);
418416

419417
// Clean up emit nodes on parse tree
420418
transform.dispose();
@@ -453,7 +451,7 @@ namespace ts {
453451
noEmitHelpers: true,
454452
module: compilerOptions.module,
455453
target: compilerOptions.target,
456-
sourceMap: compilerOptions.sourceMap,
454+
sourceMap: !forceDtsEmit && compilerOptions.declarationMap,
457455
inlineSourceMap: compilerOptions.inlineSourceMap,
458456
extendedDiagnostics: compilerOptions.extendedDiagnostics,
459457
onlyPrintJsDocStyle: true,
@@ -478,20 +476,16 @@ namespace ts {
478476
printSourceFileOrBundle(
479477
declarationFilePath,
480478
declarationMapPath,
481-
declarationTransform.transformed[0],
479+
declarationTransform,
482480
declarationPrinter,
483481
{
484-
sourceMap: !forceDtsEmit && compilerOptions.declarationMap,
482+
sourceMap: printerOptions.sourceMap,
485483
sourceRoot: compilerOptions.sourceRoot,
486484
mapRoot: compilerOptions.mapRoot,
487485
extendedDiagnostics: compilerOptions.extendedDiagnostics,
488486
// Explicitly do not passthru either `inline` option
489487
}
490488
);
491-
if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) {
492-
const sourceFile = declarationTransform.transformed[0];
493-
exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit;
494-
}
495489
}
496490
declarationTransform.dispose();
497491
if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo;
@@ -511,7 +505,8 @@ namespace ts {
511505
forEachChild(node, collectLinkedAliases);
512506
}
513507

514-
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) {
508+
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, transform: TransformationResult<SourceFile | Bundle>, printer: Printer, mapOptions: SourceMapOptions) {
509+
const sourceFileOrBundle = transform.transformed[0];
515510
const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined;
516511
const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined;
517512
const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!];
@@ -559,7 +554,7 @@ namespace ts {
559554
if (sourceMapFilePath) {
560555
const sourceMap = sourceMapGenerator.toString();
561556
writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles);
562-
if (printer.bundleFileInfo) printer.bundleFileInfo.mapHash = BuilderState.computeSignature(sourceMap, maybeBind(host, host.createHash));
557+
if (printer.bundleFileInfo) printer.bundleFileInfo.mapHash = computeSignature(sourceMap, maybeBind(host, host.createHash));
563558
}
564559
}
565560
else {
@@ -568,10 +563,10 @@ namespace ts {
568563

569564
// Write the output file
570565
const text = writer.getText();
571-
writeFile(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, { sourceMapUrlPos });
566+
writeFile(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, { sourceMapUrlPos, diagnostics: transform.diagnostics });
572567
// We store the hash of the text written in the buildinfo to ensure that text of the referenced d.ts file is same as whats in the buildinfo
573568
// This is needed because incremental can be toggled between two runs and we might use stale file text to do text manipulation in prepend mode
574-
if (printer.bundleFileInfo) printer.bundleFileInfo.hash = BuilderState.computeSignature(text, maybeBind(host, host.createHash));
569+
if (printer.bundleFileInfo) printer.bundleFileInfo.hash = computeSignature(text, maybeBind(host, host.createHash));
575570

576571
// Reset state
577572
writer.clear();
@@ -774,20 +769,20 @@ namespace ts {
774769
const jsFileText = host.readFile(Debug.checkDefined(jsFilePath));
775770
if (!jsFileText) return jsFilePath!;
776771
// If the jsFileText is not same has what it was created with, tsbuildinfo is stale so dont use it
777-
if (BuilderState.computeSignature(jsFileText, createHash) !== buildInfo.bundle.js.hash) return jsFilePath!;
772+
if (computeSignature(jsFileText, createHash) !== buildInfo.bundle.js.hash) return jsFilePath!;
778773
const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath);
779774
// error if no source map or for now if inline sourcemap
780775
if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding";
781-
if (sourceMapFilePath && BuilderState.computeSignature(sourceMapText!, createHash) !== buildInfo.bundle.js.mapHash) return sourceMapFilePath;
776+
if (sourceMapFilePath && computeSignature(sourceMapText!, createHash) !== buildInfo.bundle.js.mapHash) return sourceMapFilePath;
782777

783778
// read declaration text
784779
const declarationText = declarationFilePath && host.readFile(declarationFilePath);
785780
if (declarationFilePath && !declarationText) return declarationFilePath;
786-
if (declarationFilePath && BuilderState.computeSignature(declarationText!, createHash) !== buildInfo.bundle.dts!.hash) return declarationFilePath;
781+
if (declarationFilePath && computeSignature(declarationText!, createHash) !== buildInfo.bundle.dts!.hash) return declarationFilePath;
787782
const declarationMapText = declarationMapPath && host.readFile(declarationMapPath);
788783
// error if no source map or for now if inline sourcemap
789784
if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding";
790-
if (declarationMapPath && BuilderState.computeSignature(declarationMapText!, createHash) !== buildInfo.bundle.dts!.mapHash) return declarationMapPath;
785+
if (declarationMapPath && computeSignature(declarationMapText!, createHash) !== buildInfo.bundle.dts!.mapHash) return declarationMapPath;
791786

792787
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory()));
793788
const ownPrependInput = createInputFiles(
@@ -836,7 +831,7 @@ namespace ts {
836831
newBuildInfo.program = buildInfo.program;
837832
if (newBuildInfo.program && changedDtsText !== undefined && config.options.composite) {
838833
// Update the output signature
839-
(newBuildInfo.program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, changedDtsData, createHash);
834+
(newBuildInfo.program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, createHash, changedDtsData);
840835
newBuildInfo.program.dtsChangeTime = getCurrentTime(host).getTime();
841836
}
842837
// Update sourceFileInfo

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4026,6 +4026,7 @@ namespace ts {
40264026
export interface WriteFileCallbackData {
40274027
/*@internal*/ sourceMapUrlPos?: number;
40284028
/*@internal*/ buildInfo?: BuildInfo;
4029+
/*@internal*/ diagnostics?: readonly DiagnosticWithLocation[];
40294030
}
40304031
export type WriteFileCallback = (
40314032
fileName: string,
@@ -4335,7 +4336,6 @@ namespace ts {
43354336
diagnostics: readonly Diagnostic[];
43364337
emittedFiles?: string[]; // Array of files the compiler wrote to disk
43374338
/* @internal */ sourceMaps?: SourceMapEmitResult[]; // Array of sourceMapData if compiler emitted sourcemaps
4338-
/* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
43394339
}
43404340

43414341
/* @internal */

src/server/project.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,8 @@ namespace ts.server {
709709
this.program!,
710710
scriptInfo.path,
711711
this.cancellationToken,
712-
maybeBind(this.projectService.host, this.projectService.host.createHash)
712+
maybeBind(this.projectService.host, this.projectService.host.createHash),
713+
this.getCanonicalFileName,
713714
),
714715
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined
715716
);

src/testRunner/unittests/services/languageService.ts

-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ export function Component(x: Config): any;`
6262
emitSkipped: true,
6363
diagnostics: emptyArray,
6464
outputFiles: emptyArray,
65-
exportedModulesFromDeclarationEmit: undefined
6665
}
6766
);
6867

@@ -80,7 +79,6 @@ export function Component(x: Config): any;`
8079
text: "export {};\r\n",
8180
writeByteOrderMark: false
8281
}],
83-
exportedModulesFromDeclarationEmit: undefined
8482
}
8583
);
8684
});

0 commit comments

Comments
 (0)