You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

CodeFixVerifier.cs 7.4 kB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Threading;
  4. using Microsoft.CodeAnalysis;
  5. using Microsoft.CodeAnalysis.CodeActions;
  6. using Microsoft.CodeAnalysis.CodeFixes;
  7. using Microsoft.CodeAnalysis.Diagnostics;
  8. using Microsoft.CodeAnalysis.Formatting;
  9. using Xunit;
  10. //using Microsoft.VisualStudio.TestTools.UnitTesting;
  11. namespace TestHelper
  12. {
  13. /// <summary>
  14. /// Superclass of all Unit tests made for diagnostics with codefixes.
  15. /// Contains methods used to verify correctness of codefixes
  16. /// </summary>
  17. public abstract partial class CodeFixVerifier : DiagnosticVerifier
  18. {
  19. /// <summary>
  20. /// Returns the codefix being tested (C#) - to be implemented in non-abstract class
  21. /// </summary>
  22. /// <returns>The CodeFixProvider to be used for CSharp code</returns>
  23. protected virtual CodeFixProvider GetCSharpCodeFixProvider() => null;
  24. /// <summary>
  25. /// Returns the codefix being tested (VB) - to be implemented in non-abstract class
  26. /// </summary>
  27. /// <returns>The CodeFixProvider to be used for VisualBasic code</returns>
  28. protected virtual CodeFixProvider GetBasicCodeFixProvider() => null;
  29. /// <summary>
  30. /// Called to test a C# codefix when applied on the inputted string as a source
  31. /// </summary>
  32. /// <param name="oldSource">A class in the form of a string before the CodeFix was applied to it</param>
  33. /// <param name="newSource">A class in the form of a string after the CodeFix was applied to it</param>
  34. /// <param name="codeFixIndex">Index determining which codefix to apply if there are multiple</param>
  35. /// <param name="allowNewCompilerDiagnostics">
  36. /// A bool controlling whether or not the test will fail if the CodeFix
  37. /// introduces other warnings after being applied
  38. /// </param>
  39. protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null,
  40. bool allowNewCompilerDiagnostics = false) => VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(),
  41. GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics);
  42. /// <summary>
  43. /// Called to test a VB codefix when applied on the inputted string as a source
  44. /// </summary>
  45. /// <param name="oldSource">A class in the form of a string before the CodeFix was applied to it</param>
  46. /// <param name="newSource">A class in the form of a string after the CodeFix was applied to it</param>
  47. /// <param name="codeFixIndex">Index determining which codefix to apply if there are multiple</param>
  48. /// <param name="allowNewCompilerDiagnostics">
  49. /// A bool controlling whether or not the test will fail if the CodeFix
  50. /// introduces other warnings after being applied
  51. /// </param>
  52. protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null,
  53. bool allowNewCompilerDiagnostics = false) => VerifyFix(LanguageNames.VisualBasic,
  54. GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex,
  55. allowNewCompilerDiagnostics);
  56. /// <summary>
  57. /// General verifier for codefixes.
  58. /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes.
  59. /// Then gets the string after the codefix is applied and compares it with the expected result.
  60. /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to
  61. /// true.
  62. /// </summary>
  63. /// <param name="language">The language the source code is in</param>
  64. /// <param name="analyzer">The analyzer to be applied to the source code</param>
  65. /// <param name="codeFixProvider">The codefix to be applied to the code wherever the relevant Diagnostic is found</param>
  66. /// <param name="oldSource">A class in the form of a string before the CodeFix was applied to it</param>
  67. /// <param name="newSource">A class in the form of a string after the CodeFix was applied to it</param>
  68. /// <param name="codeFixIndex">Index determining which codefix to apply if there are multiple</param>
  69. /// <param name="allowNewCompilerDiagnostics">
  70. /// A bool controlling whether or not the test will fail if the CodeFix
  71. /// introduces other warnings after being applied
  72. /// </param>
  73. private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider,
  74. string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics)
  75. {
  76. var document = CreateDocument(oldSource, language);
  77. var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] {document});
  78. var compilerDiagnostics = GetCompilerDiagnostics(document);
  79. var attempts = analyzerDiagnostics.Length;
  80. for (var i = 0; i < attempts; ++i)
  81. {
  82. var actions = new List<CodeAction>();
  83. var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a),
  84. CancellationToken.None);
  85. codeFixProvider.RegisterCodeFixesAsync(context).Wait();
  86. if (!actions.Any()) break;
  87. if (codeFixIndex != null)
  88. {
  89. document = ApplyFix(document, actions.ElementAt((int)codeFixIndex));
  90. break;
  91. }
  92. document = ApplyFix(document, actions.ElementAt(0));
  93. analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] {document});
  94. var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document));
  95. //check if applying the code fix introduced any new compiler diagnostics
  96. if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any())
  97. {
  98. // Format and get the compiler diagnostics again so that the locations make sense in the output
  99. document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result,
  100. Formatter.Annotation, document.Project.Solution.Workspace));
  101. newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document));
  102. Assert.True(false,
  103. string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n",
  104. string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())),
  105. document.GetSyntaxRootAsync().Result.ToFullString()));
  106. }
  107. //check if there are analyzer diagnostics left after the code fix
  108. if (!analyzerDiagnostics.Any()) break;
  109. }
  110. //after applying all of the code fixes, compare the resulting string to the inputted one
  111. var actual = GetStringFromDocument(document);
  112. Assert.Equal(newSource, actual);
  113. }
  114. }
  115. }