はてブロ@ama_ch

https://twitter.com/ama_ch

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-watchnospawnというオプションが入って、これを使うと変更ファイルが取得できるらしい。これで勝つる!と意気込んだものの、ちょっと調べてみたら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はできたのでここまで!

f:id:ama-ch:20130421151951p:plain

エディタ連携

最近WebStormからIntelliJ IDEAに乗り換えました。明日まで50%オフなので今のうちに買いましょう!
IntelliJ IDEA (WebStorm) はJSLint, JSHint, Closure Linterに対応していて、設定ファイルを指定するだけでエディタと連携できます。

JSHint

f:id:ama-ch:20130421153019p:plain

Closure Linter

f:id:ama-ch:20130421153024p:plain

エディタ

f:id:ama-ch:20130421155914p:plain

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方向から締め上げられる感覚がたまりません!