Skip to content

Commit e42f37f

Browse files
authored
Merge pull request #30374 from Microsoft/jsonSourceFile
Correct the generation of output file names for tsc --b
2 parents 4e0514d + 812ff98 commit e42f37f

File tree

10 files changed

+113
-101
lines changed

10 files changed

+113
-101
lines changed

src/compiler/emitter.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,81 @@ namespace ts {
131131
return Extension.Js;
132132
}
133133

134+
function rootDirOfOptions(configFile: ParsedCommandLine) {
135+
return configFile.options.rootDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath));
136+
}
137+
138+
/* @internal */
139+
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
140+
Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && hasTSFileExtension(inputFileName));
141+
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase);
142+
const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath);
143+
return changeExtension(outputPath, Extension.Dts);
144+
}
145+
146+
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
147+
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase);
148+
const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath);
149+
const isJsonFile = fileExtensionIs(inputFileName, Extension.Json);
150+
const outputFileName = changeExtension(outputPath, isJsonFile ?
151+
Extension.Json :
152+
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ?
153+
Extension.Jsx :
154+
Extension.Js);
155+
return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.assertDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ?
156+
outputFileName :
157+
undefined;
158+
}
159+
160+
/*@internal*/
161+
export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): ReadonlyArray<string> {
162+
let outputs: string[] | undefined;
163+
const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path);
164+
if (configFile.options.outFile || configFile.options.out) {
165+
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false);
166+
addOutput(jsFilePath);
167+
addOutput(sourceMapFilePath);
168+
addOutput(declarationFilePath);
169+
addOutput(declarationMapPath);
170+
addOutput(buildInfoPath);
171+
}
172+
else {
173+
for (const inputFileName of configFile.fileNames) {
174+
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
175+
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase);
176+
addOutput(js);
177+
if (fileExtensionIs(inputFileName, Extension.Json)) continue;
178+
if (configFile.options.sourceMap) {
179+
addOutput(`${js}.map`);
180+
}
181+
if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) {
182+
const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
183+
addOutput(dts);
184+
if (configFile.options.declarationMap) {
185+
addOutput(`${dts}.map`);
186+
}
187+
}
188+
}
189+
addOutput(getOutputPathForBuildInfo(configFile.options));
190+
}
191+
return outputs || emptyArray;
192+
}
193+
194+
/*@internal*/
195+
export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string {
196+
if (configFile.options.outFile || configFile.options.out) {
197+
const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false);
198+
return Debug.assertDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`);
199+
}
200+
201+
for (const inputFileName of configFile.fileNames) {
202+
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
203+
const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase);
204+
if (jsFilePath) return jsFilePath;
205+
}
206+
return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`);
207+
}
208+
134209
/*@internal*/
135210
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
136211
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<Bundle | SourceFile>[], declarationTransformers?: TransformerFactory<Bundle | SourceFile>[], onlyBuildInfo?: boolean): EmitResult {

src/compiler/program.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ namespace ts {
833833
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
834834
for (const fileName of parsedRef.commandLine.fileNames) {
835835
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
836-
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
836+
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
837837
}
838838
}
839839
}
@@ -2378,7 +2378,7 @@ namespace ts {
23782378
const out = referencedProject.commandLine.options.outFile || referencedProject.commandLine.options.out;
23792379
return out ?
23802380
changeExtension(out, Extension.Dts) :
2381-
getOutputDeclarationFileName(fileName, referencedProject.commandLine);
2381+
getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames());
23822382
}
23832383

