JavaScript
Article

A Comparison of JavaScript Linting Tools

By Jani Hartikainen

A linting tool helps me avoid silly mistakes when writing JavaScript. Despite my many years of experience, I still type variable names incorrectly, make syntax errors and forget to handle my errors properly. A good linting tool, or a linter, will tell me about this before I waste my time—or worse, my client’s time. A good linting tool can also help make sure a project adheres to a coding standard.

There are many linters available for JavaScript, but how do you choose which one to use? Let’s take a look at both the features and the pros and cons of four popular alternatives: JSLint, JSHint, JSCS and ESLint.

Overview

The four tools work in the same basic way. They have a set of rules which they use to analyze and report problems in JavaScript files. They can be installed via npm. They are used from the command line by passing in files, are available as plugins for tools like Grunt, or are integrated into editors. They all support using comments for configuration.

But that’s where the similarities end. Each tool has its own pros and cons—it’s just that some have more pros than others.

JSLint

JSLint Logo

JSLint is the oldest of the four. Douglas Crockford created it in 2002 to enforce what, in his experience, are the good parts of JavaScript. If you agree with the good parts, JSLint can be a good tool—you install it and it’s ready to go.

The downsides are that JSLint is not configurable or extensible. You can’t disable many features at all, and some of them lack documentation. The official website is not very helpful, for example it lacks any information on how to integrate it with your editor.

Pros

  • Comes configured and ready to go (if you agree with the rules it enforces)

Cons

  • JSLint doesn’t have a configuration file, which can be problematic if you need to change the settings
  • Limited number of configuration options, many rules cannot be disabled
  • You can’t add custom rules
  • Undocumented features
  • Difficult to know which rule is causing which error

JSHint

JSHint Logo

JSHint was created as a more configurable version of JSLint (of which it is a fork). You can configure every rule, and put them into a configuration file, which makes JSHint easy to use in bigger projects. JSHint also has good documentation for each of the rules, so you know exactly what they do. Integrating it into editors is also simple.

A small downside to JSHint is that it comes with a relaxed default configuration. This means you need to do some setup to make it useful. When comparing it with ESLint, it’s also more difficult to know which rules you need to change in order to enable or disable certain error messages.

Pros

  • Most settings can be configured
  • Supports a configuration file, making it easier to use in larger projects
  • Has support for many libraries out of the box, like jQuery, QUnit, NodeJS, Mocha, etc.
  • Basic ES6 support

Cons

  • Difficult to know which rule is causing an error
  • Has two types of option: enforcing and relaxing (which can be used to make JSHint stricter, or to suppress its warnings). This can make configuration slightly confusing
  • No custom rule support

JSCS

JSCS Logo

JSCS is different from the others in that it doesn’t do anything unless you give it a configuration file or tell it to use a preset. You can download configurations from their website, so it’s not a big problem, and it has a number of presets, such as the jQuery coding style preset and the Google preset.

It has over 90 different rules, and you can create custom ones with plugins. JSCS also supports custom reporters, which makes it easier to integrate with tools that need their input in a specific format.

JSCS is a code style checker. This means it only catches issues related to code formatting, and not potential bugs or errors. As a result, it’s less flexible than the others, but if you need to enforce a specific coding style, it’s a job JSCS does well.

Pros

  • Supports custom reporters, which can make it easier to integrate with other tools
  • Presets and ready-made configuration files can make it easy to set up if you follow one of the available coding styles
  • Has a flag to include rule names in reports, so it’s easy to figure out which rule is causing which error
  • Can be extended with custom plugins

Cons

  • Only detects coding style violations. JSCS doesn’t detect potential bugs such as unused variables, or accidental globals, etc.
  • Slowest of the four, but this is not a problem in typical use

ESLint

ESLint Logo

ESLint is the most recent out of the four. It was designed to be easily extensible, comes with a large number of custom rules, and it’s easy to install more in the form of plugins. It gives concise output, but includes the rule name by default so you always know which rules are causing the error messages.

