|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Concurrent;
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.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeFixes.Suppression;
internal abstract partial class AbstractSuppressionCodeFixProvider : IConfigurationFixProvider
{
internal abstract partial class RemoveSuppressionCodeAction
{
public static FixAllProvider GetBatchFixer(AbstractSuppressionCodeFixProvider suppressionFixProvider)
=> new RemoveSuppressionBatchFixAllProvider(suppressionFixProvider);
/// <summary>
/// Batch fixer for pragma suppression removal code action.
/// </summary>
private sealed class RemoveSuppressionBatchFixAllProvider(AbstractSuppressionCodeFixProvider suppressionFixProvider) : AbstractSuppressionBatchFixAllProvider
{
private readonly AbstractSuppressionCodeFixProvider _suppressionFixProvider = suppressionFixProvider;
protected override async Task AddDocumentFixesAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
Action<(Diagnostic diagnostic, CodeAction action)> onItemFound,
FixAllState fixAllState, CancellationToken cancellationToken)
{
// Batch all the pragma remove suppression fixes by executing them sequentially for the document.
using var _1 = ArrayBuilder<IPragmaBasedCodeAction>.GetInstance(out var pragmaActionsBuilder);
using var _2 = ArrayBuilder<Diagnostic>.GetInstance(out var pragmaDiagnosticsBuilder);
foreach (var diagnostic in diagnostics.Where(d => d.Location.IsInSource && d.IsSuppressed))
{
var span = diagnostic.Location.SourceSpan;
var removeSuppressionFixes = await _suppressionFixProvider.GetFixesAsync(
document, span, [diagnostic], cancellationToken).ConfigureAwait(false);
var removeSuppressionFix = removeSuppressionFixes.SingleOrDefault();
if (removeSuppressionFix != null)
{
if (removeSuppressionFix.Action is RemoveSuppressionCodeAction codeAction)
{
if (fixAllState.IsFixMultiple)
{
codeAction = codeAction.CloneForFixMultipleContext();
}
if (codeAction is PragmaRemoveAction pragmaRemoveAction)
{
pragmaActionsBuilder.Add(pragmaRemoveAction);
pragmaDiagnosticsBuilder.Add(diagnostic);
}
else
{
onItemFound((diagnostic, codeAction));
}
}
}
}
// Get the pragma batch fix.
if (pragmaActionsBuilder.Count > 0)
{
var pragmaBatchFix = PragmaBatchFixHelpers.CreateBatchPragmaFix(
_suppressionFixProvider, document,
pragmaActionsBuilder.ToImmutableAndClear(),
pragmaDiagnosticsBuilder.ToImmutableAndClear(),
fixAllState, cancellationToken);
onItemFound((diagnostic: null, pragmaBatchFix));
}
}
protected override async Task AddProjectFixesAsync(
Project project, ImmutableArray<Diagnostic> diagnostics,
ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> bag,
FixAllState fixAllState, CancellationToken cancellationToken)
{
foreach (var diagnostic in diagnostics.Where(d => !d.Location.IsInSource && d.IsSuppressed))
{
var removeSuppressionFixes = await _suppressionFixProvider.GetFixesAsync(
project, [diagnostic], cancellationToken).ConfigureAwait(false);
if (removeSuppressionFixes.SingleOrDefault()?.Action is RemoveSuppressionCodeAction removeSuppressionCodeAction)
{
if (fixAllState.IsFixMultiple)
{
removeSuppressionCodeAction = removeSuppressionCodeAction.CloneForFixMultipleContext();
}
bag.Add((diagnostic, removeSuppressionCodeAction));
}
}
}
public override async Task<CodeAction> TryGetMergedFixAsync(
ImmutableArray<(Diagnostic diagnostic, CodeAction action)> batchOfFixes,
FixAllState fixAllState,
IProgress<CodeAnalysisProgress> progressTracker,
CancellationToken cancellationToken)
{
// Batch all the attribute removal fixes into a single fix.
// Pragma removal fixes have already been batch for each document AddDocumentFixes method.
// This ensures no merge conflicts in merging all fixes by our base implementation.
var oldSolution = fixAllState.Project.Solution;
var currentSolution = oldSolution;
var attributeRemoveFixes = new List<AttributeRemoveAction>();
var newBatchOfFixes = new List<(Diagnostic diagnostic, CodeAction action)>();
foreach (var codeAction in batchOfFixes)
{
if (codeAction.action is AttributeRemoveAction attributeRemoveFix)
{
attributeRemoveFixes.Add(attributeRemoveFix);
}
else
{
newBatchOfFixes.Add(codeAction);
}
}
if (attributeRemoveFixes.Count > 0)
{
// Batch all of attribute removal fixes.
foreach (var removeSuppressionFixesForTree in attributeRemoveFixes.GroupBy(fix => fix.SyntaxTreeToModify))
{
var tree = removeSuppressionFixesForTree.Key;
var attributeRemoveFixesForTree = removeSuppressionFixesForTree.OfType<AttributeRemoveAction>().ToImmutableArray();
var attributesToRemove = await GetAttributeNodesToFixAsync(attributeRemoveFixesForTree, cancellationToken).ConfigureAwait(false);
var document = oldSolution.GetDocument(tree);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = root.RemoveNodes(attributesToRemove, SyntaxRemoveOptions.KeepLeadingTrivia | SyntaxRemoveOptions.AddElasticMarker);
currentSolution = currentSolution.WithDocumentSyntaxRoot(document.Id, newRoot);
}
var batchAttributeRemoveFix = CodeAction.Create(
attributeRemoveFixes.First().Title,
createChangedSolution: ct => Task.FromResult(currentSolution),
equivalenceKey: fixAllState.CodeActionEquivalenceKey);
newBatchOfFixes.Insert(0, (diagnostic: null, batchAttributeRemoveFix));
}
return await base.TryGetMergedFixAsync(
[.. newBatchOfFixes], fixAllState, progressTracker, cancellationToken).ConfigureAwait(false);
}
private static async Task<ImmutableArray<SyntaxNode>> GetAttributeNodesToFixAsync(ImmutableArray<AttributeRemoveAction> attributeRemoveFixes, CancellationToken cancellationToken)
{
var builder = new FixedSizeArrayBuilder<SyntaxNode>(attributeRemoveFixes.Length);
foreach (var attributeRemoveFix in attributeRemoveFixes)
{
var attributeToRemove = await attributeRemoveFix.GetAttributeToRemoveAsync(cancellationToken).ConfigureAwait(false);
builder.Add(attributeToRemove);
}
return builder.MoveToImmutable();
}
}
}
}
|