W3cubDocs

/ESLint

Working with Custom Formatters

Writing an ESLint custom formatter is simple. All that is needed is a module that exports a function that will receive the results from the execution of ESLint.

The simplest formatter will be something like:

//my-awesome-formatter.js
module.exports = function(results) {
    return JSON.stringify(results, null, 2);
};

And to run eslint with this custom formatter:

eslint -f './my-awesome-formatter.js' src/

The output of the previous command will be something like this

[
    {
        filePath: "path/to/file.js",
        messages: [
            {
                ruleId: "curly",
                severity: 2,
                message: "Expected { after 'if' condition.",
                line: 2,
                column: 1,
                nodeType: "IfStatement"
            },
            {
                ruleId: "no-process-exit",
                severity: 2,
                message: "Don't use process.exit(); throw an error instead.",
                line: 3,
                column: 1,
                nodeType: "CallExpression"
            }
        ],
        errorCount: 2,
        warningCount: 0,
        fixableErrorCount: 0,
        fixableWarningCount: 0,
        source:
            "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n"
    },
    {
        filePath: "Gruntfile.js",
        messages: [],
        errorCount: 0,
        warningCount: 0,
        fixableErrorCount: 0,
        fixableWarningCount: 0
    }
];

As you can see the argument passed to the custom formatter function is just a list of results objects.

Description of the results

the result object

You will receive a result object from each file eslint validates, each one of them containing the list of messages for errors and/or warnings.

The following are the fields of the result object:

  • filePath: The path to the file relative to the current working directory (the path from which ESLint was executed).
  • messages: An array of message objects. See below for more info about messages.
  • errorCount: The number of errors for the given file.
  • warningCount: The number of warnings for the given file.
  • source: The source code for the given file. This property is omitted if this file has no errors/warnings or if the output property is present.
  • output: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available.

The message object

  • ruleId: the id of the rule that produced the error or warning.
  • severity: the severity of the failure, 1 for warnings and 2 for errors.
  • message: the human readable description of the error.
  • line: the line where the issue is located.
  • column: the column where the issue is located.
  • nodeType: the type of the node in the AST

Examples

Summary formatter

A formatter that only cares about the total count of errors and warnings will look like this:

module.exports = function(results) {
    // accumulate the errors and warnings
    var summary = results.reduce(
        function(seq, current) {
            seq.errors += current.errorCount;
            seq.warnings += current.warningCount;
            return seq;
        },
        { errors: 0, warnings: 0 }
    );

    if (summary.errors > 0 || summary.warnings > 0) {
        return (
            "Errors: " +
            summary.errors +
            ", Warnings: " +
            summary.warnings +
            "\n"
        );
    }

    return "";
};

Running eslint with the previous custom formatter,

eslint -f './my-awesome-formatter.js' src/

Will produce the following output:

Errors: 2, Warnings: 4

Detailed formatter

A more complex report will look something like this:

module.exports = function(results) {
    var results = results || [];

    var summary = results.reduce(
        function(seq, current) {
            current.messages.forEach(function(msg) {
                var logMessage = {
                    filePath: current.filePath,
                    ruleId: msg.ruleId,
                    message: msg.message,
                    line: msg.line,
                    column: msg.column
                };

                if (msg.severity === 1) {
                    logMessage.type = "warning";
                    seq.warnings.push(logMessage);
                }
                if (msg.severity === 2) {
                    logMessage.type = "error";
                    seq.errors.push(logMessage);
                }
            });
            return seq;
        },
        {
            errors: [],
            warnings: []
        }
    );

    if (summary.errors.length > 0 || summary.warnings.length > 0) {
        var lines = summary.errors
            .concat(summary.warnings)
            .map(function(msg) {
                return (
                    "\n" +
                    msg.type +
                    " " +
                    msg.ruleId +
                    "\n  " +
                    msg.filePath +
                    ":" +
                    msg.line +
                    ":" +
                    msg.column
                );
            })
            .join("\n");

        return lines + "\n";
    }
};

So running eslint with this custom formatter:

eslint -f './my-awesome-formatter.js' src/

The output will be

error space-infix-ops
  src/configs/bundler.js:6:8
error semi
  src/configs/bundler.js:6:10
warning no-unused-vars
  src/configs/bundler.js:5:6
warning no-unused-vars
  src/configs/bundler.js:6:6
warning no-shadow
  src/configs/bundler.js:65:32
warning no-unused-vars
  src/configs/clean.js:3:6

Passing arguments to formatters:

Using environment variables:

Let’s say we want to show only the messages that are actual errors and discard the warnings.

module.exports = function(results) {
    var skipWarnings = process.env.AF_SKIP_WARNINGS === "true"; //af stands for awesome-formatter

    var results = results || [];
    var summary = results.reduce(
        function(seq, current) {
            current.messages.forEach(function(msg) {
                var logMessage = {
                    filePath: current.filePath,
                    ruleId: msg.ruleId,
                    message: msg.message,
                    line: msg.line,
                    column: msg.column
                };

                if (msg.severity === 1) {
                    logMessage.type = "warning";
                    seq.warnings.push(logMessage);
                }
                if (msg.severity === 2) {
                    logMessage.type = "error";
                    seq.errors.push(logMessage);
                }
            });
            return seq;
        },
        {
            errors: [],
            warnings: []
        }
    );

    if (summary.errors.length > 0 || summary.warnings.length > 0) {
        var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case

        var lines = summary.errors
            .concat(warnings)
            .map(function(msg) {
                return (
                    "\n" +
                    msg.type +
                    " " +
                    msg.ruleId +
                    "\n  " +
                    msg.filePath +
                    ":" +
                    msg.line +
                    ":" +
                    msg.column
                );
            })
            .join("\n");

        return lines + "\n";
    }
};

So running eslint with this custom formatter:

AF_SKIP_WARNINGS=true eslint -f './my-awesome-formatter.js' src/

The output will not print the warnings

error space-infix-ops
  src/configs/bundler.js:6:8

error semi
  src/configs/bundler.js:6:10

Outputting to a standard format for other programs to consume

You can use ESLint’s built-in JSON formatter first, and pipe the output to another program that accepts JSON linter results.

eslint -f json src/ | your-program-that-reads-JSON

You can read more about the built-in JSON formatter here.

Final words

More complex formatters could be written by grouping differently the errors and warnings and/or grouping the data by the ruleIds.

When printing the files a recommended format will be something like this:

file:line:column

Since that allows modern fancy terminals (like iTerm2 or Guake) to make them link to files that open in your favorite text editor.

© JS Foundation and other contributors
Licensed under the MIT License.
https://eslint.org/docs/developer-guide/working-with-custom-formatters