/// <summary> /// Called to test a C# codefix when applied on the inputted string as a source /// </summary> /// <param name="oldSources">Code files, each in the form of a string before the CodeFix was applied to it</param> /// <param name="newSources">Code files, each in the form of a string after the CodeFix was applied to it</param> /// <param name="codeFixIndex">Index determining which codefix to apply if there are multiple</param> /// <param name="expectedPostFixDiagnostics">The set of diagnostics that are expected to exist after any fix(es) are applied to the original code.</param> /// <param name="hasEntrypoint"><c>true</c> to set the compiler in a mode as if it were compiling an exe (as opposed to a dll).</param> protected void VerifyCSharpFix(string[] oldSources, string[] newSources, int?codeFixIndex = null, PostFixDiagnostics expectedPostFixDiagnostics = PostFixDiagnostics.None, bool hasEntrypoint = false) { this.VerifyFix(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), this.GetCSharpCodeFixProvider(), oldSources, newSources, codeFixIndex, expectedPostFixDiagnostics, hasEntrypoint); }
/// <summary> /// 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. /// </summary> /// <param name="language">The language the source code is in</param> /// <param name="analyzer">The analyzer to be applied to the source code</param> /// <param name="codeFixProvider">The codefix to be applied to the code wherever the relevant Diagnostic is found</param> /// <param name="oldSources">Code files, each in the form of a string before the CodeFix was applied to it</param> /// <param name="newSources">Code files, each in the form of a string after the CodeFix was applied to it</param> /// <param name="codeFixIndex">Index determining which codefix to apply if there are multiple in the same location</param> /// <param name="expectedPostFixDiagnostics">The set of diagnostics that are expected to exist after any fix(es) are applied to the original code.</param> /// <param name="hasEntrypoint"><c>true</c> to set the compiler in a mode as if it were compiling an exe (as opposed to a dll).</param> private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string[] oldSources, string[] newSources, int?codeFixIndex, PostFixDiagnostics expectedPostFixDiagnostics, bool hasEntrypoint) { var project = CreateProject(oldSources, language, hasEntrypoint); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(ImmutableArray.Create(analyzer), project.Documents.ToArray(), hasEntrypoint); var compilerDiagnostics = project.Documents.SelectMany(doc => GetCompilerDiagnostics(doc)).ToList(); var attempts = analyzerDiagnostics.Length; // We'll go through enough for each diagnostic to be caught once bool fixApplied = false; for (int i = 0; i < attempts; ++i) { var diagnostic = analyzerDiagnostics[0]; // just get the first one -- the list gets smaller with each loop. var document = project.GetDocument(diagnostic.Location.SourceTree); var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); if (!actions.Any()) { continue; } document = ApplyFix(document, actions[codeFixIndex ?? 0]); fixApplied = true; project = document.Project; this.Logger.WriteLine("Code after fix:"); this.LogFileContent(document.GetSyntaxRootAsync().Result.ToFullString()); analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(ImmutableArray.Create(analyzer), project.Documents.ToArray()); } if (newSources != null && newSources[0] != null) { Assert.True(fixApplied, "No code fix offered."); // After applying all of the code fixes, compare the resulting string to the inputted one int j = 0; foreach (var document in project.Documents) { var actual = GetStringFromDocument(document); Assert.Equal(newSources[j++], actual, ignoreLineEndingDifferences: true); } } else { Assert.False(fixApplied, "No code fix expected, but was offered."); } var postFixDiagnostics = project.Documents.SelectMany(doc => GetCompilerDiagnostics(doc)).Concat(GetSortedDiagnosticsFromDocuments(ImmutableArray.Create(analyzer), project.Documents.ToArray(), hasEntrypoint)).ToList(); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, postFixDiagnostics).ToList(); IEnumerable <Diagnostic> unexpectedDiagnostics; switch (expectedPostFixDiagnostics) { case PostFixDiagnostics.None: unexpectedDiagnostics = postFixDiagnostics; break; case PostFixDiagnostics.Preexisting: unexpectedDiagnostics = newCompilerDiagnostics; break; case PostFixDiagnostics.New: unexpectedDiagnostics = Enumerable.Empty <Diagnostic>(); // We don't care what's present. break; default: throw new NotSupportedException(); } var expectedDiagnostics = postFixDiagnostics.Except(unexpectedDiagnostics); this.Logger.WriteLine("Actual diagnostics:"); this.Logger.WriteLine("EXPECTED:\r\n{0}", expectedDiagnostics.Any() ? FormatDiagnostics(expectedDiagnostics.ToArray()) : " NONE."); this.Logger.WriteLine("UNEXPECTED:\r\n{0}", unexpectedDiagnostics.Any() ? FormatDiagnostics(unexpectedDiagnostics.ToArray()) : " NONE."); // Check if applying the code fix introduced any new compiler diagnostics Assert.Empty(unexpectedDiagnostics); }