Skip to content

Commit

Permalink
New: Exit on fatal error (fixes #13711) (#14730)
Browse files Browse the repository at this point in the history
* Docs: Updated docs for exit-on-fatal-error

* New: Added exit-on-fatal-error feature

* Addressed PR feedback

* Made description consistent

* Updated dev docs for nodejs-api

* Updated cmd docs

* Added test case for 1 exit code

* Fixed typos

Co-authored-by: A-Katopodis <ankatopo@microsoft.com>
  • Loading branch information
A-Katopodis and A-Katopodis committed Jul 30, 2021
1 parent ed007c8 commit 1bfbefd
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 11 deletions.
4 changes: 3 additions & 1 deletion docs/developer-guide/nodejs-api.md
Expand Up @@ -355,7 +355,9 @@ The `LintResult` value is the information of the linting result of each file. Th
* `fixableWarningCount` (`number`)<br>
The number of warnings that can be fixed automatically by the `fix` constructor option.
* `errorCount` (`number`)<br>
The number of errors. This includes fixable errors.
The number of errors. This includes fixable errors and fatal errors.
* `fatalErrorCount` (`number`)<br>
The number of fatal errors.
* `warningCount` (`number`)<br>
The number of warnings. This includes fixable warnings.
* `output` (`string | undefined`)<br>
Expand Down
5 changes: 5 additions & 0 deletions docs/user-guide/command-line-interface.md
Expand Up @@ -81,6 +81,7 @@ Miscellaneous:
--init Run config initialization wizard - default: false
--env-info Output execution environment information - default: false
--no-error-on-unmatched-pattern Prevent errors when pattern is unmatched - default: false
--exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false
--debug Output debugging information
-h, --help Show help
-v, --version Output the version number
Expand Down Expand Up @@ -467,6 +468,10 @@ This option outputs information about the execution environment, including the v

This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This will not prevent errors when your shell can't match a glob.

#### `--exit-on-fatal-error`

This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, fatal parsing errors are reported as rule violations.

#### `--debug`

This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.
Expand Down
6 changes: 6 additions & 0 deletions lib/cli-engine/cli-engine.js
Expand Up @@ -156,6 +156,9 @@ function calculateStatsPerFile(messages) {
return messages.reduce((stat, message) => {
if (message.fatal || message.severity === 2) {
stat.errorCount++;
if (message.fatal) {
stat.fatalErrorCount++;
}
if (message.fix) {
stat.fixableErrorCount++;
}
Expand All @@ -168,6 +171,7 @@ function calculateStatsPerFile(messages) {
return stat;
}, {
errorCount: 0,
fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
Expand All @@ -183,12 +187,14 @@ function calculateStatsPerFile(messages) {
function calculateStatsPerRun(results) {
return results.reduce((stat, result) => {
stat.errorCount += result.errorCount;
stat.fatalErrorCount += result.fatalErrorCount;
stat.warningCount += result.warningCount;
stat.fixableErrorCount += result.fixableErrorCount;
stat.fixableWarningCount += result.fixableWarningCount;
return stat;
}, {
errorCount: 0,
fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
Expand Down
13 changes: 11 additions & 2 deletions lib/cli.js
Expand Up @@ -131,14 +131,16 @@ function translateOptions({
*/
function countErrors(results) {
let errorCount = 0;
let fatalErrorCount = 0;
let warningCount = 0;

for (const result of results) {
errorCount += result.errorCount;
fatalErrorCount += result.fatalErrorCount;
warningCount += result.warningCount;
}

return { errorCount, warningCount };
return { errorCount, fatalErrorCount, warningCount };
}

/**
Expand Down Expand Up @@ -314,9 +316,12 @@ const cli = {
if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {

// Errors and warnings from the original unfiltered results should determine the exit code
const { errorCount, warningCount } = countErrors(results);
const { errorCount, fatalErrorCount, warningCount } = countErrors(results);

const tooManyWarnings =
options.maxWarnings >= 0 && warningCount > options.maxWarnings;
const shouldExitForFatalErrors =
options.exitOnFatalError && fatalErrorCount > 0;

if (!errorCount && tooManyWarnings) {
log.error(
Expand All @@ -325,6 +330,10 @@ const cli = {
);
}

if (shouldExitForFatalErrors) {
return 2;
}

return (errorCount || tooManyWarnings) ? 1 : 0;
}

Expand Down
6 changes: 6 additions & 0 deletions lib/options.js
Expand Up @@ -290,6 +290,12 @@ module.exports = optionator({
default: "true",
description: "Prevent errors when pattern is unmatched"
},
{
option: "exit-on-fatal-error",
type: "Boolean",
default: "false",
description: "Exit with exit code 2 in case of fatal error"
},
{
option: "debug",
type: "Boolean",
Expand Down
9 changes: 9 additions & 0 deletions tests/bin/eslint.js
Expand Up @@ -89,6 +89,7 @@ describe("bin/eslint.js", () => {
filePath: "<text>",
messages: [],
errorCount: 0,
fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
Expand Down Expand Up @@ -117,6 +118,14 @@ describe("bin/eslint.js", () => {
return assertExitCode(child, 1);
});

it("has exit code 2 if a syntax error is thrown when exit-on-fatal-error is true", () => {
const child = runESLint(["--stdin", "--no-eslintrc", "--exit-on-fatal-error"]);

child.stdin.write("This is not valid JS syntax.\n");
child.stdin.end();
return assertExitCode(child, 2);
});

it("has exit code 1 if a linting error occurs", () => {
const child = runESLint(["--stdin", "--no-eslintrc", "--rule", "semi:2"]);

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/exit-on-fatal-error/fatal-error.js
@@ -0,0 +1 @@
var foo 1
@@ -0,0 +1,3 @@
/*eslint no-unused-vars: "error"*/

var foo = 1;
1 change: 1 addition & 0 deletions tests/fixtures/exit-on-fatal-error/no-fatal-error.js
@@ -0,0 +1 @@
var foo = 1;
29 changes: 25 additions & 4 deletions tests/lib/cli-engine/cli-engine.js
Expand Up @@ -144,6 +144,7 @@ describe("CLIEngine", () => {
assert.strictEqual(report.results.length, 1);
assert.strictEqual(report.errorCount, 5);
assert.strictEqual(report.warningCount, 0);
assert.strictEqual(report.fatalErrorCount, 0);
assert.strictEqual(report.fixableErrorCount, 3);
assert.strictEqual(report.fixableWarningCount, 0);
assert.strictEqual(report.results[0].messages.length, 5);
Expand Down Expand Up @@ -310,13 +311,15 @@ describe("CLIEngine", () => {
messages: [],
errorCount: 0,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var bar = foo;"
}
],
errorCount: 0,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
Expand Down Expand Up @@ -519,13 +522,15 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var bar = foo"
}
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
Expand Down Expand Up @@ -562,13 +567,15 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var bar = foothis is a syntax error."
}
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
Expand Down Expand Up @@ -604,13 +611,15 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var bar ="
}
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
Expand Down Expand Up @@ -692,13 +701,15 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var bar = foothis is a syntax error.\n return bar;"
}
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
Expand Down Expand Up @@ -1684,6 +1695,7 @@ describe("CLIEngine", () => {
messages: [],
errorCount: 0,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "true ? \"yes\" : \"no\";\n"
Expand All @@ -1693,6 +1705,7 @@ describe("CLIEngine", () => {
messages: [],
errorCount: 0,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
},
Expand All @@ -1713,6 +1726,7 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n"
Expand All @@ -1734,6 +1748,7 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var msg = \"hi\" + foo;\n"
Expand Down Expand Up @@ -5077,13 +5092,15 @@ describe("CLIEngine", () => {
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "/* eslint-disable */"
}
],
errorCount: 1,
warningCount: 0,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
Expand Down Expand Up @@ -6155,7 +6172,8 @@ describe("CLIEngine", () => {
}
],
source: "a == b",
warningCount: 0
warningCount: 0,
fatalErrorCount: 0
}
]);
});
Expand All @@ -6177,7 +6195,8 @@ describe("CLIEngine", () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
warningCount: 0
warningCount: 0,
fatalErrorCount: 0
}
]);
});
Expand Down Expand Up @@ -6223,7 +6242,8 @@ describe("CLIEngine", () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
warningCount: 0
warningCount: 0,
fatalErrorCount: 0
}
]);
});
Expand Down Expand Up @@ -6258,7 +6278,8 @@ describe("CLIEngine", () => {
}
],
source: "a == b",
warningCount: 0
warningCount: 0,
fatalErrorCount: 0
}
]);
});
Expand Down
32 changes: 32 additions & 0 deletions tests/lib/cli.js
Expand Up @@ -803,6 +803,38 @@ describe("cli", () => {
});
});

describe("when given the exit-on-fatal-error flag", () => {
it("should not change exit code if no fatal errors are reported", async () => {
const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js");
const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);

assert.strictEqual(exitCode, 0);
});

it("should exit with exit code 1 if no fatal errors are found, but rule violations are found", async () => {
const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js");
const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);

assert.strictEqual(exitCode, 1);
});

it("should exit with exit code 2 if fatal error is found", async () => {
const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js");
const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);

assert.strictEqual(exitCode, 2);
});

it("should exit with exit code 2 if fatal error is found in any file", async () => {
const filePath = getFixturePath("exit-on-fatal-error");
const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);

assert.strictEqual(exitCode, 2);
});


});

describe("when passed --no-inline-config", () => {
let localCLI;

Expand Down

0 comments on commit 1bfbefd

Please sign in to comment.