How to format ESLint output

Playing with the CLIEngine API

Alena Khineika
DailyJS
5 min readAug 9, 2019

--

You might have a question, why on earth would you format ESLint output when it prints good, styled, verbose reports, plus has lots of configurable options out of the box? Well, I want to learn more about the ESLint tool, play with CLIEngine API, and it definitely should be a use case for customizing it. For example, to format the terminal output when a linter succeeds, perform some extra operations before it exits, or to have more detailed debug information during the execution process.

This is how you call a regular eslint command:

eslint ./ --ext .json --ext .js

If your code does not have any linting errors or warnings, you will get the following output:

In case of success ESLint prints nothing

But what if I want to see a personalized message in this case? For example:

“Hey, great job! No syntax errors today!”

Let’s create our own module that will act like normal ESLint but will be able to format the output.

npm init{
"name": "formatted-linting",
"version": "1.0.0",
"description": "Format eslint output",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Alena Khineika",
"license": "MIT"
}

I decided to create an executable module, therefore I don’t need the main entry point, but what I need instead is to supply the bin field which maps a command name to a local file name. On install, npm will symlink that file into prefix/bin for global installs, or ./node_modules/.bin/ for local installs.

"bin": {
"formatted-linting": "./bin/index.js"
}

What also would be nice is to call our customized linter similarly to the original one.

formatted-linting --dir ./ --ext .json --ext .js

We don’t try to rewrite ESLint, we only want to extend it, therefore we need to include the original ESLint as a dependency. And the package.json file will look like this:

{
"name": "formatted-linting",
"version": "1.0.0",
"description": "Format eslint output",
"bin": {
"formatted-linting": "./bin/index.js"
},
"scripts": {
"start": "node bin/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Alena Khineika",
"license": "MIT",
"dependencies": {
"eslint": "^6.1.0",
"eslint-config-standard": "^13.0.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-json": "^1.4.0",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0"
}
}

At the beginning of the ./bin/index.js file we should place the following line:

#!/usr/bin/env node

This line is needed to tell the system what interpreter to pass that file to for execution. Without it, your application will throw unexpected token syntax error.

Now we can start implementing our module around ESLint. To do so we should use CLIEngin which is the underlying utility that runs the ESLint command-line interface.

This object reads the filesystem for configuration and file information but doesn’t print any results. Instead, it allows you direct access to the important information so you can deal with the output yourself.

The formatted-linting module is going to provide the following API:

formatted-linting --dir <value> --ext <value> --conf <value>

Where:

  • --dir is a directory to traverse for files,
  • --ext specifies an extension that should be checked by the linter,
  • --conf is a path to the .eslintrc.js file.

You can specify as much --dir or --ext options as you like, but it should be only one --conf option.

To retrieve these values we need to parse process.argv property that contains an array with the command line arguments passed when the Node.js process was launched. If you don’t want to parse it manually, you can use the Minimist package.

The first process.argv element is the path to Node itself, and the second element is the path to the script. We don’t need this information for our use case, therefore we slice these two elements and send the rest of the array to the minimist function. As a result, we get a well-parsed object populated with the array of arguments from process.argv.

{ _: [], dir: './', ext: [ '.json', '.js' ] }

Now when we know which files should be checked, we can determine which rules must be followed.

ESLint can set the rules in the form of an .eslintrc.* file, an eslintConfig field in a package.json file, or via a configuration file on the command line.

According to Node.js API, the config object can be passed to CLIEngine programmatically:

Be aware that options which are not supported by CLIEngine constructor should be defined as a baseConfig property. Check documentation for the full list of options.

According to the rules we added, a new CLIEngine instance extends a configuration called eslint-config-standard, sets a node environment and switches off semi and space-before-function-paren rules, that is enabled by default in eslint-config-standard. The eslint-plugin-json allows to lint JSON files.

If the path to a config file was not specified and there is no .eslintrc.js file in the root directory of the client app, the default config file will be used.

To lint one or more files we call the cli.executeOnFiles() method. This method accepts a single argument, which is an array of files and/or directories to traverse for files, and this is why we do a preprocessing first to always pass an array value to this method.

It returns an object containing the results of the linting operation. Here’s an example of a report object:

{
results: [
{
filePath: '/Users/alena/www/linter-consumer/client.js',
messages: [],
errorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
},
...
]
}

At this point, we are able to format the ESLint output.

I used cli.getFormatter() method to prettify the error report and make it look like the normal ESLint output. And the styling is done with the Chalk package.

The formatted ESLint output

Even though the command line ESLint interface can fulfill most of your desires, it is also possible to use ESLint functionality directly through the Node.js API. The linter improves the overall quality of the code and helps to find such problems as using undefined variables or presence of unreachable code, and the Node.js API allows plugin and tool authors to take control of the linting process to solve specific issues and bring more value to the users.

You can find the entire source code of the program on GitHub.

--

--