File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\LanguageServices\InitializeParameter\AbstractInitializerParameterService.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.InitializeParameter;
 
using static InitializeParameterHelpersCore;
 
internal abstract class AbstractInitializerParameterService<TStatementSyntax>
    : IInitializeParameterService
    where TStatementSyntax : SyntaxNode
{
    protected abstract bool IsFunctionDeclaration(SyntaxNode node);
 
    protected abstract SyntaxNode? GetAccessorBody(IMethodSymbol accessor, CancellationToken cancellationToken);
 
    protected abstract SyntaxNode GetBody(SyntaxNode methodNode);
    protected abstract SyntaxNode? TryGetLastStatement(IBlockOperation? blockStatement);
 
    protected abstract bool TryUpdateTupleAssignment(IBlockOperation? blockStatement, IParameterSymbol parameter, ISymbol fieldOrProperty, SyntaxEditor editor);
    protected abstract Task<Solution> TryAddAssignmentForPrimaryConstructorAsync(
        Document document, IParameterSymbol parameter, ISymbol fieldOrProperty, CancellationToken cancellationToken);
 
    protected abstract void InsertStatement(
        SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode? statementToAddAfter, TStatementSyntax statement);
 
    public bool TryGetBlockForSingleParameterInitialization(
        SyntaxNode functionDeclaration,
        SemanticModel semanticModel,
        ISyntaxFactsService syntaxFacts,
        CancellationToken cancellationToken,
        out IBlockOperation? blockStatement)
    {
        blockStatement = null;
 
        var functionBody = GetBody(functionDeclaration);
        if (functionBody == null)
        {
            // We support initializing parameters, even when the containing member doesn't have a
            // body. This is useful for when the user is typing a new constructor and hasn't written
            // the body yet.
            return true;
        }
 
        // In order to get the block operation for the body of an anonymous function, we need to
        // get it via `IAnonymousFunctionOperation.Body` instead of getting it directly from the body syntax.
 
        var operation = semanticModel.GetOperation(
            syntaxFacts.IsAnonymousFunctionExpression(functionDeclaration) ? functionDeclaration : functionBody,
            cancellationToken);
 
        if (operation == null)
            return false;
 
        switch (operation.Kind)
        {
            case OperationKind.AnonymousFunction:
                blockStatement = ((IAnonymousFunctionOperation)operation).Body;
                break;
            case OperationKind.Block:
                blockStatement = (IBlockOperation)operation;
                break;
            default:
                return false;
        }
 
        return true;
    }
 
    public void InsertStatement(SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode? statementToAddAfter, SyntaxNode statement)
        => InsertStatement(editor, functionDeclaration, returnsVoid, statementToAddAfter, (TStatementSyntax)statement);
 
    public async Task<Solution> AddAssignmentAsync(
        Document document,
        IParameterSymbol parameter,
        ISymbol fieldOrProperty,
        CancellationToken cancellationToken)
    {
        if (parameter is { DeclaringSyntaxReferences: [var parameterReference] })
        {
            var parameterDeclaration = parameterReference.GetSyntax(cancellationToken);
 
            var functionDeclaration = parameterDeclaration.FirstAncestorOrSelf<SyntaxNode>(IsFunctionDeclaration);
            if (functionDeclaration is not null)
            {
                // try to handle the case where the parameter is within something function-like (like a constructor)
 
                return await TryAddAssignmentForFunctionLikeDeclarationAsync(
                    document, parameter, fieldOrProperty, functionDeclaration, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                // try to handle primary constructor case.
                return await TryAddAssignmentForPrimaryConstructorAsync(
                    document, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false);
            }
        }
 
        return document.Project.Solution;
    }
 
    private async Task<Solution> TryAddAssignmentForFunctionLikeDeclarationAsync(
        Document document,
        IParameterSymbol parameter,
        ISymbol fieldOrProperty,
        SyntaxNode functionDeclaration,
        CancellationToken cancellationToken)
    {
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
        if (TryGetBlockForSingleParameterInitialization(functionDeclaration, semanticModel, syntaxFacts, cancellationToken, out var blockStatementOpt))
        {
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var editor = new SyntaxEditor(root, document.Project.Solution.Services);
            AddAssignment(functionDeclaration, blockStatementOpt, parameter, fieldOrProperty, editor);
 
            var newDocument = document.WithSyntaxRoot(editor.GetChangedRoot());
            return newDocument.Project.Solution;
        }
 
        return document.Project.Solution;
    }
 
    private void AddAssignment(
        SyntaxNode constructorDeclaration,
        IBlockOperation? blockStatement,
        IParameterSymbol parameter,
        ISymbol fieldOrProperty,
        SyntaxEditor editor)
    {
        // First see if the user has `(_x, y) = (x, y);` and attempt to update that. 
        if (TryUpdateTupleAssignment(blockStatement, parameter, fieldOrProperty, editor))
            return;
 
        var generator = editor.Generator;
 
        // Now that we've added any potential members, create an assignment between it
        // and the parameter.
        var initializationStatement = (TStatementSyntax)generator.ExpressionStatement(
            generator.AssignmentStatement(
                generator.MemberAccessExpression(
                    generator.ThisExpression(),
                    generator.IdentifierName(fieldOrProperty.Name)),
                generator.IdentifierName(parameter.Name)));
 
        // Attempt to place the initialization in a good location in the constructor
        // We'll want to keep initialization statements in the same order as we see
        // parameters for the constructor.
        var statementToAddAfter = TryGetStatementToAddInitializationAfter(parameter, blockStatement);
 
        InsertStatement(editor, constructorDeclaration, returnsVoid: true, statementToAddAfter, initializationStatement);
    }
 
    private SyntaxNode? TryGetStatementToAddInitializationAfter(
        IParameterSymbol parameter, IBlockOperation? blockStatement)
    {
        // look for an existing assignment for a parameter that comes before/after us.
        // If we find one, we'll add ourselves before/after that parameter check.
        foreach (var (sibling, before) in GetSiblingParameters(parameter))
        {
            var statement = TryFindFieldOrPropertyAssignmentStatement(sibling, blockStatement);
            if (statement != null)
            {
                if (before)
                {
                    return statement.Syntax;
                }
                else
                {
                    var statementIndex = blockStatement!.Operations.IndexOf(statement);
                    return statementIndex > 0 && blockStatement.Operations[statementIndex - 1] is { IsImplicit: false, Syntax: var priorSyntax }
                        ? priorSyntax
                        : null;
                }
            }
        }
 
        // We couldn't find a reasonable location for the new initialization statement.
        // Just place ourselves after the last statement in the constructor.
        return TryGetLastStatement(blockStatement);
    }
 
    public bool IsThrowNotImplementedProperty(Compilation compilation, IPropertySymbol property, CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var accessors);
 
        if (property.GetMethod != null)
            accessors.AddIfNotNull(GetAccessorBody(property.GetMethod, cancellationToken));
 
        if (property.SetMethod != null)
            accessors.AddIfNotNull(GetAccessorBody(property.SetMethod, cancellationToken));
 
        if (accessors.Count == 0)
            return false;
 
        foreach (var group in accessors.GroupBy(node => node.SyntaxTree))
        {
            var semanticModel = compilation.GetSemanticModel(group.Key);
            foreach (var accessorBody in accessors)
            {
                var operation = semanticModel.GetOperation(accessorBody, cancellationToken);
                if (operation is null)
                    return false;
 
                if (!operation.IsSingleThrowNotImplementedOperation())
                    return false;
            }
        }
 
        return true;
    }
}