BLOG

JS Static Analysis Part 2 - Linting with JSLint, JSHint, & ESLint

This is Part 2 of a 4 part intro series to static analysis tooling for JavaScript:

JSLint

JSLint Documentation

JSLint was the first mainstream code linter that the community embraced. It was created and is maintained by Douglas Crockford, writer of JavaScript: The Good Parts, so it comes with a certain set of biases. It accomplishes it’s goal, but it’s strength and it’s weakness is that it has strong opinions about what good JavaScript code quality looks like.

Let’s try it out and see what it can do for us:

> npm install -g jslint

Now let’s create a file and run our linter:

a.js

function addTwo(first, second) {
  return
  a = 1
}

In your terminal, run jslint:

> jslint a.js
a.js
 #1 Missing 'use strict' statement.
    return // Line 3, Pos 3
 #2 Expected ';' and instead saw 'a'.
    return // Line 3, Pos 9
 #3 Unreachable 'a' after 'return'.
    a = b // Line 4, Pos 3
 #4 'b' was used before it was defined.
    a = b // Line 4, Pos 7
 #5 Expected ';' and instead saw '}'.
    a = b // Line 4, Pos 8
 #6 Unused 'first'.
    function addTwo(first, second) { // Line 2, Pos 17
 #7 Unused 'second'.
    function addTwo(first, second) { // Line 2, Pos 24

Just from this simple file we can see several things JSLint believes is incorrect:

  • It requires the use of “use strict” at the top of your file
  • Semicolons are required
  • You cannot have any unused parameters in functions
  • It recognizes that we have code after our return statement
  • It knows that b has not been defined when we tried to use it

Now let’s fix it to something that passes, including indenting with 4 spaces on the return line, which wasn’t caught the first time around:

'use strict';

function addTwo(first, second) {
    return first + second;
}
> jslint a.js

a.js is OK.

JSHint

JSHint Documentation

JSLint works great if you like all the rules they’ve selected. But what if you don’t? Next up is JSHint. There are two compelling reasons to switch from JSLint to JSHint:

  • Ability to customize most of the validation rules
  • You can add a .jshintrc file to help specify the customizations

First, let’s install JSHint and run it against our code:

> npm install -g jshint

> jshint a.js
a.js: line 2, col 9, Missing semicolon.
a.js: line 3, col 8, Missing semicolon.

2 errors

Notice that out of the box JSHint doesn’t care about as many things as JSLint did. It starts much more relaxed, but it does care that we’re not using semicolons.

But let’s start building up what we care about. Create a .jshintrc file and stick it in the root of your folder.

.jshintrc

{
  "undef": true,  // No undefined variables
  "unused": true, // Cannot use a variable before it's defined
  "asi": true     // Require semicolons
}

Now when we run our linter we get some more useful output:

> jshint a.js
a.js: line 1, col 24, 'second' is defined but never used.
a.js: line 1, col 17, 'first' is defined but never used.
a.js: line 3, col 3, 'a' is not defined.
a.js: line 1, col 10, 'addTwo' is defined but never used.

4 errors

Another nice feature is you can stick these .jshintrc files in the root of different folders to get a different set of linting rules. Why would you want to do this?

  • So that your unit tests (maybe existing in a /test folder) can have different rules
  • You keep frontend and backend code in the same repo

To set this up, just create more than one .jshintrc file:

.jshintrc
a.js
test/
|- test/.jshintrc
|- test/b.js

ESLint

ESLint Documentation

JSHint works great for most projects, and still has a lot of adoption in the community. However the ESLint team has taken things one step further and created a linting program that doesn’t make assumptions, and expects you to configure it manually and use plugins when necessary.

Why would you want this over JSHint?

  • It’s always going to include the best support for ES(next) features (ES6/ES7/ES8/etc)
  • If you use React, the other tools listed on this page won’t parse JSX files
  • Every rule can be set to one of 3 levels: off, on, or warning-only

Let’s install it and try it out:

> npm install -g eslint

> eslint a.js

Oops! Something went wrong! :(

ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:

    eslint --init

ESLint looked for configuration files in /Users/michael/Documents/git/demo and its ancestors.

If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint

As you can see, unlike with the other linters, eslint has no functionality out of the box. It requires that you create a configuration file in order to work.

> eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? No
? Where will your code run? Node
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? JavaScript
Successfully created .eslintrc.js file in /demo

That creates a JavaScript file in the root that looks like this:

module.exports = {
    "env": {
        "node": true
    },
    "extends": "eslint:recommended",
    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
};

With that, we can now try linting again:

> eslint a.js
/demo/a.js
  1:10  error  'addTwo' is defined but never used            no-unused-vars
  1:24  error  'second' is defined but never used            no-unused-vars
  2:3   error  Expected indentation of 4 spaces but found 2  indent
  2:9   error  Missing semicolon                             semi
  3:3   error  Expected indentation of 4 spaces but found 2  indent
  3:3   error  'a' is not defined                            no-undef
  3:3   error  Unreachable code                              no-unreachable
  3:8   error  Missing semicolon                             semi

✖ 8 problems (8 errors, 0 warnings)

The nice thing about this is we can grab the configuration options we want to change right out of the output. Let’s turn all of those errors off:

module.exports = {
    "env": {
        "node": true
    },
    "extends": "eslint:recommended",
    "rules": {
        "indent": 0, // Turn this off
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": 0, // Turn this off
        "no-unused-vars": 0, // Turn this off
        "no-undef": 0, // Turn this off
        "no-unreachable": 0 // Turn this off
    }
};

Now when we run eslint, we get no feedback, meaning all rules passed:

> eslint a.js

>
calendartwitterfeedenvelopelinkedingithub-altbitbucket