// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.CodeFixes { /// /// Helper class for "Fix all occurrences" code fix providers for Simplification code fix providers that just add simplifier/formatter annotations as part of code fixes. /// This provider batches all the simplifier annotation actions within a document into a single code action, /// instead of creating separate code actions for each added annotation. /// internal class BatchSimplificationFixAllProvider : BatchFixAllProvider { public static new readonly FixAllProvider Instance = new BatchSimplificationFixAllProvider(); protected BatchSimplificationFixAllProvider() { } public override async Task AddDocumentFixesAsync( Document document, ImmutableArray diagnostics, Action addFix, FixAllState fixAllState, CancellationToken cancellationToken) { var changedDocument = await AddSimplifierAnnotationsAsync( document, diagnostics, fixAllState, cancellationToken).ConfigureAwait(false); var title = GetFixAllTitle(fixAllState); var codeAction = new MyCodeAction(title, (c) => Task.FromResult(changedDocument)); addFix(codeAction); } /// /// Get node on which to add simplifier and formatter annotation for fixing the given diagnostic. /// protected virtual SyntaxNode GetNodeToSimplify(SyntaxNode root, SemanticModel model, Diagnostic diagnostic, Workspace workspace, out string codeActionEquivalenceKey, CancellationToken cancellationToken) { codeActionEquivalenceKey = null; var span = diagnostic.Location.SourceSpan; return root.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true); } /// /// Override this method to add simplify annotations/fixup any parent nodes of original nodes to simplify. /// Additionally, this method should also add simplifier annotation to the given nodeToSimplify. /// See doc comments on . /// protected virtual Task AddSimplifyAnnotationsAsync(Document document, SyntaxNode nodeToSimplify, CancellationToken cancellationToken) { throw new NotImplementedException(); } /// /// By default, this property returns false and will just add to each node to simplify /// returned by . /// /// Override this property to return true if the fix all provider needs to add simplify annotations/fixup any of the parent nodes of the nodes to simplify. /// This could be the case if simplifying certain nodes can enable cascaded simplifications, such as parentheses removal on parenting node. /// will end up invoking for each node to simplify. /// Ensure that you override method when this property returns true. /// protected virtual bool NeedsParentFixup { get { return false; } } private async Task AddSimplifierAnnotationsAsync( Document document, ImmutableArray diagnostics, FixAllState fixAllState, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); // Find all nodes to simplify corresponding to diagnostic spans. var nodesToSimplify = new List(); foreach (var diagnostic in diagnostics) { string codeActionEquivalenceKey; var node = GetNodeToSimplify(root, model, diagnostic, fixAllState.Solution.Workspace, out codeActionEquivalenceKey, cancellationToken); if (node != null && fixAllState.CodeActionEquivalenceKey == codeActionEquivalenceKey) { nodesToSimplify.Add(node); } } // Add simplifier and formatter annotations to all nodes to simplify. // If the fix all provider needs to fixup any of the parent nodes, then we iterate through each of the nodesToSimplify // and fixup any parenting node, computing a new document with required simplifier annotations in each iteration. // Otherwise, if the fix all provider doesn't need parent fixup, we just add simplifier annotation to all nodesToSimplify. if (!NeedsParentFixup) { root = root.ReplaceNodes(nodesToSimplify, (o, n) => n.WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation)); } else { // Add a custom annotation to nodesToSimplify so we can get back to them later. var annotation = new SyntaxAnnotation(); root = root.ReplaceNodes(nodesToSimplify, (o, n) => o.WithAdditionalAnnotations(annotation)); document = document.WithSyntaxRoot(root); while (true) { root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var annotatedNodes = root.GetAnnotatedNodes(annotation); // Get the next un-processed node to simplify, processed nodes should have simplifier annotation. var annotatedNode = annotatedNodes.FirstOrDefault(n => !n.HasAnnotation(Simplifier.Annotation)); if (annotatedNode == null) { // All nodesToSimplify have been processed. // Remove all the custom annotations added for tracking nodesToSimplify. root = root.ReplaceNodes(annotatedNodes, (o, n) => o.WithoutAnnotations(annotation)); break; } document = await AddSimplifyAnnotationsAsync(document, annotatedNode, cancellationToken).ConfigureAwait(false); } } return document.WithSyntaxRoot(root); } private class MyCodeAction : CodeAction.DocumentChangeAction { public MyCodeAction(string title, Func> createChangedDocument) : base(title, createChangedDocument) { } } } }