// 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 the object creation, which // itself got replaced above. foreach (var match in postMatches) editor.RemoveNode(match.Node, SyntaxRemoveOptions.KeepUnbalancedDirectives); } } |