File: src\Analyzers\Core\CodeFixes\UseObjectInitializer\AbstractUseObjectInitializerCodeFixProvider.cs
Web Access
Project: src\src\CodeStyle\Core\CodeFixes\Microsoft.CodeAnalysis.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CodeStyle.Fixes)
// 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.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.UseObjectInitializer;
 
internal abstract class AbstractUseObjectInitializerCodeFixProvider<
    TSyntaxKind,
    TExpressionSyntax,
    TStatementSyntax,
    TObjectCreationExpressionSyntax,
    TMemberAccessExpressionSyntax,
    TAssignmentStatementSyntax,
    TLocalDeclarationStatementSyntax,
    TVariableDeclaratorSyntax,
    TAnalyzer>
    : ForkingSyntaxEditorBasedCodeFixProvider<TObjectCreationExpressionSyntax>
    where TSyntaxKind : struct
    where TExpressionSyntax : SyntaxNode
    where TStatementSyntax : SyntaxNode
    where TObjectCreationExpressionSyntax : TExpressionSyntax
    where TMemberAccessExpressionSyntax : TExpressionSyntax
    where TAssignmentStatementSyntax : TStatementSyntax
    where TLocalDeclarationStatementSyntax : TStatementSyntax
    where TVariableDeclaratorSyntax : SyntaxNode
    where TAnalyzer : AbstractUseNamedMemberInitializerAnalyzer<
        TExpressionSyntax,
        TStatementSyntax,
        TObjectCreationExpressionSyntax,
        TMemberAccessExpressionSyntax,
        TAssignmentStatementSyntax,
        TLocalDeclarationStatementSyntax,
        TVariableDeclaratorSyntax,
        TAnalyzer>, new()
{
    protected override (string title, string equivalenceKey) GetTitleAndEquivalenceKey(CodeFixContext context)
        => (AnalyzersResources.Object_initialization_can_be_simplified, nameof(AnalyzersResources.Object_initialization_can_be_simplified));
 
    protected abstract TAnalyzer GetAnalyzer();
 
    protected abstract ISyntaxKinds SyntaxKinds { get; }
    protected abstract ISyntaxFormatting SyntaxFormatting { get; }
 
    protected abstract SyntaxTrivia Whitespace(string text);
 
    protected abstract TStatementSyntax GetNewStatement(
        TStatementSyntax statement, TObjectCreationExpressionSyntax objectCreation, SyntaxFormattingOptions options,
        ImmutableArray<Match<TExpressionSyntax, TStatementSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax>> matches);
 
    public override ImmutableArray<string> FixableDiagnosticIds
        => [IDEDiagnosticIds.UseObjectInitializerDiagnosticId];
 
    protected override async Task FixAsync(
        Document document,
        SyntaxEditor editor,
        TObjectCreationExpressionSyntax objectCreation,
        ImmutableDictionary<string, string?> properties,
        CancellationToken cancellationToken)
    {
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var currentRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
        using var analyzer = GetAnalyzer();
 
        var matches = analyzer.Analyze(semanticModel, syntaxFacts, objectCreation, cancellationToken);
        if (matches.IsDefaultOrEmpty)
            return;
 
        var statement = objectCreation.FirstAncestorOrSelf<TStatementSyntax>();
        Contract.ThrowIfNull(statement);
 
        var firstToken = objectCreation.GetFirstToken();
        var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(
            this.SyntaxFormatting, cancellationToken).ConfigureAwait(false);
 
        var newStatement = GetNewStatement(statement, objectCreation, formattingOptions, matches).WithAdditionalAnnotations(Formatter.Annotation);
 
        editor.ReplaceNode(statement, newStatement);
        foreach (var match in matches)
            editor.RemoveNode(match.Statement, SyntaxRemoveOptions.KeepUnbalancedDirectives);
    }
 
    protected TExpressionSyntax Indent(TExpressionSyntax expression, SyntaxFormattingOptions options)
    {
        var endOfLineKind = this.SyntaxKinds.EndOfLineTrivia;
        var whitespaceTriviaKind = this.SyntaxKinds.WhitespaceTrivia;
        return expression.ReplaceTokens(
            expression.DescendantTokens(),
            (currentToken, _) =>
            {
                if (currentToken.LeadingTrivia is [.., var whitespace1] &&
                    whitespace1.RawKind == whitespaceTriviaKind)
                {
                    // This is a token on its own line.  With whitespace at the start of the line.
                    var leadingTrivia = currentToken.LeadingTrivia.Replace(
                        whitespace1,
                        IncreaseIndent(whitespace1, options));
 
                    currentToken = currentToken.WithLeadingTrivia(leadingTrivia);
                }
 
                if (currentToken.TrailingTrivia is [.., var endOfLine, var whitespace2] &&
                    endOfLine.RawKind == endOfLineKind &&
                    whitespace2.RawKind == whitespaceTriviaKind)
                {
                    // This is a VB line continuation case (`_`), with indentation before the next token
                    var trailingTrivia = currentToken.TrailingTrivia.Replace(
                        whitespace2,
                        IncreaseIndent(whitespace2, options));
 
                    currentToken = currentToken.WithTrailingTrivia(trailingTrivia);
                }
 
                return currentToken;
            });
    }
 
    private SyntaxTrivia IncreaseIndent(SyntaxTrivia whitespaceTrivia, SyntaxFormattingOptions options)
    {
        // Convert the existing whitespace to determine which column it corresponds to in spaces.  
        var existingWhitespace = whitespaceTrivia.ToString();
        var spaceCount = existingWhitespace.ConvertTabToSpace(
            options.TabSize,
            initialColumn: 0,
            endPosition: existingWhitespace.Length);
 
        // Then add the desired indentation spaces to it.
        var desiredSpaceCount = spaceCount + options.IndentationSize;
 
        // Now convert back to a string with the appropriate tab/space configuration.
        var desiredWhitespace = desiredSpaceCount.CreateIndentationString(options.UseTabs, options.TabSize);
        return Whitespace(desiredWhitespace);
    }
}