23842384
/**

src/compiler/tsbuild.ts

Lines changed: 5 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -276,60 +276,6 @@ namespace ts {
276276
return getOrCreateValueFromConfigFileMap<Map<T>>(configFileMap, resolved, createMap);
277277
}
278278

279-
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine) {
280-
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true);
281-
const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath);
282-
return changeExtension(outputPath, Extension.Dts);
283-
}
284-
285-
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine) {
286-
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true);
287-
const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath);
288-
const newExtension = fileExtensionIs(inputFileName, Extension.Json) ? Extension.Json :
289-
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js;
290-
return changeExtension(outputPath, newExtension);
291-
}
292-
293-
function getOutputFileNames(inputFileName: string, configFile: ParsedCommandLine): ReadonlyArray<string> {
294-
// outFile is handled elsewhere; .d.ts files don't generate outputs
295-
if (configFile.options.outFile || configFile.options.out || fileExtensionIs(inputFileName, Extension.Dts)) {
296-
return emptyArray;
297-
}
298-
299-
const outputs: string[] = [];
300-
const js = getOutputJSFileName(inputFileName, configFile);
301-
outputs.push(js);
302-
if (configFile.options.sourceMap) {
303-
outputs.push(`${js}.map`);
304-
}
305-
if (getEmitDeclarations(configFile.options) && !fileExtensionIs(inputFileName, Extension.Json)) {
306-
const dts = getOutputDeclarationFileName(inputFileName, configFile);
307-
outputs.push(dts);
308-
if (configFile.options.declarationMap) {
309-
outputs.push(`${dts}.map`);
310-
}
311-
}
312-
return outputs;
313-
}
314-
315-
function getOutFileOutputs(project: ParsedCommandLine, ignoreBuildInfo?: boolean): ReadonlyArray<string> {
316-
Debug.assert(!!project.options.outFile || !!project.options.out, "outFile must be set");
317-
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(project.options, /*forceDtsPaths*/ false);
318-
319-
let outputs: string[] | undefined = [];
320-
const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path);
321-
addOutput(jsFilePath);
322-
addOutput(sourceMapFilePath);
323-
addOutput(declarationFilePath);
324-
addOutput(declarationMapPath);
325-
if (!ignoreBuildInfo) addOutput(buildInfoPath);
326-
return outputs || emptyArray;
327-
}
328-
329-
function rootDirOfOptions(opts: CompilerOptions, configFileName: string) {
330-
return opts.rootDir || getDirectoryPath(configFileName);
331-
}
332-
333279
function newer(date1: Date, date2: Date): Date {
334280
return date2 > date1 ? date2 : date1;
335281
}
@@ -715,7 +661,7 @@ namespace ts {
715661
}
716662

717663
// Collect the expected outputs of this project
718-
const outputs = getAllProjectOutputs(project);
664+
const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames());
719665

720666
if (outputs.length === 0) {
721667
return {
@@ -1202,7 +1148,7 @@ namespace ts {
12021148
const status: Status.UpToDate = {
12031149
type: UpToDateStatusType.UpToDate,
12041150
newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime,
1205-
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile)
1151+
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames())
12061152
};
12071153
diagnostics.removeKey(proj);
12081154
projectStatus.setValue(proj, status);
@@ -1295,13 +1241,13 @@ namespace ts {
12951241
const status: Status.UpToDate = {
12961242
type: UpToDateStatusType.UpToDate,
12971243
newestDeclarationFileContentChangedTime: priorNewestUpdateTime,
1298-
oldestOutputFileName: getFirstProjectOutput(proj)
1244+
oldestOutputFileName: getFirstProjectOutput(proj, !host.useCaseSensitiveFileNames())
12991245
};
13001246
projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status);
13011247
}
13021248

