Skip to content

Commit 18035fa

Browse files
committed
Store program to persist in buildinfo and clean it as part of cleanResolutions
1 parent 7f2efd3 commit 18035fa

25 files changed

+13156
-1654
lines changed

src/compiler/builder.ts

Lines changed: 461 additions & 10 deletions
Large diffs are not rendered by default.

src/compiler/program.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,7 @@ namespace ts {
16161616
filesByName.set(newSourceFile.path, newSourceFile);
16171617
}
16181618
});
1619-
const oldFilesByNameMap = oldProgram.getFilesByNameMap() as ESMap<Path, SourceFile | Path | false | 0>;
1619+
const oldFilesByNameMap = oldProgram.getFilesByNameMap() as ESMap<Path, SourceFileOfProgramFromBuildInfo | Path | false | 0>;
16201620
oldFilesByNameMap.forEach((oldFile, path) => {
16211621
if (!oldFile) {
16221622
filesByName.set(path, oldFile as false | 0);

src/compiler/resolutionCache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace ts {
2525
clear(): void;
2626
}
2727

28-
interface ResolutionWithFailedLookupLocations {
28+
export interface ResolutionWithFailedLookupLocations {
2929
readonly failedLookupLocations: string[];
3030
isInvalidated?: boolean;
3131
refCount?: number;

src/compiler/types.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,14 +3827,13 @@ namespace ts {
38273827
/*@internal*/
38283828
export interface IdentifierOfProgramFromBuildInfo {
38293829
kind: SyntaxKind.Identifier;
3830-
escapedText: string;
3830+
escapedText: __String;
38313831
}
38323832

38333833
/*@internal*/
38343834
export interface StringLiteralLikeOfProgramFromBuildInfo {
38353835
kind: SyntaxKind.StringLiteral | SyntaxKind.NoSubstitutionTemplateLiteral;
38363836
text: string;
3837-
escapedText: string;
38383837
}
38393838

38403839
/*@internal*/
@@ -3846,14 +3845,16 @@ namespace ts {
38463845
originalFileName: string;
38473846
path: Path;
38483847
resolvedPath: Path;
3848+
// This currently is set to sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags but cant be set in type
3849+
// Change this if it changes in reusing program
38493850
flags: NodeFlags;
38503851
version: string;
38513852

38523853
typeReferenceDirectives: readonly string[];
38533854
libReferenceDirectives: readonly string[];
38543855
referencedFiles: readonly string[];
38553856
imports: readonly StringLiteralLikeOfProgramFromBuildInfo[];
3856-
moduleAugmentations: ModuleNameOfProgramFromBuildInfo[];
3857+
moduleAugmentations: readonly ModuleNameOfProgramFromBuildInfo[];
38573858
ambientModuleNames: readonly string[];
38583859
hasNoDefaultLib: boolean;
38593860

@@ -3866,22 +3867,21 @@ namespace ts {
38663867

38673868
getCompilerOptions(): CompilerOptions;
38683869
getRootFileNames(): readonly string[];
3869-
getSourceFiles(): SourceFileOfProgramFromBuildInfo[];
3870+
getSourceFiles(): readonly SourceFileOfProgramFromBuildInfo[];
38703871
getSourceFileByPath(path: Path): SourceFileOfProgramFromBuildInfo | undefined;
38713872
getPerFileModuleResolutions(): ESMap<Path, ESMap<string, ResolvedModuleWithFailedLookupLocations>>;
38723873
getPerFileTypeReferenceResolutions(): ESMap<Path, ESMap<string, ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
38733874
getProjectReferences(): readonly ProjectReference[] | undefined;
3874-
getResolvedProjectReferences(): readonly ResolvedProjectReferenceOfProgramFromBuildInfo[] | undefined;
3875+
getResolvedProjectReferences(): readonly (ResolvedProjectReferenceOfProgramFromBuildInfo | undefined)[] | undefined;
38753876
getMissingFilePaths(): readonly Path[];
38763877
getRefFileMap(): MultiMap<Path, RefFile> | undefined;
38773878
getResolvedTypeReferenceDirectives(): ESMap<string, ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
3878-
getFilesByNameMap(): ESMap<Path, Path | false | 0>;
3879+
getFilesByNameMap(): ESMap<Path, SourceFileOfProgramFromBuildInfo | Path | false | 0>;
38793880
isSourceFileFromExternalLibraryPath(path: Path): boolean;
38803881
getFileProcessingDiagnostics(): readonly ReusableDiagnostic[];
38813882

38823883
redirectTargetsMap: MultiMap<Path, string>;
38833884
sourceFileToPackageName: ESMap<Path, string>;
3884-
structureIsReused?: StructureIsReused;
38853885
}
38863886

38873887
/* @internal */

src/compiler/watchPublic.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ namespace ts {
2828
if (!content) return emitSkippedWithNoDiagnostics;
2929
const buildInfo = getBuildInfo(content);
3030
if (buildInfo.version !== version) return emitSkippedWithNoDiagnostics;
31-
if (!buildInfo.program) return emitSkippedWithNoDiagnostics;
32-
// TODO:: Clean the actual program
33-
let newContent = content;
31+
if (!buildInfo.program?.peristedProgram) return emitSkippedWithNoDiagnostics;
32+
const { program: { peristedProgram, ...program } } = buildInfo;
33+
buildInfo.program = program;
3434

3535
// Actual writeFile with new program
3636
const emitDiagnostics = createDiagnosticCollection();
37-
writeFile(host, emitDiagnostics, buildInfoPath, newContent, /*writeByteOrderMark*/ false);
37+
writeFile(host, emitDiagnostics, buildInfoPath, getBuildInfoText(buildInfo), /*writeByteOrderMark*/ false);
3838
return {
3939
emitSkipped: false,
4040
diagnostics: emitDiagnostics.getDiagnostics(),

src/testRunner/unittests/tsbuild/helpers.ts

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,12 @@ interface Symbol {
269269
tick: () => void;
270270
baseFs: vfs.FileSystem;
271271
newSys: TscCompileSystem;
272+
cleanBuildDescripencies: TscIncremental["cleanBuildDescripencies"];
272273
}
273274
function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number) {
274275
it(`Verify emit output file text is same when built clean for incremental scenario at:: ${index}`, () => {
275276
const {
276-
scenario, subScenario, commandLineArgs,
277+
scenario, subScenario, commandLineArgs, cleanBuildDescripencies,
277278
modifyFs, incrementalModifyFs,
278279
tick, baseFs, newSys
279280
} = input();
@@ -288,54 +289,82 @@ interface Symbol {
288289
incrementalModifyFs(fs);
289290
},
290291
});
292+
const descripencies = cleanBuildDescripencies?.();
291293
for (const outputFile of arrayFrom(sys.writtenFiles.keys())) {
292-
const expectedText = sys.readFile(outputFile);
293-
const actualText = newSys.readFile(outputFile);
294+
const cleanBuildText = sys.readFile(outputFile);
295+
const incrementalBuildText = newSys.readFile(outputFile);
296+
const descripencyInClean = descripencies?.get(outputFile);
294297
if (!isBuildInfoFile(outputFile)) {
295-
assert.equal(actualText, expectedText, `File: ${outputFile}`);
298+
verifyTextEqual(incrementalBuildText, cleanBuildText, descripencyInClean, `File: ${outputFile}`);
296299
}
297-
else if (actualText !== expectedText) {
300+
else if (incrementalBuildText !== cleanBuildText) {
298301
// Verify build info without affectedFilesPendingEmit
299-
const { buildInfo: actualBuildInfo, affectedFilesPendingEmit: actualAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(actualText);
300-
const { buildInfo: expectedBuildInfo, affectedFilesPendingEmit: expectedAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(expectedText);
301-
assert.deepEqual(actualBuildInfo, expectedBuildInfo, `TsBuild info text without affectedFilesPendingEmit: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
302+
const { buildInfo: incrementalBuildInfo, affectedFilesPendingEmit: incrementalBuildAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
303+
const { buildInfo: cleanBuildInfo, affectedFilesPendingEmit: incrementalAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
304+
verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descripencyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
302305
// Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
303-
if (actualAffectedFilesPendingEmit) {
304-
assert.isDefined(expectedAffectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
306+
if (incrementalBuildAffectedFilesPendingEmit && descripencyInClean === undefined) {
307+
assert.isDefined(incrementalAffectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
305308
let expectedIndex = 0;
306-
actualAffectedFilesPendingEmit.forEach(([actualFile]) => {
307-
expectedIndex = findIndex(expectedAffectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex);
308-
assert.notEqual(expectedIndex, -1, `Incremental build contains ${actualFile} file as pending emit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
309+
incrementalBuildAffectedFilesPendingEmit.forEach(([actualFile]) => {
310+
expectedIndex = findIndex(incrementalAffectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex);
311+
assert.notEqual(expectedIndex, -1, `Incremental build contains ${actualFile} file as pending emit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
309312
expectedIndex++;
310313
});
311314
}
312315
}
313316
}
317+
318+
function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descripencyInClean: CleanBuildDescripency | undefined, message: string) {
319+
if (descripencyInClean === undefined) {
320+
assert.equal(incrementalText, cleanText, message);
321+
return;
322+
}
323+
switch (descripencyInClean) {
324+
case CleanBuildDescripency.CleanFileTextDifferent:
325+
assert.isDefined(incrementalText, `Incremental file should be present:: ${message}`);
326+
assert.isDefined(cleanText, `Clean file should be present present:: ${message}`);
327+
assert.notEqual(incrementalText, cleanText, message);
328+
return;
329+
case CleanBuildDescripency.CleanFilePresent:
330+
assert.isUndefined(incrementalText, `Incremental file should be absent:: ${message}`);
331+
assert.isDefined(cleanText, `Clean file should be present:: ${message}`);
332+
return;
333+
default:
334+
Debug.assertNever(descripencyInClean);
335+
}
336+
}
314337
});
315338
}
316339

317-
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: BuildInfo | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
340+
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: string | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
318341
const buildInfo = text ? getBuildInfo(text) : undefined;
319-
if (!buildInfo?.program) return { buildInfo };
342+
if (!buildInfo?.program) return { buildInfo: text };
320343
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
321344
const { program: { affectedFilesPendingEmit, options: { noEmit, ...optionsRest}, ...programRest }, ...rest } = buildInfo;
322345
return {
323-
buildInfo: {
346+
buildInfo: getBuildInfoText({
324347
...rest,
325348
program: {
326349
options: optionsRest,
327350
...programRest
328351
}
329-
},
352+
}),
330353
affectedFilesPendingEmit
331354
};
332355
}
333356

357+
export enum CleanBuildDescripency{
358+
CleanFileTextDifferent,
359+
CleanFilePresent,
360+
}
361+
334362
export interface TscIncremental {
335363
buildKind: BuildKind;
336364
modifyFs: (fs: vfs.FileSystem) => void;
337365
subScenario?: string;
338366
commandLineArgs?: readonly string[];
367+
cleanBuildDescripencies?: () => ESMap<string, CleanBuildDescripency>;
339368
}
340369

341370
export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker {
@@ -395,7 +424,8 @@ interface Symbol {
395424
buildKind,
396425
modifyFs: incrementalModifyFs,
397426
subScenario: incrementalSubScenario,
398-
commandLineArgs: incrementalCommandLineArgs
427+
commandLineArgs: incrementalCommandLineArgs,
428+
cleanBuildDescripencies,
399429
}, index) => {
400430
describe(incrementalSubScenario || buildKind, () => {
401431
let newSys: TscCompileSystem;
@@ -424,10 +454,11 @@ interface Symbol {
424454
verifyTscBaseline(() => newSys);
425455
verifyIncrementalCorrectness(() => ({
426456
scenario,
427-
subScenario,
457+
subScenario: incrementalSubScenario || subScenario,
428458
baseFs,
429459
newSys,
430460
commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
461+
cleanBuildDescripencies,
431462
incrementalModifyFs,
432463
modifyFs,
433464
tick
@@ -519,12 +550,13 @@ interface Symbol {
519550
}));
520551
});
521552
describe("incremental correctness", () => {
522-
incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs }, index) => verifyIncrementalCorrectness(() => ({
553+
incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDescripencies }, index) => verifyIncrementalCorrectness(() => ({
523554
scenario,
524-
subScenario,
555+
subScenario: subScenario || buildKind,
525556
baseFs,
526557
newSys: incrementalSys[index],
527558
commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
559+
cleanBuildDescripencies,
528560
incrementalModifyFs: fs => {
529561
for (let i = 0; i <= index; i++) {
530562
incrementalScenarios[i].modifyFs(fs);

src/testRunner/unittests/tsbuild/persistResolutions.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,50 @@ namespace ts {
4343
subScenario: "Write file that could not be resolved",
4444
buildKind: BuildKind.IncrementalDtsChange,
4545
modifyFs: fs => fs.writeFileSync(`/src/project/src/fileNotFound.ts`, "export function something2() { return 20; }"),
46+
cleanBuildDescripencies: () => {
47+
const result = new Map<string, CleanBuildDescripency>();
48+
// when doing clean build, fileNotFound.ts would be resolved so the output order in outFile.js would change
49+
// In build mode the out is generated only when there are no errors
50+
const isBuild = input === "--b";
51+
if (outFile) {
52+
const descripency = isBuild ? CleanBuildDescripency.CleanFilePresent : CleanBuildDescripency.CleanFileTextDifferent;
53+
result.set("/src/project/outFile.tsbuildinfo", CleanBuildDescripency.CleanFileTextDifferent);
54+
result.set("/src/project/outFile.js", descripency);
55+
result.set("/src/project/outFile.d.ts", descripency);
56+
result.set("/src/project/outFile.tsbuildinfo.baseline.txt", descripency);
57+
}
58+
else if (isBuild) {
59+
// Outputs are generated, buildinfo is updated to report no errors
60+
result.set(`/src/project/src/filePresent.js`, CleanBuildDescripency.CleanFilePresent);
61+
result.set(`/src/project/src/filePresent.d.ts`, CleanBuildDescripency.CleanFilePresent);
62+
result.set(`/src/project/src/fileNotFound.js`, CleanBuildDescripency.CleanFilePresent);
63+
result.set(`/src/project/src/fileNotFound.d.ts`, CleanBuildDescripency.CleanFilePresent);
64+
result.set(`/src/project/src/anotherFileReusingResolution.js`, CleanBuildDescripency.CleanFilePresent);
65+
result.set(`/src/project/src/anotherFileReusingResolution.d.ts`, CleanBuildDescripency.CleanFilePresent);
66+
result.set(`/src/project/src/main.js`, CleanBuildDescripency.CleanFilePresent);
67+
result.set(`/src/project/src/main.d.ts`, CleanBuildDescripency.CleanFilePresent);
68+
result.set(`/src/project/src/newFile.js`, CleanBuildDescripency.CleanFilePresent);
69+
result.set(`/src/project/src/newFile.d.ts`, CleanBuildDescripency.CleanFilePresent);
70+
result.set(`/src/project/tsconfig.tsbuildinfo`, CleanBuildDescripency.CleanFileTextDifferent);
71+
}
72+
else {
73+
result.set(`/src/project/tsconfig.tsbuildinfo`, CleanBuildDescripency.CleanFileTextDifferent);
74+
}
75+
return result;
76+
},
4677
},
4778
{
4879
subScenario: "Clean resolutions",
4980
buildKind: BuildKind.IncrementalDtsChange,
5081
modifyFs: noop,
5182
commandLineArgs: [input, "src/project", "--cleanPersistedProgram"]
5283
},
84+
{
85+
subScenario: "Clean resolutions again",
86+
buildKind: BuildKind.IncrementalDtsChange,
87+
modifyFs: noop,
88+
commandLineArgs: [input, "src/project", "--cleanPersistedProgram"]
89+
},
5390
noChangeRun,
5491
{
5592
subScenario: "Modify main file",

src/testRunner/unittests/tsbuild/sample.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,19 @@ class someClass2 { }`),
509509
subScenario: "persistResolutions",
510510
baselinePrograms: true,
511511
fs: () => projFs,
512-
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({
513-
compilerOptions: {
514-
composite: true,
515-
skipDefaultLibCheck: true,
516-
persistResolutions: true,
512+
modifyFs: fs => {
513+
persistResolutions("/src/core/tsconfig.json");
514+
persistResolutions("/src/logic/tsconfig.json");
515+
persistResolutions("/src/tests/tsconfig.json");
516+
function persistResolutions(file: string) {
517+
const content = JSON.parse(fs.readFileSync(file, "utf-8"));
518+
content.compilerOptions = {
519+
...content.compilerOptions || {},
520+
persistResolutions: true
521+
};
522+
fs.writeFileSync(file, JSON.stringify(content, /*replacer*/ undefined, 4));
517523
}
518-
})),
524+
},
519525
commandLineArgs: ["--b", "/src/tests"],
520526
incrementalScenarios: [
521527
...coreChanges,

0 commit comments

Comments
 (0)