From ff03ed0d1a330fd22d3ca6f61467ed41159ba4e5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Dec 2020 12:32:04 -0800 Subject: [PATCH 1/7] Baseline showing #41801 and other issues with output path calculation --- src/testRunner/tsconfig.json | 1 + .../unittests/tsbuild/outputPaths.ts | 95 ++++++++++++ ...otDir-is-not-specified-and-is-composite.js | 104 +++++++++++++ .../when-rootDir-is-not-specified.js | 74 ++++++++++ ...iles-belong-to-rootDir-and-is-composite.js | 137 ++++++++++++++++++ ...ied-but-not-all-files-belong-to-rootDir.js | 95 ++++++++++++ .../when-rootDir-is-specified.js | 71 +++++++++ 7 files changed, 577 insertions(+) create mode 100644 src/testRunner/unittests/tsbuild/outputPaths.ts create mode 100644 tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified-and-is-composite.js create mode 100644 tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js create mode 100644 tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js create mode 100644 tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js create mode 100644 tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified.js diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index c024bdb0d6d37..7febf1b827538 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -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", diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts new file mode 100644 index 0000000000000..5a17c092f29e3 --- /dev/null +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -0,0 +1,95 @@ +namespace ts { + describe("unittests:: tsbuild - output file paths", () => { + const incrementalScenarios: TscIncremental[] = [ + noChangeRun, + { + 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"], + } + ]; + + verifyTscSerializedIncrementalEdits({ + scenario: "outputPaths", + subScenario: "when rootDir is not specified", + fs: () => loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist" + } + }) + }), + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + incrementalScenarios, + }); + + verifyTscSerializedIncrementalEdits({ + scenario: "outputPaths", + 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 + } + }) + }), + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + incrementalScenarios, + }); + + verifyTscSerializedIncrementalEdits({ + scenario: "outputPaths", + 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" + } + }) + }), + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + incrementalScenarios, + }); + + verifyTscSerializedIncrementalEdits({ + scenario: "outputPaths", + 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" + } + }) + }), + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + incrementalScenarios, + }); + + verifyTscSerializedIncrementalEdits({ + scenario: "outputPaths", + 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 + } + }) + }), + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + incrementalScenarios, + }); + }); +} diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified-and-is-composite.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified-and-is-composite.js new file mode 100644 index 0000000000000..4ca6d91cd9874 --- /dev/null +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified-and-is-composite.js @@ -0,0 +1,104 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/src/index.ts] +export const x = 10; + +//// [/src/tsconfig.json] +{"compilerOptions":{"outDir":"dist","composite":true}} + + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:01:00 AM] Projects in this build: + * src/tsconfig.json + +[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/src/index.js' does not exist + +[12:01:00 AM] Building project '/src/tsconfig.json'... + +exitCode:: ExitStatus.Success + + +//// [/src/dist/src/index.d.ts] +export declare const x = 10; + + +//// [/src/dist/src/index.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + +//// [/src/dist/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../../lib/lib.d.ts": { + "version": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../src/index.ts": { + "version": "-10726455937-export const x = 10;", + "signature": "-6057683066-export declare const x = 10;\r\n", + "affectsGlobalScope": false + } + }, + "options": { + "outDir": "./", + "composite": true, + "configFilePath": "../tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../lib/lib.d.ts", + "../src/index.ts" + ] + }, + "version": "FakeTSVersion" +} + + + +Change:: no-change-run +Input:: + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:04:00 AM] Projects in this build: + * src/tsconfig.json + +[12:04:00 AM] Project 'src/tsconfig.json' is up to date because newest input 'src/src/index.ts' is older than oldest output 'src/dist/src/index.js' + +exitCode:: ExitStatus.Success + + + + +Change:: Normal build without change, that does not block emit on error to show files that get emitted +Input:: + + +Output:: +/lib/tsc -p /src/tsconfig.json +exitCode:: ExitStatus.Success + + diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js new file mode 100644 index 0000000000000..e957d7fb2373d --- /dev/null +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js @@ -0,0 +1,74 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/src/index.ts] +export const x = 10; + +//// [/src/tsconfig.json] +{"compilerOptions":{"outDir":"dist"}} + + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:01:00 AM] Projects in this build: + * src/tsconfig.json + +[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/src/index.js' does not exist + +[12:01:00 AM] Building project '/src/tsconfig.json'... + +exitCode:: ExitStatus.Success + + +//// [/src/dist/index.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + + + +Change:: no-change-run +Input:: + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:04:00 AM] Projects in this build: + * src/tsconfig.json + +[12:04:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/src/index.js' does not exist + +[12:04:00 AM] Building project '/src/tsconfig.json'... + +exitCode:: ExitStatus.Success + + +//// [/src/dist/index.js] file written with same contents + + +Change:: Normal build without change, that does not block emit on error to show files that get emitted +Input:: + + +Output:: +/lib/tsc -p /src/tsconfig.json +exitCode:: ExitStatus.Success + + +//// [/src/dist/index.js] file written with same contents diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js new file mode 100644 index 0000000000000..dbc25b697a569 --- /dev/null +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js @@ -0,0 +1,137 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/src/index.ts] +export const x = 10; + +//// [/src/tsconfig.json] +{"compilerOptions":{"outDir":"dist","rootDir":"src","composite":true}} + +//// [/src/types/type.ts] +export type t = string; + + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:01:00 AM] Projects in this build: + * src/tsconfig.json + +[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/index.js' does not exist + +[12:01:00 AM] Building project '/src/tsconfig.json'... + +error TS6059: File '/src/types/type.ts' is not under 'rootDir' '/src/src'. 'rootDir' is expected to contain all source files. + + +Found 1 error. + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsSkipped + + + + +Change:: no-change-run +Input:: + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:04:00 AM] Projects in this build: + * src/tsconfig.json + +[12:04:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/index.js' does not exist + +[12:04:00 AM] Building project '/src/tsconfig.json'... + +error TS6059: File '/src/types/type.ts' is not under 'rootDir' '/src/src'. 'rootDir' is expected to contain all source files. + + +Found 1 error. + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsSkipped + + + + +Change:: Normal build without change, that does not block emit on error to show files that get emitted +Input:: + + +Output:: +/lib/tsc -p /src/tsconfig.json +error TS6059: File '/src/types/type.ts' is not under 'rootDir' '/src/src'. 'rootDir' is expected to contain all source files. + + +Found 1 error. + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated + + +//// [/src/dist/src/index.d.ts] +export declare const x = 10; + + +//// [/src/dist/src/index.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + +//// [/src/dist/types/type.d.ts] +export declare type t = string; + + +//// [/src/dist/types/type.js] +"use strict"; +exports.__esModule = true; + + +//// [/src/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../lib/lib.d.ts": { + "version": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "./src/index.ts": { + "version": "-10726455937-export const x = 10;", + "signature": "-6057683066-export declare const x = 10;\r\n", + "affectsGlobalScope": false + }, + "./types/type.ts": { + "version": "-4885977236-export type t = string;", + "signature": "-4409762125-export declare type t = string;\r\n", + "affectsGlobalScope": false + } + }, + "options": { + "outDir": "./dist", + "rootDir": "./src", + "composite": true, + "project": "./tsconfig.json", + "configFilePath": "./tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js new file mode 100644 index 0000000000000..a544d4add5e41 --- /dev/null +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js @@ -0,0 +1,95 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/src/index.ts] +export const x = 10; + +//// [/src/tsconfig.json] +{"compilerOptions":{"outDir":"dist","rootDir":"src"}} + +//// [/src/types/type.ts] +export type t = string; + + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:01:00 AM] Projects in this build: + * src/tsconfig.json + +[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/index.js' does not exist + +[12:01:00 AM] Building project '/src/tsconfig.json'... + +error TS6059: File '/src/types/type.ts' is not under 'rootDir' '/src/src'. 'rootDir' is expected to contain all source files. + + +Found 1 error. + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsSkipped + + + + +Change:: no-change-run +Input:: + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:04:00 AM] Projects in this build: + * src/tsconfig.json + +[12:04:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/index.js' does not exist + +[12:04:00 AM] Building project '/src/tsconfig.json'... + +error TS6059: File '/src/types/type.ts' is not under 'rootDir' '/src/src'. 'rootDir' is expected to contain all source files. + + +Found 1 error. + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsSkipped + + + + +Change:: Normal build without change, that does not block emit on error to show files that get emitted +Input:: + + +Output:: +/lib/tsc -p /src/tsconfig.json +error TS6059: File '/src/types/type.ts' is not under 'rootDir' '/src/src'. 'rootDir' is expected to contain all source files. + + +Found 1 error. + +exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated + + +//// [/src/dist/src/index.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + +//// [/src/dist/types/type.js] +"use strict"; +exports.__esModule = true; + + diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified.js new file mode 100644 index 0000000000000..d3b05377a5e90 --- /dev/null +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified.js @@ -0,0 +1,71 @@ +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/src/index.ts] +export const x = 10; + +//// [/src/tsconfig.json] +{"compilerOptions":{"outDir":"dist","rootDir":"src"}} + + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:01:00 AM] Projects in this build: + * src/tsconfig.json + +[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/index.js' does not exist + +[12:01:00 AM] Building project '/src/tsconfig.json'... + +exitCode:: ExitStatus.Success + + +//// [/src/dist/index.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + + + +Change:: no-change-run +Input:: + + +Output:: +/lib/tsc --b /src/tsconfig.json -v +[12:04:00 AM] Projects in this build: + * src/tsconfig.json + +[12:04:00 AM] Project 'src/tsconfig.json' is up to date because newest input 'src/src/index.ts' is older than oldest output 'src/dist/index.js' + +exitCode:: ExitStatus.Success + + + + +Change:: Normal build without change, that does not block emit on error to show files that get emitted +Input:: + + +Output:: +/lib/tsc -p /src/tsconfig.json +exitCode:: ExitStatus.Success + + +//// [/src/dist/index.js] file written with same contents From f79994db30117c2194807dc5fae6e8f125aaf336 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Dec 2020 13:24:39 -0800 Subject: [PATCH 2/7] Add a way to note descripencies between clean and incremental build --- src/testRunner/unittests/tsbuild/helpers.ts | 74 +++++++++++++++------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index e8ae55b88c54c..6a1f750225a89 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -270,11 +270,12 @@ interface Symbol { tick: () => void; baseFs: vfs.FileSystem; newSys: TscCompileSystem; + cleanBuildDescripencies: TscIncremental["cleanBuildDescripencies"]; } 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, cleanBuildDescripencies, modifyFs, incrementalModifyFs, tick, baseFs, newSys } = input(); @@ -289,54 +290,82 @@ interface Symbol { incrementalModifyFs(fs); }, }); + const descripencies = cleanBuildDescripencies?.(); 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 descripencyInClean = descripencies?.get(outputFile); if (!isBuildInfoFile(outputFile)) { - assert.equal(actualText, expectedText, `File: ${outputFile}`); + verifyTextEqual(incrementalBuildText, cleanBuildText, descripencyInClean, `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, descripencyInClean, `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 && descripencyInClean === 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, descripencyInClean: CleanBuildDescripency | undefined, message: string) { + if (descripencyInClean === undefined) { + assert.equal(incrementalText, cleanText, message); + return; + } + switch (descripencyInClean) { + case CleanBuildDescripency.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 CleanBuildDescripency.CleanFilePresent: + assert.isUndefined(incrementalText, `Incremental file should be absent:: ${message}`); + assert.isDefined(cleanText, `Clean file should be present:: ${message}`); + return; + default: + Debug.assertNever(descripencyInClean); + } + } }); } - 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 CleanBuildDescripency { + CleanFileTextDifferent, + CleanFilePresent, + } + export interface TscIncremental { buildKind: BuildKind; modifyFs: (fs: vfs.FileSystem) => void; subScenario?: string; commandLineArgs?: readonly string[]; + cleanBuildDescripencies?: () => ESMap; } export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker { @@ -396,7 +425,8 @@ interface Symbol { buildKind, modifyFs: incrementalModifyFs, subScenario: incrementalSubScenario, - commandLineArgs: incrementalCommandLineArgs + commandLineArgs: incrementalCommandLineArgs, + cleanBuildDescripencies, }, 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, + cleanBuildDescripencies, incrementalModifyFs, modifyFs, tick @@ -520,12 +551,13 @@ interface Symbol { })); }); describe("incremental correctness", () => { - incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs }, index) => verifyIncrementalCorrectness(() => ({ + incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDescripencies }, index) => verifyIncrementalCorrectness(() => ({ scenario, - subScenario, + subScenario: subScenario || buildKind, baseFs, newSys: incrementalSys[index], commandLineArgs: incrementalCommandLineArgs || commandLineArgs, + cleanBuildDescripencies, incrementalModifyFs: fs => { for (let i = 0; i <= index; i++) { incrementalScenarios[i].modifyFs(fs); From 8f6387efaf908e245a7cae619d674024952d8a2a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Dec 2020 13:28:53 -0800 Subject: [PATCH 3/7] Add descripency when no rootDir is specified but project is composite --- .../unittests/tsbuild/outputPaths.ts | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts index 5a17c092f29e3..49708809551b6 100644 --- a/src/testRunner/unittests/tsbuild/outputPaths.ts +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -1,13 +1,14 @@ 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, - { - 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"], - } + noChangeProject, ]; verifyTscSerializedIncrementalEdits({ @@ -38,7 +39,17 @@ namespace ts { }) }), commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], - incrementalScenarios, + incrementalScenarios: [ + noChangeRun, + { + ...noChangeProject, + cleanBuildDescripencies: () => { + const map = new Map(); + map.set("/src/dist/tsconfig.tsbuildinfo", CleanBuildDescripency.CleanFileTextDifferent); // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change. + return map; + } + } + ], }); verifyTscSerializedIncrementalEdits({ From 05d4bb4b46db6825c6af0c55b4d66247b34b6c17 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Dec 2020 13:45:59 -0800 Subject: [PATCH 4/7] if rootDir is specified, irrespective of whether all files belong to rootDir, the paths should be calculated from rootDir --- src/compiler/program.ts | 3 ++- .../amd/FolderA/FolderB/fileB.d.ts | 4 ++++ .../simple => FolderA/FolderB}/fileB.js | 0 .../amd/outdir/simple/fileB.d.ts | 4 ---- .../outdir/simple/{FolderC => }/fileC.d.ts | 0 .../amd/outdir/simple/{FolderC => }/fileC.js | 0 .../amd/rootDirectoryErrors.json | 8 +++---- .../node/FolderA/FolderB/fileB.d.ts | 4 ++++ .../simple => FolderA/FolderB}/fileB.js | 0 .../node/outdir/simple/fileB.d.ts | 4 ---- .../outdir/simple/{FolderC => }/fileC.d.ts | 0 .../node/outdir/simple/{FolderC => }/fileC.js | 0 .../node/rootDirectoryErrors.json | 8 +++---- ...iles-belong-to-rootDir-and-is-composite.js | 22 +++++++++---------- ...ied-but-not-all-files-belong-to-rootDir.js | 4 ++-- ...rors-correctly-with-file-not-in-rootDir.js | 12 +++++----- 16 files changed, 37 insertions(+), 36 deletions(-) create mode 100644 tests/baselines/reference/project/rootDirectoryErrors/amd/FolderA/FolderB/fileB.d.ts rename tests/baselines/reference/project/rootDirectoryErrors/amd/{outdir/simple => FolderA/FolderB}/fileB.js (100%) delete mode 100644 tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileB.d.ts rename tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/{FolderC => }/fileC.d.ts (100%) rename tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/{FolderC => }/fileC.js (100%) create mode 100644 tests/baselines/reference/project/rootDirectoryErrors/node/FolderA/FolderB/fileB.d.ts rename tests/baselines/reference/project/rootDirectoryErrors/node/{outdir/simple => FolderA/FolderB}/fileB.js (100%) delete mode 100644 tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileB.d.ts rename tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/{FolderC => }/fileC.d.ts (100%) rename tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/{FolderC => }/fileC.js (100%) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8d4efc3e941b3..a98fca35cda32 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1127,9 +1127,10 @@ namespace ts { function getCommonSourceDirectory() { if (commonSourceDirectory === undefined) { const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); - if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) { + if (options.rootDir) { // If a rootDir is specified use it as the commonSourceDirectory commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); + checkSourceFilesBelongToPath(emittedFiles, options.rootDir); } else if (options.composite && options.configFilePath) { // Project compilations never infer their root from the input source paths diff --git a/tests/baselines/reference/project/rootDirectoryErrors/amd/FolderA/FolderB/fileB.d.ts b/tests/baselines/reference/project/rootDirectoryErrors/amd/FolderA/FolderB/fileB.d.ts new file mode 100644 index 0000000000000..289bf1291191b --- /dev/null +++ b/tests/baselines/reference/project/rootDirectoryErrors/amd/FolderA/FolderB/fileB.d.ts @@ -0,0 +1,4 @@ +/// +declare class B { + c: C; +} diff --git a/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileB.js b/tests/baselines/reference/project/rootDirectoryErrors/amd/FolderA/FolderB/fileB.js similarity index 100% rename from tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileB.js rename to tests/baselines/reference/project/rootDirectoryErrors/amd/FolderA/FolderB/fileB.js diff --git a/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileB.d.ts b/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileB.d.ts deleted file mode 100644 index 4ff813c3839e8..0000000000000 --- a/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileB.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -/// -declare class B { - c: C; -} diff --git a/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/FolderC/fileC.d.ts b/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileC.d.ts similarity index 100% rename from tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/FolderC/fileC.d.ts rename to tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileC.d.ts diff --git a/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/FolderC/fileC.js b/tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileC.js similarity index 100% rename from tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/FolderC/fileC.js rename to tests/baselines/reference/project/rootDirectoryErrors/amd/outdir/simple/fileC.js diff --git a/tests/baselines/reference/project/rootDirectoryErrors/amd/rootDirectoryErrors.json b/tests/baselines/reference/project/rootDirectoryErrors/amd/rootDirectoryErrors.json index 77a23b3ffa477..7deea1ddb8e2f 100644 --- a/tests/baselines/reference/project/rootDirectoryErrors/amd/rootDirectoryErrors.json +++ b/tests/baselines/reference/project/rootDirectoryErrors/amd/rootDirectoryErrors.json @@ -14,9 +14,9 @@ "FolderA/FolderB/fileB.ts" ], "emittedFiles": [ - "outdir/simple/FolderC/fileC.js", - "outdir/simple/FolderC/fileC.d.ts", - "outdir/simple/fileB.js", - "outdir/simple/fileB.d.ts" + "outdir/simple/fileC.js", + "outdir/simple/fileC.d.ts", + "FolderA/FolderB/fileB.js", + "FolderA/FolderB/fileB.d.ts" ] } \ No newline at end of file diff --git a/tests/baselines/reference/project/rootDirectoryErrors/node/FolderA/FolderB/fileB.d.ts b/tests/baselines/reference/project/rootDirectoryErrors/node/FolderA/FolderB/fileB.d.ts new file mode 100644 index 0000000000000..289bf1291191b --- /dev/null +++ b/tests/baselines/reference/project/rootDirectoryErrors/node/FolderA/FolderB/fileB.d.ts @@ -0,0 +1,4 @@ +/// +declare class B { + c: C; +} diff --git a/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileB.js b/tests/baselines/reference/project/rootDirectoryErrors/node/FolderA/FolderB/fileB.js similarity index 100% rename from tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileB.js rename to tests/baselines/reference/project/rootDirectoryErrors/node/FolderA/FolderB/fileB.js diff --git a/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileB.d.ts b/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileB.d.ts deleted file mode 100644 index 4ff813c3839e8..0000000000000 --- a/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileB.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -/// -declare class B { - c: C; -} diff --git a/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/FolderC/fileC.d.ts b/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileC.d.ts similarity index 100% rename from tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/FolderC/fileC.d.ts rename to tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileC.d.ts diff --git a/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/FolderC/fileC.js b/tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileC.js similarity index 100% rename from tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/FolderC/fileC.js rename to tests/baselines/reference/project/rootDirectoryErrors/node/outdir/simple/fileC.js diff --git a/tests/baselines/reference/project/rootDirectoryErrors/node/rootDirectoryErrors.json b/tests/baselines/reference/project/rootDirectoryErrors/node/rootDirectoryErrors.json index 77a23b3ffa477..7deea1ddb8e2f 100644 --- a/tests/baselines/reference/project/rootDirectoryErrors/node/rootDirectoryErrors.json +++ b/tests/baselines/reference/project/rootDirectoryErrors/node/rootDirectoryErrors.json @@ -14,9 +14,9 @@ "FolderA/FolderB/fileB.ts" ], "emittedFiles": [ - "outdir/simple/FolderC/fileC.js", - "outdir/simple/FolderC/fileC.d.ts", - "outdir/simple/fileB.js", - "outdir/simple/fileB.d.ts" + "outdir/simple/fileC.js", + "outdir/simple/fileC.d.ts", + "FolderA/FolderB/fileB.js", + "FolderA/FolderB/fileB.d.ts" ] } \ No newline at end of file diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js index dbc25b697a569..0b529ae90fb92 100644 --- a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js @@ -81,26 +81,17 @@ Found 1 error. exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated -//// [/src/dist/src/index.d.ts] +//// [/src/dist/index.d.ts] export declare const x = 10; -//// [/src/dist/src/index.js] +//// [/src/dist/index.js] "use strict"; exports.__esModule = true; exports.x = void 0; exports.x = 10; -//// [/src/dist/types/type.d.ts] -export declare type t = string; - - -//// [/src/dist/types/type.js] -"use strict"; -exports.__esModule = true; - - //// [/src/tsconfig.tsbuildinfo] { "program": { @@ -135,3 +126,12 @@ exports.__esModule = true; "version": "FakeTSVersion" } +//// [/src/types/type.d.ts] +export declare type t = string; + + +//// [/src/types/type.js] +"use strict"; +exports.__esModule = true; + + diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js index a544d4add5e41..159c43724d83d 100644 --- a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js @@ -81,14 +81,14 @@ Found 1 error. exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated -//// [/src/dist/src/index.js] +//// [/src/dist/index.js] "use strict"; exports.__esModule = true; exports.x = void 0; exports.x = 10; -//// [/src/dist/types/type.js] +//// [/src/types/type.js] "use strict"; exports.__esModule = true; diff --git a/tests/baselines/reference/tscWatch/programUpdates/reports-errors-correctly-with-file-not-in-rootDir.js b/tests/baselines/reference/tscWatch/programUpdates/reports-errors-correctly-with-file-not-in-rootDir.js index a29375027cf86..d8f61d2134dfd 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/reports-errors-correctly-with-file-not-in-rootDir.js +++ b/tests/baselines/reference/tscWatch/programUpdates/reports-errors-correctly-with-file-not-in-rootDir.js @@ -32,7 +32,7 @@ Output:: 1 import { x } from "../b";    ~~~~~~ -[12:00:34 AM] Found 1 error. Watching for file changes. +[12:00:31 AM] Found 1 error. Watching for file changes. @@ -69,14 +69,14 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined -//// [/user/username/projects/myproject/lib/b.js] +//// [/user/username/projects/b.js] "use strict"; exports.__esModule = true; exports.x = void 0; exports.x = 10; -//// [/user/username/projects/myproject/lib/myproject/a.js] +//// [/user/username/projects/myproject/lib/a.js] "use strict"; exports.__esModule = true; @@ -93,14 +93,14 @@ import { x } from "../b"; Output:: >> Screen clear -[12:00:38 AM] File change detected. Starting incremental compilation... +[12:00:35 AM] File change detected. Starting incremental compilation... a.ts:3:19 - error TS6059: File '/user/username/projects/b.ts' is not under 'rootDir' '/user/username/projects/myproject'. 'rootDir' is expected to contain all source files. 3 import { x } from "../b";    ~~~~~~ -[12:00:42 AM] Found 1 error. Watching for file changes. +[12:00:39 AM] Found 1 error. Watching for file changes. @@ -135,4 +135,4 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined -//// [/user/username/projects/myproject/lib/myproject/a.js] file written with same contents +//// [/user/username/projects/myproject/lib/a.js] file written with same contents From c35cf288180b9f54465f2fbb11518f96dfc369b6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Dec 2020 15:21:44 -0800 Subject: [PATCH 5/7] Fix the output file names api to use the correct common source directory --- src/compiler/emitter.ts | 74 ++++++++++++++----- src/compiler/program.ts | 45 +++++------ .../when-rootDir-is-not-specified.js | 7 +- 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 7f77f70880add..3432b0b3c3b19 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -130,36 +130,32 @@ namespace ts { return Extension.Js; } - function rootDirOfOptions(configFile: ParsedCommandLine) { - return configFile.options.rootDir || getDirectoryPath(Debug.checkDefined(configFile.options.configFilePath)); - } - - function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined) { + function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { return outputDir ? resolvePath( outputDir, - getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase) + getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase) ) : inputFileName; } /* @internal */ - export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { + export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && !fileExtensionIs(inputFileName, Extension.Json)); return changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir), + getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), Extension.Dts ); } - function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { + function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { if (configFile.options.emitDeclarationOnly) return undefined; const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); const outputFileName = changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir), + getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), isJsonFile ? Extension.Json : - fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? + configFile.options.jsx === JsxEmit.Preserve && (fileExtensionIs(inputFileName, Extension.Tsx) || fileExtensionIs(inputFileName, Extension.Jsx)) ? Extension.Jsx : Extension.Js ); @@ -190,16 +186,16 @@ namespace ts { addOutput(buildInfoPath); } - function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"]) { + function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"], getCommonSourceDirectory?: () => string) { if (fileExtensionIs(inputFileName, Extension.Dts)) return; - const js = getOutputJSFileName(inputFileName, configFile, ignoreCase); + const js = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); addOutput(js); if (fileExtensionIs(inputFileName, Extension.Json)) return; if (js && configFile.options.sourceMap) { addOutput(`${js}.map`); } if (getEmitDeclarations(configFile.options)) { - const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); + const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); addOutput(dts); if (configFile.options.declarationMap) { addOutput(`${dts}.map`); @@ -207,6 +203,48 @@ namespace ts { } } + /*@internal*/ + export function getCommonSourceDirectory( + options: CompilerOptions, + emittedFiles: () => readonly string[], + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + checkSourceFilesBelongToPath?: (commonSourceDirectory: string) => void + ): string { + let commonSourceDirectory; + if (options.rootDir) { + // If a rootDir is specified use it as the commonSourceDirectory + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); + checkSourceFilesBelongToPath?.(options.rootDir); + } + else if (options.composite && options.configFilePath) { + // Project compilations never infer their root from the input source paths + commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); + checkSourceFilesBelongToPath?.(commonSourceDirectory); + } + else { + commonSourceDirectory = computeCommonSourceDirectoryOfFilenames(emittedFiles(), currentDirectory, getCanonicalFileName); + } + + if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; + } + return commonSourceDirectory; + } + + /*@internal*/ + export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string { + return getCommonSourceDirectory( + options, + () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensions)) && !fileExtensionIs(file, Extension.Dts)), + getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))), + createGetCanonicalFileName(!ignoreCase) + ); + } + /*@internal*/ export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { const { addOutput, getOutputs } = createAddOutput(); @@ -214,8 +252,9 @@ namespace ts { getSingleOutputFileNames(configFile, addOutput); } else { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); for (const inputFileName of configFile.fileNames) { - getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput); + getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput, getCommonSourceDirectory); } addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); } @@ -242,13 +281,14 @@ namespace ts { return Debug.checkDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); } + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); for (const inputFileName of configFile.fileNames) { if (fileExtensionIs(inputFileName, Extension.Dts)) continue; - const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase); + const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); if (jsFilePath) return jsFilePath; if (fileExtensionIs(inputFileName, Extension.Json)) continue; if (getEmitDeclarations(configFile.options)) { - return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); + return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); } } const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index a98fca35cda32..bd668768ca25a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -13,7 +13,7 @@ namespace ts { } /* @internal */ - export function computeCommonSourceDirectoryOfFilenames(fileNames: string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { + export function computeCommonSourceDirectoryOfFilenames(fileNames: readonly string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { let commonPathComponents: string[] | undefined; const failed = forEach(fileNames, sourceFile => { // Each file contributes into common source file path @@ -899,9 +899,15 @@ namespace ts { processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); } else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(parsedRef.commandLine, !host.useCaseSensitiveFileNames())); for (const fileName of parsedRef.commandLine.fileNames) { if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) { - processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); + processSourceFile( + getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory), + /*isDefaultLib*/ false, + /*ignoreNoDefaultLib*/ false, + /*packageId*/ undefined + ); } } } @@ -1127,26 +1133,13 @@ namespace ts { function getCommonSourceDirectory() { if (commonSourceDirectory === undefined) { const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); - if (options.rootDir) { - // If a rootDir is specified use it as the commonSourceDirectory - commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); - checkSourceFilesBelongToPath(emittedFiles, options.rootDir); - } - else if (options.composite && options.configFilePath) { - // Project compilations never infer their root from the input source paths - commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); - checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory); - } - else { - commonSourceDirectory = computeCommonSourceDirectory(emittedFiles); - } - - if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; - } + commonSourceDirectory = ts.getCommonSourceDirectory( + options, + () => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName), + currentDirectory, + getCanonicalFileName, + commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory) + ); } return commonSourceDirectory; } @@ -2708,9 +2701,10 @@ namespace ts { mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); } else { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(resolvedRef.commandLine, !host.useCaseSensitiveFileNames())); forEach(resolvedRef.commandLine.fileNames, fileName => { if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) { - const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames()); + const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory); mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); } }); @@ -2974,11 +2968,6 @@ namespace ts { } } - function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string { - const fileNames = mapDefined(sourceFiles, file => file.isDeclarationFile ? undefined : file.fileName); - return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName); - } - function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { let allFilesBelongToPath = true; const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); diff --git a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js index e957d7fb2373d..11648d4fe14c2 100644 --- a/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-specified.js @@ -27,7 +27,7 @@ Output:: [12:01:00 AM] Projects in this build: * src/tsconfig.json -[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/src/index.js' does not exist +[12:01:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/index.js' does not exist [12:01:00 AM] Building project '/src/tsconfig.json'... @@ -52,14 +52,11 @@ Output:: [12:04:00 AM] Projects in this build: * src/tsconfig.json -[12:04:00 AM] Project 'src/tsconfig.json' is out of date because output file 'src/dist/src/index.js' does not exist - -[12:04:00 AM] Building project '/src/tsconfig.json'... +[12:04:00 AM] Project 'src/tsconfig.json' is up to date because newest input 'src/src/index.ts' is older than oldest output 'src/dist/index.js' exitCode:: ExitStatus.Success -//// [/src/dist/index.js] file written with same contents Change:: Normal build without change, that does not block emit on error to show files that get emitted From 555acd6f9744e0617faf92a94177696074567fb5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Dec 2020 15:46:22 -0800 Subject: [PATCH 6/7] Tests for #41780 --- .../unittests/tsbuild/outputPaths.ts | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts index 49708809551b6..8e64818c5deee 100644 --- a/src/testRunner/unittests/tsbuild/outputPaths.ts +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -11,8 +11,28 @@ namespace ts { noChangeProject, ]; - verifyTscSerializedIncrementalEdits({ - scenario: "outputPaths", + function verify(input: Pick, 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;", @@ -22,12 +42,10 @@ namespace ts { } }) }), - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], incrementalScenarios, - }); + }, ["/src/dist/index.js"]); - verifyTscSerializedIncrementalEdits({ - scenario: "outputPaths", + verify({ subScenario: "when rootDir is not specified and is composite", fs: () => loadProjectFromFiles({ "/src/src/index.ts": "export const x = 10;", @@ -38,7 +56,6 @@ namespace ts { } }) }), - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], incrementalScenarios: [ noChangeRun, { @@ -50,10 +67,9 @@ namespace ts { } } ], - }); + }, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]); - verifyTscSerializedIncrementalEdits({ - scenario: "outputPaths", + verify({ subScenario: "when rootDir is specified", fs: () => loadProjectFromFiles({ "/src/src/index.ts": "export const x = 10;", @@ -64,12 +80,10 @@ namespace ts { } }) }), - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], incrementalScenarios, - }); + }, ["/src/dist/index.js"]); - verifyTscSerializedIncrementalEdits({ - scenario: "outputPaths", + verify({ subScenario: "when rootDir is specified but not all files belong to rootDir", fs: () => loadProjectFromFiles({ "/src/src/index.ts": "export const x = 10;", @@ -81,12 +95,10 @@ namespace ts { } }) }), - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], incrementalScenarios, - }); + }, ["/src/dist/index.js"]); - verifyTscSerializedIncrementalEdits({ - scenario: "outputPaths", + 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;", @@ -99,8 +111,7 @@ namespace ts { } }) }), - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], incrementalScenarios, - }); + }, ["/src/dist/index.js", "/src/dist/index.d.ts"]); }); } From 2f5ecf77a0fd69be097191d69b7798c951e042ce Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Dec 2020 11:09:09 -0800 Subject: [PATCH 7/7] Spelling --- src/testRunner/unittests/tsbuild/helpers.ts | 38 +++++++++---------- .../unittests/tsbuild/outputPaths.ts | 6 +-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 6a1f750225a89..d9a41fdf1a797 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -270,12 +270,12 @@ interface Symbol { tick: () => void; baseFs: vfs.FileSystem; newSys: TscCompileSystem; - cleanBuildDescripencies: TscIncremental["cleanBuildDescripencies"]; + 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, cleanBuildDescripencies, + scenario, subScenario, commandLineArgs, cleanBuildDiscrepancies, modifyFs, incrementalModifyFs, tick, baseFs, newSys } = input(); @@ -290,21 +290,21 @@ interface Symbol { incrementalModifyFs(fs); }, }); - const descripencies = cleanBuildDescripencies?.(); + const discrepancies = cleanBuildDiscrepancies?.(); for (const outputFile of arrayFrom(sys.writtenFiles.keys())) { const cleanBuildText = sys.readFile(outputFile); const incrementalBuildText = newSys.readFile(outputFile); - const descripencyInClean = descripencies?.get(outputFile); + const descrepancyInClean = discrepancies?.get(outputFile); if (!isBuildInfoFile(outputFile)) { - verifyTextEqual(incrementalBuildText, cleanBuildText, descripencyInClean, `File: ${outputFile}`); + verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`); } else if (incrementalBuildText !== cleanBuildText) { // Verify build info without affectedFilesPendingEmit const { buildInfo: incrementalBuildInfo, affectedFilesPendingEmit: incrementalBuildAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText); const { buildInfo: cleanBuildInfo, affectedFilesPendingEmit: incrementalAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText); - verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descripencyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${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 (incrementalBuildAffectedFilesPendingEmit && descripencyInClean === undefined) { + 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; incrementalBuildAffectedFilesPendingEmit.forEach(([actualFile]) => { @@ -316,23 +316,23 @@ interface Symbol { } } - function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descripencyInClean: CleanBuildDescripency | undefined, message: string) { - if (descripencyInClean === undefined) { + function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descrepancyInClean: CleanBuildDescrepancy | undefined, message: string) { + if (descrepancyInClean === undefined) { assert.equal(incrementalText, cleanText, message); return; } - switch (descripencyInClean) { - case CleanBuildDescripency.CleanFileTextDifferent: + 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 CleanBuildDescripency.CleanFilePresent: + 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(descripencyInClean); + Debug.assertNever(descrepancyInClean); } } }); @@ -355,7 +355,7 @@ interface Symbol { }; } - export enum CleanBuildDescripency { + export enum CleanBuildDescrepancy { CleanFileTextDifferent, CleanFilePresent, } @@ -365,7 +365,7 @@ interface Symbol { modifyFs: (fs: vfs.FileSystem) => void; subScenario?: string; commandLineArgs?: readonly string[]; - cleanBuildDescripencies?: () => ESMap; + cleanBuildDiscrepancies?: () => ESMap; } export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker { @@ -426,7 +426,7 @@ interface Symbol { modifyFs: incrementalModifyFs, subScenario: incrementalSubScenario, commandLineArgs: incrementalCommandLineArgs, - cleanBuildDescripencies, + cleanBuildDiscrepancies, }, index) => { describe(incrementalSubScenario || buildKind, () => { let newSys: TscCompileSystem; @@ -459,7 +459,7 @@ interface Symbol { baseFs, newSys, commandLineArgs: incrementalCommandLineArgs || commandLineArgs, - cleanBuildDescripencies, + cleanBuildDiscrepancies, incrementalModifyFs, modifyFs, tick @@ -551,13 +551,13 @@ interface Symbol { })); }); describe("incremental correctness", () => { - incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDescripencies }, index) => verifyIncrementalCorrectness(() => ({ + incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDiscrepancies }, index) => verifyIncrementalCorrectness(() => ({ scenario, subScenario: subScenario || buildKind, baseFs, newSys: incrementalSys[index], commandLineArgs: incrementalCommandLineArgs || commandLineArgs, - cleanBuildDescripencies, + cleanBuildDiscrepancies, incrementalModifyFs: fs => { for (let i = 0; i <= index; i++) { incrementalScenarios[i].modifyFs(fs); diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts index 8e64818c5deee..96298e3aeb8fb 100644 --- a/src/testRunner/unittests/tsbuild/outputPaths.ts +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -60,9 +60,9 @@ namespace ts { noChangeRun, { ...noChangeProject, - cleanBuildDescripencies: () => { - const map = new Map(); - map.set("/src/dist/tsconfig.tsbuildinfo", CleanBuildDescripency.CleanFileTextDifferent); // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change. + cleanBuildDiscrepancies: () => { + const map = new Map(); + 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; } }