Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unity開発でのミスを未然に防ぐRoslynアナライザーのすゝめ【DeNA TechCon 2022】

DeNA_Tech
March 17, 2022

Unity開発でのミスを未然に防ぐRoslynアナライザーのすゝめ【DeNA TechCon 2022】

コードをビルドすることなく、ミスを検知できたらゲーム開発が楽になると思いませんか?

.NETの静的解析器であるRoslynアナライザーを使うことで、ビルドをすることなく問題のあるコードを検出してくれるようになります。

また、Roslynアナライザーは各々のエンジニアがカスタムルールを実装できます。例えば弊社では、実行時にリフレクションでインスタンス化されるクラスのコンストラクタが、IL2CPPビルド時に未使用コードと判別され削除されないように、Roslynアナライザーを実装し、導入することでリリースブロッカーとなりえる問題を早期発見できるようにしています。

そんな便利なRoslynアナライザーですが、 情報が少なくハードルが高い印象を持っている人もいるのではないでしょうか。

本セッションではRoslynアナライザーの導入と、カスタムルール実装のハードルを下げるための情報を共有します。

資料内でのリンク集:
p153,154, https://github.com/DeNA/Dena.CodeAnalysis.Testing
p160,161,162,163,164, https://github.com/DeNA/Dena.CodeAnalysis.Testing
p183-1, https://swet.dena.com/entry/2021/05/25/100000
p183-2, https://www.nowsprinting.com/entry/2021/11/01/083258
p183-3, https://www.nowsprinting.com/entry/2021/04/18/200619
p183-4, https://github.com/dotnet/roslyn-analyzers/tree/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers
p183-5, https://github.com/code-cracker/code-cracker
p183-6, https://github.com/Cysharp/UniTask

◆ You Tube
https://youtu.be/g93LBwvOzxA

◆ You Tube チャンネル登録はこちら↓
https://youtube.com/c/denatech?sub_confirmation=1

◆ Twitter
https://twitter.com/DeNAxTech

◆ DeNA Engineering
https://engineering.dena.com/

◆ DeNA Engineer Blog
https://engineering.dena.com/blog/

◆ DeNA TechCon 2022 公式サイト
https://techcon2022.dena.dev/spring/

DeNA_Tech

