Firefox OS Advent Calendar 2013

12月11日 SpiderMonkey で syntax check してみる

Firefox OSと直接の関係性が薄い記事ですが、開発環境(IDE)まわりを物色中の旨、御了承下さい。

SpiderMonkeyは、MojillaさんのC言語によるJavaScriptエンジンで FirefoxやSeaMonkeyなどで利用されています。
SpiderMonkeyには、JavaScript shell もあって Ver1.8.5 の実行もできるそうです。

さらにYuta.Kikuchiの日記:SpiderMonkeyでのコマンドラインJavascriptを参照すると、Vim では、JSLintとSpiderMonkeyを利用した syntax check ができるそうです。
これを使えば、「JavaScriptの作法チェックやアプリ開発時のちょっとした挙動確認に便利!」
…と思いましたが Vim 使いではない私(泣)は、勉強のためコマンドラインからSpiderMonkeyでJSLintを利用できるようにしてみました。

【お詫び】
私のPC環境は、Ubuntu 12.04 LTS 64bit ですので、サンプル内容も Linux 用になっています。
他の環境の方、ごめんなさい。

別にこんなことをしなくても、素直に開発環境を使えば、 Webstorm は、標準でJSLintとJSHintが利用可能ですし、 NetBeans JSLint プラグインfirefox JSLint ADD-ONもあるそうです。(車輪の再発明万歳)

SpiderMonkey JavaScript シェルのダウンロード

Mozilla Developer Network:Introduction to the JavaScript shellを読むと…

To get the SpiderMonkey JavaScript shell, see the SpiderMonkey Build Documentation or download a compiled binary for your platform from the Nightly Builds
…とありますように、JavaScript shell を利用するだけでしたら、ソースコードを取得してビルドしなくても Nightly Build のバイナリが取得できるようです。
私は、利用中の Firefox のバージョンに合わせるため、Nightly Build ディレクトリから上に上がって 25.0.1-candidates/build1/jsshell-linux-x86_64.zip (64bit Linux版 47MB)をダウンロードしました。

SpiderMonkey JavaScript シェルのインストール

ダウンロードした jsshell-linux-x86_64.zip ファイルを[SpiderMonkey shell展開先]ディレクトりで展開してください。
展開先では jsshell-linux-x86_64 フォルダの下に、以下の4ファイルが配置されています。

  • js
  • libnspr4.so
  • libplc4.so
  • libplds4.so
コマンドラインからJavaScript shellを使えるようにするには、以下のようにパスを通すだけで良いようです。
1
2
$ export SPIDER_MONKEY_SHELL=[SpiderMonkey shell展開先]/jsshell-linux-x86_64
$ PATH=$PATH:${SPIDER_MONKEY_SHELL}

これでターミナル上で js [ENTER] とするだけで、SpiderMonkey JavaScript shell が利用できるようになります。

SpiderMonkey JavaScript シェルの使い方

前述した方法でターミナル上で js [ENTER] とするだけで、SpiderMonkey JavaScript shell が利用できるようになっています。
help() [ENTER] でヘルプ一覧が表示され、シェルを抜けるには、quit() [ENTER] を実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ JS
js> help()
JavaScript-C25.0.1
version([number])
  Get or force a script compilation version number.
options([option ...])
  Get or toggle JavaScript options.
load(['foo.js' ...])
  Load files named by string arguments. Filename is relative to the
      current working directory.
loadRelativeToScript(['foo.js' ...])
  Load files named by string arguments. Filename is relative to the
      calling script.
evaluate(code[, options])
  Evaluate code as though it were the contents of a file.
  options is an optional object that may have these properties:
      compileAndGo: use the compile-and-go compiler option (default: true)
      noScriptRval: use the no-script-rval compiler option (default: false)
      fileName: filename for error messages and debug info
      lineNumber: starting line number for error messages and debug info
      global: global in which to execute the code
      newContext: if true, create and use a new cx (default: false)
      saveFrameChain: if true, save the frame chain before evaluating code
         and restore it afterwards
      catchTermination: if true, catch termination (failure without
         an exception value, as for slow scripts or out-of-memory)
          and return 'terminated'

run('foo.js')
  Run the file named by the first argument, returning the number of
  of milliseconds spent compiling and executing it.
readline()
  Read a single line from stdin.
