Correct the generation of output file names for tsc --b

Fixes #30356
This commit is contained in:
Sheetal Nandi
2019-03-13 09:58:26 -07:00
parent b762d6205e
commit 812ff98f61
10 changed files with 113 additions and 101 deletions

View File

@@ -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<string> {
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<Bundle | SourceFile>[], declarationTransformers?: TransformerFactory<Bundle | SourceFile>[], onlyBuildInfo?: boolean): EmitResult {

View File

@@ -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());
}
/**

View File

@@ -276,60 +276,6 @@ namespace ts {
return getOrCreateValueFromConfigFileMap<Map<T>>(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<string> {
// 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<string> {
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<true>) {
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<string> {
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<T>(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) {
switch (status.type) {
case UpToDateStatusType.OutOfDateWithSelf:

View File

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