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 (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) {
|
||||||
if (isArray(raw.files)) {
|
if (isArray(raw.files)) {
|
||||||
filesSpecs = <ReadonlyArray<string>>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");
|
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)
|
createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)
|
||||||
);
|
);
|
||||||
return;
|
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) {
|
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 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 (!typeAcquisition) {
|
||||||
if (typingOptionstypeAcquisition) {
|
if (typingOptionstypeAcquisition) {
|
||||||
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
|
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
|
||||||
|
|
|
@ -946,7 +946,6 @@ namespace ts {
|
||||||
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
|
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
|
||||||
return resultFlags;
|
return resultFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configFile.fileNames.length === 0) {
|
if (configFile.fileNames.length === 0) {
|
||||||
// Nothing to build - must be a solution file, basically
|
// Nothing to build - must be a solution file, basically
|
||||||
return BuildResultFlags.None;
|
return BuildResultFlags.None;
|
||||||
|
@ -956,7 +955,8 @@ namespace ts {
|
||||||
projectReferences: configFile.projectReferences,
|
projectReferences: configFile.projectReferences,
|
||||||
host,
|
host,
|
||||||
rootNames: configFile.fileNames,
|
rootNames: configFile.fileNames,
|
||||||
options: configFile.options
|
options: configFile.options,
|
||||||
|
configFileParsingDiagnostics: configFile.errors,
|
||||||
};
|
};
|
||||||
const program = createProgram(programOptions);
|
const program = createProgram(programOptions);
|
||||||
|
|
||||||
|
@ -1149,7 +1149,6 @@ namespace ts {
|
||||||
|
|
||||||
const queue = graph.buildQueue;
|
const queue = graph.buildQueue;
|
||||||
reportBuildQueue(graph);
|
reportBuildQueue(graph);
|
||||||
|
|
||||||
let anyFailed = false;
|
let anyFailed = false;
|
||||||
for (const next of queue) {
|
for (const next of queue) {
|
||||||
const proj = configFileCache.parseConfigFile(next);
|
const proj = configFileCache.parseConfigFile(next);
|
||||||
|
@ -1157,11 +1156,15 @@ namespace ts {
|
||||||
anyFailed = true;
|
anyFailed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// report errors early when using continue or break statements
|
||||||
|
const errors = proj.errors;
|
||||||
const status = getUpToDateStatus(proj);
|
const status = getUpToDateStatus(proj);
|
||||||
verboseReportProjectStatus(next, status);
|
verboseReportProjectStatus(next, status);
|
||||||
|
|
||||||
const projName = proj.options.configFilePath!;
|
const projName = proj.options.configFilePath!;
|
||||||
if (status.type === UpToDateStatusType.UpToDate && !context.options.force) {
|
if (status.type === UpToDateStatusType.UpToDate && !context.options.force) {
|
||||||
|
reportErrors(errors);
|
||||||
// Up to date, skip
|
// Up to date, skip
|
||||||
if (defaultOptions.dry) {
|
if (defaultOptions.dry) {
|
||||||
// In a dry build, inform the user of this fact
|
// In a dry build, inform the user of this fact
|
||||||
|
@ -1171,17 +1174,20 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !context.options.force) {
|
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !context.options.force) {
|
||||||
|
reportErrors(errors);
|
||||||
// Fake build
|
// Fake build
|
||||||
updateOutputTimestamps(proj);
|
updateOutputTimestamps(proj);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.type === UpToDateStatusType.UpstreamBlocked) {
|
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);
|
if (context.options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.type === UpToDateStatusType.ContainerOnly) {
|
if (status.type === UpToDateStatusType.ContainerOnly) {
|
||||||
|
reportErrors(errors);
|
||||||
// Do nothing
|
// Do nothing
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1193,6 +1199,10 @@ namespace ts {
|
||||||
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
|
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
|
* 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", () => {
|
describe("tsbuild - graph-ordering", () => {
|
||||||
let host: fakes.SolutionBuilderHost | undefined;
|
let host: fakes.SolutionBuilderHost | undefined;
|
||||||
const deps: [string, string][] = [
|
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", () => {
|
it("returns empty config for file with only whitespaces", () => {
|
||||||
assertParseResult("", { config : {} });
|
assertParseResult("", { config : {} });
|
||||||
assertParseResult(" ", { config : {} });
|
assertParseResult(" ", { config : {} });
|
||||||
|
@ -274,6 +287,32 @@ namespace ts {
|
||||||
"files": []
|
"files": []
|
||||||
}`;
|
}`;
|
||||||
assertParseFileDiagnostics(content,
|
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",
|
"/apath/tsconfig.json",
|
||||||
"tests/cases/unittests",
|
"tests/cases/unittests",
|
||||||
["/apath/a.ts"],
|
["/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