print([exp ...])
  Evaluate and print expressions to stdout.
printErr([exp ...])
  Evaluate and print expressions to stderr.

~ 長いので省略 ~

system(command)
  Execute command on the current host, returning result code.
trap([fun, [pc,]] exp)
  Trap bytecode execution.
untrap(fun[, pc])
  Remove a trap.
js> 
js> quit()

$

コマンドライン・オプションや組み込み関数の詳細については、MOZILLA DEVELOPER NETWORK:Introduction to the JavaScript shellJavaScript シェルを使うを参照してください。

JSLint について


JavaScript: The Good Parts
-「良いパーツ」によるベストプラクティス
(著者)Douglas Crockford (翻訳)水野 貴明
JSLintは、ANALOGIG:JSLintを使おう!を参照すると、
JSLintは、私たちのJavaScriptコードの検証を行い、推奨されない書き方(アンチパターン)や構文エラー、使用されていない変数、未定義の変数の使用といった潜在的な問題が見つかった際に警告してくれます。
…だそうです。

JavaScript: The Good Partsの著者でもある Douglas Crockford さんのサイト JSLint,The JavaScript Code Quality Toolでは、JavaScriptコードを張り付けるだけでオンライン上でチェックを行ってくれます。

特徴は、JavaScript: The Good Parts の著者であるため、Bad Parts(JavaScriptの悪い部分)についてのチェックがとても厳しいことだそうです。

ですが、JavaScriptの Bad Parts に注意してコーディングしていないと、一見、何の問題もないように見えて、思いがけない挙動をとるコードを書いてしまうそうですので…
くろまほうさいきょうでんせつ:JSLintとJSHintとhoisting
hoistingってなに?
var foo = 'bar';
(function(){
    if(false){
        var foo = 'BAZ';
    }
    alert(foo);    // bar でも BAZ でもなく... undefined !
})();
↑のコードを実行すると"undefined"とアラートが出る。
深刻なバグに見舞われてしまわないよう「転ばぬ先の杖」を実践してくれているのでしょう。
引用内容の問題は、変数を関数先頭にまとめて宣言しないために発生するのだそうです。

このため普段何気なく使っているようなJavaScriptコードであっても、大量の警告が出されるので、使いこなすには、オプションのチェック項目とその内容についてをよく理解してから、チェックを外す項目を選択する必要があるそうです。
使い方やオプションについては、JSLint:Read the instructionsに記載されています。 邦訳については、前述のANALOGIG:JSLintを使おう!などのサイトを御参照ください。

あまりチェックが厳しすぎるので、黒くないすべてのものはカラスではない:JSLint から JSHint をフォークした理由(翻訳)によると、 最近では、JSHintが使われるようになってきているのだそうです。

コマンドラインから SpiderMonkey で JSLint を使う(基礎編)

JSLintをコマンドラインから使う(外部コマンドとして使えるようにする)ため、参考となる資料を探してみました。

SpiderMonkeyとJSLintを組み合わせた直接的な資料ではありませんが、
本項では、以下を参考にしています。


解説すると長くなるので
参考資料とJSLintソースを独自解析したポイントは、以下の通りです。
  1. JSLint のソースは、GitHub:douglascrockford / JSLintから取得
  2. JSLint の本体は、jslint.js (旧名:fulljslint.js)という JavaScript
  3. jslint.js の実装説明は、jslint.js内にコメントで記述されている
  4. JSLINT(source, option)関数が、jslint.js の本体
  5. JSLINT関数の第一引数は、チェックするJavaScriptのコード行(string)のArrayをとる
  6. JSLINT関数の第二引数は、チェック・オプションのObjectをとる
  7. JSLINT関数でチェックした結果は、JSLINT.errorsプロパティにObjectの配列で格納される
  8. SpiderMonkey:read ビルトイン関数で、外部JSファイルを読み取ることができる
  9. (補足)SpiderMonkey:read ビルトイン関数で読み込むテキストは、BOMを取り除く必要がある

JSLintのソースは、GitHub:douglascrockford / JSLintから取得できます。
JSLint,The JavaScript Code Quality ToolImplementationより
It is written in JavaScript. The full source code is available:https://github.com/douglascrockford/JSLint

