Aug 20, 2010

JsDoc ToolkitでJavaScriptコードモジュールのDoc Commentを出力する

Doc Commentの必要性

最近、JsDoc Toolkitの導入を考えてる。

  • ライブラリとして抜き出したコードを公開しておきたいけど、ライブラリ自体の使い方を書いたページを準備するのが面倒だし、多分誰も見てくれなさそう。
  • ソースコードの中にコメントとして使い方を埋め込んでおくと、見るべきファイルが1つで済むから良さそうだけど、どんな書式で書くと分かってもらいやすいかが問題だ。

ツリー型タブのAPI紹介等ではXPIDLっぽい書き方にしてみてるけど、オレオレ表記なので分かってもらえない可能性があるという心配はずっとしている。

ところで、Firefox自体のソースを見ていると以下のような書き方をしているのをよく見かける。

(略)

/**
 * Given a starting docshell and a URI to look up, find the docshell the URI
 * is loaded in.
 * @param   aDocument
 *          A document to find instead of using just a URI - this is more specific.
 * @param   aDocShell
 *          The doc shell to start at
 * @param   aSoughtURI
 *          The URI that we're looking for
 * @returns The doc shell that the sought URI is loaded in. Can be in
 *          subframes.
 */
function findChildShell(aDocument, aDocShell, aSoughtURI) {
(略)

これはJavaで標準的に使われているJavadocという「ソースコードの中に埋め込まれたコメントを自動的に収集してHTML形式でドキュメントを生成する」仕組みに基づいたもので、同じ形式でコメントを埋め込めるよう、Javadocの仕様に準拠した実装が言語ごとに存在しているようだ。JavaScriptならJsDoc Toolkit、C言語ならGTK-Docが一般的らしい。あとJavaScriptに関してはGoogle Closure ToolsのClosure Compilerも対応しているらしい

事実上の標準としてみんなが見慣れた形式なのであるならば、これに合わせて書くのがいいだろう。と思ったので、とりあえずライブラリとして切り出して公開しているコードにJavadoc形式で使い方の解説を埋め込んでみることにした。

JsDoc Toolkitの使い方

  1. Javaをインストールする。
  2. JsDoc Toolkitをダウンロードして展開しておく。
  3. JsDoc-Toolkitを使うで配布されているバッチファイル「jsdoc.bat」を、JsDoc Toolkitのjsrun.jarとかと同じ位置に置く。
  4. JavaScriptのファイルの中にDoc Commentを書く。書き方はJsDoc Toolkitによる開発効率向上を目指して - @IT等を見ると例がある。
  5. jsdoc.batの起動オプションにJavaScriptのファイルを渡して起動する。
  6. jsdoc.batと同じ位置にjsdocという名前のフォルダができて、その中に生成されたHTMLファイルがあるので、index.htmlをブラウザで開いて眺めてニヨニヨする。

JavaScriptコードモジュールとJsDoc Toolkit

  • JavaScriptコードモジュールではファイルの拡張子として「.jsm」を使うことが多いみたいなんだけど、.jsmなファイルにDoc Commentを埋め込んでJsDoc Toolkitに渡してみてもドキュメントを出力してくれなかった。
  • UxU用のテストケースまで走査するため、フォルダごと渡してまとめてドキュメントを生成させてみると、ファイル一覧の中にテストケースのファイルまで出てきてしまう。
  • @exampleに例を書く時に、例の中に<とか>とかのHTML的にまずい文字が含まれていると、出力されるHTMLがぶっ壊れてしまう。

JsDoc Toolkit自体のソースを見てみた所(JsDoc Toolkitはそれ自体がJavaScriptで書かれている。Java上で動作するJavaScript実行環境のRhinoの上で動作している。)、ファイルの拡張子でフィルタリングを行っているらしいということが分かった。あと、テンプレートのファイルの方を編集すれば、例に埋め込んだコードのせいでHTMLがぶっ壊れてしまう問題は回避できるようだった。

そういうわけで当面の所はこんな変更を加えて使ってみることにした。以下はjsdoc_toolkit-2.3.2.zipに対する差分です。

diff -ur jsdoc-toolkit-orig/app/lib/JSDOC/JsDoc.js jsdoc-toolkit/app/lib/JSDOC/JsDoc.js
--- jsdoc-toolkit-orig/app/lib/JSDOC/JsDoc.js   2009-01-24 18:42:04.000000000 +0900
+++ jsdoc-toolkit/app/lib/JSDOC/JsDoc.js    2010-08-20 10:17:17.602464000 +0900
@@ -69,7 +69,8 @@
 JSDOC.JsDoc._getSrcFiles = function() {
    JSDOC.JsDoc.srcFiles = [];

-   var ext = ["js"];
+   var ext = ["js", "jsm"];
+   var ignorePattern = /\.test\.js$/i;
    if (JSDOC.opt.x) {
        ext = JSDOC.opt.x.split(",").map(function($) {return $.toLowerCase()});
    }
@@ -89,7 +90,7 @@
                        }
                    }

