diff --git a/docs/src/developer-guide/working-with-custom-formatters.md b/docs/src/developer-guide/working-with-custom-formatters.md index 1d970ea5e8e..d64b0acdb46 100644 --- a/docs/src/developer-guide/working-with-custom-formatters.md +++ b/docs/src/developer-guide/working-with-custom-formatters.md @@ -132,6 +132,7 @@ Each `message` object contains information about the ESLint rule that was trigge The formatter function receives an object as the second argument. The object has two properties: * `cwd` ... The current working directory. This value comes from the `cwd` constructor option of the [ESLint](nodejs-api#-new-eslintoptions) class. +* `maxWarningsExceeded` (optional): If `--max-warnings` was set and the number of warnings exceeded the limit, this property's value will be an object containing two properties: `maxWarnings`, the value of the `--max-warnings` option, and `foundWarnings`, the number of lint warnings. * `rulesMeta` ... The `meta` property values of rules. See the [Working with Rules](working-with-rules) page for more information about rules. For example, here's what the object would look like if one rule, `no-extra-semi`, had been run: @@ -139,6 +140,10 @@ For example, here's what the object would look like if one rule, `no-extra-semi` ```js { cwd: "/path/to/cwd", + maxWarningsExceeded: { + maxWarnings: 5, + foundWarnings: 6 + }, rulesMeta: { "no-extra-semi": { type: "suggestion", @@ -153,7 +158,7 @@ For example, here's what the object would look like if one rule, `no-extra-semi` unexpected: "Unnecessary semicolon." } } - } + }, } ``` diff --git a/lib/cli.js b/lib/cli.js index 2fca65c1908..93ee2a8be48 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -201,7 +201,7 @@ async function translateOptions({ /** * Count error messages. * @param {LintResult[]} results The lint results. - * @returns {{errorCount:number;warningCount:number}} The number of error messages. + * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages. */ function countErrors(results) { let errorCount = 0; @@ -239,10 +239,11 @@ async function isDirectory(filePath) { * @param {LintResult[]} results The results to print. * @param {string} format The name of the formatter to use or the path to the formatter. * @param {string} outputFile The path for the output file. + * @param {Object} resultsMeta Warning count and max threshold. * @returns {Promise} True if the printing succeeds, false if not. * @private */ -async function printResults(engine, results, format, outputFile) { +async function printResults(engine, results, format, outputFile, resultsMeta) { let formatter; try { @@ -252,7 +253,7 @@ async function printResults(engine, results, format, outputFile) { return false; } - const output = await formatter.format(results); + const output = await formatter.format(results, resultsMeta); if (output) { if (outputFile) { @@ -407,17 +408,24 @@ const cli = { resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint); } - if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) { + const resultCounts = countErrors(results); + const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings; + const resultsMeta = tooManyWarnings + ? { + maxWarningsExceeded: { + maxWarnings: options.maxWarnings, + foundWarnings: resultCounts.warningCount + } + } + : {}; - // Errors and warnings from the original unfiltered results should determine the exit code - const { errorCount, fatalErrorCount, warningCount } = countErrors(results); + if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) { - const tooManyWarnings = - options.maxWarnings >= 0 && warningCount > options.maxWarnings; + // Errors and warnings from the original unfiltered results should determine the exit code const shouldExitForFatalErrors = - options.exitOnFatalError && fatalErrorCount > 0; + options.exitOnFatalError && resultCounts.fatalErrorCount > 0; - if (!errorCount && tooManyWarnings) { + if (!resultCounts.errorCount && tooManyWarnings) { log.error( "ESLint found too many warnings (maximum: %s).", options.maxWarnings @@ -428,7 +436,7 @@ const cli = { return 2; } - return (errorCount || tooManyWarnings) ? 1 : 0; + return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0; } return 2; diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 9a3bd66e487..3b8d4a956dc 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -625,14 +625,16 @@ class ESLint { /** * The main formatter method. * @param {LintResult[]} results The lint results to format. + * @param {Object} resultsMeta Warning count and max threshold. * @returns {string | Promise} The formatted lint results. */ - format(results) { + format(results, resultsMeta) { let rulesMeta = null; results.sort(compareResultsByFilePath); return formatter(results, { + ...resultsMeta, get cwd() { return options.cwd; }, diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index e436c464014..0886979e88c 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -1114,14 +1114,16 @@ class FlatESLint { /** * The main formatter method. * @param {LintResults[]} results The lint results to format. + * @param {Object} resultsMeta Warning count and max threshold. * @returns {string} The formatted lint results. */ - format(results) { + format(results, resultsMeta) { let rulesMeta = null; results.sort(compareResultsByFilePath); return formatter(results, { + ...resultsMeta, cwd, get rulesMeta() { if (!rulesMeta) { diff --git a/tests/lib/cli.js b/tests/lib/cli.js index a58a8c910fc..c38fa68d1ea 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -244,6 +244,45 @@ describe("cli", () => { }); }); + describe("when the --max-warnings option is passed", () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + + describe("and there are too many warnings", () => { + it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello' + 'world';", + useFlatConfig + ); + + assert.strictEqual(exit, 1); + + const { metadata } = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + metadata.maxWarningsExceeded, + { maxWarnings: 1, foundWarnings: 2 } + ); + }); + }); + + describe("and warnings do not exceed the limit", () => { + it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello world';", + useFlatConfig + ); + + assert.strictEqual(exit, 0); + + const { metadata } = JSON.parse(log.info.args[0][0]); + + assert.notProperty(metadata, "maxWarningsExceeded"); + }); + }); + }); + describe("when given an invalid built-in formatter name", () => { it(`should execute with error: with configType:${configType}`, async () => { const filePath = getFixturePath("passing.js");