JSLint の本体は、取得したソースにある、jslint.js (旧名:fulljslint.js)という JavaScriptです。
JSLintのソース一覧
  • README
  • init_ui.js
  • intercept.js
  • jslint.html
  • jslint.js
  • lint.html
  • title.png
jslint.jsの実装内容や使い方は、jslint.js内にコメントで記述されています。

jslint.js でJSLintのチェックを行う本体関数は、JSLINT(source, option)です。

コメント内での記述
1
2
3
// JSLINT is a global function. It takes two parameters.

//     var myResult = JSLINT(source, option);


JSLINT関数の第一引数は、チェックするJavaScriptのコード行(string)のArrayをとります。

コメント内での記述
1
2
3
4
// The first parameter is either a string or an array of strings. If it is a
// string, it will be split on '\n' or '\r'. If it is an array of strings, it
// is assumed that each string represents one line. The source can be a
// JavaScript text or a JSON text.


JSLINT関数の第二引数は、チェック・オプションのObjectをとります。

コメント内での記述
1
2
3
4
5
6
// The second parameter is an optional object of options that control the
// operation of JSLINT. Most of the options are booleans: They are all
// optional and have a default value of false. One of the options, predef,
// can be an array of names, which will be used to declare global variables,
// or an object whose keys are used as global names, with a boolean value
// that determines if they are assignable.

ここで指定されるチェック・オプションの内容は、 JSLint,The JavaScript Code Quality ToolOptionsで設定された内容から生成された、 JSLint Directiveの出力内容です。
出力内容が/*jslint browser: true, devel: true */でしたら、 {browser:true, devel:true}を指定することになります。

JSLINT関数でチェックした結果は、JSLINT.errorsプロパティにObjectの配列で格納されます。

コメント内での記述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// If it checks out, JSLINT returns true. Otherwise, it returns false.

// If false, you can inspect JSLINT.errors to find out the problems.
// JSLINT.errors is an array of objects containing these properties:

//  {
//      line      : The line (relative to 0) at which the lint was found
//      character : The character (relative to 0) at which the lint was found
//      reason    : The problem
//      evidence  : The text line in which the problem occurred
//      raw       : The raw message before the details were inserted
//      a         : The first detail
//      b         : The second detail
//      c         : The third detail
//      d         : The fourth detail
//  }

// If a stopping error was found, a null will be the last element of the
// JSLINT.errors array. A stopping error means that JSLint was not confident
// enough to continue. It does not necessarily mean that the error was
// especially heinous.

チェック結果配列の要素となるオブジェクトで、重要なプロパティは、line(エラー行)、character(エラー桁)、reason(問題理由)、evidens(エラー行)の4つです。

【注意】
問題が多いためにチェックが打ち切られた場合(Lint stop になった場合)は、配列の最終要素に null オブジェクトが返されています。


SpiderMonkey:read ビルトイン関数で、外部JSファイルを読み取ることができます。

MOZILLA DEVELOPER NETWORK:Shell global objectsを見ると、 JavaScript シェル中から外部ファイルの内容を読み込むビルトイン関数として、read(filename)が見つかりますので、これを利用します。
実装としては、チェックするJavaScriptファイルを読み込んで、JSLINT関数の第一引数(JavaScriptコード行の配列)を作成することになります。

SpiderMonkey:read ビルトイン関数で読み込むテキストは、BOMを取り除く必要があります。

read 関数を利用して、日本語(UTF-8)を含む JavaScript ファイルを読み込むテスト中に、BOMが読み込まれて文字化けしたJavaScriptコードとして扱われることがありました。
このため、(読込文字).charCodeAt(0) === 65279 のようにしてBOMをチェックし、除外する処理を追加しています。
詳しい方、このような実装で良いのか否か御教示ください。

コマンドラインから SpiderMonkey で JSLint を使う(実装編)

前項のポイント

  1. JSLint のソースは、GitHub:douglascrockford / JSLintから取得
  2. JSLint の本体は、jslint.js (旧名:fulljslint.js)という JavaScript
  3. jslint.js の実装説明は、jslint.js内にコメントで記述されている
  4. JSLINT(source, option)関数が、jslint.js の本体
  5. JSLINT関数の第一引数は、チェックするJavaScriptのコード行(string)のArrayをとる
  6. JSLINT関数の第二引数は、チェック・オプションのObjectをとる
  7. JSLINT関数でチェックした結果は、JSLINT.errorsプロパティにObjectの配列で格納される
  8. SpiderMonkey:read ビルトイン関数で、外部JSファイルを読み取ることができる
  9. (補足)SpiderMonkey:read ビルトイン関数で読み込むテキストは、BOMを取り除く必要がある
