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 and2
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
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 ) {
return 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.