🕵️

Dart のドキュメント内のコードを静的解析するツールを開発しました。

2024/11/22に公開

takumma です。

Dart のドキュメント内のコードを静的解析する「dartdoc_test」というツールを開発したので、その紹介をしようと思います!

dartdoc_test

dartdoc_test はドキュメンテーションコメント(///)内のコードブロックに書かれたコードサンプルを静的解析できるパッケージです。

https://pub.dev/packages/dartdoc_test

例えば以下のようなコードサンプルから、エラーを検出することができます。

/// Example:
/// ```dart
/// final result = add(2, 3) // <- 末尾にセミコロン `;` が無い
/// print(result); // 5
/// ```
int add(int a, int b) => a + b;

背景

Dart は Flutter などで利用されている言語で、ドキュメンテーションコメント上での Markdown 記法をサポートしており、コードブロック内にコードサンプルを書くことができます。人間はサンプルがあると理解しやすいため、コメントにコードサンプルを書くことはパッケージのユーザや他の開発者(もちろん未来の自分も)に使い方をわかりやすく伝えるのにとても良い方法です。

https://dart.dev/effective-dart/documentation#consider-including-code-samples-in-doc-comments

また、Dart や Flutter では dart doc コマンドを用いて(内部的には dartdoc というパッケージを利用して)API ドキュメントを生成することができますが、この API ドキュメントはコード内のドキュメンテーションコメントを元に生成されています。実際に dart:core の List クラスの API ドキュメントとソースコードを見てみると、ソースコードに書かれたものがそのまま API ドキュメントになっていることがわかります。

dart:core の List の API ドキュメント

https://api.flutter.dev/flutter/dart-core/List-class.html

List のソースコード

https://github.com/dart-lang/sdk/blob/main/sdk/lib/core/list.dart#L12-L44

Dart, Flutter やそのパッケージには API ドキュメントが豊富に用意されていることが多いですが、ドキュメントの中にはサンプルも一緒に示されている場合が多く、それらはドキュメンテーションコメント内に書かれています。また、エディタ上で Quick Documentation としても表示されるため、日々多くの開発者がコメント内に書かれたドキュメントを参照しています。

Quick Documentation

**しかし、コメント内のコードスニペットは静的解析やテストが行われないため、コードにエラーとなるようなバグが含まれていても、検出が難しい(できない)という課題があります。**特にコードベースが大規模になっていくと、それに伴ってドキュメント(コードサンプル)の量も多くなり、メンテナンスも難しくなってしまいます。

そこで、dartdoc_test ではコメント内のコードサンプルを静的解析することで、コードサンプル内のエラーを見つけ、メンテナンス性を高める方法を提供しています。

使い方

dartdoc_test は以下のコマンドで簡単に試すことができます!

dart pub add dev:dartdoc_test
dart run dartdoc_test

dartdoc_test パッケージを dev-dependencies に追加し、CLI から実行するとプロジェクト内のすべてのコードサンプルが extract され、コードサンプルに対して静的解析が行われます。例えば、コードサンプルにコンマをつけ忘れてしまっても、dartdoc_test ですぐにそれに気づくことができます。

/// ```dart
/// final result = add(2, 3)
/// print(result); // 5
/// ```
int add(int a, int b) {
  return a + b;
}

実行結果:

> dart run dartdoc_test
Extracting code samples ...
Analyzing code samples ...
package:example/example.dart:20:28: Expected to find ';'.
   ╷
20 │ /// final result = add(2, 3)
   │                            ^

dart test でコードサンプルの静的解析を実行する。

また、dartdoc_test 用のテストファイルを追加することで、コードサンプルの静的解析を dart test コマンドの実行時に行うこともできます。

dartdoc_test 用のテストファイルは以下のコマンドで生成できます。

dart run dartdoc_test add

コマンドを実行すると、以下のようなテストファイルが追加されます。解析したくないファイルがあれば exclude オプションから指定することもできます。

import 'package:dartdoc_test/dartdoc_test.dart';

/// Test code samples in documentation comments in this package.
///
/// It extracts code samples from documentation comments in this package and
/// analyzes them. If there are any errors in the code samples, the test will fail
/// and you can see the problems details.
///
/// If you want to test only specific files, you can use [exclude] option.
void main() => runDartdocTest();

他の言語での例

同様な取り組みは他の言語でも行われています。最もわかりやすい例は Rust です。Rust では、Documentation Test をサポートしており、コメントにテストを記述することで他のテストコードと一緒にテストを実行することができます。

https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html

ほかにも、Go 言語ではドキュメント内のコードは別のファイルにサンプルコードを記述してそれをドキュメントに埋め込む形になるので、そのままサンプルコードを個別に静的解析やテストを行うことができます。

https://go.dev/blog/examples

また、dartdoc_test と同様に、公式では提供されていないものの、個人が開発した解析・テストを行うことのできるパッケージがある言語もいくつか存在しています。

JavaScript (JSDoc/vitest)

https://zenn.dev/sterashima78/articles/957e093a566f3b

Ruby: yard-doctest

https://github.com/p0deje/yard-doctest

Google Summer of Code

すこし話は変わりますが、dartdoc_test は Google Summer of Code での活動を通して開発しました。

Google Summer of Code (GSoC) とは Google が主催する学生や社会人のオープンソース活動を支援するプロジェクトです。毎年 Dart をはじめ様々なオープンソースプロジェクトが参加しています。

https://summerofcode.withgoogle.com/

活動の中ではメンターの Jonas さん、Chinmoy さんと毎週コミュニケーションをとりながらさまざまなフィードバックを貰いつつ開発を行いました。GSoC に採択されるまでの話は以下で詳しく書いているので、興味のある方は是非見てください。近いうちに参加記もまとめられたらと思っています。

https://takumma.hatenablog.com/entry/2024/05/06/113413

最近 Dart の Google Summer of Code 2024 Results が出ました。僕のもの以外もとても面白いプロジェクトばかりなのでぜひ見てください。

https://medium.com/dartlang/google-summer-of-code-2024-results-ae925357d2d7

今後の展望

dartdoc_test はまだまだ不十分で実装できてない部分がたくさんあり、解決できていないバグやフィードバックも既にたくさんあるので、(最近までしばらく手を休めていたのですが、、)細々と開発も継続していこうと思っています。ひとまずの目標は Dart と Flutter(Widget)の基本的なドキュメントを解析できるくらいまでブラッシュアップできたらと考えています!

Dart を普段書いている人はぜひ一度使ってみてください!(pub.dev の like 👍 や GitHub Issues などを通してフィードバックをくれるとより嬉しいです。

https://pub.dev/packages/dartdoc_test

https://github.com/google/dart-neats/issues

Discussion