…を踏まえると
コマンドラインから SpiderMonkey で JSLint を実行するためには、 外部から JSLint(JavaScript)に、チェックするJSファイルの内容(チェックするJSファイル名から取得されるもの)を渡さなくてはなりません。

コマンドラインで実行するシェルスクリプトには、任意のJSファイル名を引数として渡せますが、SpiderMonkey に渡せるファイル名は、実行対象のJSファイル名です。

SpiderMonkey に JSLint を実行させるスクリプトを書いたJSファイルを渡せても、そのスクリプトでチェックするJSファイル名を渡す良案が思いつきません。

そこで今回は、コマンドラインで受け取ったチェックするJSファイル名の内容を temp.js にコピーして、 JSLintを実行させるスクリプトは、毎回 temp.js ファイルの内容を読み込むようにします。

以上から、
  • SpiderMonkey shell のディレクトリを[SPIDER_MONKEY_SHELL]とする
  • JSLint 本体のjslint.js[SPIDER_MONKEY_SHELL]/scriptsに配置する
  • JSLint を起動させるシェルスクリプト名をjs_syntax_checkとして
    [SPIDER_MONKEY_SHELL]/scriptsに配置する
  • JSLint を実行させるJavaScriptファイル名をmy_jslint.jsとして
    [SPIDER_MONKEY_SHELL]/scriptsに配置する
  • 作業用の temp.js ファイルは、[SPIDER_MONKEY_SHELL]/scriptsにコピーする
として、
各スクリプトを作成しました。

スクリプト等のファイル配置は、以下のようになります。

  [SPIDER_MONKEY_SHELL]/-+- js           ←* 
                         |                 | SpiderMonkeyの
                         +- libnspr4.so  ←| JavaScript シェルファイル
                         |                 | 
                         +- libplc4.so   ←|
                         |                 |
                         +- libplds4.so  ←*
                         |               
                         +- scripts/-+- jslint.js        ←JSLint 本体のJS
                                     |                     
                                     +- js_syntax_check  ←syntax checkの
                                     |                     シェル・スクリプト
                                     +- my_jslint.js     ←syntax checkのJS
                                     | 
                                     +- temp.js          ←作業用のJSコピー



続いては、各スクリプトの実装内容です。

