以前調査したtypescript-generator、その後もパラメーター調整しながら使用しているが、困ったのがヒアドキュメント。
Java側ではJavadocでクラスやプロパティの説明を書いているが、生成したTypeScriptのインターフェース定義ファイルにはデフォルトだと出力されないため、フロント実装時にJava側の定義を見ないと使いづらい。
Swaggerアノテーションの @ApiModel
をクラスに、 @ApiModelProperty
をプロパティに付与すると、TypeScript定義ファイルに出力されるが、JavadocとSwaggerアノテーション、両方書くのは面倒。
JavadocからSwagger UIを生成することはSwagger Docletでできそうだが、Swaggerアノテーションを生成するわけではないし、Javadoc書かずにSwaggerアノテーションだけ書くのも何だし、と思っていたら、typescript-generator自体が、xml-docletを使ってJavadocからTSDocを生成する機能を持っていたのでメモ。
環境
OpenJDK 8 8.275.01, typescript-generator 2.27.744, xml-doclet 1.0.5
TSDoc生成処理の概要
Javadoc · vojtechhabarta/typescript-generator Wiki · GitHub に設定方法の記載がある。
まず、xml-docletを使ってXML形式のJavadocを出力し、それをtypescript-generatorに読み込ませれば、パースしてTSDocとして出力してくれる。
設定
Gradleへのxml-docletの導入
まずはxml-docletの導入から。
プロジェクト管理ツールにMavenを使っている場合は、xml-docletのREADME、およびtypescript-generatorのWikiを見ればいいが、Gradleの場合の例がないのでやや面倒。
Javadocツールはドックレットを切り替えることで、出力形式を変更できる。GradleでJavadocタスクにカスタムドックレットを指定する方法はこちら。
javadoc
タスクを有効化するため、java
プラグインを有効化configurations
でドックレット名を指定dependencies
でドックレットの依存性を追加- この時、
implementation
など指定するところをドックレット名にする
- この時、
type: Javadoc
となるタスクを追加
java
プラグインはもともと入っていたため、以下の設定を追加。
configurations { xmlDoclet } dependencies { xmlDoclet 'com.github.markusbernhardt:xml-doclet:1.0.5' } final def tsOutputDir = 'build/typescript-generator' final def tsXmlDocletDir = "${tsOutputDir}/xml-doclet" task xmlDoclet(type: Javadoc) { source = sourceSets.main.allJava destinationDir = (tsXmlDocletDir as File).absoluteFile title = null // 「-doctitleは無効なフラグです」でエラーとなるため抑制 options.noTimestamp = false // 既に廃止済みのオプションがデフォルトで付与され、エラーとなるため抑制 options.encoding = 'UTF-8' // XMLの出力文字コード options.showAll() // publicなフィールド・メソッド以外のJavadocも出力 options.doclet = 'com.github.markusbernhardt.xmldoclet.XmlDoclet' options.docletpath = configurations.xmlDoclet.files as List }
xml-docletは3年前で更新が止まっており、デフォルトだとOpenJDKのjavadocツールと非互換が出ていたため、一部のオプションを指定して抑制している。
また、 showAll()
しているのは、getterがあるがJavadocがない場合、フィールドのJavadocを元にTSDocを出力してくれるため。Lombokを使っている場合に有効。
出力ファイル名のデフォルトは javadoc.xml
。前述の設定で gradle xmlDoclet
を実行すると、 build/typescript-generator/xml-doclet/javadoc.xml
が出力される。
ちなみに、処理の詳細としては、 build/tmp/xmlDoclet/javadoc.options
にファイルが出力され、 javadoc @build/tmp/xmlDoclet/javadoc.options
のようにJavadocがオプションファイル指定で実行される模様。
typescript-generatorにjavadoc.xmlを読み込ませる
generateTypeScript
タスクの javadocXmlFiles
に、読み込ませるXMLファイルのパスをFileのListとして設定するだけで、TSDocを出力してくれる。
また、 generateTypeScript.dependsOn
にXML出力タスクを追加しておくと gradle generateTypeScript
で合わせて実行できる。
generateTypeScript { ... javadocXmlFiles = ["${tsXmlDocletDir}/javadoc.xml" as File] } generateTypeScript.dependsOn compileJava, xmlDoclet
build.gradleの例
typescript-generatorの設定を含めた build.gradle
は、以下のようになる。いまだに apply plugin
なのがちょっと残念。
apply plugin: 'java' apply plugin: 'cz.habarta.typescript-generator' buildscript { dependencies { classpath 'cz.habarta.typescript-generator:typescript-generator-gradle-plugin:2.27.744' } } configurations { xmlDoclet } dependencies { xmlDoclet 'com.github.markusbernhardt:xml-doclet:1.0.5' } final def tsOutputDir = 'build/typescript-generator' final def tsXmlDocletDir = "${tsOutputDir}/xml-doclet" generateTypeScript { ... javadocXmlFiles = ["${tsXmlDocletDir}/javadoc.xml" as File] } task xmlDoclet(type: Javadoc) { source = sourceSets.main.allJava destinationDir = (tsXmlDocletDir as File).absoluteFile title = null options.noTimestamp = false options.encoding = 'UTF-8' options.showAll() options.doclet = 'com.github.markusbernhardt.xmldoclet.XmlDoclet' options.doclet = 'com.github.markusbernhardt.xmldoclet.XmlDoclet' } generateTypeScript.dependsOn compileJava, xmlDoclet
出力例
以下のクラスを用意。
/** インターフェース */ public interface ExampleInterface { /** * インターフェースのメソッド * * @return 文字列 */ String getInterfaceMethod(); } /** クラス */ @lombok.Data public class ExampleClass implements ExampleInterface { /** クラスのフィールド */ private String classField; private String interfaceMethod; } /** 列挙型 */ public enum ExampleEnum { /** 1 */ ONE, /** 2 */ TWO, /** 3 */ THREE; }
これに対して、前述の build.gradle
の generateTypeScript
タスクを以下のように変更して、 gradle generateTypeScript
してみる。
generateTypeScript { jsonLibrary = 'jackson2' classPatterns = [各クラスのパッケージ] outputKind = 'module' outputFileType = 'implementationFile' outputFile = "${tsOutputDir}/example.ts" sortDeclarations = true stringQuotes = 'singleQuotes' indentString = ' ' javadocXmlFiles = ["${tsXmlDocletDir}/javadoc.xml" as File] }
結果は以下のようになる(ヘッダのコメント部分は省略)。
/** * クラス */ export interface ExampleClass extends ExampleInterface { /** * クラスのフィールド */ classField: string; } /** * インターフェース */ export interface ExampleInterface { /** * インターフェースのメソッド * @return 文字列 */ interfaceMethod: string; } /** * 列挙型 * * Values: * - `ONE` - 1 * - `TWO` - 2 * - `THREE` - 3 */ export type ExampleEnum = 'ONE' | 'TWO' | 'THREE';
XMLをパースしているためか、元のJavadocをワンライナーで記述しても、TSDocの出力は複数行になっているなど、多少形式に違いはあるが、概要以外に @return
なども出力される。
EnumはデフォルトではUnionにマッピングされるため、typeのTSDocとして各値の説明が出力される。
generateTypeScript
のタスク定義に mapEnum = 'asEnum'
を追加し、Enumにマッピングすると、以下のように、各値にもTSDocが追加される。
/** * 列挙型 * * Values: * - `ONE` - 1 * - `TWO` - 2 * - `THREE` - 3 */ export const enum ExampleEnum { /** * 1 */ ONE = 'ONE', /** * 2 */ TWO = 'TWO', /** * 3 */ THREE = 'THREE', }
振り返り
Java側のコードに手を入れることなく、Gradleタスクを追加するだけでJavadocからTSDocへの変換を行えるようになった。
ただ、xml-docletが更新停止状態で、JDK11以降の新しいJavadoc APIに対応していない模様。
typescript-generatorの開発者の方がPull Request出したりしているが動きなし。
先々まで使い続けることはできないかもしれないのが難点か。