ESLint documentation can be a bit hit or miss. The rules list is easy to follow and is grouped into logical categories, but the configuration instructions are a little confusing in places. It does, however, offer links to editor integration, plugins and examples all in a single location.

Pros

  • Flexible: any rule can be toggled, and many rules have extra settings that can be tweaked
  • Very extensible and has many plugins available
  • Easy to understand output
  • Includes many rules not available in other linters, making ESLint more useful for detecting problems
  • Best ES6 support, and also the only tool to support JSX
  • Supports custom reporters

Cons

  • Some configuration required
  • Slow, but not a hindrance

My Recommendations

My choice of these four is ESLint. JSLint is strict and not configurable, whereas JSHint is lacking the extension mechanism. JSCS is a good choice if you only want to check coding style, but ESLint does that and it checks your code for bugs and other problems as well.

ESLint is also the obvious choice if you want to use ES6 (or ES2015, as they seem to be calling it now). Of all the tools mentioned, it has the widest support for ES6 features.

If you want to try ESLint, I’ve made it really easy for you by creating a 5-step quick start guide. You can download the ESLint 5-step quickstart guide from my website.

JSHint is strong second choice. If you don’t need the advanced features of ESLint, JSHint catches a good number of issues once properly configured. JSCS, with its huge number of available rules, is a top pick if you don’t need anything other than coding style checks (indentation, braces, etc.).

I’m hesitant to recommend JSLint at all. The other tools do the same things, but don’t force any specific rules on the user. The only exception here is if you happen to agree with all the rules it enforces, in which case it might well be worth looking into.

A linting tool is a good step towards catching problems, but it only sees as many errors as its rules permit. For a more foolproof automated bug-catcher, I recommend using unit tests. Code reviews can also be useful for this purpose.

How do you and your team ensure the quality of your code?

Comments
MatsSvensson

Jslint:
"The official website is not very helpful, for example it lacks any information on how to integrate it with your editor."

I can find zero information on any of the sites about integration with my editor (netbeans).
Jslint on the other hand, works just fine with netbeans.
Just add it in the plugin-settings.
No trace of the other ones there.

Paul_Wilkins

I prefer JSLint myself too because it knows what it wants. There's no letting things slide, or forgiving forgotten semicolons with jslint. It will make you cry, and make you a better coder in the process.

jhartikainen

You can get the others to be as strict as JSLint too, but it's true they do require some configuration to get to that point

felgall

Exactly, why mess about having to configure the others when JSLint is already configured for you by a JavaScript expert.

jhartikainen

Sure, if you agree with Douglas' setup. But there are plenty of JavaScript experts out there who have a different opinion. Who says JSLint's way is the only way?

As an example of why I think ESLint is great is their "handle callback err" rule. In NodeJS, it's important to handle error parameters in callbacks, and this rule can check whether or not that was done. You can't have this with JSLint at all.

Paul_Wilkins

Nobody is saying that it's the only way. I am saying though that you can always make things less good, and I'm glad that jslint has the moral fortitude to stand up for correctness.

That sounds excellent. All we want now is someone that has done the work of creating an ESLint ruleset that matches JSLint's set of rules too.

jhartikainen

For sure. Something I like about JSCS is that they provide several alternative rulesets out of the box, so you can pick something that's close to what you like, and then customize it further. Perhaps someone could make a collection like that for ESLint too. Would definitely come in handy for not having to start from scratch.

felgall

It shouldn't be a matter of what you like - the idea of any LINT is to detect any usage that may cause problems. One thing about JSLint is that all of the rules it uses are there to detect something that has been known to cause issues is scripts. Each rule has a corresponding example of a script that crashed because it didn't follow that rule.

jhartikainen

Not all of them have anything to do with issues. Examples of rules that are purely stylistic:

  • Use spaces for indentation, not tabs
  • White-space between parameters, function foo( a ) is not allowed, where function foo(a) is.

