diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 97248f402a6..1b2c52afd9b 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -131,6 +131,81 @@ namespace ts { return Extension.Js; } + function rootDirOfOptions(configFile: ParsedCommandLine) { + return configFile.options.rootDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)); + } + + /* @internal */ + export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { + Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && hasTSFileExtension(inputFileName)); + const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase); + const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath); + return changeExtension(outputPath, Extension.Dts); + } + + function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { + const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase); + const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath); + const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); + const outputFileName = changeExtension(outputPath, isJsonFile ? + Extension.Json : + fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? + Extension.Jsx : + Extension.Js); + return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.assertDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? + outputFileName : + undefined; + } + + /*@internal*/ + export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): ReadonlyArray { + let outputs: string[] | undefined; + const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path); + if (configFile.options.outFile || configFile.options.out) { + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + addOutput(jsFilePath); + addOutput(sourceMapFilePath); + addOutput(declarationFilePath); + addOutput(declarationMapPath); + addOutput(buildInfoPath); + } + else { + for (const inputFileName of configFile.fileNames) { + if (fileExtensionIs(inputFileName, Extension.Dts)) continue; + const js = getOutputJSFileName(inputFileName, configFile, ignoreCase); + addOutput(js); + if (fileExtensionIs(inputFileName, Extension.Json)) continue; + if (configFile.options.sourceMap) { + addOutput(`${js}.map`); + } + if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) { + const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); + addOutput(dts); + if (configFile.options.declarationMap) { + addOutput(`${dts}.map`); + } + } + } + addOutput(getOutputPathForBuildInfo(configFile.options)); + } + return outputs || emptyArray; + } + + /*@internal*/ + export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { + if (configFile.options.outFile || configFile.options.out) { + const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + return Debug.assertDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); + } + + for (const inputFileName of configFile.fileNames) { + if (fileExtensionIs(inputFileName, Extension.Dts)) continue; + const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase); + if (jsFilePath) return jsFilePath; + } + return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); + } + /*@internal*/ // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory[], declarationTransformers?: TransformerFactory[], onlyBuildInfo?: boolean): EmitResult { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2f817cfcf96..e2ba32b9c63 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -833,7 +833,7 @@ namespace ts { else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { for (const fileName of parsedRef.commandLine.fileNames) { if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) { - processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); + processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); } } } @@ -2378,7 +2378,7 @@ namespace ts { const out = referencedProject.commandLine.options.outFile || referencedProject.commandLine.options.out; return out ? changeExtension(out, Extension.Dts) : - getOutputDeclarationFileName(fileName, referencedProject.commandLine); + getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); } /** diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index c5895c1155c..3e2c7c0f241 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -276,60 +276,6 @@ namespace ts { return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); } - export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine) { - const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true); - const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath); - return changeExtension(outputPath, Extension.Dts); - } - - function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine) { - const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true); - const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath); - const newExtension = fileExtensionIs(inputFileName, Extension.Json) ? Extension.Json : - fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; - return changeExtension(outputPath, newExtension); - } - - function getOutputFileNames(inputFileName: string, configFile: ParsedCommandLine): ReadonlyArray { - // outFile is handled elsewhere; .d.ts files don't generate outputs - if (configFile.options.outFile || configFile.options.out || fileExtensionIs(inputFileName, Extension.Dts)) { - return emptyArray; - } - - const outputs: string[] = []; - const js = getOutputJSFileName(inputFileName, configFile); - outputs.push(js); - if (configFile.options.sourceMap) { - outputs.push(`${js}.map`); - } - if (getEmitDeclarations(configFile.options) && !fileExtensionIs(inputFileName, Extension.Json)) { - const dts = getOutputDeclarationFileName(inputFileName, configFile); - outputs.push(dts); - if (configFile.options.declarationMap) { - outputs.push(`${dts}.map`); - } - } - return outputs; - } - - function getOutFileOutputs(project: ParsedCommandLine, ignoreBuildInfo?: boolean): ReadonlyArray { - Debug.assert(!!project.options.outFile || !!project.options.out, "outFile must be set"); - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(project.options, /*forceDtsPaths*/ false); - - let outputs: string[] | undefined = []; - const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path); - addOutput(jsFilePath); - addOutput(sourceMapFilePath); - addOutput(declarationFilePath); - addOutput(declarationMapPath); - if (!ignoreBuildInfo) addOutput(buildInfoPath); - return outputs || emptyArray; - } - - function rootDirOfOptions(opts: CompilerOptions, configFileName: string) { - return opts.rootDir || getDirectoryPath(configFileName); - } - function newer(date1: Date, date2: Date): Date { return date2 > date1 ? date2 : date1; } @@ -715,7 +661,7 @@ namespace ts { } // Collect the expected outputs of this project - const outputs = getAllProjectOutputs(project); + const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); if (outputs.length === 0) { return { @@ -1202,7 +1148,7 @@ namespace ts { const status: Status.UpToDate = { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile) + oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) }; diagnostics.removeKey(proj); projectStatus.setValue(proj, status); @@ -1295,13 +1241,13 @@ namespace ts { const status: Status.UpToDate = { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime, - oldestOutputFileName: getFirstProjectOutput(proj) + oldestOutputFileName: getFirstProjectOutput(proj, !host.useCaseSensitiveFileNames()) }; projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status); } function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { - const outputs = getAllProjectOutputs(proj); + const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); if (!skipOutputs || outputs.length !== skipOutputs.getSize()) { if (options.verbose) { reportStatus(verboseMessage, proj.options.configFilePath!); @@ -1337,7 +1283,7 @@ namespace ts { reportParseConfigFileDiagnostic(proj); continue; } - const outputs = getAllProjectOutputs(parsed); + const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); for (const output of outputs) { if (host.fileExists(output)) { filesToDelete.push(output); @@ -1499,35 +1445,6 @@ namespace ts { return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName; } - export function getAllProjectOutputs(project: ParsedCommandLine): ReadonlyArray { - if (project.options.outFile || project.options.out) { - return getOutFileOutputs(project); - } - else { - const outputs: string[] = []; - for (const inputFile of project.fileNames) { - outputs.push(...getOutputFileNames(inputFile, project)); - } - const buildInfoPath = getOutputPathForBuildInfo(project.options); - if (buildInfoPath) outputs.push(buildInfoPath); - return outputs; - } - } - - function getFirstProjectOutput(project: ParsedCommandLine): string { - if (project.options.outFile || project.options.out) { - return first(getOutFileOutputs(project)); - } - - for (const inputFile of project.fileNames) { - const outputs = getOutputFileNames(inputFile, project); - if (outputs.length) { - return first(outputs); - } - } - return Debug.fail(`project ${project.options.configFilePath} expected to have at least one output`); - } - export function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { switch (status.type) { case UpToDateStatusType.OutOfDateWithSelf: diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index 3ec4f8aff9c..514defb3583 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -1,7 +1,7 @@ namespace ts { describe("unittests:: tsbuild:: with resolveJsonModule option", () => { let projFs: vfs.FileSystem; - const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"]; + const allExpectedOutputs = ["/src/dist/src/index.js", "/src/dist/src/index.d.ts", "/src/dist/src/hello.json"]; before(() => { projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite"); }); @@ -29,33 +29,53 @@ namespace ts { } it("with resolveJsonModule and include only", () => { - verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", [ + verifyProjectWithResolveJsonModule("/src/tsconfig_withInclude.json", [ Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, - "/src/tests/src/hello.json" + "/src/src/hello.json" ]); }); it("with resolveJsonModule and include of *.json along with other include", () => { - verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeOfJson.json"); + verifyProjectWithResolveJsonModule("/src/tsconfig_withIncludeOfJson.json"); }); it("with resolveJsonModule and include of *.json along with other include and file name matches ts file", () => { const fs = projFs.shadow(); - fs.rimrafSync("/src/tests/src/hello.json"); - fs.writeFileSync("/src/tests/src/index.json", JSON.stringify({ hello: "world" })); - fs.writeFileSync("/src/tests/src/index.ts", `import hello from "./index.json" + fs.rimrafSync("/src/src/hello.json"); + fs.writeFileSync("/src/src/index.json", JSON.stringify({ hello: "world" })); + fs.writeFileSync("/src/src/index.ts", `import hello from "./index.json" export default hello.hello`); - const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/index.json"]; - verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tests/tsconfig_withIncludeOfJson.json", allExpectedOutputs); + const allExpectedOutputs = ["/src/dist/src/index.js", "/src/dist/src/index.d.ts", "/src/dist/src/index.json"]; + verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tsconfig_withIncludeOfJson.json", allExpectedOutputs); }); it("with resolveJsonModule and files containing json file", () => { - verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withFiles.json"); + verifyProjectWithResolveJsonModule("/src/tsconfig_withFiles.json"); }); it("with resolveJsonModule and include and files", () => { - verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeAndFiles.json"); + verifyProjectWithResolveJsonModule("/src/tsconfig_withIncludeAndFiles.json"); + }); + + it("with resolveJsonModule and sourceMap", () => { + const fs = projFs.shadow(); + const configFile = "src/tsconfig_withFiles.json"; + replaceText(fs, configFile, `"composite": true,`, `"composite": true, "sourceMap": true,`); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, [configFile], { verbose: false }); + builder.buildAllProjects(); + host.assertDiagnosticMessages(); + for (const output of [...allExpectedOutputs, "/src/dist/src/index.js.map"]) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); + } + + const newBuilder = createSolutionBuilder(host, [configFile], { verbose: true }); + newBuilder.buildAllProjects(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild(configFile), + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFile, "src/src/index.ts", "src/dist/src/index.js"] + ); }); }); } diff --git a/tests/projects/resolveJsonModuleAndComposite/tests/src/hello.json b/tests/projects/resolveJsonModuleAndComposite/src/hello.json similarity index 100% rename from tests/projects/resolveJsonModuleAndComposite/tests/src/hello.json rename to tests/projects/resolveJsonModuleAndComposite/src/hello.json diff --git a/tests/projects/resolveJsonModuleAndComposite/tests/src/index.ts b/tests/projects/resolveJsonModuleAndComposite/src/index.ts similarity index 100% rename from tests/projects/resolveJsonModuleAndComposite/tests/src/index.ts rename to tests/projects/resolveJsonModuleAndComposite/src/index.ts diff --git a/tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withFiles.json b/tests/projects/resolveJsonModuleAndComposite/tsconfig_withFiles.json similarity index 100% rename from tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withFiles.json rename to tests/projects/resolveJsonModuleAndComposite/tsconfig_withFiles.json diff --git a/tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withInclude.json b/tests/projects/resolveJsonModuleAndComposite/tsconfig_withInclude.json similarity index 100% rename from tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withInclude.json rename to tests/projects/resolveJsonModuleAndComposite/tsconfig_withInclude.json diff --git a/tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withIncludeAndFiles.json b/tests/projects/resolveJsonModuleAndComposite/tsconfig_withIncludeAndFiles.json similarity index 100% rename from tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withIncludeAndFiles.json rename to tests/projects/resolveJsonModuleAndComposite/tsconfig_withIncludeAndFiles.json diff --git a/tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withIncludeOfJson.json b/tests/projects/resolveJsonModuleAndComposite/tsconfig_withIncludeOfJson.json similarity index 100% rename from tests/projects/resolveJsonModuleAndComposite/tests/tsconfig_withIncludeOfJson.json rename to tests/projects/resolveJsonModuleAndComposite/tsconfig_withIncludeOfJson.json