Fix diagnostic reporting for empty files in tsconfig
This commit is contained in:
parent
45101491c0
commit
c87ca2f1ab
|
@ -1843,7 +1843,9 @@ namespace ts {
|
|||
if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) {
|
||||
if (isArray(raw.files)) {
|
||||
filesSpecs = <ReadonlyArray<string>>raw.files;
|
||||
if (filesSpecs.length === 0) {
|
||||
const hasReferences = hasProperty(raw, "references") && !isNullOrUndefined(raw.references);
|
||||
const hasZeroOrNoReferences = !hasReferences || raw.references.length === 0;
|
||||
if (filesSpecs.length === 0 && hasZeroOrNoReferences) {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
|
||||
}
|
||||
}
|
||||
|
@ -2067,11 +2069,6 @@ namespace ts {
|
|||
createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)
|
||||
);
|
||||
return;
|
||||
case "files":
|
||||
if ((<ReadonlyArray<string>>value).length === 0) {
|
||||
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueNode, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) {
|
||||
|
@ -2081,6 +2078,13 @@ namespace ts {
|
|||
}
|
||||
};
|
||||
const json = convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, getTsconfigRootOptionsMap(), optionsIterator);
|
||||
const hasZeroFiles = json && json.files && json.files.length === 0;
|
||||
const hasZeroOrNoReferences = !(json && json.references) || json.references.length === 0;
|
||||
|
||||
if (hasZeroFiles && hasZeroOrNoReferences) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.The_files_list_in_config_file_0_is_empty, sourceFile.fileName));
|
||||
}
|
||||
|
||||
if (!typeAcquisition) {
|
||||
if (typingOptionstypeAcquisition) {
|
||||
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
|
||||
|
|
|
@ -946,7 +946,6 @@ namespace ts {
|
|||
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
|
||||
return resultFlags;
|
||||
}
|
||||
|
||||
if (configFile.fileNames.length === 0) {
|
||||
// Nothing to build - must be a solution file, basically
|
||||
return BuildResultFlags.None;
|
||||
|
@ -956,7 +955,8 @@ namespace ts {
|
|||
projectReferences: configFile.projectReferences,
|
||||
host,
|
||||
rootNames: configFile.fileNames,
|
||||
options: configFile.options
|
||||
options: configFile.options,
|
||||
configFileParsingDiagnostics: configFile.errors,
|
||||
};
|
||||
const program = createProgram(programOptions);
|
||||
|
||||
|
@ -1149,7 +1149,6 @@ namespace ts {
|
|||
|
||||
const queue = graph.buildQueue;
|
||||
reportBuildQueue(graph);
|
||||
|
||||
let anyFailed = false;
|
||||
for (const next of queue) {
|
||||
const proj = configFileCache.parseConfigFile(next);
|
||||
|
@ -1157,11 +1156,15 @@ namespace ts {
|
|||
anyFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// report errors early when using continue or break statements
|
||||
const errors = proj.errors;
|
||||
const status = getUpToDateStatus(proj);
|
||||
verboseReportProjectStatus(next, status);
|
||||
|
||||
const projName = proj.options.configFilePath!;
|
||||
if (status.type === UpToDateStatusType.UpToDate && !context.options.force) {
|
||||
reportErrors(errors);
|
||||
// Up to date, skip
|
||||
if (defaultOptions.dry) {
|
||||
// In a dry build, inform the user of this fact
|
||||
|
@ -1171,17 +1174,20 @@ namespace ts {
|
|||
}
|
||||
|
||||
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !context.options.force) {
|
||||
reportErrors(errors);
|
||||
// Fake build
|
||||
updateOutputTimestamps(proj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (status.type === UpToDateStatusType.UpstreamBlocked) {
|
||||
reportErrors(errors);
|
||||
if (context.options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (status.type === UpToDateStatusType.ContainerOnly) {
|
||||
reportErrors(errors);
|
||||
// Do nothing
|
||||
continue;
|
||||
}
|
||||
|
@ -1193,6 +1199,10 @@ namespace ts {
|
|||
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
|
||||
}
|
||||
|
||||
function reportErrors(errors: Diagnostic[]) {
|
||||
errors.forEach((err) => host.reportDiagnostic(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the build ordering inferred from the current project graph if we're in verbose mode
|
||||
*/
|
||||
|
|
|
@ -292,6 +292,48 @@ namespace ts {
|
|||
});
|
||||
}
|
||||
|
||||
export namespace EmptyFiles {
|
||||
const projFs = loadProjectFromDisk("tests/projects/empty-files");
|
||||
|
||||
const allExpectedOutputs = [
|
||||
"/src/core/index.js",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/index.d.ts.map",
|
||||
];
|
||||
|
||||
describe("tsbuild - empty files option in tsconfig", () => {
|
||||
it("has empty files diagnostic when files is empty and no references are provided", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/no-references"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(Diagnostics.The_files_list_in_config_file_0_is_empty);
|
||||
|
||||
// Check for outputs to not be written.
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not have empty files diagnostic when files is empty and references are provided", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/with-references"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Check for outputs to be written.
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("tsbuild - graph-ordering", () => {
|
||||
let host: fakes.SolutionBuilderHost | undefined;
|
||||
const deps: [string, string][] = [
|
||||
|
|
|
@ -61,6 +61,19 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) {
|
||||
{
|
||||
const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList);
|
||||
assert.isTrue(parsed.errors.length >= 0);
|
||||
assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`);
|
||||
}
|
||||
{
|
||||
const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList);
|
||||
assert.isTrue(parsed.errors.length >= 0);
|
||||
assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`);
|
||||
}
|
||||
}
|
||||
|
||||
it("returns empty config for file with only whitespaces", () => {
|
||||
assertParseResult("", { config : {} });
|
||||
assertParseResult(" ", { config : {} });
|
||||
|
@ -274,6 +287,32 @@ namespace ts {
|
|||
"files": []
|
||||
}`;
|
||||
assertParseFileDiagnostics(content,
|
||||
"/apath/tsconfig.json",
|
||||
"tests/cases/unittests",
|
||||
["/apath/a.ts"],
|
||||
Diagnostics.The_files_list_in_config_file_0_is_empty.code,
|
||||
/*noLocation*/ true);
|
||||
});
|
||||
|
||||
it("generates errors for empty files list when no references are provided", () => {
|
||||
const content = `{
|
||||
"files": [],
|
||||
"references": []
|
||||
}`;
|
||||
assertParseFileDiagnostics(content,
|
||||
"/apath/tsconfig.json",
|
||||
"tests/cases/unittests",
|
||||
["/apath/a.ts"],
|
||||
Diagnostics.The_files_list_in_config_file_0_is_empty.code,
|
||||
/*noLocation*/ true);
|
||||
});
|
||||
|
||||
it("does not generate errors for empty files list when one or more references are provided", () => {
|
||||
const content = `{
|
||||
"files": [],
|
||||
"references": [{ "path": "/apath" }]
|
||||
}`;
|
||||
assertParseFileDiagnosticsExclusion(content,
|
||||
"/apath/tsconfig.json",
|
||||
"tests/cases/unittests",
|
||||
["/apath/a.ts"],
|
||||
|
|
1
tests/projects/empty-files/core/index.ts
Normal file
1
tests/projects/empty-files/core/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export function multiply(a: number, b: number) { return a * b; }
|
7
tests/projects/empty-files/core/tsconfig.json
Normal file
7
tests/projects/empty-files/core/tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
}
|
||||
}
|
9
tests/projects/empty-files/no-references/tsconfig.json
Normal file
9
tests/projects/empty-files/no-references/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"references": [],
|
||||
"files": [],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
11
tests/projects/empty-files/with-references/tsconfig.json
Normal file
11
tests/projects/empty-files/with-references/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
],
|
||||
"files": [],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue