ESLint: Improving the ECMAScript 6 Search Box (Part 4)

In the past 3 posts I have discussed upgrading my search box project to ECMAScript 6, building with Gulp and styling with LESS. It’s now working. However, I also said I needed to run eslint frequently and test the component using automated techniques. It’s a little late in the process but let’s get those integrated too. I will cover linting today and testing tomorrow.

ESLint

My first step is to integrate eslint. There is a gulp-eslint package, so I added that to my package.json and required it at the top of my Gulpfile.js – just like all the other plugins. My rule for running eslint is fairly simple:

gulp.task('source:lint', function () {
    return gulp.src(['src/js/init.js', 'src/js/**/*.js'])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failOnError());
});

Run this rule using the right-click->Run in the Task Runner Explorer of Visual Studio and you will see the following:

eslint-errors

There are a bunch of errors in init.js that we can just fix – things like double-quotes for strings and a new line at the end. However handling the non-obvious ones will take some work. Let’s cover them now:

1. Require is not defined

Require is a global and is defined within our HTML page, so I need to tell ESLint that this is ok. While I am at it, I also need to tell ESLint that jQuery is ok, but only in the $ form – not in the long form. I can do this with this task configuration by including an options object and specifying a globals option:

gulp.task('source:lint', function () {
    return gulp.src(['src/js/init.js', 'src/js/**/*.js'])
        .pipe(eslint({
            globals: {
                "require": true,
                $: true,
                "jQuery": false
            }
        }))
        .pipe(eslint.format())
        .pipe(eslint.failOnError());
});

2. Document is not defined

The browser defines document. You may also see references to ‘window’ is not defined, which is the same problem. I need to tell ESLint my application runs in a browser. I do this by specifying an environment declaration in the configuration like this:

gulp.task('source:lint', function () {
    return gulp.src(['src/js/init.js', 'src/js/**/*.js'])
        .pipe(eslint({
            globals: {
                "require": true,
                $: true,
                "jQuery": false
            },
            envs: {
                browser: true
            }
        }))
        .pipe(eslint.format())
        .pipe(eslint.failOnError());
});

3. Use Strict is Required

Most people suggest that you use strict when writing ECMAScript 5. This alters my init.js to the following:

require.config({
    baseUrl: "/js",
    paths: {
        "jquery": "/lib/jquery/jquery.min",
        "bootstrap": "/lib/bootstrap/js/bootstrap.min",
        "require-css": "/lib/requirejs/css.min"
    },
    shim: {
        "bootstrap": {
            deps: [
                "jquery"
            ]
        }
    }
});

require([
    "jquery",
    "classes/MySearchBox",
    "bootstrap",
    "require-css!/lib/bootstrap/css/bootstrap.min"
], function ($) {
    "use strict";

    $(function () {
        $("#nav-search").searchbox();
    });
});

4. Invalid import declaration

You’ve probably guessed by now that this is because of ECMAScript 6 support. I need to enable this and the configuration is a little more extensive and certainly not documented well. I need to add an environment to our config to tell ESLint that I write es6 code and then add eslint configuration inside of package.json to tell it which features I use that are non-standard:

{
    "version": "1.0.0",
    "name": "BubbleSearch",
    "private": true,
    "bin-links": true,
    "dev": true,
    "dependencies": {
        "bootstrap": "3.3.2",
        "jquery": "2.1.3",
        "requirejs": "2.1.16",
        "require-css": "0.1.5"
    },
    "devDependencies": {
        "gulp": "3.8.11",
        "del": "1.1.1",
        "merge-stream": "0.1.7",
        "gulp-babel": "4.0.0",
        "gulp-sourcemaps": "1.5.0",
        "gulp-less": "3.0.1",
        "gulp-pleeease": "1.2.0",
        "gulp-concat": "2.5.2",
        "gulp-eslint": "0.6.0"
    },
    "eslintConfig": {
        "ecmaFeatures": {
            "modules": true
        }
    }
}

The extra eslintConfig section tells ESLint that I am using ECMAScript 6 modules. The configuration of the environment is done in the Gulpfile.js task definition in a similar way to the browser definition earlier:

gulp.task('source:lint', function () {
    return gulp.src(['src/js/init.js', 'src/js/**/*.js'])
        .pipe(eslint({
            globals: {
                "require": true,
                "$": true,
                "jQuery": false
            },
            envs: {
                browser: true,
                es6: true
            }
        }))
        .pipe(eslint.format())
        .pipe(eslint.failOnError());
});

When I run eslint now I get more errors but at least I am syntax checking the entire ES6 class file now. Note also that because I am running in ES6 mode, the use strict I added earlier is no longer required.

That’s 66 errors to go! In doing all the obvious ones, note the error on line 76:

  76:22  error  Expected '===' and instead saw '=='  eqeqeq

That’s actually a major style problem that ESLint found. I’m doing this all the time as a habit, so getting ESLint to break me of this habit is a good thing.

5. Unexpected console statement

There are a bunch of errors around the console statement. I left them in because I can programmatically remove them when I am minifying my code for production. I really want to make ESlint ignore them. That’s a two step process. Firstly I need to tell ESLint that console is a global – I do that the same way I did with require earlier. Once I did that and re-ran eslint, I noticed that I had a mis-spelling of console, so I corrected that (thank you eslint!) The other error eslint is throwing is Unexpected console statement. It is best to disable this rule since I use a lot of console statements. To do this, I have to alter the eslintConfig inside of package.json:

    "eslintConfig": {
        "ecmaFeatures": {
            "modules": true
        },
        "rules": {
            "no-console": 0
        }
    }

The rule name is printed on the eslint output next to the error message. If you’ve done everything correctly, you can rejoice now because you have a clean lint run. However, make sure you always do the following:

  1. Run eslint before you check in your code
  2. Run eslint whenever you complete a class or major piece of functionality
  3. Care about the errors enough to deal with them

I’ve uploaded this code to my GitHub Repository for you to review. Note that it is the complete code, so check out the other articles in the series that cover everything.