using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; using Xunit; //using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestHelper { /// /// Superclass of all Unit tests made for diagnostics with codefixes. /// Contains methods used to verify correctness of codefixes /// public abstract partial class CodeFixVerifier : DiagnosticVerifier { /// /// Returns the codefix being tested (C#) - to be implemented in non-abstract class /// /// The CodeFixProvider to be used for CSharp code protected virtual CodeFixProvider GetCSharpCodeFixProvider() => null; /// /// Returns the codefix being tested (VB) - to be implemented in non-abstract class /// /// The CodeFixProvider to be used for VisualBasic code protected virtual CodeFixProvider GetBasicCodeFixProvider() => null; /// /// Called to test a C# codefix when applied on the inputted string as a source /// /// A class in the form of a string before the CodeFix was applied to it /// A class in the form of a string after the CodeFix was applied to it /// Index determining which codefix to apply if there are multiple /// /// A bool controlling whether or not the test will fail if the CodeFix /// introduces other warnings after being applied /// protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) => VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); /// /// Called to test a VB codefix when applied on the inputted string as a source /// /// A class in the form of a string before the CodeFix was applied to it /// A class in the form of a string after the CodeFix was applied to it /// Index determining which codefix to apply if there are multiple /// /// A bool controlling whether or not the test will fail if the CodeFix /// introduces other warnings after being applied /// protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) => VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); /// /// General verifier for codefixes. /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. /// Then gets the string after the codefix is applied and compares it with the expected result. /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to /// true. /// /// The language the source code is in /// The analyzer to be applied to the source code /// The codefix to be applied to the code wherever the relevant Diagnostic is found /// A class in the form of a string before the CodeFix was applied to it /// A class in the form of a string after the CodeFix was applied to it /// Index determining which codefix to apply if there are multiple /// /// A bool controlling whether or not the test will fail if the CodeFix /// introduces other warnings after being applied /// private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, language); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] {document}); var compilerDiagnostics = GetCompilerDiagnostics(document); var attempts = analyzerDiagnostics.Length; for (var i = 0; i < attempts; ++i) { var actions = new List(); var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); if (!actions.Any()) break; if (codeFixIndex != null) { document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); break; } document = ApplyFix(document, actions.ElementAt(0)); analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] {document}); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); //check if applying the code fix introduced any new compiler diagnostics if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); Assert.True(false, string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), document.GetSyntaxRootAsync().Result.ToFullString())); } //check if there are analyzer diagnostics left after the code fix if (!analyzerDiagnostics.Any()) break; } //after applying all of the code fixes, compare the resulting string to the inputted one var actual = GetStringFromDocument(document); Assert.Equal(newSource, actual); } } }