13031249
function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap<true>) {
1304-
const outputs = getAllProjectOutputs(proj);
1250+
const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames());
13051251
if (!skipOutputs || outputs.length !== skipOutputs.getSize()) {
13061252
if (options.verbose) {
13071253
reportStatus(verboseMessage, proj.options.configFilePath!);
@@ -1337,7 +1283,7 @@ namespace ts {
13371283
reportParseConfigFileDiagnostic(proj);
13381284
continue;
13391285
}
1340-
const outputs = getAllProjectOutputs(parsed);
1286+
const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames());
13411287
for (const output of outputs) {
13421288
if (host.fileExists(output)) {
13431289
filesToDelete.push(output);
@@ -1499,35 +1445,6 @@ namespace ts {
14991445
return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName;
15001446
}
15011447

1502-
export function getAllProjectOutputs(project: ParsedCommandLine): ReadonlyArray<string> {
1503-
if (project.options.outFile || project.options.out) {
1504-
return getOutFileOutputs(project);
1505-
}
1506-
else {
1507-
const outputs: string[] = [];
1508-
for (const inputFile of project.fileNames) {
1509-
outputs.push(...getOutputFileNames(inputFile, project));
1510-
}
1511-
const buildInfoPath = getOutputPathForBuildInfo(project.options);
1512-
if (buildInfoPath) outputs.push(buildInfoPath);
1513-
return outputs;
1514-
}
1515-
}
1516-
1517-
function getFirstProjectOutput(project: ParsedCommandLine): string {
1518-
if (project.options.outFile || project.options.out) {
1519-
return first(getOutFileOutputs(project));
1520-
}
1521-
1522-
for (const inputFile of project.fileNames) {
1523-
const outputs = getOutputFileNames(inputFile, project);
1524-
if (outputs.length) {
1525-
return first(outputs);
1526-
}
1527-
}
1528-
return Debug.fail(`project ${project.options.configFilePath} expected to have at least one output`);
1529-
}
1530-
15311448
export function formatUpToDateStatus<T>(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) {
15321449
switch (status.type) {
15331450
case UpToDateStatusType.OutOfDateWithSelf:

src/testRunner/unittests/tsbuild/resolveJsonModule.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace ts {
22
describe("unittests:: tsbuild:: with resolveJsonModule option", () => {
33
let projFs: vfs.FileSystem;
4-
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"];
4+
const allExpectedOutputs = ["/src/dist/src/index.js", "/src/dist/src/index.d.ts", "/src/dist/src/hello.json"];
55
before(() => {
66
projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
77
});
@@ -29,33 +29,53 @@ namespace ts {
2929
}
3030

3131
it("with resolveJsonModule and include only", () => {
32-
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", [
32+
verifyProjectWithResolveJsonModule("/src/tsconfig_withInclude.json", [
3333
Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern,
34-
"/src/tests/src/hello.json"
34+
"/src/src/hello.json"
3535
]);
3636
});
3737

3838
it("with resolveJsonModule and include of *.json along with other include", () => {
39-
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeOfJson.json");
39+
verifyProjectWithResolveJsonModule("/src/tsconfig_withIncludeOfJson.json");
4040
});
4141

4242
it("with resolveJsonModule and include of *.json along with other include and file name matches ts file", () => {
4343
const fs = projFs.shadow();
44-
fs.rimrafSync("/src/tests/src/hello.json");
45-
fs.writeFileSync("/src/tests/src/index.json", JSON.stringify({ hello: "world" }));
46-
fs.writeFileSync("/src/tests/src/index.ts", `import hello from "./index.json"
44+
fs.rimrafSync("/src/src/hello.json");
45+
fs.writeFileSync("/src/src/index.json", JSON.stringify({ hello: "world" }));
46+
fs.writeFileSync("/src/src/index.ts", `import hello from "./index.json"
4747
4848
export default hello.hello`);
49-
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/index.json"];
50-
verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tests/tsconfig_withIncludeOfJson.json", allExpectedOutputs);
49+
const allExpectedOutputs = ["/src/dist/src/index.js", "/src/dist/src/index.d.ts", "/src/dist/src/index.json"];
50+
verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tsconfig_withIncludeOfJson.json", allExpectedOutputs);
5151
});
5252

5353
it("with resolveJsonModule and files containing json file", () => {
54-
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withFiles.json");
54+
verifyProjectWithResolveJsonModule("/src/tsconfig_withFiles.json");
5555
});
5656

5757
it("with resolveJsonModule and include and files", () => {
58-
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeAndFiles.json");
58+
verifyProjectWithResolveJsonModule("/src/tsconfig_withIncludeAndFiles.json");
59+
});
60+
61+
it("with resolveJsonModule and sourceMap", () => {
62+
const fs = projFs.shadow();
63+
const configFile = "src/tsconfig_withFiles.json";
64+
replaceText(fs, configFile, `"composite": true,`, `"composite": true, "sourceMap": true,`);
65+
const host = new fakes.SolutionBuilderHost(fs);
66+
const builder = createSolutionBuilder(host, [configFile], { verbose: false });
67+
builder.buildAllProjects();
68+
host.assertDiagnosticMessages();
69+
for (const output of [...allExpectedOutputs, "/src/dist/src/index.js.map"]) {
70+
assert(fs.existsSync(output), `Expect file ${output} to exist`);
71+
}
72+
73+
const newBuilder = createSolutionBuilder(host, [configFile], { verbose: true });
74+
newBuilder.buildAllProjects();
75+
host.assertDiagnosticMessages(
76+
getExpectedDiagnosticForProjectsInBuild(configFile),
77+
[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"]
78+
);
5979
});
6080
});
6181
}

0 commit comments

Comments
 (0)