あどけない話

インターネットに関する技術的な話など

JavaScript Lint

これまで、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)))
使い方
  • EmacsJavaScript ファイルを読み込んでいるバッファ (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)