| File: ConvertAutoPropertyToFullProperty\CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs | Web Access |
| Project: src\roslyn\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 ? Modifiers.Static : Modifiers.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 is { Initializer: null, AccessorList.Accessors: [AccessorDeclarationSyntax(SyntaxKind.GetAccessorDeclaration) { ExpressionBody: { } expressionBody, SemicolonToken: var semicolonToken }], }) { return propertyDeclaration.WithExpressionBody(expressionBody) .WithSemicolonToken(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); } }