File: src\Analyzers\Core\CodeFixes\UseCollectionInitializer\AbstractUseCollectionInitializerCodeFixProvider.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.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.UseCollectionExpression;
 
namespace Microsoft.CodeAnalysis.UseCollectionInitializer;
 
internal abstract class AbstractUseCollectionInitializerCodeFixProvider<
    TSyntaxKind,
    TExpressionSyntax,
    TStatementSyntax,
    TObjectCreationExpressionSyntax,
    TMemberAccessExpressionSyntax,
    TInvocationExpressionSyntax,
    TExpressionStatementSyntax,
    TLocalDeclarationStatementSyntax,
    TVariableDeclaratorSyntax,
    TAnalyzer>()
    : AbstractUseCollectionExpressionCodeFixProvider<TObjectCreationExpressionSyntax>(
        AnalyzersResources.Collection_initialization_can_be_simplified,
        nameof(AnalyzersResources.Collection_initialization_can_be_simplified))
    where TSyntaxKind : struct
    where TExpressionSyntax : SyntaxNode
    where TStatementSyntax : SyntaxNode
    where TObjectCreationExpressionSyntax : TExpressionSyntax
    where TMemberAccessExpressionSyntax : TExpressionSyntax
    where TInvocationExpressionSyntax : TExpressionSyntax
    where TExpressionStatementSyntax : TStatementSyntax
    where TLocalDeclarationStatementSyntax : TStatementSyntax
    where TVariableDeclaratorSyntax : SyntaxNode
    where TAnalyzer : AbstractUseCollectionInitializerAnalyzer<
        TExpressionSyntax,
        TStatementSyntax,
        TObjectCreationExpressionSyntax,
        TMemberAccessExpressionSyntax,
        TInvocationExpressionSyntax,
        TExpressionStatementSyntax,
        TLocalDeclarationStatementSyntax,
        TVariableDeclaratorSyntax,
        TAnalyzer>, new()
{
    public sealed override ImmutableArray<string> FixableDiagnosticIds
        => [IDEDiagnosticIds.UseCollectionInitializerDiagnosticId];
 
    protected abstract TAnalyzer GetAnalyzer();
 
    protected abstract Task<(SyntaxNode oldNode, SyntaxNode newNode)> GetReplacementNodesAsync(
        Document document, TObjectCreationExpressionSyntax objectCreation, bool useCollectionExpression,
        ImmutableArray<CollectionMatch<SyntaxNode>> preMatches,
        ImmutableArray<CollectionMatch<SyntaxNode>> postMatches, CancellationToken cancellationToken);
 
    protected sealed override async Task FixAsync(
        Document document,
        SyntaxEditor editor,
        TObjectCreationExpressionSyntax objectCreation,
        ImmutableDictionary<string, string?> properties,
        CancellationToken cancellationToken)
    {
        // Fix-All for this feature is somewhat complicated.  As Collection-Initializers could be arbitrarily
        // nested, we have to make sure that any edits we make to one Collection-Initializer are seen by any higher
        // ones.  In order to do this we actually process each object-creation-node, one at a time, rewriting the
        // tree for each node.  In order to do this effectively, we use the '.TrackNodes' feature to keep track of
        // all the object creation nodes as we make edits to the tree.  If we didn't do this, then we wouldn't be
        // able to find the second object-creation-node after we make the edit for the first one.
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
 
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        using var analyzer = GetAnalyzer();
 
        var useCollectionExpression = properties.ContainsKey(UseCollectionInitializerHelpers.UseCollectionExpressionName) is true;
        var (preMatches, postMatches) = analyzer.Analyze(
            semanticModel, syntaxFacts, objectCreation, useCollectionExpression, cancellationToken);
 
        if (preMatches.IsDefault || postMatches.IsDefault)
            return;
 
        var (oldNode, newNode) = await GetReplacementNodesAsync(
            document, objectCreation, useCollectionExpression, preMatches, postMatches, cancellationToken).ConfigureAwait(false);
 
        editor.ReplaceNode(oldNode, newNode);
 
        // We only need to remove the post-matches.  The pre-matches are the arguments in teh object creation, which
        // itself got replaced above.
        foreach (var match in postMatches)
            editor.RemoveNode(match.Node, SyntaxRemoveOptions.KeepUnbalancedDirectives);
    }
}