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 8d4efc3e941b3..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,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)); 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/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index e8ae55b88c54c..d9a41fdf1a797 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; + 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; } 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); diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts new file mode 100644 index 0000000000000..96298e3aeb8fb --- /dev/null +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -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, 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(); + 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"]); + }); +} 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-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..11648d4fe14c2 --- /dev/null +++ b/tests/baselines/reference/tsbuild/outputPaths/initial-build/when-rootDir-is-not-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"}} + + + +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 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..0b529ae90fb92 --- /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/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-/// \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" +} + +//// [/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 new file mode 100644 index 0000000000000..159c43724d83d --- /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/index.js] +"use strict"; +exports.__esModule = true; +exports.x = void 0; +exports.x = 10; + + +//// [/src/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 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