-                   return (ext.indexOf(thisExt) > -1); // we're only interested in files with certain extensions
+                   return (ext.indexOf(thisExt) > -1) && !ignorePattern.test($); // we're only interested in files with certain extensions
                }
            )
        );
diff -ur jsdoc-toolkit-orig/app/run.js jsdoc-toolkit/app/run.js
--- jsdoc-toolkit-orig/app/run.js   2009-01-08 06:32:58.000000000 +0900
+++ jsdoc-toolkit/app/run.js    2010-08-16 17:28:28.673092400 +0900
@@ -337,7 +337,7 @@
        if (!path) return;

        for (var lib = IO.ls(SYS.pwd+path), i = 0; i < lib.length; i++) 
-           if (/\.js$/i.test(lib[i])) load(lib[i]);
+           if (/\.jsm?$/i.test(lib[i])) load(lib[i]);
    }
 }

Only in jsdoc-toolkit: jsdoc.bat
diff -ur jsdoc-toolkit-orig/templates/jsdoc/class.tmpl jsdoc-toolkit/templates/jsdoc/class.tmpl
--- jsdoc-toolkit-orig/templates/jsdoc/class.tmpl   2009-09-03 06:37:31.000000000 +0900
+++ jsdoc-toolkit/templates/jsdoc/class.tmpl    2010-08-18 15:10:02.542253900 +0900
@@ -300,7 +300,10 @@

                <if test="data.example.length">
                <for each="example" in="data.example">
-               <pre class="code">{+example+}</pre>
+               <pre class="code">{+String(example)
+                                     .replace(/&/g, '&amp;')
+                                     .replace(/</g, '&lt;')
+                                     .replace(/>/g, '&gt;')+}</pre>
                </for>
                </if>

@@ -399,7 +402,10 @@

                    <if test="member.example.length">
                    <for each="example" in="member.example">
-                   <pre class="code">{+example+}</pre>
+                   <pre class="code">{+String(example)
+                                         .replace(/&/g, '&amp;')
+                                         .replace(/</g, '&lt;')
+                                         .replace(/>/g, '&gt;')+}</pre>
                    </for>
                    </if>

@@ -466,7 +472,10 @@

                    <if test="member.example.length">
                    <for each="example" in="member.example">
-                   <pre class="code">{+example+}</pre>
+                   <pre class="code">{+String(example)
+                                         .replace(/&/g, '&amp;')
+                                         .replace(/</g, '&lt;')
+                                         .replace(/>/g, '&gt;')+}</pre>
                    </for>
                    </if>

@@ -565,7 +574,10 @@

                    <if test="member.example.length">
                    <for each="example" in="member.example">
-                   <pre class="code">{+example+}</pre>
+                   <pre class="code">{+String(example)
+                                         .replace(/&/g, '&amp;')
+                                         .replace(/</g, '&lt;')
+                                         .replace(/>/g, '&gt;')+}</pre>
                    </for>
                    </if>

エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能