Although these can actually be disabled using the --white flag, who knows what else it does, since it's not documented anywhere.

But that defeats the purpose of not having to configure it, as you were saying. Plus, since JSLint doesn't have a configuration file, you have to remember to use the correct configuration flags whenever you run it.

As a fun factoid, the tabs vs spaces rule was silently added without warnings to JSLint. What if you had written your code in a certain way, and the next day it was decided that you no longer can do it like that? It seems a bit problematic to me. Especially as Douglas is saying new should not be used anymore, and many large codebases use it extensively. What happens if JSLint suddenly decides to not allow it anymore, just like it decided with tabs?

Paul_Wilkins

Who knows what else? We can all know as the code provides us with the details. Doing a search for the word white reveals all.

Line numbers (after Chrome pretty-print)
155 - allowed options
242 - expected at column
258 - use spaces, not tabs
318 - unexpected character
1005 - expected space (one space)
1012 - expected space (one space only)
1019 - unexpected space (no space)
1026 - unexpected space (no space only)
1031 - missing space
1043 - no comma space then to 1026
1054 - no semicolon space then to 1026
2614 - expect case at same column as switch

The following code examples are poor code, to demonstrate the different JSLint white space warnings that are triggered within the code.

Expected x at column

var x = true;
 if (x) {
    x = false;
}

Use spaces, not tabs

var x = true;
if (x) {
	x = false;
}

Unexpected character (spaces on blank line)

var x = true;
    
if (x) {
    x = false;
}

Expected space

var x = true;
if(x) {
    x = false;
}

Expected only one space

function example() {
    "use strict";
    return  '';
}

Unexpected space

var a = ( 1 + 2);

Unexpected space where only none should be

var a = -[0 , 1];

Missing space

var x = true;
if (x){
    x = false;
}

Expect case at same column as switch

var expr = "Oranges",
    msg;
switch (expr) {
    case "Oranges":
        msg = "Oranges are $0.59 a pound.";
        break;
    case "Apples":
        msg = "Apples are $0.32 a pound.";
        break;
    default:
        msg = "Sorry, we are out of " + expr + ".";
}

Also, there's a handy jslinterrors site that can be handy too, which was only used to retrieve some suitable coding examples for common issues.

I don't know about you but I would learn about why tabs cause such as issue compared with spaces, and proceed to resolve the problem which can be easily and rapidly done across all code-bases with the right tools.

jhartikainen

The issue with tabs vs spaces is purely subjective. Dog people vs cat people. Vim vs Emacs. Same thing.

Paul_Wilkins

Yes, it is subjective and you'll find that Crockford himself doesn't mind which is used either, as long as it's consistant.

The issue of tabs vs spaces only becomes a problem when you are coding with other developers, at which point there's an issue of change control among the code as developers fight like a pack of squabbling cats for control over the code base to set their own personal preference, and waste time converting everything back and forth.

As such, some sort of standard must be set for harmony to return, and it just so happens that the spaces side of the camp is seen to have less potential problems than tabs.

If you just so happen to prefer to indent using tabs, then clearly you will have problems with jslint.

jhartikainen

I don't agree but let's not derail this into a tabs vs spaces thing smile

Anyway, the point about the tabs rule and the white space around arguments rule was to show that not every rule in JSLint has an objective reason behind it. I think it's fine for a tool to set a default for such rules though, but in JSLint's case, it's not so easy to deactivate the rule because it doesn't have a configuration file.

Paul_Wilkins

Every rule came about as a response to a widely-regarded issue. Are there any other rules on which you are puzzled about its objective nature? Perhaps we can shed some light for you on the rationale behind it.

What sort of config file did you have in mind?

my-special-jslint-config.js

/* jslint white:false */
jhartikainen

Well I didn't really see anything on what problems tabs cause, or what problems white space around argument names cause.

