From 0edfe369aa5bd80a98053022bb4c6b1ea0155f44 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Oct 2023 12:48:07 -0400 Subject: [PATCH] fix: Ensure crash error messages are not duplicated (#17584) * fix: Ensure crash error messages are not duplicated Fixes #17560 * Add test * Fix merge conflicts * Add tests --------- Co-authored-by: Milos Djermanovic --- bin/eslint.js | 19 +++++++++--- tests/bin/eslint.js | 30 +++++++++++++++++++ tests/fixtures/bin/eslint.config-invalid.js | 3 ++ .../fixtures/bin/eslint.config-tick-throws.js | 24 +++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/bin/eslint.config-invalid.js create mode 100644 tests/fixtures/bin/eslint.config-tick-throws.js diff --git a/bin/eslint.js b/bin/eslint.js index 7094ac77bc4b..5c7972cc086e 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -92,6 +92,14 @@ function getErrorMessage(error) { return util.format("%o", error); } +/** + * Tracks error messages that are shown to the user so we only ever show the + * same message once. + * @type {Set} + */ + +const displayedErrors = new Set(); + /** * Catch and report unexpected error. * @param {any} error The thrown error object. @@ -101,14 +109,17 @@ function onFatalError(error) { process.exitCode = 2; const { version } = require("../package.json"); - const message = getErrorMessage(error); - - console.error(` + const message = ` Oops! Something went wrong! :( ESLint: ${version} -${message}`); +${getErrorMessage(error)}`; + + if (!displayedErrors.has(message)) { + console.error(message); + displayedErrors.add(message); + } } //------------------------------------------------------------------------------ diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index b09496202b03..dca8955d0386 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -387,6 +387,36 @@ describe("bin/eslint.js", () => { return Promise.all([exitCodeAssertion, outputAssertion]); }); + // https://github.com/eslint/eslint/issues/17560 + describe("does not print duplicate errors in the event of a crash", () => { + + it("when there is an invalid config read from a config file", () => { + const config = path.join(__dirname, "../fixtures/bin/eslint.config-invalid.js"); + const child = runESLint(["--config", config, "conf", "tools"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + + // The error text should appear exactly once in stderr + assert.strictEqual(output.stderr.match(/A config object is using the "globals" key/gu).length, 1); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + it("when there is an error in the next tick", () => { + const config = path.join(__dirname, "../fixtures/bin/eslint.config-tick-throws.js"); + const child = runESLint(["--config", config, "Makefile.js"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + + // The error text should appear exactly once in stderr + assert.strictEqual(output.stderr.match(/test_error_stack/gu).length, 1); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + }); + it("prints the error message pointing to line of code", () => { const invalidConfig = path.join(__dirname, "../fixtures/bin/eslint.config.js"); const child = runESLint(["--no-ignore", "-c", invalidConfig]); diff --git a/tests/fixtures/bin/eslint.config-invalid.js b/tests/fixtures/bin/eslint.config-invalid.js new file mode 100644 index 000000000000..6f68e1784199 --- /dev/null +++ b/tests/fixtures/bin/eslint.config-invalid.js @@ -0,0 +1,3 @@ +module.exports = [{ + globals: {} +}]; diff --git a/tests/fixtures/bin/eslint.config-tick-throws.js b/tests/fixtures/bin/eslint.config-tick-throws.js new file mode 100644 index 000000000000..c72f86a840f7 --- /dev/null +++ b/tests/fixtures/bin/eslint.config-tick-throws.js @@ -0,0 +1,24 @@ +function throwError() { + const error = new Error(); + error.stack = "test_error_stack"; + throw error; +} + +module.exports = [{ + plugins: { + foo: { + rules: { + bar: { + create() { + process.nextTick(throwError); + process.nextTick(throwError); + return {}; + } + } + } + } + }, + rules: { + "foo/bar": "error" + } +}];