js_syntax_check シェル・スクリプトの実装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/sh
if [ $# = 1 ] ; then 
    if [ -e $1 ] ; then
        echo JSLint start.
    else
        echo argument file is not exist.
        echo if you run with no arguments, help is displayed.
        echo 
        exit
    fi
else
    echo spider_monkey_syntax_check
    echo Description;
    echo  a syntax checker of JS file
    echo  by using the JSLint and SpiderMonkey.
    echo 
    echo Usage:
    echo  js_syntax_check JS_file_path
    echo 
    exit
fi

export SPIDER_MONKEY_SHELL=[SpiderMonkey shellのディレクトリ]

#SyntaxチェックするJSを作業用JSにコピー
cp $1 ${SPIDER_MONKEY_SHELL}/scripts/temp.js

#Syntaxチェック実行
${SPIDER_MONKEY_SHELL}/js -f ${SPIDER_MONKEY_SHELL}/scripts/my_jslint.sh
echo JSLint end.


my_jslint.sh JavaScriptの実装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function checkSyntax () {
    var SHELL_HOME = "[SpiderMonkey shellのディレクトリ]";

    //Syntax チェックするJSの読み込み
    var data = read(SHELL_HOME + "/scripts/temp.js");
    var srcLines = [];
    var line = "";
    for(var i = 0;  i < data.length;  i++){
        var ch = data[i];
        //BOM と CR は、無視します。
        if(ch.charCodeAt(0) === 65279 || ch === '\r') continue;

        if(ch === "\n" || i === (data.length-1)){
            srcLines.push(line);
            line = "";
        }else{
            line += ch;
        }
    }

    //1024文字分の空白を確保する。 (注)JS圧縮は、考慮していません!
    var space = "                                "; //32文字分のSpace
    space += space;
    space += space;
    space += space;
    space += space;
    space += space;

    //JSLint オプション設定 (詳細は、http://www.jslint.com/ のオプションを参照)
    var options = {browser: true, devel: true, sloppy: true};

    //JSLint 読み込み
    load(SHELL_HOME + "/scripts/jslint.js");

    //JSLint で、Syntax チェックを行う
    var result = JSLINT(srcLines, options);
    if(result === false){
        JSLINT.errors.forEach(
            function(e) { 
                if(e !== null){
                    if(e.evidence !== undefined){
  print("line:"+e.line + ", column:" + e.character + ", " + e.reason); //インデントを直す
    	                print("->" + e.evidence);
                        print(space.substr(0, e.character-1) + "  ^");
                    }else{
                        //Lint stop時の対処
  print("line:"+e.line + ", column:" + e.character + ", " + e.reason); //インデントを直す
                    }
                }
            }
        );
    }
};
checkSyntax();


コマンドラインから syntax check する

作成したスクリプトで、syntax check してみます。

Step1.syntax check を起動するシェルスクリプト(js_syntax_check)にパスを通します
パス設定は、以下のようにします。

1
2
$ export SPIDER_MONKEY_SHELL=[SpiderMonkey shellのディレクトリ]
$ PATH=$PATH:${SPIDER_MONKEY_SHELL}/scripts/js_syntax_check


Step2.js_syntax_check の引数設定
第一引数に、チェックするJSファイルのパスを指定します。
  js_syntax_check CHECK_JS_PATH
  #CHECK_JS_PATH:チェックするJSファイルへのパス

Step3.js_syntax_check 実行
js_syntax_check に、チェックするJSファイルのパスを指定して実行します。

(例)exam.js は、"for(var i = 0; i < 5; i++);"のみ定義
1
2
3
4
5
6
7
8
9
10
$ js_syntax_check exam.js
JSLint start.
line:1, column:4, Missing space between 'for' and '('.
->for(var i = 0; i < 5; i++);
     ^
line:1, column:5, Move 'var' declarations to the top of the function.
->for(var i = 0; i < 5; i++);
      ^
line:1, column:5, Stopping. (100% scanned).
JSLint end.


引数を指定しなかった場合は、ヘルプが表示されます。
1
2
3
4
5
6
7
8
$ js_syntax_check
js_syntax_check
Description
a syntax checker of JS file
by using the JSLint and SpiderMonkey.

Usage:
spider_monkey_syntax_check JS_file_path


という訳で、コマンドラインから SpiderMonkey で JSLint を使うスクリプトが完成しました。

【番外編】実際に使ってみる


コンソールの使えるエディタ(jEdit)で、JavaScriptファイルを開きながら、コンソールで syntax check させてみました。

jEditを起動するスクリプトには、あらかじめ SpiderMonkery shell や js_syntax_check へのパスを通すようにする必要がありますが、 コンソールに移動して、編集中のJSファイルのチェックも当然できます。

エラー部へのタグジャンプができれば、もっと良いのでしょうけれど...

jEditは、BeanSellというJavaオブジェクトを利用できるスクリプト機能もありますし、編集中の画面とBeanSellとの連携もできます。
BeanSellとシェルスクリプトに、SpiderMonkey(+JSLint)を組み合わせれば、簡単な自動テストが作れるかもしれません。

今後、ちょっとした実験や自動処理に活用できたら良いなぁと思いました。

【追伸】jEdit は、バージョン5.0から標準で日本語対応されたそうです。
SourceForge.JP:jEdit プロジェクト日本語トップページ

オチ...

昨日、関東Firefox OS 勉強会 5th の資料を見て気づいたのですが、
dynamisさんの関東 Firefox OS 勉強会 5th で使用したスライド
Firefox OS Updates 201311の5ページ目より

JavaScript 構文チェック
JSlint の話がありました。
Firefox OS (Gaia)では Closure Tools の gslint が使われています
https://developers.google.com/closure/utilities/docs/linter_howto?hl=ja&csw=1
コーディングルールを守ろう
Firefox OS では、JSLint でなく gslint が使われているそうです... orz

このページは、勉強のため html5-boilerplate を基に手作業で作成しました。