A global configuration. The example you give with the comment is local only to a single file, therefore not very practical in larger projects.

Paul_Wilkins

One of the reasons is:

There are also issues in regard to a consistant layout, and that the default tab stop size is just too large. While it's possible for custom tab stops to be configured, it still doesn't help to provide consistency in other places.

I hope that you are not just being difficult now, and trust that you really are interested in some answers here.

Sublime Text for example has config files for jslint, both default ones and custom user config files too.

There are also jslint configs for when using grunt, and other systems that can be used to customise it for your build process.

felgall

It depends on the editor you are using. The one I use has a configuration setting for how many spaces to convert tabs to. This means that regardless of whether I type spaces or use tabs the resultant files always contain spaces and never contain tabs.

jhartikainen

I can change tabstop to 2, 4, 8, anything, and the code will always indent the same. You only use tabs for indentation. If you follow a pattern where you use it only for indentation and nothing else, there is no problem.

What about the other case I mentioned?

Yeah that's true. I was referring to the lack of it in the tool itself, so you need another tool to complement it for something that others have out of the box. Of course you can always slap something else on top to fix things in many cases.

Considering the argumentation for JSLint seemed to be it doesn't require configuration, this seems to defeat it.

Paul_Wilkins

On your particular setup it will indent the same. When your code is shared with other people, that's where problems can occur. What works well for you at a tab stop of 2 can be a big problem where someone else has a tab stop they can't change of 8.

The problems of working with other people can be hard to deal with, and commonly requires a solution of setting unreasonable standards.

I have no wish to misrepresent you, so please clarify which case that is that you mean.

You seem to be misunderstanding what's happening here, for we are not two Titans holding a grand debate. Instead, I am working on finding solutions and answers for you. So saying, what else can we help you with here?

jhartikainen

Here's a sample with tabstop 2:

function foo() {
  hello();

  if(foo) {
    bar();
  }
}

Here's a sample with tabstop 4:

function foo() {
    hello();

    if(foo) {
        bar();
    }
}

Here's a sample with tabstop 8

function foo() {
        hello();

        if(foo) {
                bar();
        }
}

I don't see the problem.

The second case was extra spaces around an argument name.

Paul_Wilkins

The problem so far as I understand it is that deeply nested code at a tab stop of 2 will be much harder to work with at a tab stop of 8.

Now I realise that deeply nested code is a code smell that we all should work on lessening, but that simple situation where the same exact code has widely varying aspects of usability depending on which particular tab stops you choose to use, was a problem to be resolved.

What would you propose to do to resolve such a problem?

Another issue is in terms of how can you visually tell if tabs or spaces are causing a problem? Some people resort to using visual tabs and spaces to help resolve such problem.

Other issues come in displaying code on a web page, where tabs can be tricker to nail down in to place.

It seems that tabs are responsible for the greater majority of technical problems, and that removing tabs removes those problems. That is the path that Crockford has been taking, to remove that which is causing problems. It's not a popular path to take, but at times the right path is not the popular one.

What do you wish to know about this?

My understanding is that when coders argue about using one or two spaces, that no common ground can be found so a standard has to be put in place to prevent such arguments.

The need for a common ground is occurs when people attempt to work together on the same code. When someone checks out code and updates it, with their own personal spin on the formatting applied, that ends up screwing up any form of change control within the code.

Remove the cause of the problem and set a coding standard, and things are sailing on an even keel once again.

We want to be making progress on the code. Bickering about how many spaces are to be used to separate terms impedes any such progress.

jhartikainen

If someone really really wants to use a tabstop of 8, then I think it's their problem. But that is certainly something to think about.

tab-size CSS property works well unless you're using a wonky browser (read: IE)

Claims were made that all the rules in JSLint have an objective benefit. I agree there is an objective benefit from a codebase having the same style used for spacing, but the specific way of spacing enforced by JSLint is equally subjective as any other variation.

