File: ConvertAutoPropertyToFullProperty\CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.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.
 
#nullable disable
 
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.ConvertAutoPropertyToFullProperty;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.ConvertAutoPropertyToFullProperty;
 
using static CSharpSyntaxTokens;
using static SyntaxFactory;
 
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertAutoPropertyToFullProperty), Shared]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider()
    : AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider<PropertyDeclarationSyntax, TypeDeclarationSyntax, CSharpCodeGenerationContextInfo>
{
    protected override async Task<string> GetFieldNameAsync(Document document, IPropertySymbol property, CancellationToken cancellationToken)
    {
        var rule = await document.GetApplicableNamingRuleAsync(
            new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field),
            property.IsStatic ? DeclarationModifiers.Static : DeclarationModifiers.None,
            Accessibility.Private,
            cancellationToken).ConfigureAwait(false);
 
        var fieldName = rule.NamingStyle.MakeCompliant(property.Name).First();
        return NameGenerator.GenerateUniqueName(fieldName, n => !(property.ContainingType.Name == n || property.ContainingType.GetMembers(n).Any()));
    }
 
    protected override (SyntaxNode newGetAccessor, SyntaxNode newSetAccessor) GetNewAccessors(
        CSharpCodeGenerationContextInfo info,
        PropertyDeclarationSyntax property,
        string fieldName,
        CancellationToken cancellationToken)
    {
        // Replace the bodies with bodies that reference the new field name.
        return GetNewAccessors(info, property, fieldName.ToIdentifierName(), cancellationToken);
    }
 
    private static (SyntaxNode newGetAccessor, SyntaxNode newSetAccessor) GetNewAccessors(
        CSharpCodeGenerationContextInfo info,
        PropertyDeclarationSyntax property,
        ExpressionSyntax backingFieldExpression,
        CancellationToken cancellationToken)
    {
        // C# might have trivia with the accessors that needs to be preserved.  
        // so we will update the existing accessors instead of creating new ones
        var accessorListSyntax = property.AccessorList;
        var (getAccessor, setAccessor) = GetExistingAccessors(accessorListSyntax);
 
        var getAccessorStatement = ReturnStatement(backingFieldExpression);
        var newGetter = GetUpdatedAccessor(getAccessor, getAccessorStatement);
 
        var newSetter = setAccessor;
        if (newSetter != null)
        {
            var setAccessorStatement = ExpressionStatement(AssignmentExpression(
                SyntaxKind.SimpleAssignmentExpression,
                backingFieldExpression,
                IdentifierName("value")));
            newSetter = GetUpdatedAccessor(setAccessor, setAccessorStatement);
        }
 
        return (newGetter, newSetter);
 
        AccessorDeclarationSyntax GetUpdatedAccessor(AccessorDeclarationSyntax accessor, StatementSyntax statement)
        {
            if (accessor.Body != null || accessor.ExpressionBody != null)
                return ReplaceFieldExpression(accessor);
 
            var accessorDeclarationSyntax = accessor.WithBody(Block(
                OpenBraceToken.WithLeadingTrivia(ElasticCarriageReturnLineFeed),
                [statement],
                CloseBraceToken.WithTrailingTrivia(accessor.SemicolonToken.TrailingTrivia)));
 
            var preference = info.Options.PreferExpressionBodiedAccessors.Value;
            if (preference == ExpressionBodyPreference.Never)
                return accessorDeclarationSyntax.WithSemicolonToken(default);
 
            if (!accessorDeclarationSyntax.Body.TryConvertToArrowExpressionBody(
                    accessorDeclarationSyntax.Kind(), info.LanguageVersion, preference, cancellationToken,
                    out var arrowExpression, out _))
            {
                return accessorDeclarationSyntax.WithSemicolonToken(default);
            }
 
            return accessorDeclarationSyntax
                .WithExpressionBody(arrowExpression)
                .WithBody(null)
                .WithSemicolonToken(accessorDeclarationSyntax.SemicolonToken)
                .WithAdditionalAnnotations(Formatter.Annotation);
        }
 
        AccessorDeclarationSyntax ReplaceFieldExpression(AccessorDeclarationSyntax accessor)
        {
            return accessor.ReplaceNodes(
                accessor.DescendantNodes().OfType<FieldExpressionSyntax>(),
                (oldNode, _) => backingFieldExpression.WithTriviaFrom(oldNode));
        }
    }
 
    private static (AccessorDeclarationSyntax getAccessor, AccessorDeclarationSyntax setAccessor)
        GetExistingAccessors(AccessorListSyntax accessorListSyntax)
        => (accessorListSyntax.Accessors.FirstOrDefault(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)),
            accessorListSyntax.Accessors.FirstOrDefault(a => a.Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKind.InitAccessorDeclaration));
 
    protected override SyntaxNode ConvertPropertyToExpressionBodyIfDesired(
        CSharpCodeGenerationContextInfo info, SyntaxNode property)
    {
        var propertyDeclaration = (PropertyDeclarationSyntax)property;
 
        var preference = info.Options.PreferExpressionBodiedProperties.Value;
        if (preference == ExpressionBodyPreference.Never)
            return propertyDeclaration;
 
        // if there is a get accessors only, we can move the expression body to the property
        if (propertyDeclaration.AccessorList?.Accessors.Count == 1 &&
            propertyDeclaration.AccessorList.Accessors[0].Kind() == SyntaxKind.GetAccessorDeclaration)
        {
            var getAccessor = propertyDeclaration.AccessorList.Accessors[0];
            if (getAccessor.ExpressionBody != null)
            {
                return propertyDeclaration.WithExpressionBody(getAccessor.ExpressionBody)
                    .WithSemicolonToken(getAccessor.SemicolonToken)
                    .WithAccessorList(null);
            }
        }
 
        return propertyDeclaration;
    }
 
    protected override SyntaxNode GetTypeBlock(SyntaxNode syntaxNode)
        => syntaxNode;
 
    protected override SyntaxNode GetInitializerValue(PropertyDeclarationSyntax property)
        => property.Initializer?.Value;
 
    protected override PropertyDeclarationSyntax GetPropertyWithoutInitializer(PropertyDeclarationSyntax property)
        => property.WithInitializer(null);
 
    protected override async Task<Document> ExpandToFieldPropertyAsync(
        Document document, PropertyDeclarationSyntax property, CancellationToken cancellationToken)
    {
        var info = (CSharpCodeGenerationContextInfo)await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, cancellationToken).ConfigureAwait(false);
 
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
        // Update the getter/setter to reference the 'field' expression instead.
        var (newGetAccessor, newSetAccessor) = GetNewAccessors(info, property, FieldExpression(), cancellationToken);
 
        var finalProperty = CreateFinalProperty(document, property, info, newGetAccessor, newSetAccessor);
        var finalRoot = root.ReplaceNode(property, finalProperty);
 
        return document.WithSyntaxRoot(finalRoot);
    }
}