private static async Task AreEqualAsync(IReadOnlyList <string> expected, Solution actual, string messageHeader) { var index = 0; foreach (var project in actual.Projects) { for (var i = 0; i < project.DocumentIds.Count; i++) { var fixedSource = await CodeReader.GetStringFromDocumentAsync(project.GetDocument(project.DocumentIds[i]), Formatter.Annotation, CancellationToken.None).ConfigureAwait(false); CodeAssert.AreEqual(expected[index], fixedSource, messageHeader); index++; } } }
/// <summary> /// Verifies that /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics when analyzed. /// 2. The code fix fixes the code. /// </summary> /// <param name="analyzer">The analyzer to run on the code..</param> /// <param name="codeFix">The code fix to apply.</param> /// <param name="diagnosticsAndSources">The code and expected diagnostics.</param> /// <param name="fixedCode">The expected code produced by the code fix.</param> /// <param name="fixTitle">The title of the fix to apply if more than one.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param> /// <param name="metadataReferences">The meta data metadataReferences to add to the compilation.</param> /// <param name="allowCompilationErrors">If compilation errors are accepted in the fixed code.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static async Task CodeFixAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, string fixedCode, string fixTitle, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences, AllowCompilationErrors allowCompilationErrors) { AssertCodeFixCanFixDiagnosticsFromAnalyzer(analyzer, codeFix); var data = await DiagnosticsWithMetadataAsync(analyzer, diagnosticsAndSources, compilationOptions, metadataReferences).ConfigureAwait(false); var fixableDiagnostics = data.ActualDiagnostics.SelectMany(x => x) .Where(x => codeFix.FixableDiagnosticIds.Contains(x.Id)) .ToArray(); if (fixableDiagnostics.Length == 0) { var message = $"Code analyzed with {analyzer} did not generate any diagnostics fixable by {codeFix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}"; throw AssertException.Create(message); } if (fixableDiagnostics.Length > 1) { var message = $"Code analyzed with {analyzer} generated more than one diagnostic fixable by {codeFix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}{Environment.NewLine}" + $"Maybe you meant to call AnalyzerAssert.FixAll?"; throw AssertException.Create(message); } var diagnostic = fixableDiagnostics.Single(); var fixedSolution = await Fix.ApplyAsync(data.Solution, codeFix, diagnostic, fixTitle, CancellationToken.None).ConfigureAwait(false); if (ReferenceEquals(data.Solution, fixedSolution)) { throw AssertException.Create($"{codeFix} did not change any document."); } var fixedSource = await CodeReader.GetStringFromDocumentAsync( fixedSolution.GetDocument(data.Solution.GetDocument(diagnostic.Location.SourceTree).Id), Formatter.Annotation, CancellationToken.None) .ConfigureAwait(false); CodeAssert.AreEqual(fixedCode, fixedSource); if (allowCompilationErrors == AllowCompilationErrors.No) { await AssertNoCompilerErrorsAsync(codeFix, fixedSolution).ConfigureAwait(false); } }
private static async Task VerifyFixAsync(Solution sln, IReadOnlyList <ImmutableArray <Diagnostic> > diagnostics, DiagnosticAnalyzer analyzer, CodeFixProvider fix, string fixedCode, string fixTitle = null, AllowCompilationErrors allowCompilationErrors = AllowCompilationErrors.No) { var fixableDiagnostics = diagnostics.SelectMany(x => x) .Where(x => fix.FixableDiagnosticIds.Contains(x.Id)) .ToArray(); if (fixableDiagnostics.Length == 0) { var message = $"Code analyzed with {analyzer} did not generate any diagnostics fixable by {fix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", diagnostics.SelectMany(x => x).Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", fix.FixableDiagnosticIds)}}}"; throw new AssertException(message); } if (fixableDiagnostics.Length > 1) { var message = $"Code analyzed with {analyzer} generated more than one diagnostic fixable by {fix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", diagnostics.SelectMany(x => x).Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", fix.FixableDiagnosticIds)}}}{Environment.NewLine}" + $"Maybe you meant to call AnalyzerAssert.FixAll?"; throw new AssertException(message); } var diagnostic = fixableDiagnostics.Single(); var fixedSolution = await Fix.ApplyAsync(sln, fix, diagnostic, fixTitle).ConfigureAwait(false); if (ReferenceEquals(sln, fixedSolution)) { throw new AssertException($"{fix} did not change any document."); } var fixedSource = await CodeReader.GetStringFromDocumentAsync( fixedSolution.GetDocument(sln.GetDocument(diagnostic.Location.SourceTree).Id), Formatter.Annotation, CancellationToken.None) .ConfigureAwait(false); CodeAssert.AreEqual(fixedCode, fixedSource); if (allowCompilationErrors == AllowCompilationErrors.No) { await VerifyNoCompilerErrorsAsync(fix, fixedSolution).ConfigureAwait(false); } }
private static async Task AreEqualAsync(IReadOnlyList <string> expected, Solution actual, string?messageHeader) { var actualCount = actual.Projects.SelectMany(x => x.Documents).Count(); if (expected.Count != actualCount) { throw new AssertException($"Expected {expected.Count} documents the fixed solution has {actualCount} documents."); } foreach (var project in actual.Projects) { foreach (var document in project.Documents) { var fixedSource = await CodeReader.GetStringFromDocumentAsync(document, CancellationToken.None).ConfigureAwait(false); CodeAssert.AreEqual(FindExpected(fixedSource), fixedSource, messageHeader); } } string FindExpected(string fixedSource) { var fixedNamespace = CodeReader.Namespace(fixedSource); var fixedFileName = CodeReader.FileName(fixedSource); var match = expected.FirstOrDefault(x => x == fixedSource); if (match != null) { return(match); } foreach (var candidate in expected) { if (CodeReader.Namespace(candidate) == fixedNamespace && CodeReader.FileName(candidate) == fixedFileName) { return(candidate); } } throw new AssertException($"The fixed solution contains a document {fixedFileName} in namespace {fixedNamespace} that is not in the expected documents."); } }
private static async Task AssertNoCompilerErrorsAsync(CodeFixProvider codeFix, Solution fixedSolution) { var diagnostics = await Analyze.GetDiagnosticsAsync(fixedSolution).ConfigureAwait(false); var introducedDiagnostics = diagnostics .SelectMany(x => x) .Where(IsIncluded) .ToArray(); if (introducedDiagnostics.Select(x => x.Id) .Except(DiagnosticSettings.AllowedErrorIds()) .Any()) { var errorBuilder = StringBuilderPool.Borrow(); errorBuilder.AppendLine($"{codeFix} introduced syntax error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}."); foreach (var introducedDiagnostic in introducedDiagnostics) { var errorInfo = await introducedDiagnostic.ToStringAsync(fixedSolution).ConfigureAwait(false); errorBuilder.AppendLine($"{errorInfo}"); } errorBuilder.AppendLine("First source file with error is:"); var sources = await Task.WhenAll(fixedSolution.Projects.SelectMany(p => p.Documents).Select(d => CodeReader.GetStringFromDocumentAsync(d, Formatter.Annotation, CancellationToken.None))); var lineSpan = introducedDiagnostics.First().Location.GetMappedLineSpan(); var match = sources.SingleOrDefault(x => CodeReader.FileName(x) == lineSpan.Path); errorBuilder.Append(match); errorBuilder.AppendLine(); throw AssertException.Create(errorBuilder.Return()); } }
private static async Task VerifyNoCompilerErrorsAsync(CodeFixProvider fix, Solution fixedSolution) { var diagnostics = await Analyze.GetDiagnosticsAsync(fixedSolution).ConfigureAwait(false); var introducedDiagnostics = diagnostics .SelectMany(x => x) .Where(IsIncluded) .ToArray(); if (introducedDiagnostics.Select(x => x.Id) #pragma warning disable CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() .Except(SuppressedDiagnostics) #pragma warning restore CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() .Any()) { var errorBuilder = StringBuilderPool.Borrow(); errorBuilder.AppendLine($"{fix.GetType().Name} introduced syntax error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}."); foreach (var introducedDiagnostic in introducedDiagnostics) { errorBuilder.AppendLine($"{introducedDiagnostic.ToErrorString()}"); } var sources = await Task.WhenAll(fixedSolution.Projects.SelectMany(p => p.Documents).Select(d => CodeReader.GetStringFromDocumentAsync(d, CancellationToken.None))); errorBuilder.AppendLine("First source file with error is:"); var lineSpan = introducedDiagnostics.First().Location.GetMappedLineSpan(); if (sources.TrySingle(x => CodeReader.FileName(x) == lineSpan.Path, out var match)) { errorBuilder.AppendLine(match); } else if (sources.TryFirst(x => CodeReader.FileName(x) == lineSpan.Path, out _)) { errorBuilder.AppendLine($"Found more than one document for {lineSpan.Path}."); foreach (string source in sources.Where(x => CodeReader.FileName(x) == lineSpan.Path)) { errorBuilder.AppendLine(source); } } else { errorBuilder.AppendLine($"Did not find a single document for {lineSpan.Path}."); } throw new AssertException(errorBuilder.Return()); } }