diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 378b9c16b8d..e4a3a1e42c1 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -404,7 +404,7 @@ This edit information means replacing the range of the `range` property by the `
The `Formatter` value is the object to convert the [LintResult] objects to text. The [eslint.loadFormatter()][eslint-loadformatter] method returns it. It has the following method:
-* `format` (`(results: LintResult[]) => string`)
+* `format` (`(results: LintResult[]) => string | Promise`)
The method to convert the [LintResult] objects to text.
---
diff --git a/docs/developer-guide/working-with-custom-formatters.md b/docs/developer-guide/working-with-custom-formatters.md
index a5e9ade81bf..181aa69cec5 100644
--- a/docs/developer-guide/working-with-custom-formatters.md
+++ b/docs/developer-guide/working-with-custom-formatters.md
@@ -11,6 +11,16 @@ module.exports = function(results) {
};
```
+Formatter can also be an async function (from ESLint v8.4.0), the following shows a simple example:
+
+```js
+//my-awesome-formatter.js
+module.exports = async function(results) {
+ const formatted = await asyncTask();
+ return formatted;
+};
+```
+
To run ESLint with this formatter, you can use the `-f` (or `--format`) command line flag:
```bash
diff --git a/lib/cli.js b/lib/cli.js
index 477310da585..f09d143d255 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -178,7 +178,7 @@ async function printResults(engine, results, format, outputFile) {
return false;
}
- const output = formatter.format(results);
+ const output = await formatter.format(results);
if (output) {
if (outputFile) {
diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js
index 8886d45ab43..29d7e5cbc92 100644
--- a/lib/eslint/eslint.js
+++ b/lib/eslint/eslint.js
@@ -34,7 +34,12 @@ const { version } = require("../../package.json");
/** @typedef {import("../shared/types").LintMessage} LintMessage */
/** @typedef {import("../shared/types").Plugin} Plugin */
/** @typedef {import("../shared/types").Rule} Rule */
-/** @typedef {import("./load-formatter").Formatter} Formatter */
+
+/**
+ * The main formatter object.
+ * @typedef Formatter
+ * @property {function(LintResult[]): string | Promise} format format function.
+ */
/**
* The options with which to configure the ESLint instance.
@@ -629,7 +634,7 @@ class ESLint {
/**
* The main formatter method.
* @param {LintResults[]} results The lint results to format.
- * @returns {string} The formatted lint results.
+ * @returns {string | Promise} The formatted lint results.
*/
format(results) {
let rulesMeta = null;
diff --git a/tests/fixtures/formatters/async.js b/tests/fixtures/formatters/async.js
new file mode 100644
index 00000000000..b5651a697a4
--- /dev/null
+++ b/tests/fixtures/formatters/async.js
@@ -0,0 +1,4 @@
+/*global module*/
+module.exports = function(results) {
+ return Promise.resolve('from async formatter');
+};
diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js
index 31f59bf51b4..16beb92b87b 100644
--- a/tests/lib/cli-engine/cli-engine.js
+++ b/tests/lib/cli-engine/cli-engine.js
@@ -1160,7 +1160,7 @@ describe("CLIEngine", () => {
const report = engine.executeOnFiles([getFixturePath("formatters")]);
- assert.strictEqual(report.results.length, 3);
+ assert.strictEqual(report.results.length, 4);
assert.strictEqual(report.errorCount, 0);
assert.strictEqual(report.warningCount, 0);
assert.strictEqual(report.fixableErrorCount, 0);
@@ -1200,14 +1200,18 @@ describe("CLIEngine", () => {
assert.strictEqual(report.results[0].warningCount, 0);
assert.strictEqual(report.results[0].fixableErrorCount, 0);
assert.strictEqual(report.results[0].fixableWarningCount, 0);
- assert.strictEqual(report.results[1].errorCount, 3);
+ assert.strictEqual(report.results[1].errorCount, 0);
assert.strictEqual(report.results[1].warningCount, 0);
- assert.strictEqual(report.results[1].fixableErrorCount, 3);
+ assert.strictEqual(report.results[1].fixableErrorCount, 0);
assert.strictEqual(report.results[1].fixableWarningCount, 0);
assert.strictEqual(report.results[2].errorCount, 3);
assert.strictEqual(report.results[2].warningCount, 0);
assert.strictEqual(report.results[2].fixableErrorCount, 3);
assert.strictEqual(report.results[2].fixableWarningCount, 0);
+ assert.strictEqual(report.results[3].errorCount, 3);
+ assert.strictEqual(report.results[3].warningCount, 0);
+ assert.strictEqual(report.results[3].fixableErrorCount, 3);
+ assert.strictEqual(report.results[3].fixableWarningCount, 0);
});
it("should process when file is given by not specifying extensions", () => {
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index 1b3828b4090..143a3ac1efc 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -287,6 +287,17 @@ describe("cli", () => {
});
});
+ describe("when given an async formatter path", () => {
+ it("should execute without any errors", async () => {
+ const formatterPath = getFixturePath("formatters", "async.js");
+ const filePath = getFixturePath("passing.js");
+ const exit = await cli.execute(`-f ${formatterPath} ${filePath}`);
+
+ assert.strictEqual(log.info.getCall(0).args[0], "from async formatter");
+ assert.strictEqual(exit, 0);
+ });
+ });
+
describe("when executing a file with a lint error", () => {
it("should exit with error", async () => {
const filePath = getFixturePath("undef.js");
diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js
index eaf4aaaaf05..d984f5a3082 100644
--- a/tests/lib/eslint/eslint.js
+++ b/tests/lib/eslint/eslint.js
@@ -1212,7 +1212,7 @@ describe("ESLint", () => {
});
const results = await eslint.lintFiles([getFixturePath("formatters")]);
- assert.strictEqual(results.length, 3);
+ assert.strictEqual(results.length, 4);
assert.strictEqual(results[0].messages.length, 0);
assert.strictEqual(results[1].messages.length, 0);
assert.strictEqual(results[2].messages.length, 0);