/// <summary> /// Verifies that specified source will produce compiler diagnostic. /// </summary> /// <param name="data"></param> /// <param name="expected"></param> /// <param name="options"></param> /// <param name="cancellationToken"></param> public async Task VerifyFixAsync( CompilerDiagnosticFixTestData data, ExpectedTestState expected, TestOptions options = null, CancellationToken cancellationToken = default) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (expected == null) { throw new ArgumentNullException(nameof(expected)); } cancellationToken.ThrowIfCancellationRequested(); options ??= Options; TFixProvider fixProvider = Activator.CreateInstance <TFixProvider>(); VerifyFixableDiagnostics(fixProvider, data.DiagnosticId); using (Workspace workspace = new AdhocWorkspace()) { (Document document, ImmutableArray <ExpectedDocument> expectedDocuments) = CreateDocument(workspace.CurrentSolution, data.Source, data.AdditionalFiles, options); Project project = document.Project; document = project.GetDocument(document.Id); ImmutableArray <Diagnostic> previousDiagnostics = ImmutableArray <Diagnostic> .Empty; var fixRegistered = false; while (true) { cancellationToken.ThrowIfCancellationRequested(); Compilation compilation = await document.Project.GetCompilationAsync(cancellationToken); ImmutableArray <Diagnostic> diagnostics = compilation.GetDiagnostics(cancellationToken: cancellationToken); int length = diagnostics.Length; if (length == 0) { if (!fixRegistered) { Fail("No compiler diagnostic found."); } break; } if (previousDiagnostics.Any()) { VerifyNoNewCompilerDiagnostics(previousDiagnostics, diagnostics, options); } if (DiagnosticDeepEqualityComparer.Equals(diagnostics, previousDiagnostics)) { Fail("Same diagnostics returned before and after the fix was applied.", diagnostics); } Diagnostic diagnostic = FindDiagnosticToFix(diagnostics); if (diagnostic == null) { if (!fixRegistered) { Fail($"No compiler diagnostic with ID '{data.DiagnosticId}' found.", diagnostics); } break; } CodeAction action = null; List <CodeAction> candidateActions = null; var context = new CodeFixContext( document, diagnostic, (a, d) => { if ((data.EquivalenceKey == null || string.Equals(data.EquivalenceKey, a.EquivalenceKey, StringComparison.Ordinal)) && d.Contains(diagnostic)) { if (action != null) { Fail($"Multiple fixes registered by '{fixProvider.GetType().Name}'.", new CodeAction[] { action, a }); } action = a; } else { (candidateActions ??= new List <CodeAction>()).Add(a); } },
/// <summary> /// Verifies that specified source will produce compiler diagnostic. /// </summary> /// <param name="state"></param> /// <param name="expected"></param> /// <param name="options"></param> /// <param name="cancellationToken"></param> public async Task VerifyFixAsync( CompilerDiagnosticFixTestState state, ExpectedTestState expected, TestOptions options = null, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); options ??= Options; TFixProvider fixProvider = Activator.CreateInstance <TFixProvider>(); VerifyFixableDiagnostics(fixProvider, state.DiagnosticId); using (Workspace workspace = new AdhocWorkspace()) { (Document document, ImmutableArray <ExpectedDocument> expectedDocuments) = CreateDocument(workspace.CurrentSolution, state.Source, state.AdditionalFiles, options); Project project = document.Project; document = project.GetDocument(document.Id); ImmutableArray <Diagnostic> previousDiagnostics = ImmutableArray <Diagnostic> .Empty; var fixRegistered = false; while (true) { cancellationToken.ThrowIfCancellationRequested(); Compilation compilation = await document.Project.GetCompilationAsync(cancellationToken); ImmutableArray <Diagnostic> diagnostics = compilation.GetDiagnostics(cancellationToken: cancellationToken); int length = diagnostics.Length; if (length == 0) { break; } if (previousDiagnostics.Any()) { VerifyNoNewCompilerDiagnostics(previousDiagnostics, diagnostics, options); } if (DiagnosticDeepEqualityComparer.Equals(diagnostics, previousDiagnostics)) { Assert.True(false, "Same diagnostics returned before and after the fix was applied." + diagnostics.ToDebugString()); } Diagnostic diagnostic = FindDiagnosticToFix(diagnostics); if (diagnostic == null) { break; } CodeAction action = null; var context = new CodeFixContext( document, diagnostic, (a, d) => { if ((state.EquivalenceKey == null || string.Equals(state.EquivalenceKey, a.EquivalenceKey, StringComparison.Ordinal)) && d.Contains(diagnostic)) { if (action != null) { Assert.True(false, $"Multiple fixes registered by '{fixProvider.GetType().Name}'."); } action = a; } }, cancellationToken); await fixProvider.RegisterCodeFixesAsync(context); if (action == null) { break; } fixRegistered = true; document = await VerifyAndApplyCodeActionAsync(document, action, expected.CodeActionTitle); previousDiagnostics = diagnostics; } Assert.True(fixRegistered, "No code fix has been registered."); await VerifyExpectedDocument(expected, document, cancellationToken); if (expectedDocuments.Any()) { await VerifyAdditionalDocumentsAsync(document.Project, expectedDocuments, cancellationToken); } } Diagnostic FindDiagnosticToFix(ImmutableArray <Diagnostic> diagnostics) { Diagnostic match = null; foreach (Diagnostic diagnostic in diagnostics) { if (string.Equals(diagnostic.Id, state.DiagnosticId, StringComparison.Ordinal)) { if (match == null || diagnostic.Location.SourceSpan.Start > match.Location.SourceSpan.Start) { match = diagnostic; } } } return(match); } }