$30 off During Our Annual Pro Sale. View Details »

TDDで作るRoslynアナライザー【DeNA TechCon 2023】

DeNA_Tech
March 02, 2023

TDDで作るRoslynアナライザー【DeNA TechCon 2023】

youtube:https://youtu.be/ETWHB2yMdxg

概要:
.NETの静的解析器であるRoslynアナライザーを使うことで、ビルドすることなく問題のあるコードを検出できます。
便利なRoslynアナライザーですが、 アナライザーのテストで苦労された人もいるのではないでしょうか?
DeNAが公開しているRoslynアナライザーのためのテスティングライブラリであるDena.CodeAnalysis.Testingを使うことで、アナライザーのユニットテストを簡単に導入できます。

このセッションではDena.CodeAnalysis.Testingを用いてRoslynアナライザーをTDDで作成する方法について紹介します。

登壇内でのリンク集:
p9, https://speakerdeck.com/dena_tech/unitykai-fa-defalsemisuwowei-ran-nifang-guroslynanaraizafalsesu-me-dena-techcon-2022
p53, https://github.com/DeNA/RoslynAnalyzerTemplate
p54-1, https://github.com/DeNA/BanAsyncTaskAnalyzer
p54-2, https://github.com/DeNA/BanAsyncTaskAnalyzer/commit/0b2af3934a3a3759fbe25e73e527d4777b8a9d3b
p58, https://github.com/Cysharp/UniTask

◆ チャンネル登録はこちら↓
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 2023 公式サイト
https://techcon2023.dena.dev/

DeNA_Tech