RyanReese

Not really IEs fault. IE11 was only introduced in OCT 2011 and that's basically when tab-size was introduced.

IE9-11 aren't terrible CSS browsers frowning .

Paul_Wilkins

Yes indeed, and sometimes people just can't help the type of software they're stuck with.

Okay, let's get away from subjective claims and towards the more objective nitty gritty. What in particular about a the spacing style seems to you to be subjective? An example of code that demonstrates the difference and your preference will be appreciated too.

Such as for example:

var a = (2 + 3) * 4;

vs

var a = ( 2 + 3 ) * 4;
jhartikainen

Well, looking at for example what you just posted. Is one of the ways you showed objectively better than the other?

Paul_Wilkins

There are three main styles of white-space separation that are used. I'll use a haversine formula for the example, so that a combination of functions, numbers, and variables can be seen.

if(!Math.TAU){Math.TAU=Math.PI*2;}
function degreesToRadians(deg){return deg*Math.TAU/360;}
function haversine(lat1,lon1,lat2,lon2){
    var earthRadius=6372.8,//km
        dLat=degreesToRadians(lat2)-degreesToRadians(lat1),
        dLon=degreesToRadians(lon2)-degreesToRadians(lon1),
        a=Math.pow(Math.sin(dLat/2),2)+Math.pow(Math.sin(dLon/2),2)*Math.cos(lat1)*Math.cos(lat2),
        c=2*Math.asin(Math.sqrt(a));
    return earthRadius*c;
}

The above is a style that many consider to be too cramped to easily make out names and details when they are used within the code.

if (!Math.TAU) {
    Math.TAU = Math.PI * 2;
}
function degreesToRadians(deg) {
    return deg * Math.TAU / 360;
}
function haversine(lat1, lon1, lat2, lon2) {
    var earthRadius = 6372.8, // km
        dLat = degreesToRadians(lat2) - degreesToRadians(lat1),
        dLon = degreesToRadians(lon2) - degreesToRadians(lon1),
        a = Math.pow(Math.sin(dLat / 2), 2) + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2),
        c = 2 * Math.asin(Math.sqrt(a));

    return earthRadius * c;
}

The above tends to be the default spacing style, primarily due to it being used in the ECMASCRIPT specifications. Also, the spaces between operands act as punctuation, helping you to more easily understand what's going on.

if ( !Math.TAU ) {
    Math.TAU = Math.PI * 2;
}

function degreesToRadians ( deg ) {
    return deg * Math.TAU / 360;
}

function haversine ( lat1, lon1, lat2, lon2 ) {
    var earthRadius = 6372.8, // km
        dLat = degreesToRadians( lat2 ) - degreesToRadians( lat1 ),
        dLon = degreesToRadians( lon2 ) - degreesToRadians( lon1 ),
        a = Math.pow( Math.sin( dLat / 2 ), 2 ) + Math.pow( Math.sin( dLon / 2 ), 2 ) * Math.cos( lat1 ) * Math.cos( lat2 ),
        c = 2 * Math.asin( Math.sqrt( a ) );
    
    return earthRadius * c;
}

The above tends to result in lines of code that are around 10% longer in length, and seems to make it trickier to interpret the operations being performed. When all of the code is equally spread out it's more difficult to determine what's important.

jhartikainen

The last two are clearly better than the first. But whether the 2nd or 3rd is best is what's not so clear. You can say that the lines are 10% longer, but you could also say that especially when function calls are nested like Math.pow( Math.sin( dLat / 2 ), 2 ), it's easier to tell them apart.

Who can say for certain whether the length difference is significant, or whether the readability of nested calls is significant? Or whether it's actually more or less readable at all?

To make matters even more interesting, we could ask someone with dyslexia smile

Jeff_Mott

My understanding is that the criticism of tabs comes down to 1) alignment problems, and 2) editor problems.

Alignment problems because, for example, something like this is common:

