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) {
    console.log(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,
        "source": "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n"
    },
    {
        "filePath": "Gruntfile.js",
        "messages": [],
        "errorCount": 0,
        "warningCount": 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:

The message object

Examples

Summary formatter

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

module.exports = function ( results ) {
    var results = 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 ) {
        console.log( 'Errors: ' + summary.errors + ', Warnings: ' + summary.warnings + '\n' );
    }
};

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

Using a JSON formatter first

It is a bit more complicated, but using a simple formatter we can get the raw output to stdout

// json.js
module.exports = function ( results ) {
    console.log( JSON.stringify( results ) );
}

And then the formatter can read from stdin

eslint -f './json.js' | ./my-awesome-formatter-cli.js --skip-warnings

And the content of my-awesome-formatter-cli.js would be something like:

#!/usr/bin/env node
var stdin = process.stdin;
var stdout = process.stdout;
var strChunks = [ ];

stdin.resume();
stdin.setEncoding( 'utf8' );

stdin.on( 'data', function ( chunk ) {
    strChunks.push( chunk );
} );

stdin.on( 'end', function () {
    var inputJSON = strChunks.join();
    var eslintResults = JSON.parse( inputJSON );
    var skipWarnings = process.argv.indexOf( '--skip-warnings' ) > -1;

    var result = require( './my-awesome-formatter' )( eslintResults, {
        skipWarnings: skipWarnings
    } );

    stdout.write( result );
} );

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.