March 17, 2022
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 - Roslynアナライザーの概要 - Roslynアナライザーができること、使い方 - カスタムルールの作り方

    - カスタムアナライザーをテストする方法 - 作成したアナライザーをUPMパッケージに内包して配布、利用する NOTE: バージョンによってバグによる制約があるため、 このスライドではUnity 2021.2 + Rider Editor package v3.0.9を基準に書いています。 目次
  2. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 - Roslynアナライザーの概要 - Roslynアナライザーができること、使い方 - カスタムルールの作り方

    - カスタムアナライザーをテストする方法 - 作成したアナライザーをUPMパッケージに内包して配布、利用する NOTE: バージョンによってバグによる制約があるため、 このスライドではUnity 2021.2 + Rider Editor package v3.0.9を基準に書いています。 目次
  3. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 コンパイラ オレオレルール - 型違反 - 未定義関数

    - 未定義変数 - etc... - コーディング規約 - 非推奨のAPIの使用 - etc... 一般的なルール
  4. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 コンパイラ オレオレルール - 型違反 - 未定義関数

    - 未定義変数 - etc... - コーディング規約 - 非推奨のAPIの使用 - etc... 一般的なルール
  5. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 - Roslynアナライザーの概要 - Roslynアナライザーができること、使い方 - カスタムルールの作り方

    - カスタムアナライザーをテストする方法 - 作成したアナライザーをUPMパッケージに内包して配布、利用する NOTE: バージョンによってバグによる制約があるため、 このスライドではUnity 2021.2 + Rider Editor package v3.0.9を基準に書いています。 目次
  6. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 様々な診断ルールを持つアナライザーの紹介 - Microsoft.CodeAnalysis.BannedApiAnalyzer - 汎用的に特定のAPIを使用禁止にできるアナライザー -

    例: UniTaskでなくTaskを使用していたら怒るルール - codecracker.CSharp - dispose漏れを怒るルール(CS0022) - DeNA内で作成したアナライザー - Preserve属性のつけ忘れを怒るルール
  7. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 様々な診断ルールを持つアナライザーの紹介 - Microsoft.CodeAnalysis.BannedApiAnalyzer - 汎用的に特定のAPIを使用禁止にできるアナライザー -

    例: UniTaskでなくTaskを使用していたら怒るルール - codecracker.CSharp - dispose漏れを怒るルール(CS0022) - DeNA内で作成したアナライザー - Preserveのつけ忘れを怒るルール
  8. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); } Taskを使用した例 UniTaskを使用した例 public async UniTask M() { await UniTask.Delay(100); }
  9. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); } Taskを使用した例 UniTaskを使用した例 UniTaskでなく、Taskを使っている public async UniTask M() { await UniTask.Delay(100); }
  10. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); } Taskを使用した例 UniTaskを使用した例 Do not used banned APIs public async UniTask M() { await UniTask.Delay(100); }
  11. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 様々な診断ルールを持つアナライザーの紹介 - Microsoft.CodeAnalysis.BannedApiAnalyzer - 汎用的に特定のAPIを使用禁止にできるアナライザー -

    例: UniTaskでなくTaskを使用していたら怒るルール - codecracker.CSharp - dispose漏れを怒るルール(CS0022) - DeNA内で作成したアナライザー - Preserveのつけ忘れを怒るルール
  12. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public class MustDisposeClass : IDisposable {

    } void M() { var hoge = new MustDisposeClass(); } using (var hoge = new MustDisposeClass()) { } void M() { var hoge = new MustDisposeClass(); hoge.Dispose(); }
  13. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public class MustDisposeClass : IDisposable {

    } void M() { var hoge = new MustDisposeClass(); } using (var hoge = new MustDisposeClass()) { } void M() { var hoge = new MustDisposeClass(); hoge.Dispose(); }
  14. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 様々な診断ルールを持つアナライザーの紹介 - Microsoft.CodeAnalysis.BannedApiAnalyzer - 汎用的に特定のAPIを使用禁止にできるアナライザー -

    例: UniTaskでなくTaskを使用していたら怒るルール - codecracker.CSharp - dispose漏れを怒るルール(CS0022) - DeNA内で作成したアナライザー - Preserve属性のつけ忘れを怒るルール
  15. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public class GoodService : IGoodService {

    [Preserve] public GoodService() { } } public class BadServiceNoInject : IBadServiceNoInject { public BadServiceNoInject() { } } var services = new ServiceCollection(); services.AddSingleton<IGoodService, GoodService>(); services.AddSingleton<IBadServiceNoInject, BadServiceNoInject>(); DIコンテナへの登録
  16. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Preserve属性あり Preserve属性なし public class GoodService :

    IGoodService { [Preserve] public GoodService() { } } public class BadServiceNoInject : IBadServiceNoInject { public BadServiceNoInject() { } } var services = new ServiceCollection(); services.AddSingleton<IGoodService, GoodService>(); services.AddSingleton<IBadServiceNoInject, BadServiceNoInject>(); DIコンテナへの登録
  17. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Preserve属性あり Preserve属性なし public class GoodService :

    IGoodService { [Preserve] public GoodService() { } } public class BadServiceNoInject : IBadServiceNoInject { public BadServiceNoInject() { } } var services = new ServiceCollection(); services.AddSingleton<IGoodService, GoodService>(); services.AddSingleton<IBadServiceNoInject, BadServiceNoInject>(); DIコンテナへの登録
  18. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 var services = new ServiceCollection(); services.AddSingleton<IGoodService,

    GoodService>(); services.AddSingleton<IBadServiceNoInject, BadServiceNoInject>(); DIコンテナへの登録 Preserve属性あり Preserve属性なし public class GoodService : IGoodService { [Preserve] public GoodService() { } } public class BadServiceNoInject : IBadServiceNoInject { public BadServiceNoInject() { } } IL2CPPでビルドした時に最適化によって、直接使用されない且つ Preserve 属性の無いコンストラクタを持つクラスはストリップされる。
  19. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 アナライザなし アナライザあり ビルドが失敗する ミスが早く見つかる public class

    BadServiceNoInject : IBadServiceNoInject { public BadServiceNoInject () { } } クラッシュする public class BadServiceNoInject : IBadServiceNoInject { public BadServiceNoInject () { } } ビルドが成功する 端末等で操作
  20. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assets/Hoge/ ├── Editor │ └── YourLibrary.Editor.asmdef

    ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef
  21. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assets/Hoge/ ├── Editor │ └── YourLibrary.Editor.asmdef

    ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets/Hoge/ ├── Analyzers │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets下の任意のディレクトリに アナライザーのdllを配置する
  22. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assets/Hoge/ ├── Editor │ └── YourLibrary.Editor.asmdef

    ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets/Hoge/ ├── Analyzers │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets下の任意のディレクトリに アナライザーのdllを配置する
  23. Unity上での設定の仕方 1. アナライザーのDLLをProjectウィンドウで選択(インス ペクタが開く) 2. Select Platforms for plugin下のチェックボックスをすべ てoff

    3. Applyボタンをクリック 4. 栞アイコンをクリック 5. ウィンドウに”RoslynAnalyzer”と入力し、Enterを押す
  24. Unity上での設定の仕方 1. アナライザーのDLLをProjectウィンドウで選択(インス ペクタが開く) 2. Select Platforms for plugin下のチェックボックスをすべ てoff

    3. Applyボタンをクリック 4. 栞アイコンをクリック 5. ウィンドウに”RoslynAnalyzer”と入力し、Enterを押す
  25. Unity上での設定の仕方 1. アナライザーのDLLをProjectウィンドウで選択(インス ペクタが開く) 2. Select Platforms for plugin下のチェックボックスをすべ てoff

    3. Applyボタンをクリック 4. 栞アイコンをクリック 5. ウィンドウに”RoslynAnalyzer”と入力し、Enterを押す
  26. Unity上での設定の仕方 1. アナライザーのDLLをProjectウィンドウで選択(インス ペクタが開く) 2. Select Platforms for plugin下のチェックボックスをすべ てoff

    3. Applyボタンをクリック 4. 栞アイコンをクリック 5. ウィンドウに”RoslynAnalyzer”と入力し、Enterを押す
  27. Unity上での設定の仕方 1. アナライザーのDLLをProjectウィンドウで選択(インス ペクタが開く) 2. Select Platforms for plugin下のチェックボックスをすべ てoff

    3. Applyボタンをクリック 4. 栞アイコンをクリック 5. ウィンドウに”RoslynAnalyzer”と入力し、Enterを押す
  28. Unity上での設定の仕方 1. アナライザーのDLLをProjectウィンドウで選択(インス ペクタが開く) 2. Select Platforms for plugin下のチェックボックスをすべ てoff

    3. Applyボタンをクリック 4. 栞アイコンをクリック 5. ウィンドウに”RoslynAnalyzer”と入力し、Enterを押す
  29. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 アナライザーが動作する範囲の設定をする Assets/Hoge/ ├── Analyzers │ ├──

    your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef
  30. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 アナライザーが動作する範囲の設定をする Assets/Hoge/ ├── Analyzers │ ├──

    your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef YourLibrary.Runtimeアセンブリに含まれる C#スクリプト(YourLibraryCode.cs) にアナライザーを適用したい
  31. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 アナライザーが動作する範囲の設定をする Assets/Hoge/ ├── Analyzers │ ├──

    your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets/Hoge/ ├── Analyzers │ ├── Analyzers.asmdef │ ├── dummyFile.cs │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets下のアナライザーDLLが含まれる ディレクトリにasmdefとダミーのC#スクリ プトを配置する
  32. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 アナライザーが動作する範囲の設定をする Assets/Hoge/ ├── Analyzers │ ├──

    your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Assets/Hoge/ ├── Analyzers │ ├── Analyzers.asmdef │ ├── dummyFile.cs │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef Analyzersを別アセンブリから参照する際にはア センブリの生成が必要です。そのため、何かしら のC#スクリプトを置く必要があります。
  33. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 アナライザーが動作する範囲の設定をする Assets/Hoge/ ├── Analyzers │ ├──

    Analyzers.asmdef │ ├── dummyFile.cs │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef 1. アナライザーを適用したい asmdefをinspecterで開く (無い場合はasmdef作成する) 2. Assembly Defintion Reference に アナライザーを含む アセンブリ(Analyzers)を追加 3. applyを押す
  34. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assets/Hoge/ ├── Analyzers │ ├── Analyzers.asmdef

    │ ├── dummyFile.cs │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef アナライザーが動作する範囲の設定をする Assets/your.library/ ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef YourLibrary.Runtimeアセンブリに含まれる C#コード(YourLibraryCode.cs)に アナライザーが適用されます 。
  35. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assets/Hoge/ ├── Analyzers │ ├── your.library.analyzers.asmdef

    │ ├── your.library.analyzers.dll │ └── your.library.analyzers.dll.meta ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef アナライザーが動作する範囲の設定をする Assets/your.library/ ├── Editor │ └── YourLibrary.Editor.asmdef ├── Runtime │ ├── YourLibrary.Runtime.asmdef │ └── YourLibraryCode.cs └── Tests └── YourLibrary.Tests.asmdef YourLibrary.Runtimeアセンブリに含まれる C#コード(YourLibraryCode.cs)に アナライザーが適用されます 。
  36. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 - CS1002: エラー - CS0219: 警告

    CS0219をエラーにした いなぁ... 各アナライザにはデフォルトの重 要度が設定されている
  37. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 ルールセット ファイルによる重要度の設定 Assets/ ├── your.library.analyzers.dll ├──

    your.library.analyzers.dll.meta ├── RethrowError.cs ├── Default.ruleset └── Subfolder ├── Subfoloder.asmdef └── RethrowError.cs Assets直下に Default.rulesetを配置する
  38. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 ルールセット ファイルによる重要度の設定 Assets/ ├── your.library.analyzers.dll ├──

    your.library.analyzers.dll.meta ├── RethrowError.cs ├── Default.ruleset └── Subfolder ├── Subfoloder.asmdef └── RethrowError.cs Assets直下に ”Default.ruleset” という名前で ファイルを配置する <?xml version="1.0" encoding="utf-8"?> <RuleSet Name="New Rule Set" Description=" " ToolsVersion="10.0"> <Rules AnalyzerId="your.library.analyzers" RuleNamespace="your.library.analyzers"> <Rule Id="YourAnalyzersID" Action="Error" /> </Rules> </RuleSet>
  39. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 ルールセット ファイルによる重要度の設定 <?xml version="1.0" encoding="utf-8"?> <RuleSet

    Name="New Rule Set" Description=" " ToolsVersion="10.0"> <Rules AnalyzerId="your.library.analyzers" RuleNamespace="your.library.analyzers"> <Rule Id="YourAnalyzersID" Action="Error" /> </Rules> </RuleSet>
  40. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 ルールセット ファイルによる重要度の設定 <?xml version="1.0" encoding="utf-8"?> <RuleSet

    Name="New Rule Set" Description=" " ToolsVersion="10.0"> <Rules AnalyzerId="your.library.analyzers" RuleNamespace="your.library.analyzers"> <Rule Id="YourAnalyzersID" Action="Error" /> </Rules> </RuleSet> アナライザーのIDを指定 アナライザーの重要度を上書き
  41. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 ルールセット ファイルによる重要度の設定 <?xml version="1.0" encoding="utf-8"?> <RuleSet

    Name="New Rule Set" Description=" " ToolsVersion="10.0"> <Rules AnalyzerId="your.library.analyzers" RuleNamespace="your.library.analyzers"> <Rule Id="YourAnalyzersID" Action="Error" /> </Rules> </RuleSet> アナライザーのIDを指定 アナライザーの重要度を上書き
  42. ファイルパス、拡張子単位の抑制 1. Preferences… | Editor | Inspection Settings を開く 2.

    Elements to Skip で除外するフォルダや拡張子のパターン を追加する 3. Saveボタン右のプルダウンで、 Solution YOUR_PROJECT_NAME team-shared を選択して保存
  43. ファイルパス、拡張子単位の抑制 1. Preferences… | Editor | Inspection Settings を開く 2.

    Elements to Skip で除外するフォルダや拡張子のパターン を追加する 3. Saveボタン右のプルダウンで、 Solution YOUR_PROJECT_NAME team-shared を選択して保存
  44. ファイルパス、拡張子単位の抑制 1. Preferences… | Editor | Inspection Settings を開く 2.

    Elements to Skip で除外するフォルダや拡張子のパターン を追加する 3. Saveボタン右のプルダウンで、 Solution YOUR_PROJECT_NAME team-shared を選択して保存
  45. ファイルパス、拡張子単位の抑制 1. Preferences… | Editor | Inspection Settings を開く 2.

    Elements to Skip で除外するフォルダや拡張子のパターン を追加する 3. Saveボタン右のプルダウンで、 Solution YOUR_PROJECT_NAME team-shared を選択して保存
  46. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); } このメソッド内だけに限り 使うことを許可したいなぁ ...
  47. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 [SuppressMessage("ApiDesign", "RS0030")] public async Task M()

    { await Task.Delay(100); } このメソッド内だけに限り 使うことを許可したいなぁ ... アナライザーのID アナライザーのカテゴリー
  48. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 [SuppressMessage("ApiDesign", "RS0030")] public async Task M()

    { await Task.Delay(100); } 警告を抑制出来る! アナライザーのID アナライザーのカテゴリー
  49. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); await Task.Delay(100); } Do not use Task. Use the UniTask. このステートメントに限り 警告を抑制したい
  50. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); #pragma warning disable RS0030 await Task.Delay(100); #pragma warning restore RS0030 }
  51. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); #pragma warning disable RS0030 await Task.Delay(100); #pragma warning restore RS0030 }
  52. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); #pragma warning disable RS0030 await Task.Delay(100); #pragma warning restore RS0030 }
  53. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public async Task M() { await

    Task.Delay(100); #pragma warning disable RS0030 await Task.Delay(100); #pragma warning restore RS0030 } このステートメントに限り 抑制出来る!
  54. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 目次 - Roslynアナライザーの概要 - Roslynアナライザーができること、使い方 -

    カスタムルールの作り方 - カスタムアナライザをテストする方法 - 作成したアナライザーをUPMパッケージに内包して配布、利用する NOTE: バージョンによってバグによる制約があるため、 このスライドではUnity 2021.2 + Rider Editor package v3.0.9を基準に書いています。
  55. ソリューション・プロジェクトを作成する 1. ソリューションを作成する 2. Analyzerのプロジェクトを作成する 3. プロジェクトをソリューションに追加する 4. 必要なパッケージをインストールする RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.csproj │ └── RoslynToyAnalyzer.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet new sln -o RoslynToyAnalyzer
  56. ソリューション・プロジェクトを作成する 1. ソリューションを作成する 2. Analyzerのプロジェクトを作成する 3. プロジェクトをソリューションに追加する 4. 必要なパッケージをインストールする RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.csproj │ └── RoslynToyAnalyzer.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet new classlib -o RoslynToyAnalyzer
  57. ソリューション・プロジェクトを作成する 1. ソリューションを作成する 2. Analyzerのプロジェクトを作成する 3. プロジェクトをソリューションに追加する 4. 必要なパッケージをインストールする RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.csproj │ └── RoslynToyAnalyzer.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet sln add RoslynToyAnalyzer/RoslynToyAnalyzer.csproj
  58. ソリューション・プロジェクトを作成する 1. ソリューションを作成する 2. Analyzerのプロジェクトを作成する 3. プロジェクトをソリューションに追加する 4. 必要なパッケージをインストールする RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.csproj │ └── RoslynToyAnalyzer.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet add RoslynToyAnalyzer package Microsoft.CodeAnalysis.Analyzers --version 3.3.2 dotnet add RoslynToyAnalyzer package Microsoft.CodeAnalysis.CSharp --version 3.5.0
  59. ソリューション・プロジェクトを作成する 1. ソリューションを作成する 2. Analyzerのプロジェクトを作成する 3. プロジェクトをソリューションに追加する 4. 必要なパッケージをインストールする RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.csproj │ └── RoslynToyAnalyzer.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet add RoslynToyAnalyzer package Microsoft.CodeAnalysis.Analyzers --version 3.3.2 dotnet add RoslynToyAnalyzer package Microsoft.CodeAnalysis.CSharp --version 3.5.0
  60. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 定義を検査する例 public class Foo { }

    NamedTypeはclass、Enum、struct、 Interfaceを含んだシンボルの名前を意味し ます。
  61. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax;

    using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; namespace RoslynToyAnalyzer { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RoslynToyAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor _toyRule = new DiagnosticDescriptor( id: "TOY001", title: "NamedTypeのシンボルに小文字を含む命名を禁止するアナライザー", messageFormat: "NamedTypeのシンボルに小文字を含んだ命名 {0} が使用されています", category: "RoslynToyAnalyzer", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(_toyRule); } } public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); } private static void AnalyzeSymbol(SymbolAnalysisContext context) { var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) { var diagnostic = Diagnostic.Create( _toyRule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); context.ReportDiagnostic(diagnostic); } } }
  62. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax;

    using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; namespace RoslynToyAnalyzer { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RoslynToyAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor _toyRule = new DiagnosticDescriptor( id: "TOY001", title: "NamedTypeのシンボルに小文字を含む命名を禁止するアナライザー", messageFormat: "NamedTypeのシンボルに小文字を含んだ命名 {0} が使用されています", category: "RoslynToyAnalyzer", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(_toyRule); } } public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); } private static void AnalyzeSymbol(SymbolAnalysisContext context) { var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) { var diagnostic = Diagnostic.Create( _toyRule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); context.ReportDiagnostic(diagnostic); } } } アナライザークラス アナライザーの診断ルールの宣言 SupportedDiagnosticsプロパティ(必須プロパティ) Initializeメソッド(必須メソッド) 実際の診断を行うメソッドの実装
  63. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RoslynToyAnalyzer : DiagnosticAnalyzer

    { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { } public override void Initialize(AnalysisContext context) { } } アナライザーのベースとなるクラスを継承 DiagnosticAnalyzer属性をつける SupportedDiagnosticsメソッドの実装が必要(後述) Initializeメソッドの実装が必要(後述) アナライザークラスの作成
  64. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 private static readonly DiagnosticDescriptor _toyRule =

    new DiagnosticDescriptor( id: "TOY001", title: "NamedTypeのシンボルに小文字を含む命名を禁止するアナライザー ", messageFormat: "NamedTypeのシンボルに小文字を含んだ命名 {0} が使用されています ", category: "RoslynToyAnalyzer", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); アナライザーのカテゴリを指定 診断時に出力する警告文のテンプレートを定義 デフォルトの重要度を設定 デフォルトで診断が有効な場合はtrueを返す アナライザーの診断ルールの宣言 アナライザーのIDを指定 診断内容を説明するタイトル
  65. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get

    { return ImmutableArray.Create(_toyRule); } } 作成したアナライザが提供するアナライザーの一覧を配列で返す SupportedDiagnosticsプロパティの実装(必須) DiagnosticAnalyzerを継承したので、SupportedDiagnosticsプロパティの実装が必要
  66. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public override void Initialize(AnalysisContext context) {

    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); } コールバックを受け取りたい契機をAnalysisContextに登録する Initializeメソッドの実装(必須) Roslynコンパイラによってアナライザーがロードされると呼ばれるメソッド DiagnosticAnalyzerを継承したので、Initializeメソッドの実装が必要 シンボルの意味解析ごとに動作させたいメソッド ”AnalyzeSymbol”を、”RegisterSymbolAction”で登録
  67. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 private static void AnalyzeSymbol(SymbolAnalysisContext context) {

    var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) { var diagnostic = Diagnostic.Create( _toyRule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); context.ReportDiagnostic(diagnostic); } } 診断に必要な情報を取得 実際の診断を行うメソッドの実装 診断結果を作成して返す シンボルの名前に小文字を含むか判定
  68. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 呼び出しを検査する例 var hoge = new ArrayList();

    System.Collections.ArrayListをインスタン ス化しています。 System.Collections.Generic.Listを代わりに 使用してください。
  69. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics;

    using System.Collections.Immutable; using Microsoft.CodeAnalysis.Operations; namespace RoslynToyAnalyzer { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RoslynToyAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor _toyRule2 = new DiagnosticDescriptor( id: "TOY002", title: "System.Collections.ArrayListのインスタンス作成を禁止するアナライザー ", messageFormat: "System.Collections.ArrayListをインスタンス化しています。 System.Collections.Generic.Listを代わりに使用してくださ い。", category: "RoslynToyAnalyzer", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(_toyRule2); } } public override void Initialize(AnalysisContext context) { context.RegisterOperationAction(AnalyzeCreationObject, OperationKind.ObjectCreation); } private static void AnalyzeCreationObject(OperationAnalysisContext context) { var operation = (IObjectCreationOperation)context.Operation; var symbol = operation.Type; var symbolFullName = GetSymbolFullName(symbol); if (symbolFullName != "System.Collections.ArrayList") return; var diagnostic = Diagnostic.Create( _toyRule2, operation.Syntax.GetLocation(), symbol.Name); context.ReportDiagnostic(diagnostic); } private static string GetSymbolFullName(INamespaceOrTypeSymbol symbol) { var collectedNamespace = new List<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } } }
  70. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics;

    using System.Collections.Immutable; using Microsoft.CodeAnalysis.Operations; namespace RoslynToyAnalyzer { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RoslynToyAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor _toyRule2 = new DiagnosticDescriptor( id: "TOY002", title: "System.Collections.ArrayListのインスタンス作成を禁止するアナライザー ", messageFormat: "System.Collections.ArrayListをインスタンス化しています。 System.Collections.Generic.Listを代わりに使用してくださ い。", category: "RoslynToyAnalyzer", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(_toyRule2); } } public override void Initialize(AnalysisContext context) { context.RegisterOperationAction(AnalyzeCreationObject, OperationKind.ObjectCreation); } private static void AnalyzeCreationObject(OperationAnalysisContext context) { var operation = (IObjectCreationOperation)context.Operation; var symbol = operation.Type; var symbolFullName = GetSymbolFullName(symbol); if (symbolFullName != "System.Collections.ArrayList") return; var diagnostic = Diagnostic.Create( _toyRule2, operation.Syntax.GetLocation(), symbol.Name); context.ReportDiagnostic(diagnostic); } private static string GetSymbolFullName(INamespaceOrTypeSymbol symbol) { var collectedNamespace = new List<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } } } Initializeメソッド(必須メソッド) 実際の診断を行うメソッド 完全修飾型名の取得
  71. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public override void Initialize(AnalysisContext context) {

    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterOperationAction(AnalyzeCreationObject, OperationKind.ObjectCreation); } コールバックを受け取りたい契機をAnalysisContextに登録する Initializeメソッドの実装(必須) Roslynコンパイラによってアナライザーがロードされると呼ばれるメソッド DiagnosticAnalyzerを継承したので、Initializeメソッドの実装が必要 オブジェクトのインスタンスが作成されるごとに動作させたいメソッド ”AnalyzeCreationObject”を、”RegisterOperationAction”で登録
  72. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public override void Initialize(AnalysisContext context) {

    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterOperationAction(AnalyzeCreationObject, OperationKind.ObjectCreation); } Initializeメソッドの実装(必須) - メソッド呼び出し(Invocation) - 変数初期化(VariableInitializer) - 配列の作成(ArrayCreation) - Etc… See also: https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.codeanalysis.operationkind
  73. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 private static void AnalyzeCreationObject(OperationAnalysisContext context) {

    var operation = (IObjectCreationOperation)context.Operation; var symbol = operation.Type; var symbolFullName = GetSymbolFullName(symbol); if (symbolFullName != "System.Collections.ArrayList") return; var diagnostic = Diagnostic.Create( _toyRule2, operation.Syntax.GetLocation(), symbol.Name); context.ReportDiagnostic(diagnostic); } 診断に必要な情報を取得 実際の診断を行うメソッドの実装 診断結果を作成して返す シンボルの完全修飾型名を取得(後述) シンボルの型を取得
  74. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 private static string GetSymbolFullName(INamespaceOrTypeSymbol symbol) {

    var collectedNamespace = new List<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } シンボルの完全修飾型名を取得するメソッドの実装 .を連結して文字列として返す シンボルの型を再帰的にたどる NOTE: .NETでは完全修飾型名には”.”以外の 文字列が含まれますが、Roslynの既存実装で は”+”を使っていないため、それに合わせて います。
  75. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 目次 - Roslynアナライザーの概要 - Roslynアナライザーができること、使い方 -

    カスタムルールの作り方 - カスタムアナライザをテストする方法 - 作成したアナライザーをUPMパッケージに内包して配布、利用する NOTE: バージョンによってバグによる制約があるため、 このスライドではUnity 2021.2 + Rider Editor package v3.0.9を基準に書いています。
  76. ソリューション・プロジェクトを作成する 1. テストのプロジェクトを作成 2. テストプロジェクトをソリューションに追加 3. テストコードプロジェクトからの参照を追加 4. テストに必要なパッケージをインストール RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.cs │ └── RoslynToyAnalyzer.csproj ├── RoslynToyAnalyzer.Test │ ├── RoslynToyAnalyzer.Test.csproj │ └── RoslynToyAnalyzerTest.cs └── RoslynToyAnalyzers.sln 実行コマンド
  77. ソリューション・プロジェクトを作成する 1. テストのプロジェクトを作成 2. テストプロジェクトをソリューションに追加 3. テストコードプロジェクトからの参照を追加 4. テストに必要なパッケージをインストール RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.cs │ └── RoslynToyAnalyzer.csproj ├── RoslynToyAnalyzer.Test │ ├── RoslynToyAnalyzer.Test.csproj │ └── RoslynToyAnalyzerTest.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet new nunit -o RoslynToyAnalyzer.Test
  78. ソリューション・プロジェクトを作成する 1. テストのプロジェクトを作成 2. テストプロジェクトをソリューションへ追加 3. テストコードプロジェクトからの参照を追加 4. テストに必要なパッケージをインストール RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.cs │ └── RoslynToyAnalyzer.csproj ├── RoslynToyAnalyzer.Test │ ├── RoslynToyAnalyzer.Test.csproj │ └── RoslynToyAnalyzerTest.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet sln RoslynToyAnalyzers.sln add RoslynToyAnalyzer.Test/RoslynToyAnalyzer.Test.csproj
  79. ソリューション・プロジェクトを作成する 1. テストのプロジェクトを作成 2. テストプロジェクトをソリューションへ追加 3. テストコードプロジェクトからの参照を追加 4. テストに必要なパッケージをインストール RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.cs │ └── RoslynToyAnalyzer.csproj ├── RoslynToyAnalyzer.Test │ ├── RoslynToyAnalyzer.Test.csproj │ └── RoslynToyAnalyzerTest.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet add RoslynToyAnalyzer.Test/RoslynToyAnalyzer.Test.csproj reference RoslynToyAnalyzer/RoslynToyAnalyzer.csproj
  80. ソリューション・プロジェクトを作成する 1. テストのプロジェクトを作成 2. テストプロジェクトをソリューションへ追加 3. テストコードプロジェクトからの参照を追加 4. テストに必要なパッケージをインストール RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.cs │ └── RoslynToyAnalyzer.csproj ├── RoslynToyAnalyzer.Test │ ├── RoslynToyAnalyzer.Test.csproj │ └── RoslynToyAnalyzerTest.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.Analyzers --version 3.3.2 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp --version 3.5.0 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp.Workspaces --version 3.5.0 dotnet add RoslynToyAnalyzer.Test package Dena.CodeAnalysis.Testing --version 2.0.0
  81. ソリューション・プロジェクトを作成する 1. テストのプロジェクトを作成 2. テストプロジェクトをソリューションへ追加 3. テストコードプロジェクトからの参照を追加 4. テストに必要なパッケージをインストール RoslynToyAnalyzer

    ├── RoslynToyAnalyzer │ ├── RoslynToyAnalyzer.cs │ └── RoslynToyAnalyzer.csproj ├── RoslynToyAnalyzer.Test │ ├── RoslynToyAnalyzer.Test.csproj │ └── RoslynToyAnalyzerTest.cs └── RoslynToyAnalyzers.sln 実行コマンド dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.Analyzers --version 3.3.2 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp --version 3.5.0 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp.Workspaces --version 3.5.0 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest --version 1.1.0 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest --version 1.1.0 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest --version 1.1.0 dotnet add RoslynToyAnalyzer.Test package Dena.CodeAnalysis.Testing --version 2.0.0 実行コマンド dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.Analyzers --version 3.3.2 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp --version 3.5.0 dotnet add RoslynToyAnalyzer.Test package Microsoft.CodeAnalysis.CSharp.Workspaces --version 3.5.0 dotnet add RoslynToyAnalyzer.Test package Dena.CodeAnalysis.Testing --version 2.0.0
  82. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 using System.Linq; using System.Threading.Tasks; using Dena.CodeAnalysis.CSharp.Testing;

    using Microsoft.CodeAnalysis.Text; using NUnit.Framework; namespace RoslynToyAnalyzer.Test { public class RoslynToyAnalyzerTest {     [Test] public async Task RoslynToyAnalyzer_クラスのシンボル名に小文字を含む_Toy001がレポートされる() { const string testData = @" /// public static class Foo { } "; var analyzer = new RoslynToyAnalyzer(); var actual = await DiagnosticAnalyzerRunner.Run(analyzer, testData); Assert.AreEqual(1, actual.Length); Assert.AreEqual("TOY001", actual.First().Id); Assert.AreEqual("NamedTypeのシンボルに小文字を含んだ命名 Foo が使用されています", actual.First().GetMessage()); LocationAssert.HaveTheSpan( new LinePosition(2, 20), new LinePosition(2, 23), actual.First().Location); } } }
  83. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 using System.Linq; using System.Threading.Tasks; using Dena.CodeAnalysis.CSharp.Testing;

    using Microsoft.CodeAnalysis.Text; using NUnit.Framework; namespace RoslynToyAnalyzer.Test { public class RoslynToyAnalyzerTest {     [Test] public async Task RoslynToyAnalyzer_クラスのシンボル名に小文字を含む_Toy001がレポートされる() { const string testData = @" /// public static class Foo { } "; var analyzer = new RoslynToyAnalyzer(); var actual = await DiagnosticAnalyzerRunner.Run(analyzer, testData); Assert.AreEqual(1, actual.Length); Assert.AreEqual("TOY001", actual.First().Id); Assert.AreEqual("NamedTypeのシンボルに小文字を含んだ命名 Foo が使用されています", actual.First().GetMessage()); LocationAssert.HaveTheSpan( new LinePosition(2, 20), new LinePosition(2, 23), actual.First().Location); } } } テストメソッドの準備 テストデータの準備 テストの実行 期待値と実測値の比較
  84. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public class RoslynToyAnalyzerTest {     [Test] public

    async Task RoslynToyAnalyzer_クラスのシンボル名に小文字を含む _Toy001がレポートされる() { // ここにテストを書いていく } } テストメソッドの準備 テストメソッドに[Test]属性をつける
  85. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 const string testData = @" ///

    public static class Foo { } "; テストデータの準備 アナライザーの診断対象として渡すC#のコードを記述
  86. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 var analyzer = new RoslynToyAnalyzer(); var

    actual = await DiagnosticAnalyzerRunner.Run(analyzer, testData); テストの実行 テスト対象のアナライザーのインスタンスを作成 アナライザーのインスタンスとテストデータを渡し、実測値を取得 (DeNAがOSSとして公開しているパッケージです) See also: https://github.com/DeNA/Dena.CodeAnalysis.Testing
  87. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assert.AreEqual(1, actual.Length); Assert.AreEqual("TOY001", actual.First().Id); Assert.AreEqual("NamedTypeのシンボルに小文字を含んだ命名 Foo

    が使用されています ", actual.First().GetMessage()); LocationAssert.HaveTheSpan( new LinePosition(2, 20), // 期待する始点 new LinePosition(2, 23), // 期待する終点 actual.First().Location); 実測値と期待値の比較 アナライザーが出力した警告の数と期待値を比較 診断結果に含まれる解析対象の位置と期待値(始点と終点)を比較 (DeNAがOSSとして公開しているパッケージです) See also: https://github.com/DeNA/Dena.CodeAnalysis.Testing アナライザーのIDと期待値を比較 アナライザーが出力した警告文と期待値を比較 差分表示例
  88. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 const string testData = @" ///

    public static class Foo { } "; テストデータの準備 アナライザーの診断対象として渡すC#のコードを記述
  89. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 const string testData = @" ///

    public static class Foo { } "; テストデータの準備 どの部分がアナライザーに怒られることを期待するのかわからない... テストの意図が、テストデータを見ただけで分かりづらい...
  90. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public static class {|Foo|TOY001|クラス名のシンボルに小文字を含んだ命名 Foo が使用されています

    |} { } public static class Foo { } 参考 https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs
  91. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public static class {|Foo|TOY001|クラス名のシンボルに小文字を含んだ命名 Foo が使用されています

    |} { } public static class Foo { } 警告が出る箇所の期待値 警告が出る位置 期待するアナライザー のID 期待する警告文 参考 https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs
  92. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public static class {|Foo|TOY001|クラス名のシンボルに小文字を含んだ命名 Foo が使用されています

    |} { } public static class Foo { } 警告が出る箇所の期待値 警告が出る位置 期待するアナライザー のID 期待する警告文 マーカーを抽出する 参考 https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs
  93. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public static class {|Foo|TOY001|クラス名のシンボルに小文字を含んだ命名 Foo が使用されています

    |} { } public static class Foo { } 警告が出る箇所の期待値 警告が出る位置 期待するアナライザー のID 期待する警告文 マーカーを抽出する マーカーを除外し、ソースコードを 作成 マーカーから期待値(DiagnosticのList) を作成 参考 https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs
  94. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 public static class {|Foo|TOY001|クラス名のシンボルに小文字を含んだ命名 Foo が使用されています

    |} { } public static class Foo { } 警告が出る箇所の期待値 警告が出る位置 期待するアナライザー のID 期待する警告文 マーカーを除外し、ソースコードを 作成 マーカーから期待値(DiagnosticのList) を作成 アサーションで実測値と比較する アナライザーに渡し、実測値を作成する 参考 https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/UnitTests/SymbolIsBannedAnalyzerTests.cs
  95. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。     var analyzer = new RoslynToyAnalyzer();     var

    (source, expectedDiagnostics) =     ReadAndParseTestData.CreateSourceAndExpectedDiagnosticFromFile(@" /// public static class {|Foo|TOY001|NamedTypeのシンボルに小文字を含んだ命名 Foo が使用されています |} { } ");     var actualDiagnostics = await DiagnosticAnalyzerRunner.Run( analyzer, source     ); テストの意図が、テストデータを見ただけでわかる! テストの期待値をマーカーから自動で生成できる!
  96. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 Assert.AreEqual(expectedDiagnostics.Count, actualDiagnostics.Length); Assert.AreEqual(expectedDiagnostics.First().Id, actualDiagnostics.First().Id); Assert.AreEqual(expectedDiagnostics.First().GetMessage(), actualDiagnostics.First().GetMessage());

    Assert.AreEqual(expectedDiagnostics.First().Location.GetLineSpan(), actualDiagnostics.First().Location.GetLineSpan()); - レポートされた問題の数 - レポートされた問題のファイル内の位置 - アナライザーの ID - レポートされた問題を人間にわかりやすく説明したメッ セージ を比較している。
  97. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 DiagnosticsAssert.AreEqual(IEnumerable<Diagnostic> expected, IEnumerable<Diagnostic> actual); レポートされた問題の数( DiagnosticのIEnumerableの個数)

    レポートされた問題のファイル内の位置(ファイルパス含む) アナライザーの ID メッセージ の4つの観点を比較している Assert.AreEqual(expectedDiagnostics.Count, actualDiagnostics.Length); Assert.AreEqual(expectedDiagnostics.First().Id, actualDiagnostics.First().Id); Assert.AreEqual(expectedDiagnostics.First().GetMessage(), actualDiagnostics.First().GetMessage()); Assert.AreEqual(expectedDiagnostics.First().Location.GetLineSpan(), actualDiagnostics.First().Location.GetLineSpan());
  98. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 DiagnosticsAssert.AreEqual(IEnumerable<Diagnostic> expected, IEnumerable<Diagnostic> actual); レポートされた問題の数( DiagnosticのIEnumerableの個数)

    レポートされた問題のファイル内の位置(ファイルパス含む) アナライザーの ID メッセージ の4つの観点を比較している Assert.Fail failed. Missing 1 diagnostics, extra 1 diagnostics of all 1 diagnostics: missing path/to/file1.cs: (1,2)-(3,4), CS1001, Identifier expected extra path/to/file2.cs: (5,6)-(7,8), CS1002, ; expected 出力例 Assert.AreEqual(expectedDiagnostics.Count, actualDiagnostics.Length); Assert.AreEqual(expectedDiagnostics.First().Id, actualDiagnostics.First().Id); Assert.AreEqual(expectedDiagnostics.First().GetMessage(), actualDiagnostics.First().GetMessage()); Assert.AreEqual(expectedDiagnostics.First().Location.GetLineSpan(), actualDiagnostics.First().Location.GetLineSpan());
  99. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 - Roslynアナライザーの概要 - Roslynアナライザーができること、使い方 - カスタムルールの作り方

    - カスタムアナライザーをテストする方法 - 作成したアナライザーをUPMパッケージに内包して配布、利用する NOTE: バージョンによってバグによる制約があるため、 このスライドではUnity 2021.2 + Rider Editor package v3.0.9を基準に書いています。 目次
  100. こちらに登壇時の映 像をいれるので、文 字を載せないように して下さい。 参考にしたサイト - Unityプロジェクト向けRoslynアナライザの作り方 - https://swet.dena.com/entry/2021/05/25/100000 -

    カスタムRoslyn AnalyzerをUPMパッケージとして配布する - https://www.nowsprinting.com/entry/2021/11/01/083258 - Unity 2020.時点のRoslyn Analyzerサポート状況まとめ - https://www.nowsprinting.com/entry/2021/04/18/200619 - Microsoft.CodeAnalysis.BannedApiAnalyzer - https://github.com/dotnet/roslyn-analyzers/tree/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers - codecracker.CSharp - https://github.com/code-cracker/code-cracker - UniTask - https://github.com/Cysharp/UniTask