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
outputproperty 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,
1for warnings and2for 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.