private static async Task <(CodeAction, Document)> GetFixAsync( ImmutableArray <Diagnostic> diagnostics, DiagnosticDescriptor descriptor, CodeFixProvider fixer, Project project, CodeFixerOptions options, IFormatProvider formatProvider = null, CancellationToken cancellationToken = default) { if (diagnostics.Length == 1) { return(await GetFixAsync(diagnostics[0], fixer, project, options, formatProvider, cancellationToken).ConfigureAwait(false)); } FixAllProvider fixAllProvider = fixer.GetFixAllProvider(); if (fixAllProvider == null) { if (options.DiagnosticIdsFixableOneByOne.Contains(descriptor.Id)) { return(await GetFixAsync(diagnostics[0], fixer, project, options, formatProvider, cancellationToken).ConfigureAwait(false)); } WriteLine($" '{fixer.GetType().FullName}' does not have FixAllProvider", ConsoleColor.Yellow, Verbosity.Diagnostic); return(default);
internal IEnumerable <Tuple <Diagnostic, CodeFixCollection> > GetDiagnosticAndFixes( IEnumerable <Diagnostic> diagnostics, DiagnosticAnalyzer provider, CodeFixProvider fixer, TestDiagnosticAnalyzerDriver testDriver, Document document, TextSpan span, string annotation, string fixAllActionId) { foreach (var diagnostic in diagnostics) { if (annotation == null) { var fixes = new List <CodeFix>(); var context = new CodeFixContext(document, diagnostic, (a, d) => fixes.Add(new CodeFix(a, d)), CancellationToken.None); fixer.RegisterCodeFixesAsync(context).Wait(); if (fixes.Any()) { var codeFix = new CodeFixCollection(fixer, diagnostic.Location.SourceSpan, fixes); yield return(Tuple.Create(diagnostic, codeFix)); } } else { var fixAllProvider = fixer.GetFixAllProvider(); Assert.NotNull(fixAllProvider); FixAllScope scope = GetFixAllScope(annotation); Func <Document, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getDocumentDiagnosticsAsync = (d, diagIds, c) => { var root = d.GetSyntaxRootAsync().Result; var diags = testDriver.GetDocumentDiagnostics(provider, d, root.FullSpan); diags = diags.Where(diag => diagIds.Contains(diag.Id)); return(Task.FromResult(diags)); }; Func <Project, bool, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getProjectDiagnosticsAsync = (p, includeAllDocumentDiagnostics, diagIds, c) => { var diags = includeAllDocumentDiagnostics ? testDriver.GetAllDiagnostics(provider, p) : testDriver.GetProjectDiagnostics(provider, p); diags = diags.Where(diag => diagIds.Contains(diag.Id)); return(Task.FromResult(diags)); }; var diagnosticIds = ImmutableHashSet.Create(diagnostic.Id); var fixAllDiagnosticProvider = new FixAllCodeActionContext.FixAllDiagnosticProvider(diagnosticIds, getDocumentDiagnosticsAsync, getProjectDiagnosticsAsync); var fixAllContext = new FixAllContext(document, fixer, scope, fixAllActionId, diagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); var fixAllFix = fixAllProvider.GetFixAsync(fixAllContext).WaitAndGetResult(CancellationToken.None); if (fixAllFix != null) { var codeFix = new CodeFixCollection(fixAllProvider, diagnostic.Location.SourceSpan, ImmutableArray.Create(new CodeFix(fixAllFix, diagnostic))); yield return(Tuple.Create(diagnostic, codeFix)); } } } }
/// <summary> /// Gets an optional <see cref="FixAllProviderInfo"/> for the given code fix provider. /// </summary> private static FixAllProviderInfo?CreateWithCodeFixer(CodeFixProvider provider) { var fixAllProvider = provider.GetFixAllProvider(); if (fixAllProvider == null) { return(null); } var diagnosticIds = fixAllProvider.GetSupportedFixAllDiagnosticIds(provider); if (diagnosticIds == null || diagnosticIds.IsEmpty()) { return(null); } var scopes = fixAllProvider.GetSupportedFixAllScopes().ToImmutableArrayOrEmpty(); if (scopes.IsEmpty) { return(null); } return(new CodeFixerFixAllProviderInfo(fixAllProvider, diagnosticIds, scopes)); }
internal static async Task <ImmutableList <CodeFixEquivalenceGroup> > CreateAsync(CodeFixProvider codeFixProvider, IEnumerable <Diagnostic> allDiagnostics, Solution solution) { var fixAllProvider = codeFixProvider.GetFixAllProvider(); var relevantDiagnostics = allDiagnostics.Where(diagnostic => codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)).ToImmutableArray(); if (fixAllProvider == null) { return(ImmutableList.Create <CodeFixEquivalenceGroup>()); } List <CodeAction> actions = new List <CodeAction>(); foreach (var diagnostic in relevantDiagnostics) { actions.AddRange(await GetFixesAsync(solution, codeFixProvider, diagnostic).ConfigureAwait(false)); } List <CodeFixEquivalenceGroup> groups = new List <CodeFixEquivalenceGroup>(); foreach (var item in actions.GroupBy(x => x.EquivalenceKey)) { groups.Add(new CodeFixEquivalenceGroup(item.Key, solution, fixAllProvider, codeFixProvider, relevantDiagnostics)); } return(groups.ToImmutableList()); }
private static void VerifyFixAllCodeFix(string path, string pathToExpected, DiagnosticAnalyzer diagnosticAnalyzer, CodeFixProvider codeFixProvider, string codeFixTitle) { var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return; } using (var workspace = new AdhocWorkspace()) { var file = new FileInfo(path); var parseOptions = GetParseOptionsWithDifferentLanguageVersions(null, file.Extension); foreach (var parseOption in parseOptions) { var document = CreateProject(file, GeneratedAssemblyName, workspace) .AddDocument(file, true) .Documents .Single(d => d.Name == file.Name); RunFixAllProvider(diagnosticAnalyzer, codeFixProvider, codeFixTitle, fixAllProvider, document, parseOption, pathToExpected); } } }
private async Task <(ImmutableArray <Diagnostic>, ImmutableArray <CodeAction>, CodeAction actionToinvoke)> GetDiagnosticAndFixesAsync( IEnumerable <Diagnostic> diagnostics, DiagnosticAnalyzer provider, CodeFixProvider fixer, TestDiagnosticAnalyzerDriver testDriver, Document document, TextSpan span, FixAllScope?scope, int index) { Assert.NotEmpty(diagnostics); var intersectingDiagnostics = diagnostics.Where(d => d.Location.SourceSpan.IntersectsWith(span)) .ToImmutableArray(); var fixes = new List <CodeFix>(); foreach (var diagnostic in intersectingDiagnostics) { var context = new CodeFixContext( document, diagnostic, (a, d) => fixes.Add(new CodeFix(document.Project, a, d)), CancellationToken.None); await fixer.RegisterCodeFixesAsync(context); } var actions = fixes.SelectAsArray(f => f.Action); actions = actions.SelectMany(a => a is TopLevelSuppressionCodeAction ? a.NestedCodeActions : ImmutableArray.Create(a)).ToImmutableArray(); actions = MassageActions(actions); if (scope == null) { // Simple code fix. return(intersectingDiagnostics, actions, actions.Length == 0 ? null : actions[index]); } else { var equivalenceKey = actions[index].EquivalenceKey; // Fix all fix. var fixAllProvider = fixer.GetFixAllProvider(); Assert.NotNull(fixAllProvider); var fixAllState = GetFixAllState( fixAllProvider, diagnostics, provider, fixer, testDriver, document, scope.Value, equivalenceKey); var fixAllContext = fixAllState.CreateFixAllContext(new ProgressTracker(), CancellationToken.None); var fixAllFix = await fixAllProvider.GetFixAsync(fixAllContext); // We have collapsed the fixes down to the single fix-all fix, so we just let our // caller know they should pull that entry out of the result. return(intersectingDiagnostics, ImmutableArray.Create(fixAllFix), fixAllFix); } }
private async Task <IEnumerable <Tuple <Diagnostic, CodeFixCollection> > > GetDiagnosticAndFixesAsync( IEnumerable <Diagnostic> diagnostics, DiagnosticAnalyzer provider, CodeFixProvider fixer, TestDiagnosticAnalyzerDriver testDriver, Document document, TextSpan span, FixAllScope?scope, string fixAllActionId) { Assert.NotEmpty(diagnostics); var result = new List <Tuple <Diagnostic, CodeFixCollection> >(); if (scope == null) { // Simple code fix. foreach (var diagnostic in diagnostics) { var fixes = new List <CodeFix>(); var context = new CodeFixContext(document, diagnostic, (a, d) => fixes.Add(new CodeFix(document.Project, a, d)), CancellationToken.None); await fixer.RegisterCodeFixesAsync(context); if (fixes.Any()) { var codeFix = new CodeFixCollection( fixer, diagnostic.Location.SourceSpan, fixes.ToImmutableArray(), fixAllState: null, supportedScopes: ImmutableArray <FixAllScope> .Empty, firstDiagnostic: null); result.Add(Tuple.Create(diagnostic, codeFix)); } } } else { // Fix all fix. var fixAllProvider = fixer.GetFixAllProvider(); Assert.NotNull(fixAllProvider); var fixAllState = GetFixAllState(fixAllProvider, diagnostics, provider, fixer, testDriver, document, scope.Value, fixAllActionId); var fixAllContext = fixAllState.CreateFixAllContext(new ProgressTracker(), CancellationToken.None); var fixAllFix = await fixAllProvider.GetFixAsync(fixAllContext); if (fixAllFix != null) { // Same fix applies to each diagnostic in scope. foreach (var diagnostic in diagnostics) { var diagnosticSpan = diagnostic.Location.IsInSource ? diagnostic.Location.SourceSpan : default(TextSpan); var codeFix = new CodeFixCollection( fixAllProvider, diagnosticSpan, ImmutableArray.Create(new CodeFix(document.Project, fixAllFix, diagnostic)), fixAllState: null, supportedScopes: ImmutableArray <FixAllScope> .Empty, firstDiagnostic: null); result.Add(Tuple.Create(diagnostic, codeFix)); } } } return(result); }
static XElement CreateFixAllProviderElement(CodeFixProvider fixer) { FixAllProvider fixAllProvider = fixer.GetFixAllProvider(); if (fixAllProvider != null) { return(new XElement("FixAllProvider", new XAttribute("Name", fixAllProvider.GetType().FullName))); } return(null); }
private async static Task VerifyFixAllAsync(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, bool allowNewCompilerDiagnostics, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB, string equivalenceKey = null) { var document = CreateDocument(oldSource, language, languageVersionCSharp, languageVersionVB); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true); var getDocumentDiagnosticsAsync = analyzer != null ? (Func <Document, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > >)(async(doc, ids, ct) => await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { doc }).ConfigureAwait(true)) : (async(doc, ids, ct) => { var compilerDiags = await GetCompilerDiagnosticsAsync(doc).ConfigureAwait(true); return(compilerDiags.Where(d => codeFixProvider.FixableDiagnosticIds.Contains(d.Id))); }); Func <Project, bool, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getProjectDiagnosticsAsync = async(proj, b, ids, ct) => { var theDocs = proj.Documents; var diags = await Task.WhenAll(theDocs.Select(d => getDocumentDiagnosticsAsync?.Invoke(d, ids, ct))).ConfigureAwait(true); return(diags.SelectMany(d => d)); }; var fixAllProvider = codeFixProvider.GetFixAllProvider(); var fixAllDiagnosticProvider = new FixAllDiagnosticProvider(codeFixProvider.FixableDiagnosticIds.ToImmutableHashSet(), getDocumentDiagnosticsAsync, getProjectDiagnosticsAsync); if (equivalenceKey == null) { equivalenceKey = codeFixProvider.GetType().Name; } var fixAllContext = new FixAllContext(document, codeFixProvider, FixAllScope.Document, equivalenceKey, codeFixProvider.FixableDiagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(true); if (action == null) { throw new Exception("No action supplied for the code fix."); } document = await ApplyFixAsync(document, action).ConfigureAwait(true); //check if applying the code fix introduced any new compiler diagnostics var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output document = document.WithSyntaxRoot(Formatter.Format(await document.GetSyntaxRootAsync().ConfigureAwait(true), Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n\r\nNew document:\r\n{(await document.GetSyntaxRootAsync().ConfigureAwait(true)).ToFullString()}\r\n"); } var actual = await GetStringFromDocumentAsync(document).ConfigureAwait(true); Assert.Equal(newSource, actual); }
private async Task VerifyFixAllAsync(CodeFixProvider codeFixProvider, string[] oldSources, string[] newSources, bool allowNewCompilerDiagnostics) { var diagnosticIds = codeFixProvider.FixableDiagnosticIds; var project = CreateProject(oldSources, LanguageNames.CSharp); var compilerDiagnostics = (await Task.WhenAll(project.Documents.Select(d => GetCompilerDiagnosticsAsync(d))).ConfigureAwait(true)).SelectMany(d => d); Func <Document, Task <IEnumerable <Diagnostic> > > getDocumentDiagnosticsAsync = async doc => { var compilerDiags = await GetCompilerDiagnosticsAsync(doc).ConfigureAwait(true); return(compilerDiags.Where(d => diagnosticIds.Contains(d.Id))); }; Func <Project, bool, Task <IEnumerable <Diagnostic> > > getProjectDiagnosticsAsync = async(proj, b) => { var theDocs = proj.Documents; var diags = await Task.WhenAll(theDocs.Select(d => getDocumentDiagnosticsAsync(d))).ConfigureAwait(true); return(diags.SelectMany(d => d)); }; var fixAllProvider = codeFixProvider.GetFixAllProvider(); var fixAllContext = NewFixAllContext(null, project, codeFixProvider, FixAllScope.Solution, null,//code action ids in codecracker are always null diagnosticIds, (doc, diagIds, cancelationToken) => getDocumentDiagnosticsAsync(doc), (theProject, b, diagIds, cancelationToken) => getProjectDiagnosticsAsync(theProject, b), CancellationToken.None); var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(true); if (action == null) { throw new Exception("No action supplied for the code fix."); } project = await ApplyFixAsync(project, action).ConfigureAwait(true); //check if applying the code fix introduced any new compiler diagnostics var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, (await Task.WhenAll(project.Documents.Select(d => GetCompilerDiagnosticsAsync(d))).ConfigureAwait(true)).SelectMany(d => d)); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n"); } var docs = project.Documents.ToArray(); for (int i = 0; i < docs.Length; i++) { var document = docs[i]; var actual = await GetStringFromDocumentAsync(document).ConfigureAwait(true); Assert.Equal(newSources[i], actual); } }
private bool?IsBatchFixer(CodeFixProvider provider) { var fixAllProvider = provider.GetFixAllProvider(); if (fixAllProvider == null) { return(null); } else { return(fixAllProvider.GetType() == _batchFixerType); } }
private async Task VerifyFixAllAsync(string language, CodeFixProvider codeFixProvider, string oldSource, string newSource, bool allowNewCompilerDiagnostics) { var diagnosticIds = codeFixProvider.FixableDiagnosticIds; var document = CreateDocument(oldSource, language); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true); Func <Document, Task <IEnumerable <Diagnostic> > > getDocumentDiagnosticsAsync = async doc => { var compilerDiags = await GetCompilerDiagnosticsAsync(doc).ConfigureAwait(true); return(compilerDiags.Where(d => diagnosticIds.Contains(d.Id))); }; Func <Project, bool, Task <IEnumerable <Diagnostic> > > getProjectDiagnosticsAsync = async(proj, b) => { var theDocs = proj.Documents; var diags = await Task.WhenAll(theDocs.Select(d => getDocumentDiagnosticsAsync(d))).ConfigureAwait(true); return(diags.SelectMany(d => d)); }; var fixAllProvider = codeFixProvider.GetFixAllProvider(); var fixAllContext = NewFixAllContext(document, document.Project, codeFixProvider, FixAllScope.Document, null,//code action ids in codecracker are always null diagnosticIds, (doc, diagIds, cancelationToken) => getDocumentDiagnosticsAsync(doc), (theProject, b, diagIds, cancelationToken) => getProjectDiagnosticsAsync(theProject, b), CancellationToken.None); var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(true); if (action == null) { throw new Exception("No action supplied for the code fix."); } document = await ApplyFixAsync(document, action).ConfigureAwait(true); //check if applying the code fix introduced any new compiler diagnostics var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output document = document.WithSyntaxRoot(Formatter.Format(await document.GetSyntaxRootAsync().ConfigureAwait(true), Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n\r\nNew document:\r\n{(await document.GetSyntaxRootAsync().ConfigureAwait(true)).ToFullString()}\r\n"); } var actual = await GetStringFromDocumentAsync(document).ConfigureAwait(true); Assert.Equal(newSource, actual); }
public async Task <Solution> ApplyCodeFixesAsync( Solution solution, CodeAnalysisResult result, CodeFixProvider codeFix, string diagnosticId, ILogger logger, CancellationToken cancellationToken) { var fixAllProvider = codeFix.GetFixAllProvider(); if (fixAllProvider?.GetSupportedFixAllScopes()?.Contains(FixAllScope.Solution) != true) { logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_doesnt_support_Fix_All_in_Solution, diagnosticId, codeFix.GetType().Name); return(solution); } var project = solution.Projects.FirstOrDefault(); if (project == null) { throw new InvalidOperationException(string.Format(Resources.Solution_0_has__no_projects, solution)); } var fixAllContext = new FixAllContext( project: project, codeFixProvider: codeFix, scope: FixAllScope.Solution, codeActionEquivalenceKey: null, diagnosticIds: codeFix.FixableDiagnosticIds, fixAllDiagnosticProvider: new DiagnosticProvider(result), cancellationToken: cancellationToken); try { var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); var operations = action != null ? await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false) : ImmutableArray <CodeActionOperation> .Empty; var applyChangesOperation = operations.OfType <ApplyChangesOperation>().SingleOrDefault(); return(applyChangesOperation?.ChangedSolution ?? solution); } catch (Exception ex) { logger.LogWarning(Resources.Failed_to_apply_code_fix_0_for_1_2, codeFix?.GetType().Name, diagnosticId, ex.Message); return(solution); } }
private void VerifyFixAll( Document document, DiagnosticAnalyzer analyzerOpt, CodeFixProvider codeFixProvider, string newSource, string newSourceFileName, IEnumerable <TestAdditionalDocument> additionalFiles, bool allowNewCompilerDiagnostics, TestValidationMode validationMode) { var fixableDiagnosticIds = codeFixProvider.FixableDiagnosticIds.ToSet(); Func <IEnumerable <Diagnostic>, ImmutableArray <Diagnostic> > getFixableDiagnostics = diags => diags.Where(d => fixableDiagnosticIds.Contains(d.Id)).ToImmutableArrayOrEmpty(); var analyzerDiagnostics = GetSortedDiagnostics(analyzerOpt, new[] { document }, additionalFiles: additionalFiles, validationMode: validationMode); var compilerDiagnostics = document.GetSemanticModelAsync().Result.GetDiagnostics(); var fixableDiagnostics = getFixableDiagnostics(analyzerDiagnostics.Concat(compilerDiagnostics)); var fixAllProvider = codeFixProvider.GetFixAllProvider(); var diagnosticProvider = new FixAllDiagnosticProvider(analyzerOpt, additionalFiles, validationMode, getFixableDiagnostics); var fixAllContext = new FixAllContext(document, codeFixProvider, FixAllScope.Document, string.Empty, fixableDiagnostics.Select(d => d.Id), diagnosticProvider, CancellationToken.None); var codeAction = fixAllProvider.GetFixAsync(fixAllContext).Result; document = document.Apply(codeAction); additionalFiles = document.Project.AdditionalDocuments.Select(a => new TestAdditionalDocument(a)); additionalFiles = document.Project.AdditionalDocuments.Select(a => new TestAdditionalDocument(a)); analyzerDiagnostics = GetSortedDiagnostics(analyzerOpt, new[] { document }, additionalFiles: additionalFiles, validationMode: validationMode); var updatedCompilerDiagnostics = document.GetSemanticModelAsync().Result.GetDiagnostics(); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, updatedCompilerDiagnostics); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, document.GetSemanticModelAsync().Result.GetDiagnostics()); Assert.True(false, string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", newCompilerDiagnostics.Select(d => d.ToString()).Join("\r\n"), document.GetSyntaxRootAsync().Result.ToFullString())); } var actualText = GetActualTextForNewDocument(document, newSourceFileName); Assert.Equal(newSource, actualText.ToString()); }
public static async Task <ImmutableArray <CodeFixEquivalenceGroup> > CreateAsync(CodeFixProvider codeFixProvider, ImmutableArray <Diagnostic> allDiagnostics, Project project, CancellationToken cancellationToken) { var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(ImmutableArray.Create <CodeFixEquivalenceGroup>()); } var groupLookup = new Dictionary <string, Builder>(); var equivalenceKeys = new HashSet <string>(); foreach (var diagnostic in allDiagnostics) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { continue; } var codeActions = await GetFixesAsync(project, codeFixProvider, diagnostic, cancellationToken).ConfigureAwait(false); equivalenceKeys.Clear(); foreach (var action in codeActions) { if (action.EquivalenceKey != null) { equivalenceKeys.Add(action.EquivalenceKey); } } foreach (var key in equivalenceKeys) { if (!groupLookup.TryGetValue(key, out var group)) { groupLookup.Add(key, group = new Builder(key, project, fixAllProvider, codeFixProvider)); } group.AddDiagnostic(diagnostic); } } return(groupLookup.Select(x => x.Value.ToEquivalenceGroup()).ToImmutableArray()); }
/// <summary> /// Fix the solution by applying the code fix. /// </summary> /// <returns>The fixed solution or the same instance if no fix.</returns> internal static async Task <Solution> ApplyAsync(CodeFixProvider codeFix, FixAllScope scope, TestDiagnosticProvider diagnosticProvider, CancellationToken cancellationToken) { var context = new FixAllContext( diagnosticProvider.Document, codeFix, scope, diagnosticProvider.EquivalenceKey, codeFix.FixableDiagnosticIds, diagnosticProvider, cancellationToken); var action = await codeFix.GetFixAllProvider().GetFixAsync(context).ConfigureAwait(false); var operations = await action.GetOperationsAsync(cancellationToken) .ConfigureAwait(false); return(operations.OfType <ApplyChangesOperation>() .Single() .ChangedSolution); }
private async Task VerifyFixAllAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string[] oldSources, string[] newSources, bool allowNewCompilerDiagnostics) { var project = CreateProject(oldSources, LanguageNames.CSharp); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, project.Documents.ToArray()).ConfigureAwait(true); var compilerDiagnostics = (await Task.WhenAll(project.Documents.Select(d => GetCompilerDiagnosticsAsync(d))).ConfigureAwait(true)).SelectMany(d => d); var fixAllProvider = codeFixProvider.GetFixAllProvider(); var fixAllContext = NewFixAllContext(null, project, codeFixProvider, FixAllScope.Solution, null,//code action ids in codecracker are always null codeFixProvider.FixableDiagnosticIds, (doc, diagnosticIds, cancelationToken) => Task.FromResult(analyzerDiagnostics.Where(d => d.Location.SourceTree.FilePath == doc.Name)), (theProject, b, diagnosticIds, cancelationToken) => Task.FromResult((IEnumerable <Diagnostic>)analyzerDiagnostics), //todo: verify, probably wrong CancellationToken.None); var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(true); if (action == null) { throw new Exception("No action supplied for the code fix."); } project = await ApplyFixAsync(project, action).ConfigureAwait(true); //check if applying the code fix introduced any new compiler diagnostics var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, (await Task.WhenAll(project.Documents.Select(d => GetCompilerDiagnosticsAsync(d))).ConfigureAwait(true)).SelectMany(d => d)); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n"); } var docs = project.Documents.ToArray(); for (int i = 0; i < docs.Length; i++) { var document = docs[i]; var actual = await GetStringFromDocumentAsync(document).ConfigureAwait(true); Assert.Equal(newSources[i], actual); } }
/// <summary> /// Verifies that /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics /// 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="suppressedDiagnostics">The diagnostics to suppress when compiling.</param> /// <param name="metadataReferences">The meta data metadataReferences to add to the compilation.</param> /// <param name="fixTitle">The title of the fix to apply if more than one.</param> /// <param name="allowCompilationErrors">If compilation errors are accepted in the fixed code.</param> public static void FixAll(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList <string> fixedCode, IEnumerable <string> suppressedDiagnostics, IEnumerable <MetadataReference> metadataReferences, string fixTitle, AllowCompilationErrors allowCompilationErrors) { VerifyAnalyzerSupportsDiagnostics(analyzer, diagnosticsAndSources.ExpectedDiagnostics); VerifyCodeFixSupportsAnalyzer(analyzer, codeFix); var sln = CodeFactory.CreateSolution(diagnosticsAndSources, analyzer, suppressedDiagnostics, metadataReferences); var diagnostics = Analyze.GetDiagnostics(analyzer, sln); VerifyDiagnostics(diagnosticsAndSources, diagnostics); FixAllOneByOne(analyzer, codeFix, sln, fixedCode, fixTitle, allowCompilationErrors); var fixAllProvider = codeFix.GetFixAllProvider(); if (fixAllProvider != null) { foreach (var scope in fixAllProvider.GetSupportedFixAllScopes()) { FixAllByScope(analyzer, codeFix, sln, fixedCode, fixTitle, allowCompilationErrors, scope); } } }
/// <summary> /// Verifies that /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics /// 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="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param> /// <param name="metadataReference">The meta data metadataReference to add to the compilation.</param> /// <param name="fixTitle">The title of the fix to apply if more than one.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <param name="allowCompilationErrors">If compilation errors are accepted in the fixed code.</param> public static async Task FixAllAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList <string> fixedCode, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReference, string fixTitle, AllowCompilationErrors allowCompilationErrors) { var data = await CreateDiagnosticsMetadataAsync( analyzer, codeFix, diagnosticsAndSources, compilationOptions, metadataReference); await FixAllOneByOneAsync(analyzer, codeFix, fixedCode, fixTitle, allowCompilationErrors, data).ConfigureAwait(false); var fixAllProvider = codeFix.GetFixAllProvider(); if (fixAllProvider != null) { foreach (var scope in fixAllProvider.GetSupportedFixAllScopes()) { await FixAllByScopeAsync(analyzer, codeFix, fixedCode, fixTitle, allowCompilationErrors, data, scope); } } }
private static void VerifyFixAllCodeFix(string path, string pathToExpected, DiagnosticAnalyzer diagnosticAnalyzer, CodeFixProvider codeFixProvider, string codeFixTitle) { var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return; } using (var workspace = new AdhocWorkspace()) { var document = GetDocument(path, GeneratedAssemblyName, workspace); List <Diagnostic> diagnostics; string actualCode; CalculateDiagnosticsAndCode(diagnosticAnalyzer, document, out diagnostics, out actualCode); Assert.AreNotEqual(0, diagnostics.Count); var fixAllDiagnosticProvider = new FixAllDiagnosticProvider( codeFixProvider.FixableDiagnosticIds.ToImmutableHashSet(), (doc, ids, ct) => Task.FromResult( GetDiagnostics(document.Project.GetCompilationAsync().Result, diagnosticAnalyzer)), null); var fixAllContext = new FixAllContext(document, codeFixProvider, FixAllScope.Document, codeFixTitle, codeFixProvider.FixableDiagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); var codeActionToExecute = fixAllProvider.GetFixAsync(fixAllContext).Result; Assert.IsNotNull(codeActionToExecute); document = ApplyCodeFix(document, codeActionToExecute); CalculateDiagnosticsAndCode(diagnosticAnalyzer, document, out diagnostics, out actualCode); Assert.AreEqual(File.ReadAllText(pathToExpected), actualCode); } }
/// <summary> /// Fix the solution by applying the code fix. /// </summary> /// <returns>The fixed solution or the same instance if no fix.</returns> internal static async Task <Solution> ApplyAsync(CodeFixProvider fix, FixAllScope scope, TestDiagnosticProvider diagnosticProvider, CancellationToken cancellationToken) { var context = new FixAllContext( diagnosticProvider.Document, fix, scope, diagnosticProvider.EquivalenceKey, fix.FixableDiagnosticIds, diagnosticProvider, cancellationToken); var action = await fix.GetFixAllProvider().GetFixAsync(context).ConfigureAwait(false); var operations = await action.GetOperationsAsync(cancellationToken) .ConfigureAwait(false); if (operations.TrySingleOfType(out ApplyChangesOperation? operation)) { return(operation !.ChangedSolution); } throw new InvalidOperationException($"Expected one operation, was {string.Join(", ", operations)}"); }
private async Task VerifyFixAllAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, LanguageNames.CSharp); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(document); var fixAllProvider = codeFixProvider.GetFixAllProvider(); var fixAllContext = NewFixAllContext(document, document.Project, codeFixProvider, FixAllScope.Document, null,//code action ids in codecracker are always null analyzerDiagnostics.Select(a => a.Id), (project, doc, diagnosticIds, cancelationToken) => Task.FromResult <IEnumerable <Diagnostic> >(analyzerDiagnostics), CancellationToken.None); var action = await fixAllProvider.GetFixAsync(fixAllContext); if (action == null) { throw new Exception("No action supplied for the code fix."); } document = await ApplyFixAsync(document, action); //check if applying the code fix introduced any new compiler diagnostics var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document)); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output document = document.WithSyntaxRoot(Formatter.Format(await document.GetSyntaxRootAsync(), Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document)); Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n\r\nNew document:\r\n{(await document.GetSyntaxRootAsync()).ToFullString()}\r\n"); } var actual = await GetStringFromDocumentAsync(document); Assert.Equal(newSource, actual); }
public async Task <Solution> ApplyCodeFixesAsync( Solution solution, CodeAnalysisResult result, CodeFixProvider codeFix, string diagnosticId, ILogger logger, CancellationToken cancellationToken) { var fixAllProvider = codeFix.GetFixAllProvider(); if (fixAllProvider?.GetSupportedFixAllScopes()?.Contains(FixAllScope.Solution) != true) { logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_doesnt_support_Fix_All_in_Solution, diagnosticId, codeFix.GetType().Name); return(solution); } var document = result.Diagnostics .SelectMany(kvp => kvp.Value) .Select(diagnostic => solution.GetDocument(diagnostic.Location.SourceTree)) .FirstOrDefault(); if (document is null) { return(solution); } var fixAllContext = new FixAllContext( document: document, codeFixProvider: codeFix, scope: FixAllScope.Solution, codeActionEquivalenceKey: null, diagnosticIds: new[] { diagnosticId }, fixAllDiagnosticProvider: new DiagnosticProvider(result), cancellationToken: cancellationToken); try { var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (action is null) { logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_didnt_return_a_Fix_All_action, diagnosticId, codeFix.GetType().Name); return(solution); } var operations = await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false); var applyChangesOperation = operations.OfType <ApplyChangesOperation>().SingleOrDefault(); if (action is null) { logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_returned_an_unexpected_operation, diagnosticId, codeFix.GetType().Name); return(solution); } return(applyChangesOperation.ChangedSolution); } catch (Exception ex) { logger.LogWarning(Resources.Failed_to_apply_code_fix_0_for_1_2, codeFix?.GetType().Name, diagnosticId, ex.Message); return(solution); } }
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 static Task VerifyFixAllAsync(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string[] oldSources, string[] newSources, bool allowNewCompilerDiagnostics, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB, string equivalenceKey = null) { var project = CreateProject(oldSources, language, languageVersionCSharp, languageVersionVB); var compilerDiagnostics = (await Task.WhenAll(project.Documents.Select(d => GetCompilerDiagnosticsAsync(d))).ConfigureAwait(true)).SelectMany(d => d); var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (equivalenceKey == null) { equivalenceKey = codeFixProvider.GetType().Name; } FixAllContext fixAllContext; if (analyzer != null) { var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, project.Documents.ToArray()).ConfigureAwait(true); Func <Document, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getDocumentDiagnosticsAsync = (doc, ids, ct) => Task.FromResult(analyzerDiagnostics.Where(d => d.Location.SourceTree.FilePath == doc.Name)); Func <Project, bool, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getProjectDiagnosticsAsync = (proj, b, ids, ct) => Task.FromResult((IEnumerable <Diagnostic>)analyzerDiagnostics); //todo: verify, probably wrong var fixAllDiagnosticProvider = new FixAllDiagnosticProvider(codeFixProvider.FixableDiagnosticIds.ToImmutableHashSet(), getDocumentDiagnosticsAsync, getProjectDiagnosticsAsync); fixAllContext = new FixAllContext(project.Documents.First(), codeFixProvider, FixAllScope.Solution, equivalenceKey, codeFixProvider.FixableDiagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); } else { Func <Document, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getDocumentDiagnosticsAsync = async(doc, ids, ct) => { var compilerDiags = await GetCompilerDiagnosticsAsync(doc).ConfigureAwait(true); return(compilerDiags.Where(d => codeFixProvider.FixableDiagnosticIds.Contains(d.Id))); }; Func <Project, bool, ImmutableHashSet <string>, CancellationToken, Task <IEnumerable <Diagnostic> > > getProjectDiagnosticsAsync = async(proj, b, ids, ct) => { var theDocs = proj.Documents; var diags = await Task.WhenAll(theDocs.Select(d => getDocumentDiagnosticsAsync(d, ids, ct))).ConfigureAwait(true); return(diags.SelectMany(d => d)); }; var fixAllDiagnosticProvider = new FixAllDiagnosticProvider(codeFixProvider.FixableDiagnosticIds.ToImmutableHashSet(), getDocumentDiagnosticsAsync, getProjectDiagnosticsAsync); fixAllContext = new FixAllContext(project.Documents.First(), codeFixProvider, FixAllScope.Solution, equivalenceKey, codeFixProvider.FixableDiagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); } var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(true); if (action == null) { throw new Exception("No action supplied for the code fix."); } project = await ApplyFixAsync(project, action).ConfigureAwait(true); //check if applying the code fix introduced any new compiler diagnostics var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, (await Task.WhenAll(project.Documents.Select(d => GetCompilerDiagnosticsAsync(d))).ConfigureAwait(true)).SelectMany(d => d)); if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n"); } var docs = project.Documents.ToArray(); for (int i = 0; i < docs.Length; i++) { var document = docs[i]; var actual = await GetStringFromDocumentAsync(document).ConfigureAwait(true); newSources[i].Should().Be(actual); } }
public static async Task <CodeAction> GetFixAsync( ImmutableArray <Diagnostic> diagnostics, DiagnosticDescriptor descriptor, CodeFixProvider fixer, Project project, CodeFixerOptions options, IFormatProvider formatProvider = null, CancellationToken cancellationToken = default) { if (diagnostics.Length == 1) { return(await GetFixAsync(diagnostics[0], fixer, project, options, formatProvider, cancellationToken).ConfigureAwait(false)); } FixAllProvider fixAllProvider = fixer.GetFixAllProvider(); if (fixAllProvider == null) { if (options.DiagnosticIdsFixableOneByOne.Contains(descriptor.Id)) { return(await GetFixAsync(diagnostics[0], fixer, project, options, formatProvider, cancellationToken).ConfigureAwait(false)); } WriteLine($" '{fixer.GetType().FullName}' does not have FixAllProvider", ConsoleColor.Yellow, Verbosity.Diagnostic); return(null); } if (!fixAllProvider.GetSupportedFixAllDiagnosticIds(fixer).Any(f => f == descriptor.Id)) { WriteLine($" '{fixAllProvider.GetType().FullName}' does not support diagnostic '{descriptor.Id}'", ConsoleColor.Yellow, Verbosity.Diagnostic); return(null); } if (!fixAllProvider.GetSupportedFixAllScopes().Any(f => f == FixAllScope.Project)) { WriteLine($" '{fixAllProvider.GetType().FullName}' does not support scope '{FixAllScope.Project}'", ConsoleColor.Yellow, Verbosity.Diagnostic); return(null); } var multipleFixesInfos = new HashSet <MultipleFixesInfo>(); CodeAction action = null; options.DiagnosticFixMap.TryGetValue(descriptor.Id, out string equivalenceKey); foreach (Diagnostic diagnostic in diagnostics) { cancellationToken.ThrowIfCancellationRequested(); if (!diagnostic.Location.IsInSource) { continue; } Document document = project.GetDocument(diagnostic.Location.SourceTree); if (document == null) { continue; } CodeAction fixCandidate = await GetFixAsync(diagnostic, fixer, document, multipleFixesInfos, options, cancellationToken).ConfigureAwait(false); if (fixCandidate == null) { continue; } if (equivalenceKey != null && equivalenceKey != fixCandidate.EquivalenceKey) { break; } action = fixCandidate; var fixAllContext = new FixAllContext( document, fixer, FixAllScope.Project, action.EquivalenceKey, new string[] { descriptor.Id }, new FixAllDiagnosticProvider(diagnostics), cancellationToken); CodeAction fix = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (fix != null) { WriteLine($" CodeFixProvider: '{fixer.GetType().FullName}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); if (!string.IsNullOrEmpty(action.EquivalenceKey)) { WriteLine($" EquivalenceKey: '{action.EquivalenceKey}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); } WriteLine($" FixAllProvider: '{fixAllProvider.GetType().FullName}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); return(fix); } WriteLine($" Fixer '{fixer.GetType().FullName}' registered no action for diagnostic '{descriptor.Id}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); WriteDiagnostics(diagnostics, baseDirectoryPath: Path.GetDirectoryName(project.FilePath), formatProvider: formatProvider, indentation: " ", maxCount: 10, verbosity: Verbosity.Diagnostic); } return(null); }
private static async Task <CodeAction> GetFixAsync( string diagnosticId, Project project, ImmutableArray <Diagnostic> diagnostics, CodeFixProvider fixer, CancellationToken cancellationToken) { FixAllProvider fixAll = fixer.GetFixAllProvider(); if (!fixAll.GetSupportedFixAllDiagnosticIds(fixer).Any(f => f == diagnosticId)) { return(null); } if (!fixAll.GetSupportedFixAllScopes().Any(f => f == FixAllScope.Project)) { return(null); } foreach (Diagnostic diagnostic in diagnostics) { cancellationToken.ThrowIfCancellationRequested(); if (!diagnostic.Location.IsInSource) { continue; } Document document = project.GetDocument(diagnostic.Location.SourceTree); Debug.Assert(document != null, ""); if (document == null) { continue; } CodeAction action = null; var context = new CodeFixContext( document, diagnostic, (a, _) => { if (action == null) { action = a; } else if (!string.Equals(a.EquivalenceKey, action.EquivalenceKey, StringComparison.Ordinal)) { #if DEBUG WriteLine($"'{fixer.GetType().Name}' registered multiple actions for diagnostic '{diagnosticId}'", ConsoleColor.DarkYellow); WriteLine($" {action.EquivalenceKey}", ConsoleColor.DarkYellow); WriteLine($" {a.EquivalenceKey}", ConsoleColor.DarkYellow); #endif action = null; } }, cancellationToken); await fixer.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (action == null) { continue; } var fixAllContext = new FixAllContext( document, fixer, FixAllScope.Project, action.EquivalenceKey, new string[] { diagnosticId }, new FixAllDiagnosticProvider(diagnostics), cancellationToken); CodeAction fixAllAction = await fixAll.GetFixAsync(fixAllContext).ConfigureAwait(false); if (fixAllAction == null && diagnosticId.StartsWith("RCS")) { WriteLine($"'{fixer.GetType().FullName}' registered no action for diagnostics:", ConsoleColor.Magenta); Write(diagnostics, 10, ConsoleColor.Magenta); } return(fixAllAction); } return(null); }
internal static async Task <ImmutableArray <CodeFixEquivalenceGroup> > CreateAsync(CodeFixProvider codeFixProvider, ImmutableDictionary <ProjectId, ImmutableArray <Diagnostic> > allDiagnostics, Solution solution, CancellationToken cancellationToken) { var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(ImmutableArray.Create <CodeFixEquivalenceGroup>()); } Dictionary <ProjectId, Dictionary <string, List <Diagnostic> > > relevantDocumentDiagnostics = new Dictionary <ProjectId, Dictionary <string, List <Diagnostic> > >(); Dictionary <ProjectId, List <Diagnostic> > relevantProjectDiagnostics = new Dictionary <ProjectId, List <Diagnostic> >(); foreach (var projectDiagnostics in allDiagnostics) { foreach (var diagnostic in projectDiagnostics.Value) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { continue; } if (diagnostic.Location.IsInSource) { string sourcePath = diagnostic.Location.GetLineSpan().Path; Dictionary <string, List <Diagnostic> > projectDocumentDiagnostics; if (!relevantDocumentDiagnostics.TryGetValue(projectDiagnostics.Key, out projectDocumentDiagnostics)) { projectDocumentDiagnostics = new Dictionary <string, List <Diagnostic> >(); relevantDocumentDiagnostics.Add(projectDiagnostics.Key, projectDocumentDiagnostics); } List <Diagnostic> diagnosticsInFile; if (!projectDocumentDiagnostics.TryGetValue(sourcePath, out diagnosticsInFile)) { diagnosticsInFile = new List <Diagnostic>(); projectDocumentDiagnostics.Add(sourcePath, diagnosticsInFile); } diagnosticsInFile.Add(diagnostic); } else { List <Diagnostic> diagnosticsInProject; if (!relevantProjectDiagnostics.TryGetValue(projectDiagnostics.Key, out diagnosticsInProject)) { diagnosticsInProject = new List <Diagnostic>(); relevantProjectDiagnostics.Add(projectDiagnostics.Key, diagnosticsInProject); } diagnosticsInProject.Add(diagnostic); } } } ImmutableDictionary <ProjectId, ImmutableDictionary <string, ImmutableArray <Diagnostic> > > documentDiagnosticsToFix = relevantDocumentDiagnostics.ToImmutableDictionary(i => i.Key, i => i.Value.ToImmutableDictionary(j => j.Key, j => j.Value.ToImmutableArray(), StringComparer.OrdinalIgnoreCase)); ImmutableDictionary <ProjectId, ImmutableArray <Diagnostic> > projectDiagnosticsToFix = relevantProjectDiagnostics.ToImmutableDictionary(i => i.Key, i => i.Value.ToImmutableArray()); HashSet <string> equivalenceKeys = new HashSet <string>(); foreach (var diagnostic in relevantDocumentDiagnostics.Values.SelectMany(i => i.Values).SelectMany(i => i).Concat(relevantProjectDiagnostics.Values.SelectMany(i => i))) { foreach (var codeAction in await GetFixesAsync(solution, codeFixProvider, diagnostic, cancellationToken).ConfigureAwait(false)) { equivalenceKeys.Add(codeAction.EquivalenceKey); } } List <CodeFixEquivalenceGroup> groups = new List <CodeFixEquivalenceGroup>(); foreach (var equivalenceKey in equivalenceKeys) { groups.Add(new CodeFixEquivalenceGroup(equivalenceKey, solution, fixAllProvider, codeFixProvider, documentDiagnosticsToFix, projectDiagnosticsToFix)); } return(groups.ToImmutableArray()); }
private static async Task <Project> FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope scope, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) { int expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(null); } bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } if (--numberOfIterations < 0) { Assert.True(false, "The upper limit for the number of fix all iterations was exceeded"); } Diagnostic firstDiagnostic = null; string equivalenceKey = null; foreach (var diagnostic in analyzerDiagnostics) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } var actions = new List <CodeAction>(); var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (actions.Count > (codeFixIndex ?? 0)) { firstDiagnostic = diagnostic; equivalenceKey = actions[codeFixIndex ?? 0].EquivalenceKey; break; } } if (firstDiagnostic == null) { return(project); } previousDiagnostics = analyzerDiagnostics; done = true; FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); IEnumerable <string> analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id); IEnumerable <string> compilerDiagnosticIds = codeFixProvider.FixableDiagnosticIds.Where(x => x.StartsWith("CS", StringComparison.Ordinal)); IEnumerable <string> disabledDiagnosticIds = project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key); IEnumerable <string> relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct(); FixAllContext fixAllContext = new FixAllContext(project.GetDocument(firstDiagnostic.Location.SourceTree), codeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken); CodeAction 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, cancellationToken).ConfigureAwait(false); } }while (!done); if (expectedNumberOfIterations >= 0) { Assert.Equal($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); } return(project); }
internal async Task <(ImmutableArray <Diagnostic>, ImmutableArray <CodeAction>, CodeAction actionToInvoke)> GetDiagnosticAndFixesAsync( IEnumerable <Diagnostic> diagnostics, CodeFixProvider fixer, TestDiagnosticAnalyzerDriver testDriver, Document document, TextSpan span, string annotation, int index) { if (diagnostics.IsEmpty()) { return(ImmutableArray <Diagnostic> .Empty, ImmutableArray <CodeAction> .Empty, null); } var scope = GetFixAllScope(annotation); if (scope is FixAllScope.ContainingMember or FixAllScope.ContainingType && document.GetLanguageService <IFixAllSpanMappingService>() is IFixAllSpanMappingService spanMappingService) { var documentsAndSpansToFix = await spanMappingService.GetFixAllSpansAsync( document, span, scope.Value, CancellationToken.None).ConfigureAwait(false); if (documentsAndSpansToFix.IsEmpty) { return(ImmutableArray <Diagnostic> .Empty, ImmutableArray <CodeAction> .Empty, null); } } var intersectingDiagnostics = diagnostics.Where(d => d.Location.SourceSpan.IntersectsWith(span)) .ToImmutableArray(); var fixes = new List <CodeFix>(); foreach (var diagnostic in intersectingDiagnostics) { var context = new CodeFixContext( document, diagnostic.Location.SourceSpan, ImmutableArray.Create(diagnostic), (a, d) => fixes.Add(new CodeFix(document.Project, a, d)), testDriver.FallbackOptions, isBlocking: false, CancellationToken.None); await fixer.RegisterCodeFixesAsync(context); } VerifyCodeActionsRegisteredByProvider(fixer, fixes); var actions = MassageActions(fixes.SelectAsArray(f => f.Action)); if (scope == null) { // Simple code fix. return(intersectingDiagnostics, actions, actions.Length == 0 ? null : actions[index]); } var equivalenceKey = actions[index].EquivalenceKey; // Fix all fix. var fixAllProvider = fixer.GetFixAllProvider(); Assert.NotNull(fixAllProvider); var fixAllState = GetFixAllState( fixAllProvider, diagnostics, fixer, testDriver, document, scope.Value, equivalenceKey, testDriver.FallbackOptions); var fixAllContext = new FixAllContext(fixAllState, new ProgressTracker(), CancellationToken.None); var fixAllFix = await fixAllProvider.GetFixAsync(fixAllContext); // We have collapsed the fixes down to the single fix-all fix, so we just let our // caller know they should pull that entry out of the result. return(intersectingDiagnostics, ImmutableArray.Create(fixAllFix), fixAllFix); }