Code Linting: An Inside Look

By: Estelle DeBlois

A little while back, I wrote a blog post introducing ember-suave, an addon that we created at DockYard to help enforce a common code style across all of our projects. With the addon installed, any code that doesn’t align with the established styleguide will cause the build to fail. During development, as files are modified, the linter will reprocess the changed files, displaying errors in the console right away.

If you’ve adopted ember-suave in your own projects, you may be content to find that things just work. But there’s a lot of satisfaction to be had with understanding how the addon does what it does.

Let’s start at the core with JSCS.

JSCS

JSCS is the wonderful library that uses a set of predefined code style rules to lint your code. It comes with a very simple API:

let Checker = require('jscs');
let checker = new Checker();

checker.registerDefaultRules();

checker.configure({
  verbose: true, // shows rule name next to error messages
  requireSpaceBeforeBinaryOperators: true,
  requireSpaceAfterBinaryOperators: true
});

As this snippet shows, once you install jscs from NPM and create a new Checker instance, all you need to do is register the default JSCS rules and configure which ones to enable. You can now start linting your code:

let results = checker.checkString('let x = y+z;');
let errors = results.getErrorList().map((error) => {
  return results.explainError(error);
});
console.log(errors.join('\n'));

Running this example will output the following:

requireSpaceBeforeBinaryOperators: Operator + should not stick to preceding expression at input :
     1 |let x = y+z;
-----------------^
requireSpaceAfterBinaryOperators: Operator + should not stick to following expression at input :
     1 |let x = y+z;
------------------^

It’s as simple as that! Of course, this example is merely checking a String literal. In practice, you would want to check the content of your application files, one at a time. This is where broccoli-jscs comes in.

broccoli-jscs

broccoli-jscs was created by Kelly Selden, and encompasses both a Broccoli plugin and an Ember CLI addon.

As a plugin, broccoli-jscs implements a JSCSFilter function that accepts a collection of input nodes, which map to file paths. Each file is read into a string, which is then passed to JSCS’s checkString method for linting. Every time that you make a change to a file while ember s is running, the file will be reprocessed by the plugin, and any JSCS errors found will be displayed in the console.

In addition, JSCSFilter will generate a test file for every file represented in the input nodes. Given foo/example.js, the plugin will output a test file named foo/example.jscs-test.js, with the following content:

module('JSCS - foo');
test('foo/example.js should pass jscs', function() {
  ok(false, 'foo/example.js should pass jscs.\nrequireSpaceBeforeBinaryOperators: Operator + should not stick to preceding expression at foo/example.js :\n     1 |let x = y+z;\n-----------------^\n     2 |\nrequireSpaceAfterBinaryOperators: Operator + should not stick to following expression at foo/example.js :\n     1 |let x = y+z;\n------------------^\n     2 |');
});

If no JSCS errors are found, the test file is still generated, but with a passing assertion: ok(true, /* message */).

As an addon, broccoli-jscs implements the lintTree function, which is one of the many hooks that Ember CLI exposes, as a way to extend the core build pipeline. In this case, the function simply creates a new instance of the JSCSFilter plugin and returns it. Ember CLI will call the hook for every addon discovered inside a project, if it is defined. So if you install broccoli-jscs as an addon, its lintTree function will be called on every build or re-build, therefore linting your code every time it changes.

It is worth mentioning that the JSCSFilter plugin instance returned by lintTree is also considered a node (representing the collection of JSCS test files), and this node further serves as an input node to additional plugins along the course of building out the project. For instance, it is passed to broccoli-babel-transpile to turn ES2015 code into ES5 syntax, then to broccoli-concat in order to be concatenated with other files and produce a single assets/tests.js file.

So where does ember-suave fit in this picture?

If you install ember-suave, the Ember CLI addon portion of broccoli-jscs isn’t used. The goal of ember-suave is to augment the plugin provided by broccoli-jscs, by configuring it with a set of rules (both JSCS built-in rules as well as custom ones defined inside of ember-suave), so you don’t have to do so for each project. As such, it has its own lintTree implementation that calls out to the JSCSPlugin.

Eager for more?

Even though this post focuses on ember-suave, you can imagine the process being very similar for other linters, such as JSHint or ESLint.

Understanding the inner workings of a build helps tremendously for various situations: when something breaks, you know where and what to look for. Furthermore, you are now better equipped to build extensions of your own, should the need arise.

If you found this post informative, and would love to learn more about Ember CLI’s build process, available hooks, and how to use them, EmberConf is right around the corner. I’ll be diving more into this topic as part of my talk on “Dissecting an Ember CLI Build”.