[VSCode拡張機能] チーム開発向けのTODO管理ツールを作った ~ Todo List for Teams ~
ソースコードにTODO:
や FIXME:
のようなプリフィックスをつけてコメントを書いて、それを Todo Tree や Todo+ といった拡張機能で管理している人はいると思います。そんな人はチーム開発していてTODOリストを見たときに、
他の人が書いたTODO邪魔だなあ。非表示にできればなあ。
と思ったことはないでしょうか?ないわけ無いですよね?ありますよね?あるんです。
今回はこの問題を解決する拡張機能を作りました。Todo List for Teams
です。
Todo List for Teams
機能は単純で、よくあるTODOリストに無視する用のIGNORE LIST
を設けました。無視したいTODOコメントを右クリックすればIGNORE LIST
に加えることができます。
「自分が書いたTODOコメントだけみたい」というのがモチベーションだったので、本当はコミットユーザーごとにフィルタリングする機能などをつけたかったのですが、自分のTODOコメントを他の人が移動した場合、あるいはその逆でもコミットするとユーザーが変わってしまってかなり処理が複雑になります。そこでとりあえず「無視リスト」を設けるというシンプルな形にしました。
また、IGNORE LIST
はワークスペースごとに保存するようにしているので、再度開いたときでもIGNORE LIST
に入れたTODOコメントを保持してくれます。
TODOコメントの抽出の仕方
一番苦労した部分について解説します。
基本的にgrep
やgit grep
を使ってTODOコメントを抽出しています。Node.jsのchild_process
モジュールを使ってTypeScript(実行時はJavaScript)からShellコマンドを呼び出しています。
現在は現在のブランチ上にあるTODOコメントを「コミット済みファイルのTODOコメント」+「未追跡ファイルのTODOコメント」に分けて抽出しています。
なお、今はただTODO:
とかFIXME:
とかをgrepしているだけなのでそのコードがコメントであるかは判断していないです(Todo Treeとかはコメントのみ抽出する仕様だったはずです)。
コミット済みファイルの場合
Copilotによる説明
このコードは、指定されたワークスペースディレクトリに移動し、git grep
を使用して特定の検索語を含む行を追跡中のファイルから検索します。その後、各一致行に対してgit blame
を実行して、その行を最後に変更したコミットとその作者を特定します。
詳細に分解してみましょう:
-
bash -c
:bashシェルを起動し、引数として渡されたコマンドを実行します。(筆者の補足:child_processは環境によってShellが異なる可能性があるのでbashで実行させるようにしました) -
cd "${this.workspaceRoot}"
:指定されたワークスペースディレクトリに移動します。 -
git grep -n -E \"${searchWord.source}"
:git grep
を使用して、正規表現(-E
オプション)として指定された検索語を含む行を追跡中のファイルから検索します。-n
オプションは、一致する行の行番号を出力します。 -
while IFS=: read -r i j _; do ... done
:git grep
からの出力を読み取り、コロン(:
)で区切られた各行を解析します。最初のフィールド(ファイルパス)はi
に、2番目のフィールド(行番号)はj
に格納されます。 -
/bin/echo "filePath $i"
:一致したファイルのパスを出力します。 -
git blame -L "$j","$j" "$i" --porcelain
:一致した行に対してgit blame
を実行します。-L
オプションは、特定の行範囲(この場合は一致した行だけ)に対してblame
を実行します。--porcelain
オプションは、出力を機械可読な形式にします。 -
/bin/echo ""
:一致の終わりを示すために空行を出力します。 - 最後に、
execSync
はこのコマンドを同期的に実行し、その出力を文字列として返します。
未追跡ファイルの場合
Copilotによる説明
このコードは、指定されたワークスペースディレクトリに移動し、git ls-files --others --exclude-standard
を使用して未追跡のファイルをリストアップします。その後、各ファイルに対してgrep
を実行して、特定の検索語を含む行を検索します。
詳細に分解してみましょう:
-
cd ${this.workspaceRoot}
:指定されたワークスペースディレクトリに移動します。 -
IFS=' '
:IFS(Internal Field Separator)を改行に設定します。これにより、for
ループは改行で区切られた各行を個別の項目として扱います。 -
files=$(git ls-files --others --exclude-standard)
:git ls-files --others --exclude-standard
を使用して未追跡のファイルをリストアップし、その結果をfiles
変数に格納します。--others
オプションは、未追跡のファイルを表示します。--exclude-standard
オプションは、.gitignore
、.git/info/exclude
、core.excludesFile
で指定されたパターンに一致するファイルを除外します。 -
for file in $files; do ... done
:files
変数に格納された各ファイルに対してループを実行します。 -
grep --with-filename -n -E ${searchWordShell.source} "$file"
:grep
を使用して、正規表現(-E
オプション)として指定された検索語を含む行を検索します。--with-filename
オプションは、一致する行の前にファイル名を表示します。-n
オプションは、一致する行の行番号を出力します。 -
|| [ $? -eq 1 ]
:grep
がエラーコード1(一致する行がない)を返した場合には、エラーを無視します。これにより、一致する行がないファイルがあってもスクリプト全体が失敗することはありません。 - 最後に、
execSync
はこのコマンドを同期的に実行し、その出力を文字列として返します。
ちなみにgit grep --untracked
で未追跡ファイルを含めて一括で抽出することもできるのですが、未追跡ファイルではgit blame
が使えないので、Shell上で条件分岐など色々する必要があり、それだったらとTypeScript上で2つに分けることにしました。
他にもNPMモジュールの simple-gitとか使おうとしたんですが、git blame
がなかったので諦めました、、、
Shell部分は結構簡単になるかなと思ったらそこそこのロジックになって、今まで知らなかったコマンドやオプションやら調べる羽目になりましたね。バグもここが一番出やすかったです。
ここらへんはもっといい方法がありそうです。VSCodeとかどうやっているんでしょうね。気が向いたらソース読んでみます。
VSCodeを実際に起動して行う結合テスト
私は今まで2つVSCode拡張機能を作ってきました(Multi Line Uncomment, Format Test Each)が、テストコードは書いてきませんでした。今回はShell部分や初回リリース時にわけのわからないバグが出たりと、これはちゃんとテストしたほうがいいなと感じたので初めてテストコードを書いてみました。
また、実際にコミットしたときときやShellコマンドがVSCode上でしっかり動くかどうかを検証したかったので(単純に興味もあったので)、実際にVSCode上でテストする結合テストを行いました。
今回特筆すべきポイントは以下2点です。
- Mocha -> Jest への移行
- TreeViewデータの取得
Mocha -> Jest への移行
VSCode公式が提供している拡張機能開発のテンプレート生成 ではTesting LibraryにMochaが使われています。しかし今回 afterEach
のようなメソッドでテスト毎に環境をリセットしたかったのですが、
ReferenceError: afterEach is not defined
というようにエラーが出てしまい、同じようなIssueを探しましたが見つからず、自力解決が難しそうでした。
このエラー関連で探していたところに見つけたのがこのIssueです。
このIssueはJestへの置き換えをする試みで、ここにあるコメントから下のリポジトリのようにJest用のテンプレートを作ってくださった方がおり、大変参考になりました。JestではafterEach
のメソッドを使うことができたので満足です。
TreeViewデータの取得
TODOリストは左サイドバーにTreeViewとして表示していますが、TODOリストデータを取得したあとに本当にTreeViewに表示されているかどうかをテストする必要がありました。
あまり情報がなかったので我流ですが、再帰関数を使ってTreeのデータを取得することができました(昔TypeScript Compiler APIでASTを色々操作していた経験が生きました)。
getElements
関数を定義して、455行目で再度getElements
を呼び出すことで再帰的にTreeアイテムを取得できます。
もしかしたらVSCodeモジュールにいい感じのメソッドがあるかもしれませんが、これでTreeViewを実装した部分のコードを疑いながらテストすることができます。
GitHub ActionsでのCI
ちなみに、結合テストはGitHub Actionsでも動かしています。
公式ドキュメントが用意されておりコピペで動かせました。ぜひ導入してみてください。
まとめ
チーム開発向けのTODOリストを作ってみました。ぜひ使ってみてください(あと頑張ったのでGitHubのスターください)。要望があればぜひコメントやIssueへどうぞ。
また、今回初めてVSCode拡張機能で真面目にテストしてみました。やはりテストコードがないと手戻りとかデグレとか多くなりがちですね。拡張機能開発する際は実際にVSCodeを起動して行うテストを導入してみてはいかがでしょうか。
Discussion