0
1

TypescriptでMermaidシーケンス図を作るプログラムを作ってみたかった

Last updated at Posted at 2024-08-04

前置きと挫折

Javaへのマイグレーション業務にてシーケンス図やクラス図などのUMLを作るかの検討を行なっており、ソースなどからUML起こすことを自動化できないか、を考えたのがきっかけ。

VSCodeの拡張機能などでないか探したが、なさそうだったので自分で作るか、といったん決意。

ただ、調べていくと、Javaのソースコードの構文解析を一から作成するのは無理そうだったので、せめてファイル設定値に従ってMermaid記法のシーケンス図を作るプログラムを作るか...ということで作業開始。

将来的にVSCodeの拡張機能化したいと考えていたため、Typescriptを選んだ。

Mermaidとは

上記にリンク添付、端的に説明すると、
Markdownテキストでクラス図やシーケンス図グラフを作成できるダイアグラムツール。
使えると便利。

Repository

概要

以下のようなYamlファイルから

classes : 
  - name: ServiceImpl.java
    alias : service
  - name : Repository.java
    alias : repository
  - name : Test.java
    alias : test
flows : 
  - type : arrow
    from : service
    to : repository
    action : findByTestModel
    description : find records by TestModel <br>from repository
    returnmsg : return records
    args : 
      - type : TestModel
        name : testModel
      - type : TestModel
        name : testModel2
  - type : arrow
    from : repository
    to : test
    action : testMethod
    description : test説明です
    returnmsg : 'return test'
    args : 
      - type : TestModel
        name : testModel
  - type : arrow
    from : test
    to : test2
    action : test2
    returnmsg : 'return test2'
    args : 
      - type : TestModel
        name : testModel
  - type : arrow
    from : test
    to : test
    action : testRecursive
    args : 
      - type : TestModel
        name : testModel

Mermaid記法のシーケンス図を作成するプログラムである。

説明

シーケンス図の用語などは説明しません。

classes

classes:
  - name: ServiceImpl.java
    alias: service
  - name: Repository.java
    alias: repository
  - name: Test.java
    alias: test

ライフライン用の設定。

flow

typeを定義しているが、現状同期処理しか表現できない。
from,toが矢印の元、先。
actionがメソッド名、descriptionが矢印の下に表示される説明、
returnmsgが定義されていれば、戻り矢印に文言が表示される。

戻りの処理は、from,toのオブジェクト名をスタックに保存しているため、
このyamlファイルでは、同期処理に限って言えば戻り用のfrom,toを記載する必要はなく、
階層が上がれば自動的に戻り処理のMermaid文字列を組み立てるようになっている。

flows : 
  - type : arrow
    from : service
    to : repository
    action : findByTestModel
    description : find records by TestModel <br>from repository
    returnmsg : return records
    args : 
      - type : TestModel
        name : testModel
      - type : TestModel
        name : testModel2
  - type : arrow
    from : repository
    to : test
    action : testMethod
    description : test説明です
    returnmsg : 'return test'
    args : 
      - type : TestModel
        name : testModel
  - type : arrow
    from : test
    to : test
    action : testRecursive
    args :
      - type : TestModel
        name : testModel

実装

インターフェース

export const enum flowType {
  Arrow = 'arrow'
}

export interface Arg {
  type: string;
  name: string;
}

export interface ArrowFlow {
  type: string;
  from: string;
  to: string;
  action: string;
  description: string;
  returnmsg: string;
  args: Arg[];
}

実装中身

import {ArrowFlow} from "../model/YamlDataTypeModel";

/**
 * type = arrow の時の文字列組み立て関数
 * build string function when type is arrow
 * @param flow yaml
 * @param fromStack stack for from data
 * @param toStack stack for to data
 * @param returnMsgStack stack for return message
 */
export function arrowStrBuild(
  flow: ArrowFlow,
  fromStack: string[],
  toStack: string[],
  returnMsgStack: string[]
): string {
  console.log(flow);
  const fromName = flow.from;
  const toName = flow.to;

  let buildStr = "";
  const action = flow.action;
  const description = flow.description || "";
  const rerunDescription = flow.returnmsg || "";
  const args = flow.args
    ? "<br>(" +
      flow.args.map((arg: any) => `${arg.name} : ${arg.type}`).join(", <br>") +
      ")"
    : "";

  let arrowOperator = "";

  const existIndex = fromStack.lastIndexOf(fromName);
  if (existIndex > -1) {
    // 階層が上がっているか確認、上がっていれば上昇分だけ戻り矢印の処理を組み立てる。
    let count = fromStack.length - existIndex;
    buildStr += buildReturnArrowStr(count, fromStack, toStack, returnMsgStack);
  } 
  if (fromName !== toName) {
    // 内部処理でないか確認、内部処理でなければStackに追加する。
    fromStack.push(fromName);
    toStack.push(toName);
    returnMsgStack.push(rerunDescription);
    arrowOperator = "+";
  }

  buildStr += `${fromName} ->>${arrowOperator} ${toName}: ${action} ${args}\n`;
  if (description) {
    buildStr += `Note over ${fromName},${toName}: ${description}\n`;
  }

  return buildStr;
}

/**
 * 戻り同期処理のシーケンス組み立て用関数
 * @param count
 * @param fromStack
 * @param toStack
 * @param returnMsgStack
 */
export function buildReturnArrowStr(
  count: number,
  fromStack: string[],
  toStack: string[],
  returnMsgStack: string[]
) {
  let str = "";
  for (let i = 0; i < count; i++) {
    str += `${toStack.pop()} -->>- ${fromStack.pop()}: ${returnMsgStack.pop()}\n`;
  }
  return str;
}

課題、反省

  • Type = Arrow、つまり同期処理のライフラインしか現状表現できないので、Loopや分岐、戻りを必要としない非同期処理などの実装が必要
  • 入力形式としてYamlを選んだが、CSVなどの方が、汎用性があって良いかもしれない
  • Mermaid記法のチートシート確認しながら自分でマークダウン書いた方が早いかもしれない
  • PlantUMLなど、そもそもシーケンス図自動生成ツールは探せばありそう(、と作り始めてから思った)

最後に

冒頭にも書いた通り、本当はJavaコードを解析してシーケンス図を自動で作る、というプログラムを作りたい。
Typescriptのアウトプットの勉強になるか、ということでいったん作成して公開したが、
本来であれば、
Javaコードを解析する→解析結果をYAML形式などのインプットにする→インプットをもとにシーケンス図作成!を実現したかった。

ちょっと調べて難しそうだったので保留したが、構文解析のやり方が理解できるような書籍なりサイトがあれば教えてもらえるとありがたい。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1