node-jslintでflymake

javascript-modeでのflymakeに、今までSpiderMonkeyを使っていたけど、試しにnode-jslintを使ってみることにした。
GitHub - reid/node-jslint: The JavaScript Code Quality Tool — for Node.js.
node-jslintはnpmでinstallすると"jslint"コマンドを提供してくれるコマンドラインツール。lintnodeという、nodeでwebサーバを立ち上げてそこでjslintを実行する、というものもあるようだったが、そこまでするのはなぁ…ということで見送り。
まずはコマンドラインで使ってみる。

$ npm install jslint -g
$ cat hoge.js
a;
var b;
alert("hoge")
var c = {
    foo: "hoge",
    bar: "fuga",
};
$ jslint hoge.js

hoge.js
/*jslint node: true, es5: true */
  1 1,1: Expected an assignment or function call and instead saw an expression.
    a;
  2 3,14: Expected ';' and instead saw 'var'.
    alert("hoge")
$ jslint --no-es5 hoge.js

hoge.js
/*jslint es5: false, node: true */
  1 1,1: Expected an assignment or function call and instead saw an expression.
    a;
  2 3,14: Expected ';' and instead saw 'var'.
    alert("hoge")
  3 6,16: Unexpected ','.
    bar: "fuga",

デフォルトでes5オプションが有効になっていて、これだとObjectの最後のカンマの検出などがされない。"--no-es5"オプションを渡すと検出してくれる。
エラー出力の形式がjslint packageのreporterによって決められているので、これに従って"flymake-err-line-patterns"を調整。
EmacsWiki: Flymake Java Scriptを参考に。

(require 'flymake)
(add-to-list 'flymake-allowed-file-name-masks '("\\.js\\'" flymake-js-init))
(defun flymake-js-init ()
  (let* ((temp-file (flymake-init-create-temp-buffer-copy
                     'flymake-create-temp-inplace))
         (local-file (file-relative-name
                      temp-file
                      (file-name-directory buffer-file-name))))
    (list "jslint" (list "--no-es5" local-file))))
(defun flymake-js-load ()
  (interactive)
  (setq flymake-err-line-patterns
        (cons '("^ *[[:digit:]] \\([[:digit:]]+\\),\\([[:digit:]]+\\)\: \\(.+\\)$"
                nil 1 2 3)
              flymake-err-line-patterns))
  (flymake-mode t))

(add-hook 'js-mode-hook
          (lambda ()
            (flymake-js-load)))


下記設定をしておくことにより"M-e"でエラー行に飛んでminibufferにエラー内容が表示される。

(require 'flymake)
(global-set-key "\M-e" 'flymake-goto-next-error)
(global-set-key "\M-E" 'flymake-goto-prev-error)

;; gotoした際にエラーメッセージをminibufferに表示する
(defun display-error-message ()
  (message (get-char-property (point) 'help-echo)))
(defadvice flymake-goto-prev-error (after flymake-goto-prev-error-display-message)
  (display-error-message))
(defadvice flymake-goto-next-error (after flymake-goto-next-error-display-message)
  (display-error-message))
(ad-activate 'flymake-goto-prev-error 'flymake-goto-prev-error-display-message)
(ad-activate 'flymake-goto-next-error 'flymake-goto-next-error-display-message)

Cocoa Emacs特有? かどうかよく分からないけど、flymakeがsubprocessとしてjslintを実行する際にPATHとかexec-pathが通ってないとうまくいかないので下記設定を追加してある。

;; exec-pathにshellから得られる$PATHを追加
(loop for x in (reverse
                (split-string (substring (shell-command-to-string "echo $PATH") 0 -1) ":"))
      do (add-to-list 'exec-path x))
;; process-environmentも変更
(setenv "PATH" (substring (shell-command-to-string "echo $PATH") 0 -1))