mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-16 15:51:35 -05:00
Handle output file names descripency between tsc --b and actual program emit file path calculation (#41811)
* Baseline showing #41801 and other issues with output path calculation * Add a way to note descripencies between clean and incremental build * Add descripency when no rootDir is specified but project is composite * if rootDir is specified, irrespective of whether all files belong to rootDir, the paths should be calculated from rootDir * Fix the output file names api to use the correct common source directory * Tests for #41780 * Spelling
This commit is contained in:
@@ -125,6 +125,7 @@
|
||||
"unittests/tsbuild/moduleSpecifiers.ts",
|
||||
"unittests/tsbuild/noEmitOnError.ts",
|
||||
"unittests/tsbuild/outFile.ts",
|
||||
"unittests/tsbuild/outputPaths.ts",
|
||||
"unittests/tsbuild/referencesWithRootDirInParent.ts",
|
||||
"unittests/tsbuild/resolveJsonModule.ts",
|
||||
"unittests/tsbuild/sample.ts",
|
||||
|
||||
@@ -270,11 +270,12 @@ interface Symbol {
|
||||
tick: () => void;
|
||||
baseFs: vfs.FileSystem;
|
||||
newSys: TscCompileSystem;
|
||||
cleanBuildDiscrepancies: TscIncremental["cleanBuildDiscrepancies"];
|
||||
}
|
||||
function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number) {
|
||||
it(`Verify emit output file text is same when built clean for incremental scenario at:: ${index}`, () => {
|
||||
const {
|
||||
scenario, subScenario, commandLineArgs,
|
||||
scenario, subScenario, commandLineArgs, cleanBuildDiscrepancies,
|
||||
modifyFs, incrementalModifyFs,
|
||||
tick, baseFs, newSys
|
||||
} = input();
|
||||
@@ -289,54 +290,82 @@ interface Symbol {
|
||||
incrementalModifyFs(fs);
|
||||
},
|
||||
});
|
||||
const discrepancies = cleanBuildDiscrepancies?.();
|
||||
for (const outputFile of arrayFrom(sys.writtenFiles.keys())) {
|
||||
const expectedText = sys.readFile(outputFile);
|
||||
const actualText = newSys.readFile(outputFile);
|
||||
const cleanBuildText = sys.readFile(outputFile);
|
||||
const incrementalBuildText = newSys.readFile(outputFile);
|
||||
const descrepancyInClean = discrepancies?.get(outputFile);
|
||||
if (!isBuildInfoFile(outputFile)) {
|
||||
assert.equal(actualText, expectedText, `File: ${outputFile}`);
|
||||
verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`);
|
||||
}
|
||||
else if (actualText !== expectedText) {
|
||||
else if (incrementalBuildText !== cleanBuildText) {
|
||||
// Verify build info without affectedFilesPendingEmit
|
||||
const { buildInfo: actualBuildInfo, affectedFilesPendingEmit: actualAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(actualText);
|
||||
const { buildInfo: expectedBuildInfo, affectedFilesPendingEmit: expectedAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(expectedText);
|
||||
assert.deepEqual(actualBuildInfo, expectedBuildInfo, `TsBuild info text without affectedFilesPendingEmit: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
|
||||
const { buildInfo: incrementalBuildInfo, affectedFilesPendingEmit: incrementalBuildAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
|
||||
const { buildInfo: cleanBuildInfo, affectedFilesPendingEmit: incrementalAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
|
||||
verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descrepancyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
|
||||
// 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
|
||||
if (actualAffectedFilesPendingEmit) {
|
||||
assert.isDefined(expectedAffectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
|
||||
if (incrementalBuildAffectedFilesPendingEmit && descrepancyInClean === undefined) {
|
||||
assert.isDefined(incrementalAffectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
|
||||
let expectedIndex = 0;
|
||||
actualAffectedFilesPendingEmit.forEach(([actualFile]) => {
|
||||
expectedIndex = findIndex(expectedAffectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex);
|
||||
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}`);
|
||||
incrementalBuildAffectedFilesPendingEmit.forEach(([actualFile]) => {
|
||||
expectedIndex = findIndex(incrementalAffectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex);
|
||||
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}`);
|
||||
expectedIndex++;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descrepancyInClean: CleanBuildDescrepancy | undefined, message: string) {
|
||||
if (descrepancyInClean === undefined) {
|
||||
assert.equal(incrementalText, cleanText, message);
|
||||
return;
|
||||
}
|
||||
switch (descrepancyInClean) {
|
||||
case CleanBuildDescrepancy.CleanFileTextDifferent:
|
||||
assert.isDefined(incrementalText, `Incremental file should be present:: ${message}`);
|
||||
assert.isDefined(cleanText, `Clean file should be present present:: ${message}`);
|
||||
assert.notEqual(incrementalText, cleanText, message);
|
||||
return;
|
||||
case CleanBuildDescrepancy.CleanFilePresent:
|
||||
assert.isUndefined(incrementalText, `Incremental file should be absent:: ${message}`);
|
||||
assert.isDefined(cleanText, `Clean file should be present:: ${message}`);
|
||||
return;
|
||||
default:
|
||||
Debug.assertNever(descrepancyInClean);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: BuildInfo | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
|
||||
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: string | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
|
||||
const buildInfo = text ? getBuildInfo(text) : undefined;
|
||||
if (!buildInfo?.program) return { buildInfo };
|
||||
if (!buildInfo?.program) return { buildInfo: text };
|
||||
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
|
||||
const { program: { affectedFilesPendingEmit, options: { noEmit, ...optionsRest}, ...programRest }, ...rest } = buildInfo;
|
||||
return {
|
||||
buildInfo: {
|
||||
buildInfo: getBuildInfoText({
|
||||
...rest,
|
||||
program: {
|
||||
options: optionsRest,
|
||||
...programRest
|
||||
}
|
||||
},
|
||||
}),
|
||||
affectedFilesPendingEmit
|
||||
};
|
||||
}
|
||||
|
||||
export enum CleanBuildDescrepancy {
|
||||
CleanFileTextDifferent,
|
||||
CleanFilePresent,
|
||||
}
|
||||
|
||||
export interface TscIncremental {
|
||||
buildKind: BuildKind;
|
||||
modifyFs: (fs: vfs.FileSystem) => void;
|
||||
subScenario?: string;
|
||||
commandLineArgs?: readonly string[];
|
||||
cleanBuildDiscrepancies?: () => ESMap<string, CleanBuildDescrepancy>;
|
||||
}
|
||||
|
||||
export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker {
|
||||
@@ -396,7 +425,8 @@ interface Symbol {
|
||||
buildKind,
|
||||
modifyFs: incrementalModifyFs,
|
||||
subScenario: incrementalSubScenario,
|
||||
commandLineArgs: incrementalCommandLineArgs
|
||||
commandLineArgs: incrementalCommandLineArgs,
|
||||
cleanBuildDiscrepancies,
|
||||
}, index) => {
|
||||
describe(incrementalSubScenario || buildKind, () => {
|
||||
let newSys: TscCompileSystem;
|
||||
@@ -425,10 +455,11 @@ interface Symbol {
|
||||
verifyTscBaseline(() => newSys);
|
||||
verifyIncrementalCorrectness(() => ({
|
||||
scenario,
|
||||
subScenario,
|
||||
subScenario: incrementalSubScenario || subScenario,
|
||||
baseFs,
|
||||
newSys,
|
||||
commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
|
||||
cleanBuildDiscrepancies,
|
||||
incrementalModifyFs,
|
||||
modifyFs,
|
||||
tick
|
||||
@@ -520,12 +551,13 @@ interface Symbol {
|
||||
}));
|
||||
});
|
||||
describe("incremental correctness", () => {
|
||||
incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs }, index) => verifyIncrementalCorrectness(() => ({
|
||||
incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDiscrepancies }, index) => verifyIncrementalCorrectness(() => ({
|
||||
scenario,
|
||||
subScenario,
|
||||
subScenario: subScenario || buildKind,
|
||||
baseFs,
|
||||
newSys: incrementalSys[index],
|
||||
commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
|
||||
cleanBuildDiscrepancies,
|
||||
incrementalModifyFs: fs => {
|
||||
for (let i = 0; i <= index; i++) {
|
||||
incrementalScenarios[i].modifyFs(fs);
|
||||
|
||||
117
src/testRunner/unittests/tsbuild/outputPaths.ts
Normal file
117
src/testRunner/unittests/tsbuild/outputPaths.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild - output file paths", () => {
|
||||
const noChangeProject: TscIncremental = {
|
||||
buildKind: BuildKind.NoChangeRun,
|
||||
modifyFs: noop,
|
||||
subScenario: "Normal build without change, that does not block emit on error to show files that get emitted",
|
||||
commandLineArgs: ["-p", "/src/tsconfig.json"],
|
||||
};
|
||||
const incrementalScenarios: TscIncremental[] = [
|
||||
noChangeRun,
|
||||
noChangeProject,
|
||||
];
|
||||
|
||||
function verify(input: Pick<VerifyTsBuildInput, "subScenario" | "fs" | "incrementalScenarios">, expectedOuptutNames: readonly string[]) {
|
||||
verifyTscSerializedIncrementalEdits({
|
||||
scenario: "outputPaths",
|
||||
commandLineArgs: ["--b", "/src/tsconfig.json", "-v"],
|
||||
...input
|
||||
});
|
||||
|
||||
it("verify getOutputFileNames", () => {
|
||||
const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as TscCompileSystem;
|
||||
;
|
||||
assert.deepEqual(
|
||||
getOutputFileNames(
|
||||
parseConfigFileWithSystem("/src/tsconfig.json", {}, {}, sys, noop)!,
|
||||
"/src/src/index.ts",
|
||||
/*ignoreCase*/ false
|
||||
),
|
||||
expectedOuptutNames
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
verify({
|
||||
subScenario: "when rootDir is not specified",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/src/index.ts": "export const x = 10;",
|
||||
"/src/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
outDir: "dist"
|
||||
}
|
||||
})
|
||||
}),
|
||||
incrementalScenarios,
|
||||
}, ["/src/dist/index.js"]);
|
||||
|
||||
verify({
|
||||
subScenario: "when rootDir is not specified and is composite",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/src/index.ts": "export const x = 10;",
|
||||
"/src/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
outDir: "dist",
|
||||
composite: true
|
||||
}
|
||||
})
|
||||
}),
|
||||
incrementalScenarios: [
|
||||
noChangeRun,
|
||||
{
|
||||
...noChangeProject,
|
||||
cleanBuildDiscrepancies: () => {
|
||||
const map = new Map<string, CleanBuildDescrepancy>();
|
||||
map.set("/src/dist/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent); // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change.
|
||||
return map;
|
||||
}
|
||||
}
|
||||
],
|
||||
}, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]);
|
||||
|
||||
verify({
|
||||
subScenario: "when rootDir is specified",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/src/index.ts": "export const x = 10;",
|
||||
"/src/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
outDir: "dist",
|
||||
rootDir: "src"
|
||||
}
|
||||
})
|
||||
}),
|
||||
incrementalScenarios,
|
||||
}, ["/src/dist/index.js"]);
|
||||
|
||||
verify({
|
||||
subScenario: "when rootDir is specified but not all files belong to rootDir",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/src/index.ts": "export const x = 10;",
|
||||
"/src/types/type.ts": "export type t = string;",
|
||||
"/src/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
outDir: "dist",
|
||||
rootDir: "src"
|
||||
}
|
||||
})
|
||||
}),
|
||||
incrementalScenarios,
|
||||
}, ["/src/dist/index.js"]);
|
||||
|
||||
verify({
|
||||
subScenario: "when rootDir is specified but not all files belong to rootDir and is composite",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/src/index.ts": "export const x = 10;",
|
||||
"/src/types/type.ts": "export type t = string;",
|
||||
"/src/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
outDir: "dist",
|
||||
rootDir: "src",
|
||||
composite: true
|
||||
}
|
||||
})
|
||||
}),
|
||||
incrementalScenarios,
|
||||
}, ["/src/dist/index.js", "/src/dist/index.d.ts"]);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user