private static CodeAction TryGetCodeActionToApply(List <CodeAction> actions, int?codeFixIndex, string codeFixEquivalenceKey, IVerifier verifier) { if (codeFixIndex.HasValue && codeFixEquivalenceKey != null) { if (actions.Count <= codeFixIndex) { return(null); } verifier.Equal( codeFixEquivalenceKey, actions[codeFixIndex.Value].EquivalenceKey, "The code action equivalence key and index must be consistent when both are specified."); return(actions[codeFixIndex.Value]); } else if (codeFixEquivalenceKey != null) { return(actions.Find(x => x.EquivalenceKey == codeFixEquivalenceKey)); } else if (actions.Count > (codeFixIndex ?? 0)) { return(actions[codeFixIndex ?? 0]); } else { return(null); } }
protected static CodeAction?TryGetCodeActionToApply(ImmutableArray <CodeAction> actions, int?codeActionIndex, string?codeActionEquivalenceKey, IVerifier verifier) { if (codeActionIndex.HasValue && codeActionEquivalenceKey != null) { if (actions.Length <= codeActionIndex) { return(null); } verifier.Equal( codeActionEquivalenceKey, actions[codeActionIndex.Value].EquivalenceKey, "The code action equivalence key and index must be consistent when both are specified."); return(actions[codeActionIndex.Value]); } else if (codeActionEquivalenceKey != null) { return(actions.FirstOrDefault(x => x.EquivalenceKey == codeActionEquivalenceKey)); } else if (actions.Length > (codeActionIndex ?? 0)) { return(actions[codeActionIndex ?? 0]); } else { return(null); } }
private async Task VerifyFixAsync( string language, ImmutableArray <DiagnosticAnalyzer> analyzers, ImmutableArray <CodeFixProvider> codeFixProviders, SolutionState oldState, SolutionState newState, int numberOfIterations, Func <ImmutableArray <DiagnosticAnalyzer>, ImmutableArray <CodeFixProvider>, int?, string?, Project, int, IVerifier, CancellationToken, Task <Project> > getFixedProject, IVerifier verifier, CancellationToken cancellationToken) { var project = await CreateProjectAsync(oldState.Sources.ToArray(), oldState.AdditionalFiles.ToArray(), oldState.AdditionalProjects.ToArray(), oldState.AdditionalReferences.ToArray(), language, cancellationToken); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); project = await getFixedProject(analyzers, codeFixProviders, CodeFixIndex, CodeFixEquivalenceKey, project, numberOfIterations, verifier, cancellationToken).ConfigureAwait(false); // After applying all of the code fixes, compare the resulting string to the inputted one var updatedDocuments = project.Documents.ToArray(); verifier.Equal(newState.Sources.Count, updatedDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.Sources)}' and '{nameof(updatedDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.Sources)}' contains '{newState.Sources.Count}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); for (var i = 0; i < updatedDocuments.Length; i++) { var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); verifier.EqualOrDiff(newState.Sources[i].content.ToString(), actual.ToString(), $"content of '{newState.Sources[i].filename}' did not match. Diff shown with expected as baseline:"); verifier.Equal(newState.Sources[i].content.Encoding, actual.Encoding, $"encoding of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.Encoding}' but was '{actual.Encoding}'"); verifier.Equal(newState.Sources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); verifier.Equal(newState.Sources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{newState.Sources[i].filename}' but was '{updatedDocuments[i].Name}'"); } var updatedAdditionalDocuments = project.AdditionalDocuments.ToArray(); verifier.Equal(newState.AdditionalFiles.Count, updatedAdditionalDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' and '{nameof(updatedAdditionalDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' contains '{newState.AdditionalFiles.Count}' documents and '{nameof(updatedAdditionalDocuments)}' contains '{updatedAdditionalDocuments.Length}' documents"); for (var i = 0; i < updatedAdditionalDocuments.Length; i++) { var actual = await updatedAdditionalDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); verifier.EqualOrDiff(newState.AdditionalFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AdditionalFiles[i].filename}' did not match. Diff shown with expected as baseline:"); verifier.Equal(newState.AdditionalFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.Encoding}' but was '{actual.Encoding}'"); verifier.Equal(newState.AdditionalFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); verifier.Equal(newState.AdditionalFiles[i].filename, updatedAdditionalDocuments[i].Name, $"file name was expected to be '{newState.AdditionalFiles[i].filename}' but was '{updatedAdditionalDocuments[i].Name}'"); } }
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); }