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.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;
 
[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, SyntaxGenerator generator, 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 = generator.ReturnStatement(generator.IdentifierName(fieldName));
        var newGetter = GetUpdatedAccessor(info, getAccessor, getAccessorStatement, cancellationToken);
 
        var newSetter = setAccessor;
        if (newSetter != null)
        {
            var setAccessorStatement = generator.ExpressionStatement(generator.AssignmentStatement(
                generator.IdentifierName(fieldName),
                generator.IdentifierName("value")));
            newSetter = GetUpdatedAccessor(info, setAccessor, setAccessorStatement, cancellationToken);
        }
 
        return (newGetter, newSetter);
    }
 
    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));
 
    private static AccessorDeclarationSyntax GetUpdatedAccessor(CSharpCodeGenerationContextInfo info,
        AccessorDeclarationSyntax accessor, SyntaxNode statement, CancellationToken cancellationToken)
    {
        if (accessor.Body != null || accessor.ExpressionBody != null)
            return accessor;
 
        var newAccessor = AddStatement(accessor, statement);
        var accessorDeclarationSyntax = (AccessorDeclarationSyntax)newAccessor;
 
        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);
    }
 
    internal static SyntaxNode AddStatement(SyntaxNode accessor, SyntaxNode statement)
    {
        var blockSyntax = SyntaxFactory.Block(
            OpenBraceToken.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed),
            new SyntaxList<StatementSyntax>((StatementSyntax)statement),
            CloseBraceToken
                .WithTrailingTrivia(((AccessorDeclarationSyntax)accessor).SemicolonToken.TrailingTrivia));
 
        return ((AccessorDeclarationSyntax)accessor).WithBody(blockSyntax);
    }
 
    protected override SyntaxNode ConvertPropertyToExpressionBodyIfDesired(
        CSharpCodeGenerationContextInfo info, SyntaxNode property)
    {
        var propertyDeclaration = (PropertyDeclarationSyntax)property;
 
        var preference = info.Options.PreferExpressionBodiedProperties.Value;
        if (preference == ExpressionBodyPreference.Never)
        {
            return propertyDeclaration.WithSemicolonToken(default);
        }
 
        // 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.WithSemicolonToken(default);
    }
 
    protected override SyntaxNode GetTypeBlock(SyntaxNode syntaxNode)
        => syntaxNode;
 
    protected override SyntaxNode GetInitializerValue(SyntaxNode property)
        => ((PropertyDeclarationSyntax)property).Initializer?.Value;
 
    protected override SyntaxNode GetPropertyWithoutInitializer(SyntaxNode property)
        => ((PropertyDeclarationSyntax)property).WithInitializer(null);
}