Skip to content

Commit c87ca2f

Browse files
committed
Fix diagnostic reporting for empty files in tsconfig
1 parent 4510149 commit c87ca2f

File tree

8 files changed

+132
-9
lines changed

8 files changed

+132
-9
lines changed

src/compiler/commandLineParser.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,7 +1843,9 @@ namespace ts {
18431843
if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) {
18441844
if (isArray(raw.files)) {
18451845
filesSpecs = <ReadonlyArray<string>>raw.files;
1846-
if (filesSpecs.length === 0) {
1846+
const hasReferences = hasProperty(raw, "references") && !isNullOrUndefined(raw.references);
1847+
const hasZeroOrNoReferences = !hasReferences || raw.references.length === 0;
1848+
if (filesSpecs.length === 0 && hasZeroOrNoReferences) {
18471849
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
18481850
}
18491851
}
@@ -2067,11 +2069,6 @@ namespace ts {
20672069
createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)
20682070
);
20692071
return;
2070-
case "files":
2071-
if ((<ReadonlyArray<string>>value).length === 0) {
2072-
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueNode, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"));
2073-
}
2074-
return;
20752072
}
20762073
},
20772074
onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) {
@@ -2081,6 +2078,13 @@ namespace ts {
20812078
}
20822079
};
20832080
const json = convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, getTsconfigRootOptionsMap(), optionsIterator);
2081+
const hasZeroFiles = json && json.files && json.files.length === 0;
2082+
const hasZeroOrNoReferences = !(json && json.references) || json.references.length === 0;
2083+
2084+
if (hasZeroFiles && hasZeroOrNoReferences) {
2085+
errors.push(createCompilerDiagnostic(Diagnostics.The_files_list_in_config_file_0_is_empty, sourceFile.fileName));
2086+
}
2087+
20842088
if (!typeAcquisition) {
20852089
if (typingOptionstypeAcquisition) {
20862090
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?

src/compiler/tsbuild.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,6 @@ namespace ts {
946946
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
947947
return resultFlags;
948948
}
949-
950949
if (configFile.fileNames.length === 0) {
951950
// Nothing to build - must be a solution file, basically
952951
return BuildResultFlags.None;
@@ -956,7 +955,8 @@ namespace ts {
956955
projectReferences: configFile.projectReferences,
957956
host,
958957
rootNames: configFile.fileNames,
959-
options: configFile.options
958+
options: configFile.options,
959+
configFileParsingDiagnostics: configFile.errors,
960960
};
961961
const program = createProgram(programOptions);
962962

@@ -1149,19 +1149,22 @@ namespace ts {
11491149

11501150
const queue = graph.buildQueue;
11511151
reportBuildQueue(graph);
1152-
11531152
let anyFailed = false;
11541153
for (const next of queue) {
11551154
const proj = configFileCache.parseConfigFile(next);
11561155
if (proj === undefined) {
11571156
anyFailed = true;
11581157
break;
11591158
}
1159+
1160+
// report errors early when using continue or break statements
1161+
const errors = proj.errors;
11601162
const status = getUpToDateStatus(proj);
11611163
verboseReportProjectStatus(next, status);
11621164

11631165
const projName = proj.options.configFilePath!;
11641166
if (status.type === UpToDateStatusType.UpToDate && !context.options.force) {
1167+
reportErrors(errors);
11651168
// Up to date, skip
11661169
if (defaultOptions.dry) {
11671170
// In a dry build, inform the user of this fact
@@ -1171,17 +1174,20 @@ namespace ts {
11711174
}
11721175

11731176
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !context.options.force) {
1177+
reportErrors(errors);
11741178
// Fake build
11751179
updateOutputTimestamps(proj);
11761180
continue;
11771181
}
11781182

11791183
if (status.type === UpToDateStatusType.UpstreamBlocked) {
1184+
reportErrors(errors);
11801185
if (context.options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName);
11811186
continue;
11821187
}
11831188

11841189
if (status.type === UpToDateStatusType.ContainerOnly) {
1190+
reportErrors(errors);
11851191
// Do nothing
11861192
continue;
11871193
}
@@ -1193,6 +1199,10 @@ namespace ts {
11931199
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
11941200
}
11951201

1202+
function reportErrors(errors: Diagnostic[]) {
1203+
errors.forEach((err) => host.reportDiagnostic(err));
1204+
}
1205+
11961206
/**
11971207
* Report the build ordering inferred from the current project graph if we're in verbose mode
11981208
*/

src/testRunner/unittests/tsbuild.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,48 @@ namespace ts {
292292
});
293293
}
294294

295+
export namespace EmptyFiles {
296+
const projFs = loadProjectFromDisk("tests/projects/empty-files");
297+
298+
const allExpectedOutputs = [
299+
"/src/core/index.js",
300+
"/src/core/index.d.ts",
301+
"/src/core/index.d.ts.map",
302+
];
303+
304+
describe("tsbuild - empty files option in tsconfig", () => {
305+
it("has empty files diagnostic when files is empty and no references are provided", () => {
306+
const fs = projFs.shadow();
307+
const host = new fakes.SolutionBuilderHost(fs);
308+
const builder = createSolutionBuilder(host, ["/src/no-references"], { dry: false, force: false, verbose: false });
309+
310+
host.clearDiagnostics();
311+
builder.buildAllProjects();
312+
host.assertDiagnosticMessages(Diagnostics.The_files_list_in_config_file_0_is_empty);
313+
314+
// Check for outputs to not be written.
315+
for (const output of allExpectedOutputs) {
316+
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
317+
}
318+
});
319+
320+
it("does not have empty files diagnostic when files is empty and references are provided", () => {
321+
const fs = projFs.shadow();
322+
const host = new fakes.SolutionBuilderHost(fs);
323+
const builder = createSolutionBuilder(host, ["/src/with-references"], { dry: false, force: false, verbose: false });
324+
325+
host.clearDiagnostics();
326+
builder.buildAllProjects();
327+
host.assertDiagnosticMessages(/*empty*/);
328+
329+
// Check for outputs to be written.
330+
for (const output of allExpectedOutputs) {
331+
assert(fs.existsSync(output), `Expect file ${output} to exist`);
332+
}
333+
});
334+
});
335+
}
336+
295337
describe("tsbuild - graph-ordering", () => {
296338
let host: fakes.SolutionBuilderHost | undefined;
297339
const deps: [string, string][] = [

src/testRunner/unittests/tsconfigParsing.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ namespace ts {
6161
}
6262
}
6363

64+
function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) {
65+
{
66+
const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList);
67+
assert.isTrue(parsed.errors.length >= 0);
68+
assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`);
69+
}
70+
{
71+
const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList);
72+
assert.isTrue(parsed.errors.length >= 0);
73+
assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`);
74+
}
75+
}
76+
6477
it("returns empty config for file with only whitespaces", () => {
6578
assertParseResult("", { config : {} });
6679
assertParseResult(" ", { config : {} });
@@ -274,6 +287,32 @@ namespace ts {
274287
"files": []
275288
}`;
276289
assertParseFileDiagnostics(content,
290+
"/apath/tsconfig.json",
291+
"tests/cases/unittests",
292+
["/apath/a.ts"],
293+
Diagnostics.The_files_list_in_config_file_0_is_empty.code,
294+
/*noLocation*/ true);
295+
});
296+
297+
it("generates errors for empty files list when no references are provided", () => {
298+
const content = `{
299+
"files": [],
300+
"references": []
301+
}`;
302+
assertParseFileDiagnostics(content,
303+
"/apath/tsconfig.json",
304+
"tests/cases/unittests",
305+
["/apath/a.ts"],
306+
Diagnostics.The_files_list_in_config_file_0_is_empty.code,
307+
/*noLocation*/ true);
308+
});
309+
310+
it("does not generate errors for empty files list when one or more references are provided", () => {
311+
const content = `{
312+
"files": [],
313+
"references": [{ "path": "/apath" }]
314+
}`;
315+
assertParseFileDiagnosticsExclusion(content,
277316
"/apath/tsconfig.json",
278317
"tests/cases/unittests",
279318
["/apath/a.ts"],
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function multiply(a: number, b: number) { return a * b; }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"composite": true,
4+
"declaration": true,
5+
"declarationMap": true
6+
}
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"references": [],
3+
"files": [],
4+
"compilerOptions": {
5+
"composite": true,
6+
"declaration": true,
7+
"forceConsistentCasingInFileNames": true
8+
}
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"references": [
3+
{ "path": "../core" },
4+
],
5+
"files": [],
6+
"compilerOptions": {
7+
"composite": true,
8+
"declaration": true,
9+
"forceConsistentCasingInFileNames": true
10+
}
11+
}

0 commit comments

Comments
 (0)