これまで、JavaScript の構文チェックには JSLint を使ってきました。かなり強力なんですが、JavaScript で書かれているためブラウザー越しに使わなければならない、そして速度が遅いという問題がありました。
コマンドラインから使える構文チェッカとしては Rhino の js があります。しかし、これは僕の要求を満たしてくれるほど、チェックが厳しくありませんでした。
MacPorts をいじっていて、JavaScript Lint なるものがあることに気付き早速使ってみました。Crockford先生についても触れていることから分かるように、素晴らしいです。
使い方
コマンド名は、jsl です。JavaScript ファイルや script タグを含んだ HTML ファイルを引数 -process の後に渡します。
% jsl -process prototype-1.6.0rc1.js prototype.js(32): lint warning: missing semicolon K: function(x) { return x } ............................^ ... 0 error(s), 191 warning(s)
拡張子が ".js" であれば、JavaScript のファイル、それ以外は HTML ファイルとして取り扱ってくれるようです。
設定
警告の種類を設定するには、以下のようにしてデフォルトの設定ファイルを作ります。
% jsl -help:conf > jsl.conf
できた設定ファイル(上記では jsl.conf)を、"+" と "-" を入れ替えることによって、適当に編集します。
この設定ファイルを利用するには、オプション -conf を指定します。
% jsl -conf jsl.conf -process prototype-1.6.0rc1.js
JSON
JSLint では、(代入とかなしに)オブジェクト・リテラルのみを書くと、JSON だと判断してチェックしてくれます。しかし、JavaScript Lint では JSON をチェックする機能がないようです。これは、今後に期待ですね。JSON をチェックするには、しばらく JSLint を使いましょう。
Emacs から使う
Emacs で JavaScript のシンタックスエラーを検出するという記事に刺激を受けて、僕も flymake を JavaScript Lint へ対応させてみました。
準備
- 最後にある flymake-jsl.el を Emacs が参照しているディレクトリに置く
- たとえば、javascript-mode.el を使っているなら、以下のようなフックを設定する
(add-hook 'javascript-mode-hook (lambda () (require 'flymake-jsl) (setq flymake-check-was-interrupted t)))
- ecmascript-mode.el を使っているなら、次のように設定する。
(setq flymake-jsl-mode-map 'ecmascript-mode-map) (add-hook 'javascript-mode-hook (lambda () (require 'flymake-jsl) (setq flymake-check-was-interrupted t)))
使い方
- Emacs で JavaScript ファイルを読み込んでいるバッファ (JavaScript モードになっているバッファ)で、
M-x flymake-mode
と入力。これで、エラーや警告部分がハイライトされる。
コマンド
- C-cfn で次のエラー/警告へ
- C-cfp で前のエラー/警告へ
- C-cf? でエラー/警告の内容を表示
- C-cfc でもう一度 jsl にかける
flymake-jsl.el
以下が flymake-jsl.el です。まだ、再考すべき部分は残されていますが、Emacs Lisp としてある程度の品質には達していると思います。
;;; flymake-jsl.el ;;; ;;; Copyright (C) 2007 Kazu Yamamoto ;;; ;; (add-hook 'javascript-mode-hook ;; (lambda () ;; (require 'flymake-jsl) ;; (setq flymake-check-was-interrupted t))) ;; (setq flymake-jsl-mode-map 'ecmascript-mode-map) ;; (add-hook 'ecmascript-mode-hook ;; (lambda () ;; (require 'flymake-jsl) ;; (setq flymake-check-was-interrupted t))) (require 'flymake) (defvar flymake-jsl-minor-mode-prefix "\C-cf") (easy-mmode-defmap flymake-jsl-minor-mode-map '(("p" . flymake-goto-prev-error) ("n" . flymake-goto-next-error) ("c" . flymake-start-syntax-check) ("?" . flymake-display-err-menu-for-current-line)) "") (defvar flymake-jsl-mode-map 'javascript-mode-map) (easy-mmode-define-keymap `((,flymake-jsl-minor-mode-prefix . ,flymake-jsl-minor-mode-map)) nil (symbol-value flymake-jsl-mode-map)) (defvar flymake-jsl-allowed-file-name-masks '(("\\.js$" flymake-jsl-init flymake-simple-cleanup flymake-get-real-file-name))) (defvar flymake-jsl-err-line-patterns '(("^\\(.+\\)(\\([0-9]+\\)): \\(SyntaxError:.+\\)$" 1 2 nil 3) ("^\\(.+\\)(\\([0-9]+\\)): \\(.*warning:.+\\)$" 1 2 nil 3))) (setq flymake-allowed-file-name-masks (append flymake-jsl-allowed-file-name-masks flymake-allowed-file-name-masks)) (setq flymake-err-line-patterns (append flymake-jsl-err-line-patterns flymake-err-line-patterns)) (defun flymake-jsl-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 "jsl" (list "-process" local-file)))) (provide 'flymake-jsl)