Explorations In Automatically Fixing JavaScript Linting-errors
September 30, 2014
Linting is a common step in our JavaScript iteration workflow. Most developers probably use JSHint (or ESLint) for this purpose. However, when a tool is capable of informing you of linting issues, the next logical question is..why can't it fix these issues for us? Is this taking automation too far, or a logical complement to source formatting?
FixMyJS
FixMyJS (by Josh Perez) was created to try solving this problem. It aims to automatically fix linting errors in a non-destructive way. To an extent, it achieves this goal.
Under the hood the module uses Esprima for source parsing and Escodegen (from the Mozilla Parser API AST) to generate it back. While any form of automation code transformation should be approached with a healthy dose of skepticism, FixMyJS is able to handle fixing basic JSHint errors fairly well. Some of what it supports includes:
- Adding missing semicolons
- Enforcing camelCase and snake_case conventions
- Adding curly braces to statements
- Removing debugger statements
- Enforcing single and double quite styles
- Adding the radix parameter to parseInt
- Dot notation conversion
- Handling extra trailing commas
and a few other common linting issues.
For example, it can take the following code block (containing 15 JSHint warnings):
var a = Array(); var b = Object(); var c = []; var d debugger; delete c; a == null; var e = undefined; var foo = new Foo; foo([1, 2, 3,]); a = 1; a++; var x = 1;; a == NaN; a != NaN var q = .25;
and transform it into the following, which has only three linting issues:
var a = []; var b = {}; var c = []; var d; c = undefined; a == null; var e; var foo = new Foo(); foo([ 1, 2, 3 ]); a = 1; a++; var x = 1; isNaN(a); !isNaN(a); var q = 0.25;
CLI
Using FixMyJS as a CLI utility is pretty straight-forward. Just install from npm and run it:
$ npm install fixmyjs -g $ fixmyjs app.js
The above option will update app.js using in a non-destructive way. It's also possible to do a trial run of running the CLI:
$ fixmyjs -r app.js
or alternatively, generate a patch based on the suggested changes:
$fixmyjs -p app.js
Note: the examples in this post are intentionally using anti-patterns JSHint will flag in order to demonstrate what FixMyJS can address.
Gotchas. There are a few.
You've probably noted that I've used the phrase 'non-destructive' more than once here. This isn't entirely true and FixMyJS comes with an important gotcha not covered in the documentation.
As mentioned earlier, the current version of the module uses Escodegen, which rewrites your source and doesn't take into account original styling information (i.e it will strip it). This makes it easier for the author to support complex new rules as they operate with an AST rather than relying on less reliable approaches like string replacement.
The slightly better news is FixMyJS does support a mode that won't destructively remove styling information in the form of a legacy
option. This uses a combination of JSHint linting errors and string replacement to perform linting fixes. It isn't quite as feature-complete as the escodegen version of the module, but does help fix basic linting issues.
This can be run as a flag:
fixmyjs app.js --legacy
or from the API itself:
var jshint = require('jshint').JSHINT; var fixmyjs = require('fixmyjs'); // ... jshint(stringOfCode, objectOfOptions); // ... var stringFixedCode = fixmyjs(jshint.data(), stringOfCode, objectOfOptions).run();
Here's an example of the output of fixing linting issues with and without legacy mode.
Sample:
Legacy mode:
As you'll notice, a number of linting issues have been addressed here (semi-colon insertion, object literal notation and so forth).
Default mode:
But a more complete set of issues have been addressed in default mode - in fact, all of our JSHint linting issues. Unfortunately, it has also removed our line breaks.
Until someone is able to submit a PR to escodegen supporting extra newlines above blocks, default mode is really only tenable if you intend on running a JavaScript formatter (e.g jsfmt, which Paul Irish has written a corresponding Sublime package for).
Sublime Text plugin
For those who would like to use FixMyJS from within their editors today, I've created a new SublimeText 2/3 plugin I've been using above called Sublime-FixMyJS. It supports both the current version of FixMyJS and a legacy mode which maintains source formatting more cleanly.
You can install the plugin straight from Package Control. You need to have Node.js installed. Make sure it's in your $PATH by running node -v
in your command-line.
Getting started
In a JS file, open the Command Palette (Cmd+Shift+P) and choose FixMyJS
. Your source will be formatted through FixMyJS.
You can alternatively create one or more selections before running the command to only fix those parts. E.g:
becomes:
Options
(Preferences > Package Settings > FixMyJS > Settings - User)
Legacy mode
By default, this plugin uses the FixMyJS legacy mode.
It does not include all of the fixes the current version of FixMyJS exposes, but does do a much better job of preserving source formatting. To disable legacy mode, set legacy
to false in your user settings for the package.
{ "legacy": false }
Atom Editor & Brackets plugins
Sindre Sorhus has written an Atom plugin for FixMyJS which brings a similar set of functionality to the Atom Editor. By default, it also uses the FixMyJS legacy mode, which can be disabled in Settings.
A Brackets plugin by Drew Fyock is also now available.
Complimentary tools
In my day to day workflow, I typically use JSHint along with JSCS. This allows my source to be linted for issues in code quality and adherence to my style guide. Good editor plugins for both are available for both Sublime Text and Atom Editor:
Wrapping up
FixMyJS is certainly not without its flaws and I encourage you to file bugs for edge-cases around the supporting linting optimizations it offers. As always, linting your code early and often is the best way to avoid issues building up. Use automation where it makes sense and is reliable.
FixMyJS Links of interest
With thanks to Sindre Sorhus, Pascal Hartig and Stephen Sawchuk for their technical reviews