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

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. using Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier <YourAnalyzer>; [Test] public async Task EmptySourceCode_NoDiagnosticReport()

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

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

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

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

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

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

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

    { var source = ""; await Verify.VerifyAnalyzerAsync(source); } using Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier <YourAnalyzer(new Component())>; [Test] public async Task Foo() { var source = ""; await Verify.VerifyAnalyzerAsync(source); } 引数が必要なアナライザーに対応できない! コンパイルエラー 37
  12. 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 <YourAnalyzer(new Component())>; [Test] public async Task Foo() { var source = ""; await Verify.VerifyAnalyzerAsync(source); } コンパイルエラー 38
  13. 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 <YourAnalyzer(new Component())>; [Test] public async Task Foo() { var source = ""; await Verify.VerifyAnalyzerAsync(source); } コンパイルエラー テストコード内で インスタンス化 39
  14. 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 <YourAnalyzer(new Component())>; [Test] public async Task Foo() { var source = ""; await Verify.VerifyAnalyzerAsync(source); } コンパイルエラー テストコード内で インスタンス化 40
  15. 標準ライブラリを使った テストコード using Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier <YourAnalyzer>; [Test] public async

    Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } 42
  16. using Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic()

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

    { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } どこがエラー箇所? 44
  18. using Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier <YourAnalyzer>; [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
  19. 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 <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } 46
  20. 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 <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } 47 エラー箇所
  21. 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 <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } 48 DDID エラー箇所
  22. 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 <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } 49
  23. 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 <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); }
  24. 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 <YourAnalyzer>; [Test] public async Task TypeNameContainingLowercase_ReportOneDiagnostic() { var source = @" class TypeName {} "; await Verify.VerifyAnalyzerAsync(source); } 作成された期待結果を利用
  25. public class C { public async Task Foo() { }

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

    } 怒る • async Task → async UniTask • async void → async UniTaskVoid 作成するアナライザー 57
  27. 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
  28. 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
  29. 動作 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 このケースを追加
  30. [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
  31. [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
  32. [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
  33. [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
  34. [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 実際の値を加工している
  35. [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
  36. [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
  37. [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
  38. 動作 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
  39. 動作 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 このケースを追加
  40. [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
  41. [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
  42. [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 レポート箇所
  43. [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
  44. [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 エラーメッセージ
  45. [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); } } }
  46. [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
  47. [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
  48. [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
  49. [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 レポート箇所
  50. [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
  51. [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 エラーメッセージ
  52. [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 エラーメッセージ
  53. 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<DiagnosticDescriptor> 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
  54. 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<DiagnosticDescriptor> 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
  55. 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<DiagnosticDescriptor> 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
  56. 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<DiagnosticDescriptor> 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 定義されたメソッドに関する情報を取得
  57. 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<DiagnosticDescriptor> 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
  58. 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<DiagnosticDescriptor> 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
  59. 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<DiagnosticDescriptor> 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
  60. 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<DiagnosticDescriptor> 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
  61. 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<DiagnosticDescriptor> 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
  62. 動作 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
  63. 動作 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 このケースを追加
  64. 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
  65. 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
  66. 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
  67. 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 { } } } ダミー実装
  68. 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
  69. 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と同じ
  70. 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
  71. 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
  72. if (methodSymbol.ReturnType.Name == "Task") { var diagnostic = Diagnostic.Create(Rule01, methodSymbol.Locations[0],

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

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

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

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

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

    methodSymbol.ReturnType); context.ReportDiagnostic(diagnostic); return; } 変更前 完全修飾型名を見ていない System.Threading.Tasks.Task 現実装 109
  78. 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
  79. 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
  80. 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<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } } 完全修飾型名を取得する 拡張メソッド 112
  81. 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<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } } ✔Passed 113
  82. 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<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } } Refactor Add Test 114
  83. 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<string>(); while (symbol.Name != "") { collectedNamespace.Add(symbol.Name); symbol = symbol.ContainingNamespace; } collectedNamespace.Reverse(); return string.Join(".", collectedNamespace); } } Refactor Add Test 115
  84. 動作 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
  85. 動作 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 このケースを追加
  86. 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
  87. 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() { } } 検査対象ソースコード
  88. 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を指定
  89. 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の ダミー実装
  90. 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
  91. 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
  92. 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
  93. 動作 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
  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 このケースを追加 126
  95. [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
  96. [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); } } } 検査対象ソースコード
  97. [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); } } } レポート箇所
  98. [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
  99. [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); } } } エラーメッセージ
  100. 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
  101. [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
  102. 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
  103. 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
  104. 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
  105. 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
  106. 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
  107. 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
  108. 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
  109. 144