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:
Sheetal Nandi 2020-12-07 11:53:22 -08:00 committed by GitHub
parent 37e898cfd7
commit bfb259128b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 745 additions and 87 deletions

View File

@ -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<typeof createAddOutput>["addOutput"]) {
function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType<typeof createAddOutput>["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);

View File

@ -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,25 +1133,13 @@ namespace ts {
function getCommonSourceDirectory() {
if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
}
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;
}
@ -2707,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);
}
});
@ -2973,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));

View File

@ -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",

View File

@ -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);

View 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"]);
});
}

View File

@ -0,0 +1,4 @@
/// <reference path="../../outdir/simple/fileC.d.ts" />
declare class B {
c: C;
}

View File

@ -1,4 +0,0 @@
/// <reference path="FolderC/fileC.d.ts" />
declare class B {
c: C;
}

View File

@ -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"
]
}

View File

@ -0,0 +1,4 @@
/// <reference path="../../outdir/simple/fileC.d.ts" />
declare class B {
c: C;
}

View File

@ -1,4 +0,0 @@
/// <reference path="FolderC/fileC.d.ts" />
declare class B {
c: C;
}

View File

@ -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"
]
}

View File

@ -0,0 +1,104 @@
Input::
//// [/lib/lib.d.ts]
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
"signature": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\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

View File

@ -0,0 +1,71 @@
Input::
//// [/lib/lib.d.ts]
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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/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

View File

@ -0,0 +1,137 @@
Input::
//// [/lib/lib.d.ts]
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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/index.d.ts]
export declare const x = 10;
//// [/src/dist/index.js]
"use strict";
exports.__esModule = true;
exports.x = void 0;
exports.x = 10;
//// [/src/tsconfig.tsbuildinfo]
{
"program": {
"fileInfos": {
"../lib/lib.d.ts": {
"version": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
"signature": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\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"
}
//// [/src/types/type.d.ts]
export declare type t = string;
//// [/src/types/type.js]
"use strict";
exports.__esModule = true;

View File

@ -0,0 +1,95 @@
Input::
//// [/lib/lib.d.ts]
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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/index.js]
"use strict";
exports.__esModule = true;
exports.x = void 0;
exports.x = 10;
//// [/src/types/type.js]
"use strict";
exports.__esModule = true;

View File

@ -0,0 +1,71 @@
Input::
//// [/lib/lib.d.ts]
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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

View File

@ -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