TypeScriptのコード(d.ts)からAPIドキュメントを自動生成する

TypeScriptで書いたライブラリのAPIリファレンスをコードから生成したいときに使えるツールがなかったので、簡単な仕組みで作ってみました。

[Almin.js](https://almin.js.org/ "Introduction · Almin.js")というJavaScriptのライブラリを書いているのですが、コードベースをTypeScriptに変更してからAPIリファレンスが自動生成できなくて困っていました。

- [almin/almin: Almin.js provide Flux/CQRS patterns for JavaScript application](https://github.com/almin/almin "almin/almin: Almin.js provide Flux/CQRS patterns for JavaScript application")
- [Inroducing Almin 0.10.0: TypeScript, FlowType, Logger | Web Scratch](http://efcl.info/2017/03/08/almim0.10/ "Inroducing Almin 0.10.0: TypeScript, FlowType, Logger | Web Scratch")

TypeScriptで[JSDoc](http://usejsdoc.org/ "JSDoc")や[ESDoc](https://esdoc.org/ "ESDoc")、[documentationjs](https://github.com/documentationjs "documentationjs")のようなツールとしては、[typedoc](https://github.com/TypeStrong/typedoc "typedoc")があります。

しかし、[typedoc](https://github.com/TypeStrong/typedoc "typedoc")は[documentationjs](https://github.com/documentationjs "documentationjs")のようにMarkdownを出力にすることができなさそうでした。

以前は、[documentationjs](https://github.com/documentationjs "documentationjs") + JSDocで、次のような感じのMarkdownをドキュメントを吐いて使っていました。

- [azu/documentation-markdown-api-theme: markdown theme for documentation.js](https://github.com/azu/documentation-markdown-api-theme "azu/documentation-markdown-api-theme: markdown theme for documentation.js")

探していて、TypeScriptの`.d.ts`ファイルがそもそもそこまで読みにくいものではないなーと思って、`.d.ts`ファイル自体からリファレンスを作ればよさそうと思ってやってみました。

例) https://github.com/almin/almin/blob/0.10.0/src/Dispatcher.ts

```ts
/// <reference types="node" />
import { EventEmitter } from "events";
import { DispatcherPayloadMeta } from "./DispatcherPayloadMeta";
import { Payload } from "./payload/Payload";
import { ErrorPayload } from "./payload/ErrorPayload";
import { CompletedPayload } from "./payload/CompletedPayload";
import { DidExecutedPayload } from "./payload/DidExecutedPayload";
import { WillExecutedPayload } from "./payload/WillExecutedPayload";
export declare const ON_DISPATCH = "__ON_DISPATCH__";
/**
 *  Actual payload object types
 */
export declare type DispatchedPayload = Payload | ErrorPayload | CompletedPayload | DidExecutedPayload | WillExecutedPayload;
/**
 * Dispatcher is the **central** event bus system.
 *
 * also have these method.
 *
 * - `onDispatch(function(payload){  });`
 * - `dispatch(payload);`
 *
 * Almost event pass the (on)dispatch.
 *
 * ### FAQ
 *
 * Q. Why use `Payload` object instead emit(key, ...args).
 *
 * A. It is for optimization and limitation.
 * If apply emit style, we cast ...args for passing other dispatcher at every time.
 */
export declare class Dispatcher extends EventEmitter {
    /**
     * if {@link v} is instance of Dispatcher, return true
     */
    static isDispatcher(v: any): v is Dispatcher;
    constructor();
    /**
     * add onAction handler and return unbind function
     * @param   handler
     * @returns call the function and release handler
     */
    onDispatch(handler: (payload: DispatchedPayload, meta: DispatcherPayloadMeta) => void): () => void;
    /**
     * dispatch action object.
     * StoreGroups receive this action and reduce state.
     * @param payload
     * @param [meta]    meta is internal arguments
     */
    dispatch(payload: DispatchedPayload, meta?: DispatcherPayloadMeta): void;
    /**
     * delegate payload object to other dispatcher.
     * @param   toDispatcher
     * @returns call the function and release handler
     */
    pipe(toDispatcher: Dispatcher): () => void;
}

```

この`.d.ts`ファイルからMarkdownを吐いて、最終的にそのMarkdownをGitBookで取り込んでドキュメントにするという形です。

### .d.ts -> Markdown

`.d.ts`ファイルから次の事をすれば大体読めるMarkdownになるんじゃないかなと思いました

- importやprivateなどリファレンスとしていらない部分を削る
- コメントを普通のMarkdownとして表示する

これをやるベストなツールがなかったのですが、[docco](https://github.com/jashkenas/docco "docco")とテンプレートを組み合わせればできそうでした。
doccoはLiterate Programming的なドキュメントを作るツールです

複数行コメントに対応してなかったのでforkしたものを作りました(本体の更新停止してるし)

```
npm install -g @azu/docco
```

`.jst`という拡張子でdoccoのテンプレートファイルを作ることができます。
`.d.ts`から不要なものを取り除くテンプレートを書いていきます。


	# <%= title.replace(".d.ts", "") %>
	<!-- THIS DOCUMENT IS AUTOMATICALLY GENERATED FROM src/*.ts -->
	<!-- Please edit src/*.ts and `npm run build:docs:api` -->
	<%
	function shouldIgnoreSection(section) {
	   if (/^\s*$/.test(section.docsText)) { return true; }
	   if (/^\s*$/.test(section.codeText)) { return true; }
	   // private method
	   if (/private /.test(section.codeText)) { return true; }
	   // @private
	   if (/<b>private<\/b>/.test(section.docsText)) { return true; }
	   // **internal** flag
	   if (/\*\*internal\*\*/.test(section.docsText)) { return true; }
	   if (/<reference type/.test(section.docsText)) { return true; }
	   return false;
	}
	function stripIndent(str) {
	   const match = str.match(/^[ \t]*(?=\S)/gm);
	
	   if (!match) {
	       return str;
	   }
	
	   const indent = Math.min(...match.map(x => x.length));
	   const re = new RegExp(`^[ \\t]{${indent}}`, 'gm');
	
	   return indent > 0 ? str.replace(re, "") : str;
	};
	// Multiple line or Single Header
	function renderAPIHeader(section) {
	   // remove } block
	   const header = stripIndent(section.codeText.replace(/}$/m, "")).trim();
	   if(header.split("\n").length > 1){
	     return "### Interface of \n"
	      + "```typescript\n"
	      + header + "\n"
	      + "```";
	   }
	   return "### `" + header + "`";
	}
	%>
	
	## Interface
	
	```typescript
	<%= sections.filter(section => !shouldIgnoreSection(section)).map(section => section.codeText).join("\n").trim() %>
	```
	
	----
	<% sections.filter(section => !shouldIgnoreSection(section)).forEach(section => { %>
	<%= renderAPIHeader(section) %>
	
	<%= section.docsText %>
	
	----
	<% }) %>


doccoはhtmlの拡張子でしかファイルを吐けないので、

1. `.d.ts`を`.html`のドキュメントに変換(中身はMarkdown)
2. 拡張子を`.html`から`.md`に変更

というようなスクリプトを書いて回しています。

```bash
#!/bin/bash
declare projectDir=$(git rev-parse --show-toplevel)
declare srcDir="${projectDir}/lib"
declare docDir="${projectDir}/docs"
# add build result
function addDoc(){
    declare filePath=$1;
    declare fileName=$(basename ${filePath} "d.ts")
    $(npm bin)/docco --blocks -t ${projectDir}/tools/d.ts-markdown.jst ${filePath} --output ${projectDir}/__obj/docs
    mv ${projectDir}/__obj/docs/**${fileName}*.html ${projectDir}/docs/api/${fileName}md
    echo "Create: ${docDir}/api/${fileName}md"
}
npm run build
# update
addDoc "${srcDir}/Context.d.ts"
addDoc "${srcDir}/Dispatcher.d.ts"
addDoc "${srcDir}/DispatcherPayloadMeta.d.ts"
addDoc "${srcDir}/Store.d.ts"
addDoc "${srcDir}/UILayer/StoreGroup.d.ts"
addDoc "${srcDir}/UseCase.d.ts"
addDoc "${srcDir}/UseCaseContext.d.ts"
```

### 完成例

実際にできたドキュメントは次のサイトでみることができます。

- [Dispatcher · Almin.js](https://almin.js.org/docs/api/Dispatcher.html "Dispatcher · Almin.js")

![Dispatcher · Almin.js 2017-03-24 11-03-33.png](https://qiita-image-store.s3.amazonaws.com/0/734/04c0588c-260f-101c-9601-3aa7efc80ad3.png "Dispatcher · Almin.js 2017-03-24 11-03-33.png")

変換処理は[almin/tools at master · almin/almin](https://github.com/almin/almin/tree/master/tools "almin/tools at master · almin/almin")あたりを見るとわかります。

APIリファレンスの目的としては、そのAPIはundocumentであることを明示したいというのが主だったのでコスパを考えるとこれぐらいでも十分かなという感じでした。

イメージとしては[rustdoc](https://doc.rust-lang.org/book/documentation.html "rustdoc")のような、コメントにMarkdownを書いてそれがドキュメントとなるものが欲しい感じでした。

この辺もっといい感じのツールがあったら知りたいです。