Roslyn を使って独自のディレクティブを実装する
前日譚
Unity 環境において、.NET 10 の単一ファイル実行で使われる #:
ディレクティブはコンパイルエラーを起こします。(ファイル先頭の #!
は地味に大丈夫)
テストスクリプト自体は Tests~
等の Unity に取り込まれないフォルダーに退避したとしても、Unity プロジェクトは .csproj を全部ルートに書き出すという構造なので参照が面倒という問題が残ります。そもそも Unity の生成した .csproj は VS Code だとビルド出来ないかもしれません。
そして Visual Studio Insiders だと単一ファイル実行の .cs ファイルは IntelliSense が効きません。しかしビルド出来ない(かもしれない)VS Code は効きます。
要するに過渡期で色々と面倒なので、Unity への依存が無いクラスであれば取り込んでしまえばいい! という事で、独自のプリプロセッサーディレクティブ風の構文をソースジェネレーターを使って作りました。
つかいかた
#:package FUnit@*
#:package FUnit.Directives@*
// 👇 Directives を参照すると //:funit: が使えるようになる
//:funit:include ../Runtime/MyClass.cs
return await FUnit.RunAsync(args, describe =>
{
describe("Test", it =>
{
it("should work", () =>
{
// ソースジェネレーターが普通にファイルを読み込んで生成した体で出力するので、
// internal メンバーまで見える状態でテストが可能!
Must.BeEqual(310, MyClass.Method());
// ~~~~~~~~~~~~~~ MyClass.cs からインクルードした型とメソッド
})
});
});
include
ディレクティブ
//:funit:include <対象ファイル.cs> ※必ず行頭に置く
引数(?)を .cs ファイルからの相対パスとして扱い、単純に System.IO.File
で読み込んでそのままソースジェネレーターから「生成したソースコード」として出力します。
プロジェクトが循環参照になっちゃうとか、そもそも参照していないとか、その他あらゆる垣根を飛び越えて指定したファイルをサクッと取り込むことが出来ます。
注意点
Roslyn には RS1035
というエラー診断が存在しますが無かった事にしてビルドしています。
ディレクティブ実装にあたって、IIncrementalGenerator
はコード削除の検出が難しい都合からISourceGenerator
として実装していて、「単一ファイル実行に限れば」一度しか実行されないので問題にはならなと思います。
IIncrementalGenerator
限定のエラーっぽい。つまり C# でも include は合法!
それ以外にも、構文チェックやファイルの存在確認はしていますが、読み込むファイルが適切かどうかは一切確認していないので、自分自身を読み込むと全てが曖昧になります。(同一ファイルの複数回の include は問題なし)
👇 元のソース(すべてが曖昧に)
👇 生成されたソース(名前の重複等々)
おわりに
#:item
というディレクティブを追加してくれ、という要望が GitHub に上がっていたので、そのうちインクルードに対応する気がします。今のままだとあと一歩たりない! って感じですからね。
公式が対応するまでの繋ぎにどうぞ。
以上です。お疲れ様でした。
Discussion