(function () {
    // ...
    
    if (cond1
        cond2
        cond3) {
    }
    
    // ...
}());


If you're going to use tabs to indent and you still want this code to look correctly formatted for everyone else, then you need to take special care to use the right mixture of tabs and spaces.

(function () {
→// ...

→if (cond1
→••••cond2
→••••cond3) {
→}

→// ...
}());

Most people don't get this mixture right. Even some of the best programmers don't always get this right. I recall seeing alignment problems in the Linux kernel, for example.

And the other problem is editors, which I'm told sometimes replaces a file's indentation style to match the editor's configured style, so code commits end up with a lot of white space diffs.

In a perfect world, tabs would be the way to go. As the argument goes, everyone would get to indent to whatever width they prefer. But, unfortunately, a lot of human error creeps in, and tabs end up doing more harm than good.

Paul_Wilkins

Well let's compare the difference, with a simpler to examine situation involving parenthesis.

The question is: should an opening parenthesis always be followed by a space, and a closing parenthesis have a space just before it?

When there is no inter-parenthetical space involved, we have the following code:

function calc(a) {
    return (a + 3) * 4;
}

When there is a space involved, we end up with confusion in regard to the function parenthesis:

// air-gapped parenthesis
function calc ( a ) {
    return ( a + 3 ) * 4;
}

Do function parameters parenthesis behave the same as expression parenthesis? Or are they different?

// only air-gap expression parenthesis
function calc (a) {
    return ( a + 3 ) * 4;
}

Are the function names to be separated from the parenthesis too? They then become too easily confused with variables instead.

What happens when there are no function parenthesis?

// how to handle empty function parameters
function calc( ) {
    return ( 2 + 3 ) * 4;
}

Should the linter insist on a double space in the middle, one for each parenthesis, or just stick with a single space?

// a double space, one for each parenthesis
function calc(  ) {
    return ( 2 + 3 ) * 4;
}

Does it also mean that immediately invoked function expressions have to be spaced out too?

( function ( ) {
    ...
} ( ) );

Just how are such parenthesis spacing issues to be determined by a static linter, let along a human being?

There is a way around such quandaries, and that is to take the path of least confusion. That being to insist on no space on immediately inside of parenthesis.

function calc(a) {
    return (a + 3) * 4;
}

On a separate note, have you chosen a particular set of coding style standards to stick to? There are quite a number of them, which are nicely compared at https://github.com/Seravo/js-winning-style

bahmutov

If you use gulp, I have a plugin that brings all 3 linting tools at once (jshint, jscs and eslint) so you don't have to install the separate plugins https://github.com/bahmutov/gulp-lint-everything

rommguy

I use eslint with Webstorm. There is a very cool plugin for integrating Eslint with intelliJ/Webstorm - https://plugins.jetbrains.com/plugin/7494?pr=phpStorm

Pullo

@mrjoelkemp we updated the article to reflect this point. Thank you.

gotofritz

For ESLINT. "Some configuration required" is a cons?? Surely that's one of the its strongest points - that you can configure it to death.

qfox

Thanks for pretty nice article.

But I'm not sure why you comparing linting tools with code style checker (JSCS).

I don't know a project which using JSCS without a linter. JSCS itself is not a linter, but a lint-like tool that checks your code style (but not report suspicious parts like undefined variables or other bad parts).

Linting for me is not the same as linting for you wink

OPIUM_KiD

Because the "expert" that decided that the 's' in a switch statement needs to be vertically aligned with the 'c' in each case statement was clearly WRONG!

Can you cite any "JS rule" where an opening brace doesn't mandate indentation for the remainder of the closure?

I do not care about popular opinion, I want examples that don't pander to said erroneously-propagated rule.

felgall

What does that have to do with whether you use tabs or spaces for the indentation?

Paul_Wilkins

That is why it is called an exception to the rule.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in JavaScript, once a week, for free.