JSHint+Closure Linter+Gruntで最強のLint環境を目指してみた
先日JSHint 1.1.0がリリースされ、そのリリースノートに気になる記述がありました。
Adds new option gcl to make JSHint style checks compatible with Google Closure Linter.
うそーまじでーなにそれヤバイ!
普段は仕事でClosureにどっぷりなせいもあってClosure Linterを使っているんですが、Closure LinterってJSDocとかお作法的なとこはチェックしてくれるけど、他のLintツールが当たり前にチェックしてくれるとこはやってくれないんですよね。たとえばvarの付け忘れとか。
Closure Compilerを使ってコンパイル時にチェックすることもできるけど、規模が大きくなってくるとその結果が出るまでに10分とか待たないといけなくっていやな感じー。Closure LinterとJSHintが一緒に使えたらいいんだけどなー。
でも今まではfunctionの後のスペース有無でClosure LinterとJSHintが喧嘩してしまい、同時に使うのが難しかったんです。
// Closure Linterのスタイル var hoge = function() {};
// JSHintのスタイル var hoge = function () {};
それがJSHint 1.1.0で解消したと。素晴らしいですね!
せっかくなのでGruntの練習もかねて、JSHintとClosure Linterが一括でかけられるGruntプロジェクトを作ってみました。
grunt-gjshint
https://github.com/ama-ch/grunt-gjshint
使い方
gjslintをインストールしたことがない場合は、たぶん事前に easy_install python-gflags
が必要です。
- JSHint
$ grunt jshint:all
- Closure Linter
$ grunt gjslint:all
- Closure Linter(fixjsstyle)
$ grunt fixjsstyle:all
- ぜんぶLint
$ grunt
デフォルトタスクにjshint:allとgjslint:allが設定してあります。
- watch
$ grunt watch
変更したファイルのみjshint+gjslintして、結果をGrowlで通知します。
解説など
以下、中身の解説です。
--flagfileを渡したい
gjslintを実行するGruntタスクとしてはgrunt-closure-linterがありますが、gjslintコマンドに任意の引数が渡せないっぽかったので練習もかねてタスクを自作しました。gjslintの設定はファイルにして--flagfileオプションで渡せるようにしておくと、.jshintrcと似た設定が書けて後述のエディタ連携でも使えます。
Closure Linterの80文字制限が邪魔
Closure Linterを使ってて一番キツイのが一行80文字制限だと思います。なぜかこれはハードコーディングされていて、設定ができないという。。JSHintならmaxlenで設定できるので、Closure Linterの80文字制限は不要です。この記事を参考にして、ラッパースクリプトを追加して80文字制限を除外しました。
あとはこのラッパースクリプトをGruntfile.jsにgjslint.cmdというパラメータで記述して、それを実行するようにしています。
watchして変更したファイルだけlint
Gruntのサンプルを見ていると大体watchで全部のファイルがlintやminifyの対象になっていて、そこそこの規模のプロジェクトで使うのは厳しいです。watchの時は変更したファイルだけタスクの対象にしたいですよね。
Gruntが出てきた時にそれができなくて手を出すのをやめて、継続的にウォッチだけしていました。少し前にgrunt-contrib-watchにnospawnというオプションが入って、これを使うと変更ファイルが取得できるらしい。これで勝つる!と意気込んだものの、ちょっと調べてみたらgrunt-regardeっていうやつの方が良いらしいので結局そっちを使いました。
具体的な書き方を一部抜き出すとこんな感じ。
grunt.initConfig({ jshint: { all: { src: '<%= target %>' }, watch: { src: '<%= grunt.regarde.changed %>' } }, regarde: { js: { files: '<%= target %>', tasks: ['jshint:watch'] } }, target: ['Gruntfile.js', 'js/**/*.js'] });
jshintにwatchというターゲットを追加して、srcにgrunt.regarde.changed
と指定すると、変更されたファイルだけを対象にしてタスクが実行できます。イマイチ筋の良いやり方かどうかは分かりませんが。。
ところでregardeって全然覚えられなくて困っていたら
grunt.renameTask('regarde', 'watch');
と書けることを知ってgrunt watch
でregardeが動くようになりました。やったね!Gruntfile.jsもwatchで書けるようになります。
watchしてるときにエラーが出ても落ちないようにする
watchタスクの実行中にlintエラーが出るとタスク自体が終了してしまって、全然watchできない。。。オプションにforceをつけるとか何種類か回避方法はあるみたいですが。ここのやりとりを参考にして以下のようにしました。
grunt.registerTask('eatwarnings', function() { process.exit = function() {}; });
watchの結果をGrowl通知
Growl通知はgrunt-notifyのnotify_hooksを使うだけでできました。ただgjslintはstdoutに改行が含まれるせいか、Ubuntuのnotify-sendがうまく通知してくれませんでした。Growlでは問題ないので、ひとまず後回し。通知内容の精査もしたいけど、ひとまずwatch & notifyはできたのでここまで!
エディタ連携
最近WebStormからIntelliJ IDEAに乗り換えました。明日まで50%オフなので今のうちに買いましょう!
IntelliJ IDEA (WebStorm) はJSLint, JSHint, Closure Linterに対応していて、設定ファイルを指定するだけでエディタと連携できます。
JSHint
Closure Linter
エディタ
JSHintはちょっとバンドルされているバージョンが古いようで誤判定される部分もありますが、概ね快適に使えています。
繰り返しますが、IntelliJ IDEAはじめJetBrains製品は明日22日まで50%オフなので買うなら今がチャンスですよ!
その他
- JSHintの
indent: 2
とgjslintの--jslint_error=indentation
は同時に設定するとswitch文で喧嘩する。Closure野郎ならClosure Linter、そうでなければJSHintだけ有効にするのが良さそう - Gruntのデバッグ中は
grunt --verbose --stack
で動かすと色々出力される - node-inspectorでデバッグしたい時は
node --debug-brk $(which grunt)
おわり
JSHint+Closure Linterおすすめです。2方向から締め上げられる感覚がたまりません!