File: src\Analyzers\Core\CodeFixes\RemoveUnusedMembers\AbstractRemoveUnusedMembersCodeFixProvider.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.RemoveUnusedMembers;
 
internal abstract class AbstractRemoveUnusedMembersCodeFixProvider<TFieldDeclarationSyntax> : SyntaxEditorBasedCodeFixProvider
    where TFieldDeclarationSyntax : SyntaxNode
{
    public sealed override ImmutableArray<string> FixableDiagnosticIds
        => [IDEDiagnosticIds.RemoveUnusedMembersDiagnosticId];
 
    /// <summary>
    /// This method adjusts the <paramref name="declarators"/> to remove based on whether or not all variable declarators
    /// within a field declaration should be removed,
    /// i.e. if all the fields declared within a field declaration are unused,
    /// we can remove the entire field declaration instead of individual variable declarators.
    /// </summary>
    protected abstract void AdjustAndAddAppropriateDeclaratorsToRemove(HashSet<TFieldDeclarationSyntax> fieldDeclarators, HashSet<SyntaxNode> declarators);
 
    public override Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        RegisterCodeFix(context, AnalyzersResources.Remove_unused_member, nameof(AnalyzersResources.Remove_unused_member));
        return Task.CompletedTask;
    }
 
    protected override async Task FixAllAsync(
        Document document,
        ImmutableArray<Diagnostic> diagnostics,
        SyntaxEditor editor,
        CancellationToken cancellationToken)
    {
        var declarators = new HashSet<SyntaxNode>();
        var fieldDeclarators = new HashSet<TFieldDeclarationSyntax>();
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var declarationService = document.GetRequiredLanguageService<ISymbolDeclarationService>();
 
        // Compute declarators to remove, and also track common field declarators.
        foreach (var diagnostic in diagnostics)
        {
            // Get symbol to be removed.
            var diagnosticNode = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken);
            var symbol = semanticModel.GetDeclaredSymbol(diagnosticNode, cancellationToken);
            Contract.ThrowIfNull(symbol);
 
            // Get symbol declarations to be removed.
            foreach (var declReference in declarationService.GetDeclarations(symbol))
            {
                var node = await declReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
                declarators.Add(node);
 
                // For fields, the declaration node is the variable declarator.
                // We also track the ancestor FieldDeclarationSyntax which may declare more then one field.
                if (symbol.Kind == SymbolKind.Field)
                {
                    var fieldDeclarator = node.FirstAncestorOrSelf<TFieldDeclarationSyntax>();
                    Contract.ThrowIfNull(fieldDeclarator);
                    fieldDeclarators.Add(fieldDeclarator);
                }
            }
        }
 
        // If all the fields declared within a field declaration are unused,
        // we can remove the entire field declaration instead of individual variable declarators.
        if (fieldDeclarators.Count > 0)
        {
            AdjustAndAddAppropriateDeclaratorsToRemove(fieldDeclarators, declarators);
        }
 
        // Remove all the symbol declarator nodes.
        foreach (var declarator in declarators)
        {
            editor.RemoveNode(declarator);
        }
    }
 
    /// <summary>
    /// If all the <paramref name="childDeclarators"/> are contained in <paramref name="declarators"/>,
    /// the removes the <paramref name="childDeclarators"/> from <paramref name="declarators"/>, and
    /// adds the <paramref name="parentDeclaration"/> to the <paramref name="declarators"/>.
    /// </summary>
    protected static void AdjustAndAddAppropriateDeclaratorsToRemove(SyntaxNode parentDeclaration, IEnumerable<SyntaxNode> childDeclarators, HashSet<SyntaxNode> declarators)
    {
        if (declarators.Contains(parentDeclaration))
        {
            Debug.Assert(childDeclarators.All(c => !declarators.Contains(c)));
            return;
        }
 
        var declaratorsContainsAllChildren = true;
        foreach (var childDeclarator in childDeclarators)
        {
            if (!declarators.Contains(childDeclarator))
            {
                declaratorsContainsAllChildren = false;
                break;
            }
        }
 
        if (declaratorsContainsAllChildren)
        {
            // Remove the entire parent declaration instead of individual child declarators within it.
            declarators.Add(parentDeclaration);
            declarators.RemoveAll(childDeclarators);
        }
    }
}