protected static CodeAction?TryGetCodeActionToApply(int iteration, ImmutableArray <CodeAction> actions, int?codeActionIndex, string?codeActionEquivalenceKey, Action <CodeAction, IVerifier>?codeActionVerifier, IVerifier verifier) { CodeAction?result; if (codeActionIndex.HasValue && codeActionEquivalenceKey != null) { var expectedAction = actions.FirstOrDefault(action => action.EquivalenceKey == codeActionEquivalenceKey); if (expectedAction is null && iteration > 0) { // No matching code action was found. This is acceptable if this is not the first iteration. return(null); } verifier.True( actions.Length > codeActionIndex, $"Expected to find a code action at index '{codeActionIndex}', but only '{actions.Length}' code actions were found."); verifier.Equal( codeActionEquivalenceKey, actions[codeActionIndex.Value].EquivalenceKey, "The code action equivalence key and index must be consistent when both are specified."); result = actions[codeActionIndex.Value]; } else if (codeActionEquivalenceKey != null) { result = actions.FirstOrDefault(x => x.EquivalenceKey == codeActionEquivalenceKey); } else if (actions.Length > (codeActionIndex ?? 0)) { result = actions[codeActionIndex ?? 0]; } else { return(null); } if (result is object) { codeActionVerifier?.Invoke(result, verifier); } return(result); }
private async Task <Project> FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope scope, ImmutableArray <DiagnosticAnalyzer> analyzers, ImmutableArray <CodeFixProvider> codeFixProviders, int?codeFixIndex, string codeFixEquivalenceKey, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) { var expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsAsync(project.Solution, analyzers, CompilerDiagnostics, cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } verifier.False(--numberOfIterations < -1, "The upper limit for the number of fix all iterations was exceeded"); Diagnostic firstDiagnostic = null; CodeFixProvider effectiveCodeFixProvider = null; string equivalenceKey = null; foreach (var diagnostic in analyzerDiagnostics) { var actions = new List <(CodeAction, CodeFixProvider)>(); foreach (var codeFixProvider in codeFixProviders) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add((a, codeFixProvider)), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); } var actionToApply = TryGetCodeActionToApply(actions.Select(a => a.Item1).ToList(), codeFixIndex, codeFixEquivalenceKey, verifier); if (actionToApply != null) { firstDiagnostic = diagnostic; effectiveCodeFixProvider = actions.SingleOrDefault(a => a.Item1 == actionToApply).Item2; equivalenceKey = actionToApply.EquivalenceKey; break; } } var fixAllProvider = effectiveCodeFixProvider?.GetFixAllProvider(); if (firstDiagnostic == null || fixAllProvider == null) { numberOfIterations++; break; } previousDiagnostics = analyzerDiagnostics; done = true; FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); var analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id); var compilerDiagnosticIds = codeFixProviders.SelectMany(codeFixProvider => codeFixProvider.FixableDiagnosticIds).Where(x => x.StartsWith("CS", StringComparison.Ordinal) || x.StartsWith("BC", StringComparison.Ordinal)); var disabledDiagnosticIds = project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key); var relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct(); var fixAllContext = new FixAllContext(project.GetDocument(firstDiagnostic.Location.SourceTree), effectiveCodeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken); var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (action == null) { return(project); } var fixedProject = await ApplyFixAsync(project, action, cancellationToken).ConfigureAwait(false); if (fixedProject != project) { done = false; project = await RecreateProjectDocumentsAsync(fixedProject, verifier, cancellationToken).ConfigureAwait(false); } }while (!done); if (expectedNumberOfIterations >= 0) { verifier.Equal(expectedNumberOfIterations, expectedNumberOfIterations - numberOfIterations, $"Expected '{expectedNumberOfIterations}' iterations but found '{expectedNumberOfIterations - numberOfIterations}' iterations."); } else { verifier.True(numberOfIterations >= 0, "The upper limit for the number of code fix iterations was exceeded"); } return(project); }
private async Task <Project> FixEachAnalyzerDiagnosticAsync(ImmutableArray <DiagnosticAnalyzer> analyzers, ImmutableArray <CodeFixProvider> codeFixProviders, int?codeFixIndex, string codeFixEquivalenceKey, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) { var expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsAsync(project.Solution, analyzers, CompilerDiagnostics, cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } verifier.True(--numberOfIterations >= -1, "The upper limit for the number of code fix iterations was exceeded"); previousDiagnostics = analyzerDiagnostics; done = true; var anyActions = false; foreach (var diagnostic in analyzerDiagnostics) { var actions = new List <CodeAction>(); foreach (var codeFixProvider in codeFixProviders) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); } var actionToApply = TryGetCodeActionToApply(actions, codeFixIndex, codeFixEquivalenceKey, verifier); if (actionToApply != null) { anyActions = true; var fixedProject = await ApplyFixAsync(project, actionToApply, cancellationToken).ConfigureAwait(false); if (fixedProject != project) { done = false; project = await RecreateProjectDocumentsAsync(fixedProject, verifier, cancellationToken).ConfigureAwait(false); break; } } } if (!anyActions) { verifier.True(done, "Expected to be done executing actions."); // Avoid counting iterations that do not provide any code actions numberOfIterations++; } }while (!done); if (expectedNumberOfIterations >= 0) { verifier.Equal(expectedNumberOfIterations, expectedNumberOfIterations - numberOfIterations, $"Expected '{expectedNumberOfIterations}' iterations but found '{expectedNumberOfIterations - numberOfIterations}' iterations."); } else { verifier.True(numberOfIterations >= 0, "The upper limit for the number of code fix iterations was exceeded"); } return(project); }