March 02, 2023
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. TDDで作るRoslynアナライザー
    品質本部品質管理部SWET第二グループ
    Kazuma Inagaki
    1

    View Slide

  2. Kazuma Inagaki
    品質本部品質管理部SWET第二グループ
    @get_me_power

    令和にVimで就活した2021年新卒


    ゲーム領域における静的解析器の作成



    @get-me-power

    ※ TWITTER, TWEET, RETWEET and the Twitter Bird logo are trademarks of Twitter Inc. or its affiliates.
    2

    View Slide

  3. この発表で伝えたいこと
    3

    View Slide

  4. Dena.CodeAnalysis.Testingで
    Roslynアナライザーの
    ユニットテストを簡単にしよう
    4

    View Slide

  5. Dena.CodeAnalysis.Testingで
    Roslynアナライザーの
    ユニットテストを簡単にしよう
    5

    View Slide

  6. Roslynとは
    公開されているもの
    ■ C#、VB向けコンパイラ
    ■ コード生成API
    ■ コード解析API
    C#6.0から導入された.NETコンパイラプラットフォームの通称
    6

    View Slide

  7. Roslynとは
    公開されているもの
    ■ C#、VB向けコンパイラ
    ■ コード生成API
    ■ コード解析API
    C#6.0から導入された.NETコンパイラプラットフォームの通称
    7

    View Slide

  8. Roslynとは
    公開されているもの
    ■ C#、VB向けコンパイラ
    ■ コード生成API
    ■ コード解析API
    コードの問題を検出するプログラム
    Roslynアナライザー
    C#6.0から導入された.NETコンパイラプラットフォームの通称
    8

    View Slide

  9. https://speakerdeck.com/dena_tech/unitykai-fa-defalsemisuwowei-ran-nifang-guroslynanaraizafalsesu-me-dena-techcon-2022
    9

    View Slide

  10. Dena.CodeAnalysis.Testingで
    Roslynアナライザーの
    ユニットテストを簡単にしよう
    10

    View Slide

  11. ユニットテストの必要性
    11

    View Slide

  12. ユニットテストの必要性
    早期に欠陥を検知できる
    デグレーションを検知できる
    12

    View Slide

  13. Roslynアナライザーを作る上でも
    ユニットテストは有効
    13

    View Slide

  14. Roslynアナライザーのユニットテストに必要なもの
    ● 検査対象ソースコード
    ● アナライザーが出力する警告の期待結果
    ● アナライザーが実際に出力した値
    14

    View Slide

  15. Roslynアナライザーのユニットテストに必要なもの
    ● 検査対象ソースコード
    ● アナライザーが出力する警告の期待結果
    ● アナライザーが実際に出力した値
    15

    View Slide

  16. Roslynアナライザーのユニットテストは難しい
    16

    View Slide

  17. Roslynアナライザーのユニットテストは難しい
    既存のテスティングライブラリにデメリットがある
    17

    View Slide

  18. 既存のテスティングライブラリにデメリットがある
    Roslynアナライザーのユニットテストは難しい
    18

    View Slide

  19. 既存のテスティングライブラリのデメリット
    ● アナライザーの実行とアサーションが一体化している
    ● 引数が必要なアナライザーをインスタンス化するのが難しい
    ● 検査対象のソースコードのうちどこが問題なのか分かりにくい
    ● CodeFixProviderを指定する必要があった

    19

    View Slide

  20. 解決法
    20

    View Slide

  21. 解決法
    Dena.CodeAnalysis.Testingを使おう!
    21

    View Slide

  22. Dena.CodeAnalysis.Testingで
    Roslynアナライザーのユニットテストを簡単
    にしよう
    22

    View Slide

  23. Dena.CodeAnalysis.Testingができること

    23

    View Slide

  24. Dena.CodeAnalysis.Testingができること
    ● アナライザーの実行とアサーションを分離させる
    ● 引数が必要なアナライザーを指定する
    ● 検査対象ソースコードから得られる情報を増やす
    24

    View Slide

  25. Dena.CodeAnalysis.Testingができること
    ● アナライザーの実行とアサーションを分離させる
    ● 引数が必要なアナライザーを指定する
    ● 検査対象ソースコードから得られる情報を増やす
    25

    View Slide

  26. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    標準ライブラリを使った
    テストコード
    26

    View Slide

  27. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    アナライザー実行と
    アサーションが一体化している
    27

    View Slide

  28. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    独自の比較ができない アナライザー実行とアサーションの間
    に処理を挟めない
    アナライザー実行と
    アサーションが一体化している
    28

    View Slide

  29. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    var analyzer = new YourAnalyzer();
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(diagnostics.Length, Is.EqualTo(0));
    }
    Dena.CodeAnalysis.Testing使用
    29

    View Slide

  30. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    var analyzer = new YourAnalyzer();
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(diagnostics.Length, Is.EqualTo(0));
    }
    Dena.CodeAnalysis.Testing使用
    アナライザー実行とアサーションを
    分離できる!
    30

    View Slide

  31. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    var source = "";
    var analyzer = new YourAnalyzer();
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(diagnostics.Length, Is.EqualTo(0));
    }
    Dena.CodeAnalysis.Testing使用
    アナライザー実行とアサーションを
    分離できる!
    31

    View Slide

  32. Dena.CodeAnalysis.Testingができること
    ● アナライザーの実行とアサーションを分離させる
    ● 引数が必要なアナライザーを指定する
    ● 検査対象ソースコードから得られる情報を増やす
    32

    View Slide

  33. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    標準ライブラリを使った
    テストコード
    33

    View Slide

  34. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    アナライザーを指定
    34

    View Slide

  35. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    アナライザーを指定
    35

    View Slide

  36. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    アナライザーを指定
    コンパイルエラー
    36

    View Slide

  37. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    引数が必要なアナライザーに対応できない!
    コンパイルエラー
    37

    View Slide

  38. using Dena.CodeAnalysis.CSharp.Testing;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    const string source = "";
    var analyzer = new YourAnalyzer(new Component());
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(diagnostics.Length, Is.EqualTo(0));
    }
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    コンパイルエラー
    38

    View Slide

  39. using Dena.CodeAnalysis.CSharp.Testing;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    const string source = "";
    var analyzer = new YourAnalyzer(new Component());
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(diagnostics.Length, Is.EqualTo(0));
    }
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    コンパイルエラー
    テストコード内で
    インスタンス化
    39

    View Slide

  40. using Dena.CodeAnalysis.CSharp.Testing;
    [Test]
    public async Task EmptySourceCode_NoDiagnosticReport()
    {
    const string source = "";
    var analyzer = new YourAnalyzer(new Component());
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(diagnostics.Length, Is.EqualTo(0));
    }
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task Foo()
    {
    var source = "";
    await Verify.VerifyAnalyzerAsync(source);
    }
    コンパイルエラー
    テストコード内で
    インスタンス化
    40

    View Slide

  41. Dena.CodeAnalysis.Testingができること
    ● アナライザーの実行とアサーションを分離させる
    ● 引数が必要なアナライザーを指定する
    ● 検査対象ソースコードから得られる情報を増やす
    41

    View Slide

  42. 標準ライブラリを使った
    テストコード
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    42

    View Slide

  43. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    検査対象ソースコード
    43

    View Slide

  44. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    どこがエラー箇所?
    44

    View Slide

  45. using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    Dena.CodeAnalysis.Testing
    を使ったテストコード
    45

    View Slide

  46. public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    46

    View Slide

  47. public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    47
    エラー箇所

    View Slide

  48. public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    48
    DDID
    エラー箇所

    View Slide

  49. public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    エラーメッセージ
    DDID
    エラー箇所
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    49

    View Slide

  50. public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    書式を基に期待結果
    を作成
    作成された期待結果を利用
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }

    View Slide

  51. public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var analyzer = new RoslynAnalyzerTemplate();
    var (source, expected) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(@"
    namespace TypeNameContainingLowercase
    {
    class {|TypeName|RoslynAnalyzerTemplate0001|Type name 'TypeName' contains lowercase letters|}
    {
    }
    }");
    var actual = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    Assert.That(actual.Length, Is.EqualTo(expected.Count));
    Assert.That(actual.First().Id, Is.EqualTo(expected.First().Id))
    Assert.That(actual.First().GetMessage(), Is.EqualTo(expected.First().GetMessage()));
    書式を基に期待結果
    を作成
    using Verify =
    Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier
    ;
    [Test]
    public async Task TypeNameContainingLowercase_ReportOneDiagnostic()
    {
    var source = @"
    class TypeName
    {}
    ";
    await Verify.VerifyAnalyzerAsync(source);
    }
    作成された期待結果を利用

    View Slide

  52. Let’s Try!
    実際にDena.CodeAnalysis.Testing
    を使ってテストを書いてみよう
    52

    View Slide

  53. アナライザーを作成するための準備
    1. テンプレートリポジトリを使ってアナライザーを作成
    a. https://github.com/DeNA/RoslynAnalyzerTemplate
    2. テンプレートに含まれるテストメソッドを削除する
    3. アナライザーのテンプレートを削除する
    53

    View Slide

  54. アナライザーを作成するための準備
    1. テンプレートリポジトリを使ってアナライザーを作成
    a. https://github.com/DeNA/RoslynAnalyzerTemplate
    2. テンプレートに含まれるテストメソッドを削除する
    3. アナライザーのテンプレートを削除する
    54
    デモリポジトリurl:
    https://github.com/DeNA/BanAsyncTaskAnalyzer
    該当コミットハッシュ:
    https://github.com/DeNA/BanAsyncTaskAnalyzer/commi
    t/0b2af3934a3a3759fbe25e73e527d4777b8a9d3b

    View Slide

  55. リファクタリング
    テストを追加する
    実装する
    テストを追加する
    TDDのモデル
    ✔Passed
    ❌Failed
    ✔Passed
    55

    View Slide

  56. public class C
    {
    public async Task Foo()
    {
    }
    }
    作成するアナライザー
    56

    View Slide

  57. public class C
    {
    public async Task Foo()
    {
    }
    }
    怒る
    ● async Task → async UniTask
    ● async void → async UniTaskVoid
    作成するアナライザー
    57

    View Slide

  58. public class C
    {
    public async Task Foo()
    {
    }
    }
    怒る
    ● async Task → async UniTask
    ● async void → async UniTaskVoid
    https://github.com/Cysharp/UniTask
    Copyright (c) 2019 Yoshifumi Kawai / Cysharp, Inc.
    作成するアナライザー
    58

    View Slide

  59. public class C
    {
    public async Task Foo()
    {
    }
    }
    動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    怒る
    ● async Task → async UniTask
    ● async void → async UniTaskVoid
    作成するアナライザー
    59

    View Slide

  60. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    作成するアナライザー
    60
    このケースを追加

    View Slide

  61. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("NoAsyncMethodCase.txt");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    61

    View Slide

  62. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("NoAsyncMethodCase.txt");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    class NoAsyncMethodCase
    {
    void NoAsyncMethod()
    {
    }
    }
    検査対象ソースコード
    62

    View Slide

  63. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("NoAsyncMethodCase.txt");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    private static string[] ReadCodes(params string[] sources)
    {
    const string Path = "../../../TestData";
    return sources.Select(file => File.ReadAllText($"{Path}/{file}", Encoding.UTF8)).ToArray();
    }
    63

    View Slide

  64. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("NoAsyncMethodCase.txt");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    検査対象ソースコードを渡し、アナライザーを実

    64

    View Slide

  65. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("NoAsyncMethodCase.txt");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    65
    実際の値を加工している

    View Slide

  66. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadFromFile.ReadFile(TestData.GetPath("NoAsyncMethodCase.txt"));
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    実際の値を加工している
    ✔Passed
    66

    View Slide

  67. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadFromFile.ReadFile(TestData.GetPath("NoAsyncMethodCase.txt"));
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    実際の値を加工している
    Refactor
    Add Test
    67

    View Slide

  68. [Test]
    public async Task 非asyncメソッド_戻り値がvoid_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadFromFile.ReadFile(TestData.GetPath("NoAsyncMethodCase.txt"));
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    実際の値を加工している
    Refactor
    Add Test
    68

    View Slide

  69. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    69

    View Slide

  70. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    70
    このケースを追加

    View Slide

  71. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    テストメソッド
    71

    View Slide

  72. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async Task {|UseTask|BanAsyncTask0001|Do not use 'System.Threading.Tasks.Task', Should use UniTask|}()
    {
    await Task.Delay(100);
    }
    }
    }
    検査対象ソースコード
    72

    View Slide

  73. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async Task {|UseTask|BanAsyncTask0001|Do not use 'System.Threading.Tasks.Task', Should use UniTask|}()
    {
    await Task.Delay(100);
    }
    }
    }
    73
    レポート箇所

    View Slide

  74. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async Task {|UseTask|BanAsyncTask0001|Do not use 'System.Threading.Tasks.Task', Should use UniTask|}()
    {
    await Task.Delay(100);
    }
    }
    }
    DDID
    74

    View Slide

  75. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async Task {|UseTask|BanAsyncTask0001|Do not use 'System.Threading.Tasks.Task', Should use UniTask|}()
    {
    await Task.Delay(100);
    }
    }
    }
    75
    エラーメッセージ

    View Slide

  76. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    ソースコードと期
    待結果を作成
    76
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async Task {|UseTask|BanAsyncTask0001|Do not use 'System.Threading.Tasks.Task', Should use UniTask|}()
    {
    await Task.Delay(100);
    }
    }
    }

    View Slide

  77. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    ● Diagnosticのcollectionを順不同で比較
    ● 以下の観点がすべて一致するDIagnosticを等価と判断する
    ○ レポートされた問題のファイル内の位置(ファイルパス含む)
    ○ アナライザーのID
    ○ メッセージ
    77

    View Slide

  78. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    ● Diagnosticのcollectionを順不同で比較
    ● 以下の観点がすべて一致するDIagnosticを等価と判断する
    ○ レポートされた問題のファイル内の位置(ファイルパス含む)
    ○ アナライザーのID
    ○ メッセージ
    78
    ❌Failed

    View Slide

  79. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    Missing 1 diagnostics, extra 0 diagnostics of all 0 diagnostics:
    missing /0/Test0.cs: (7,18)-(7,25), BanAsyncTask0001, Do not use
    'System.Threading.Tasks.Task', Should use UniTask
    79

    View Slide

  80. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    Missing 1 diagnostics, extra 0 diagnostics of all 0 diagnostics:
    missing /0/Test0.cs: (7,18)-(7,25), BanAsyncTask0001, Do not use
    'System.Threading.Tasks.Task', Should use UniTask
    80
    レポート箇所

    View Slide

  81. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    Missing 1 diagnostics, extra 0 diagnostics of all 0 diagnostics:
    missing /0/Test0.cs: (7,18)-(7,25), BanAsyncTask0001, Do not use
    'System.Threading.Tasks.Task', Should use UniTask
    81
    DDID

    View Slide

  82. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    Missing 1 diagnostics, extra 0 diagnostics of all 0 diagnostics:
    missing /0/Test0.cs: (7,18)-(7,25), BanAsyncTask0001, Do not use
    'System.Threading.Tasks.Task', Should use UniTask
    82
    エラーメッセージ

    View Slide

  83. [Test]
    public async Task asyncメソッド_戻り値がTask_BanAsyncTask0001がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("NoAsyncMethodCase.txt");
    var (source, expectedDiagnostics) =
    TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591")
    .Where(x => x.Id != "CS8019")
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    Missing 1 diagnostics, extra 0 diagnostics of all 0 diagnostics:
    missing /0/Test0.cs: (7,18)-(7,25), BanAsyncTask0001, Do not use
    'System.Threading.Tasks.Task', Should use UniTask
    83
    エラーメッセージ

    View Slide

  84. アナライザー本体を実装
    84

    View Slide

  85. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    アナライザー本体のソースコード
    85

    View Slide

  86. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    アナライザーのDiagnosticを定義
    86

    View Slide

  87. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    アナライザー本体の実装
    87

    View Slide

  88. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    88
    定義されたメソッドに関する情報を取得

    View Slide

  89. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    解析対象がAsyncメソッド以外ならreturn
    89

    View Slide

  90. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    定義されたメソッドの戻り値の型名が
    ”Task”だったらレポートする 90

    View Slide

  91. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    91
    ✔Passed

    View Slide

  92. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    92
    Refactor
    Add Test

    View Slide

  93. using System.Collections.Immutable;
    using BanAsyncTaskAnalyzer.Extensions;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    namespace BanAsyncTaskAnalyzer;
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class BanAsyncTaskAnalyzer : DiagnosticAnalyzer
    {
    private static readonly DiagnosticDescriptor Rule01 = new DiagnosticDescriptor(
    id: "BanAsyncTask0001",
    title: "Banned Task Rule",
    messageFormat: "Do not use '{0}', Should use UniTask",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule01);
    public override void Initialize(AnalysisContext context)
    {
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.EnableConcurrentExecution();
    context.RegisterSymbolAction(AnalyzeAsyncMethod, SymbolKind.Method);
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    }
    93
    Refactor
    Add Test

    View Slide

  94. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    94

    View Slide

  95. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    95
    このケースを追加

    View Slide

  96. public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    テストメソッド
    96

    View Slide

  97. public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    using NotSystem.Threading.Tasks;
    namespace UseDummyTask
    {
    class C
    {
    async Task UseDummyTask()
    {
    }
    }
    }
    検査対象ソースコード
    97

    View Slide

  98. public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    using NotSystem.Threading.Tasks;
    namespace UseDummyTask
    {
    class C
    {
    async Task UseDummyTask()
    {
    }
    }
    }
    98
    ”Task”という名前の、asyncメソッドの
    戻り値になる型を持つnamespace

    View Slide

  99. public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    99
    namespace NotSystem.Threading.Tasks
    {
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuil
    der))]
    public struct Task
    {
    }
    public class CompilerServices
    {
    public class AsyncUniTaskMethodBuilder
    {
    }
    }
    }
    ダミー実装

    View Slide

  100. public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    ダミー実装と検査対象ソースコードを
    渡しアナライザーを実行
    100

    View Slide

  101. 101
    public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    Diagnosticが存在しないことを検証できるカスタムアサーション
    出力形式は、前述したDiagnosticsAssert.AreEqualと同じ

    View Slide

  102. 102
    public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    Diagnosticが存在しないことを検証できるカスタムアサーション
    出力形式は、前述したDiagnosticsAssert.AreEqualと同じ
    ❌Failed

    View Slide

  103. 103
    public async Task asyncメソッド_戻り値がDummyTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseDummyTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    expected no diagnostics, but 1 diagnostics are reported
    extra /0/Test0.cs: (4,15)-(4,27), BanAsyncTask0001,
    Do not use 'NotSystem.Threading.Tasks.Task', Should use UniTask

    View Slide

  104. if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更前
    アナライザー本体のコード
    104

    View Slide

  105. if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更前
    完全修飾型名を見ていない
    105

    View Slide

  106. if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更前
    完全修飾型名を見ていない
    106

    View Slide

  107. if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更前
    完全修飾型名を見ていない
    System.Threading.Tasks.Task
    完全修飾型名
    107

    View Slide

  108. if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更前
    完全修飾型名を見ていない
    System.Threading.Tasks.Task
    現実装
    108

    View Slide

  109. if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更前
    完全修飾型名を見ていない
    System.Threading.Tasks.Task
    現実装
    109

    View Slide

  110. if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更後
    変更前
    完全修飾型名を見ていない
    110

    View Slide

  111. if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更後
    変更前
    完全修飾型名を見ていない
    完全修飾型名を見る
    111

    View Slide

  112. if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更後
    変更前
    using System.Collections.Generic;
    using Microsoft.CodeAnalysis;
    namespace BanAsyncTaskAnalyzer.Extensions;
    public static class NamespaceOrTypeSymbolExtensions
    {
    public static string FullName(this INamespaceOrTypeSymbol symbol)
    {
    var collectedNamespace = new List();
    while (symbol.Name != "")
    {
    collectedNamespace.Add(symbol.Name);
    symbol = symbol.ContainingNamespace;
    }
    collectedNamespace.Reverse();
    return string.Join(".", collectedNamespace);
    }
    }
    完全修飾型名を取得する
    拡張メソッド
    112

    View Slide

  113. if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task"
    )
    {
    var diagnostic = Diagnostic.Create(
    Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更後
    変更前
    using System.Collections.Generic;
    using Microsoft.CodeAnalysis;
    namespace BanAsyncTaskAnalyzer.Extensions;
    public static class NamespaceOrTypeSymbolExtensions
    {
    public static string FullName(this INamespaceOrTypeSymbol symbol)
    {
    var collectedNamespace = new List();
    while (symbol.Name != "")
    {
    collectedNamespace.Add(symbol.Name);
    symbol = symbol.ContainingNamespace;
    }
    collectedNamespace.Reverse();
    return string.Join(".", collectedNamespace);
    }
    }
    ✔Passed
    113

    View Slide

  114. if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task"
    )
    {
    var diagnostic = Diagnostic.Create(
    Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更後
    変更前
    using System.Collections.Generic;
    using Microsoft.CodeAnalysis;
    namespace BanAsyncTaskAnalyzer.Extensions;
    public static class NamespaceOrTypeSymbolExtensions
    {
    public static string FullName(this INamespaceOrTypeSymbol symbol)
    {
    var collectedNamespace = new List();
    while (symbol.Name != "")
    {
    collectedNamespace.Add(symbol.Name);
    symbol = symbol.ContainingNamespace;
    }
    collectedNamespace.Reverse();
    return string.Join(".", collectedNamespace);
    }
    }
    Refactor
    Add Test
    114

    View Slide

  115. if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task"
    )
    {
    var diagnostic = Diagnostic.Create(
    Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.Name == "Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    変更後
    変更前
    using System.Collections.Generic;
    using Microsoft.CodeAnalysis;
    namespace BanAsyncTaskAnalyzer.Extensions;
    public static class NamespaceOrTypeSymbolExtensions
    {
    public static string FullName(this INamespaceOrTypeSymbol symbol)
    {
    var collectedNamespace = new List();
    while (symbol.Name != "")
    {
    collectedNamespace.Add(symbol.Name);
    symbol = symbol.ContainingNamespace;
    }
    collectedNamespace.Reverse();
    return string.Join(".", collectedNamespace);
    }
    }
    Refactor
    Add Test
    115

    View Slide

  116. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    116

    View Slide

  117. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    117
    このケースを追加

    View Slide

  118. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    テストメソッド
    118

    View Slide

  119. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    119
    using Cysharp.Threading.Tasks;
    class C
    {
    async UniTask UseUniTask()
    {
    }
    }
    検査対象ソースコード

    View Slide

  120. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    120
    using Cysharp.Threading.Tasks;
    class C
    {
    async UniTask UseUniTask()
    {
    }
    }
    UniTaskの
    namespaceを指定

    View Slide

  121. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    121
    using Cysharp.Threading.Tasks;
    class C
    {
    async UniTask UseUniTask()
    {
    }
    }
    using System.Runtime.CompilerServices;
    namespace Cysharp.Threading.Tasks
    {
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTask
    {
    }
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTaskVoid
    {
    }
    }
    namespace Cysharp.Threading.Tasks.CompilerServices
    {
    public struct AsyncUniTaskMethodBuilder
    {
    }
    }
    UniTaskの
    ダミー実装

    View Slide

  122. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    122
    using Cysharp.Threading.Tasks;
    class C
    {
    async UniTask UseUniTask()
    {
    }
    }
    using System.Runtime.CompilerServices;
    namespace Cysharp.Threading.Tasks
    {
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTask
    {
    }
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTaskVoid
    {
    }
    }
    namespace Cysharp.Threading.Tasks.CompilerServices
    {
    public struct AsyncUniTaskMethodBuilder
    {
    }
    }
    ✔Passed

    View Slide

  123. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    123
    using Cysharp.Threading.Tasks;
    class C
    {
    async UniTask UseUniTask()
    {
    }
    }
    using System.Runtime.CompilerServices;
    namespace Cysharp.Threading.Tasks
    {
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTask
    {
    }
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTaskVoid
    {
    }
    }
    namespace Cysharp.Threading.Tasks.CompilerServices
    {
    public struct AsyncUniTaskMethodBuilder
    {
    }
    }
    Refactor
    Add Test

    View Slide

  124. public async Task asyncメソッド_戻り値がUniTask_何もレポートされない()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var source = ReadCodes("UseUniTaskCase.txt", "Fakes.cs");
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actual = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS1998") // Ignore "This async method lacks 'await' operators and will run
    synchronously".
    .ToArray();
    DiagnosticsAssert.IsEmpty(actual);
    }
    124
    using Cysharp.Threading.Tasks;
    class C
    {
    async UniTask UseUniTask()
    {
    }
    }
    using System.Runtime.CompilerServices;
    namespace Cysharp.Threading.Tasks
    {
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTask
    {
    }
    [AsyncMethodBuilder(typeof(CompilerServices.AsyncUniTaskMethodBuilder))]
    public struct UniTaskVoid
    {
    }
    }
    namespace Cysharp.Threading.Tasks.CompilerServices
    {
    public struct AsyncUniTaskMethodBuilder
    {
    }
    }
    Refactor
    Add Test

    View Slide

  125. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    125

    View Slide

  126. 動作
    BanAsyncTask0001 BanAsyncTask0002 何もレポートされない
    条件
    async使用関数
    System.Threading.Tasks.Task Y N N
    System.Void N Y N
    Cysharp.Threading.Tasks.UniTask N N Y
    NotSystem.Threading.Tasks.Task N N Y
    async未使用関数 System.Void N N Y
    このケースを追加
    126

    View Slide

  127. [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    127

    View Slide

  128. [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    128
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async void {|UseAsyncVoid|BanAsyncTask0002|Do not use 'void' in async Method, Should use UniTaskVoid|}()
    {
    await Task.Delay(100);
    }
    }
    }
    検査対象ソースコード

    View Slide

  129. [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    129
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async void {|UseAsyncVoid|BanAsyncTask0002|Do not use 'void' in async Method, Should use UniTaskVoid|}()
    {
    await Task.Delay(100);
    }
    }
    } レポート箇所

    View Slide

  130. [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    130
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async void {|UseAsyncVoid|BanAsyncTask0002|Do not use 'void' in async Method, Should use UniTaskVoid|}()
    {
    await Task.Delay(100);
    }
    }
    } DDID

    View Slide

  131. [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    131
    using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async void {|UseAsyncVoid|BanAsyncTask0002|Do not use 'void' in async Method, Should use UniTaskVoid|}()
    {
    await Task.Delay(100);
    }
    }
    } エラーメッセージ

    View Slide

  132. using System.Threading;
    using System.Threading.Tasks;
    namespace UseTask
    {
    class C
    {
    async void {|UseAsyncVoid|BanAsyncTask0002|Do not use 'void' in async Method, Should use UniTaskVoid|}()
    {
    await Task.Delay(100);
    }
    }
    }
    [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    ❌Failed
    132

    View Slide

  133. [Test]
    public async Task asyncメソッド_戻り値がvoid_BanAsyncTask0002がレポートされる()
    {
    var analyzer = new BanAsyncTaskAnalyzer();
    var testData = ReadCodes("AsyncVoidCase.txt");
    var (source, expectedDiagnostics) = TestDataParser.CreateSourceAndExpectedDiagnostic(testData[0]);
    var diagnostics = await DiagnosticAnalyzerRunner.Run(analyzer, source);
    var actualDiagnostics = diagnostics
    .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member"
    .Where(x => x.Id != "CS8019") // Ignore "Unnecessary using directive"
    .Where(x => x.Id !=
    "CS1998") // Ignore "This async method lacks 'await' operators and will run synchronously."
    .ToArray();
    DiagnosticsAssert.AreEqual(expectedDiagnostics, actualDiagnostics);
    }
    133
    extra 0 diagnostics of all 0 diagnostics:
    missing /0/Test0.cs: (7,18)-(7,30), BanAsyncTask0002, Do not use
    'void' in async Method, Should use UniTaskVoid

    View Slide

  134. アナライザー本体を実装
    134

    View Slide

  135. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    変更前
    アナライザー本体のソースコード
    135

    View Slide

  136. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    変更前
    void型の指定を検知できない
    136

    View Slide

  137. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Void")
    {
    var diagnostic = Diagnostic.Create(Rule02, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    ✔変更後
    変更前
    137

    View Slide

  138. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Void")
    {
    var diagnostic = Diagnostic.Create(Rule02, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    変更前
    ✔変更後
    条件分岐を追加
    138

    View Slide

  139. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Void")
    {
    var diagnostic = Diagnostic.Create(Rule02, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    変更前
    ✔変更後
    完全修飾型名がSystem.Voidかどうか判定
    139

    View Slide

  140. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Void")
    {
    var diagnostic = Diagnostic.Create(Rule02, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    変更前
    private static readonly DiagnosticDescriptor Rule02 = new
    DiagnosticDescriptor(
    id: "BanAsyncTask0002",
    title: "Banned void in async Method",
    messageFormat: "Do not use '{0}' in async Method, Should use
    UniTaskVoid",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    ✔変更後
    System.Voidの使用をレポートする
    ルールを追加
    140

    View Slide

  141. private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Void")
    {
    var diagnostic = Diagnostic.Create(Rule02, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    }
    }
    private static void AnalyzeAsyncMethod(SymbolAnalysisContext context)
    {
    var methodSymbol = (IMethodSymbol)context.Symbol;
    if (!methodSymbol.IsAsync)
    {
    return;
    }
    if (methodSymbol.ReturnType.FullName() == "System.Threading.Tasks.Task")
    {
    var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0], methodSymbol.ReturnType);
    context.ReportDiagnostic(diagnostic);
    return;
    }
    }
    変更前
    private static readonly DiagnosticDescriptor Rule02 = new
    DiagnosticDescriptor(
    id: "BanAsyncTask0002",
    title: "Banned void in async Method",
    messageFormat: "Do not use '{0}' in async Method, Should use
    UniTaskVoid",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Type names should be all uppercase.");
    ✔Passed
    ✔変更後
    141

    View Slide

  142. すべてのテストケースの実装が完了
    142

    View Slide

  143. まとめ
    ● DiagnosticAnalyzer.Runを使ってアナライザーの実際の値を簡
    単に入手しよう
    ● DiagnosticsAssertでDiagnostic同士を比較しよう
    ● TestDataParserで簡単にテストの期待結果を作成しよう
    143

    View Slide

  144. 144

    View Slide