From f0488333e63ec1cd20477a2ab3dacdc152aa06aa Mon Sep 17 00:00:00 2001 From: chborl Date: Thu, 29 Jun 2017 14:54:17 -0700 Subject: [PATCH] First draft PR - Convert Auto Property To Full Property Refactoring --- .../ConvertAutoPropertyToFullPropertyTests.cs | 528 ++++++++++++++++++ src/EditorFeatures/TestUtilities/Traits.cs | 1 + ...tyToFullPropertyCodeRefactoringProvider.cs | 137 +++++ ...tyToFullPropertyCodeRefactoringProvider.cs | 238 ++++++++ .../Portable/FeaturesResources.Designer.cs | 9 + .../Core/Portable/FeaturesResources.resx | 3 + 6 files changed, 916 insertions(+) create mode 100644 src/EditorFeatures/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs create mode 100644 src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs create mode 100644 src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs diff --git a/src/EditorFeatures/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs b/src/EditorFeatures/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs new file mode 100644 index 00000000000..f4f05b0cbfd --- /dev/null +++ b/src/EditorFeatures/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs @@ -0,0 +1,528 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.ConvertAutoPropertyToFullProperty; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertAutoPropertyToFullProperty +{ + public class ConvertAutoPropertyToFullPropertyTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider(); + + private IDictionary PreferExpressionBodiedAccessors => + OptionsSet(SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement)); + + private IDictionary DoNotPreferExpressionBodiedAccessors => + OptionsSet(SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement)); + + private IDictionary DoNotPreferExpressionBodiedAccessorsAndPropertyOpenBraceOnSameLine => + OptionsSet( + SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement), + SingleOption(CSharpFormattingOptions.NewLinesForBracesInProperties, false)); + + private IDictionary DoNotPreferExpressionBodiedAccessorsAndAccessorOpenBraceOnSameLine => + OptionsSet( + SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement), + SingleOption(CSharpFormattingOptions.NewLinesForBracesInAccessors, false)); + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task SimpleAutoPropertyTest() + { + var text = @" +class foo +{ + public int F[||]oo { get; set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + public int Foo + { + get + { + return _foo; + } + set + { + _foo = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task AutoPropertyWithPrivateSetter() + { + var text = @" +class foo +{ + public int F[||]oo { get; private set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + public int Foo + { + get + { + return _foo; + } + private set + { + _foo = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task AutoPropertyWithFieldNameAlreadyUsed() + { + var text = @" +class foo +{ + private int _foo; + + public int F[||]oo { get; private set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + private int _foo1; + + public int Foo + { + get + { + return _foo1; + } + private set + { + _foo1 = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task WithComments() + { + var text = @" +class foo +{ + // Comments before + public int F[||]oo { get; private set; } //Comments during + //Comments after +} +"; + var expected = @" +class foo +{ + private int _foo; + + // Comments before + public int Foo + { + get + { + return _foo; + } + private set + { + _foo = value; + } + } //Comments during + //Comments after +} +"; + await TestInRegularAndScriptAsync(text, expected, options:DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task WithExpressionBody() + { + var text = @" +class foo +{ + public int F[||]oo { get; set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + public int Foo { get => _foo; set => _foo = value; } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: PreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task WithExpressionBodyWithTrivia() + { + var text = @" +class foo +{ + public int F[||]oo { get /* test */ ; set /* test2 */ ; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + public int Foo { get /* test */ => _foo; set /* test2 */ => _foo = value; } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: PreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task WithPropertyOpenBraceOnSameLine() + { + var text = @" +class foo +{ + public int F[||]oo { get; set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + public int Foo { + get + { + return _foo; + } + set + { + _foo = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessorsAndPropertyOpenBraceOnSameLine, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task WithAccessorOpenBraceOnSameLine() + { + var text = @" +class foo +{ + public int F[||]oo { get; set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + public int Foo + { + get { + return _foo; + } + set { + _foo = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessorsAndAccessorOpenBraceOnSameLine, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task StaticProperty() + { + var text = @" +class foo +{ + public static int F[||]oo { get; set; } +} +"; + var expected = @" +class foo +{ + private static int s_foo; + + public static int Foo + { + get + { + return s_foo; + } + set + { + s_foo = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task PropertyWithAttributes() + { + var text = @" +class foo +{ + [A] + public int F[||]oo { get; set; } +} +"; + var expected = @" +class foo +{ + private int _foo; + + [A] + public int Foo + { + get + { + return _foo; + } + set + { + _foo = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task CommentsInAccessors() + { + var text = @" +class foo +{ + /// + /// test stuff here + /// + public int testf[||]oo { /* test1 */ get /* test2 */; /* test3 */ set /* test4 */; /* test5 */ } /* test6 */ +} +"; + var expected = @" +class foo +{ + private int _testfoo; + + /// + /// test stuff here + /// + public int testfoo + { /* test1 */ + get /* test2 */ + { + return _testfoo; + } /* test3 */ + set /* test4 */ + { + _testfoo = value; + } /* test5 */ + } /* test6 */ +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task OverrideProperty() + { + var text = @" +class MyBaseClass +{ + public virtual string Name { get; set; } +} + +class MyDerivedClass : MyBaseClass +{ + public override string N[||]ame {get; set;} +} +"; + var expected = @" +class MyBaseClass +{ + public virtual string Name { get; set; } +} + +class MyDerivedClass : MyBaseClass +{ + private string _name; + + public override string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task SealedProperty() + { + var text = @" +class MyClass +{ + public sealed string N[||]ame {get; set;} +} +"; + var expected = @" +class MyClass +{ + private string _name; + + public sealed string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task VirtualProperty() + { + var text = @" +class MyBaseClass +{ + public virtual string N[||]ame { get; set; } +} + +class MyDerivedClass : MyBaseClass +{ + public override string Name {get; set;} +} +"; + var expected = @" +class MyBaseClass +{ + private string _name; + + public virtual string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } +} + +class MyDerivedClass : MyBaseClass +{ + public override string Name {get; set;} +} +"; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, ignoreTrivia: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task AbstractProperty() + { + var text = @" +class MyBaseClass +{ + public abstract string N[||]ame { get; set; } +} + +class MyDerivedClass : MyBaseClass +{ + public override string Name {get; set;} +} +"; + await TestMissingAsync(text); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task GetterOnly() + { + var text = @" +class foo +{ + public int F[||]oo { get;} +} +"; + await TestMissingAsync(text); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task SetterOnly() + { + var text = @" +class foo +{ + public int F[||]oo +````{ + set {} + } +} +"; + await TestMissingAsync(text); + } + + [Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)] + public async Task ExpressionBodiedAccessors() + { + var text = @" +class foo +{ + private int _testfoo; + + public int testf[||]oo {get => _testfoo; set => _testfoo = value; } +} +"; + await TestMissingAsync(text); + } + + } +} diff --git a/src/EditorFeatures/TestUtilities/Traits.cs b/src/EditorFeatures/TestUtilities/Traits.cs index 9b74f127df8..f546d884b0a 100644 --- a/src/EditorFeatures/TestUtilities/Traits.cs +++ b/src/EditorFeatures/TestUtilities/Traits.cs @@ -123,6 +123,7 @@ public static class Features public const string CodeModelMethodXml = "CodeModel.MethodXml"; public const string CommentSelection = nameof(CommentSelection); public const string Completion = nameof(Completion); + public const string ConvertAutoPropertyToFullProperty = nameof(ConvertAutoPropertyToFullProperty); public const string DebuggingBreakpoints = "Debugging.Breakpoints"; public const string DebuggingDataTips = "Debugging.DataTips"; public const string DebuggingIntelliSense = "Debugging.IntelliSense"; diff --git a/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs new file mode 100644 index 00000000000..ced30097ece --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using System.Diagnostics; +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.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertAutoPropertyToFullProperty +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider)), Shared] + internal class CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider : AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider + { + internal async override Task ConvertToExpressionBodyIfDesiredAsync( + Document document, + SyntaxNode accessor, + CancellationToken cancellationToken) + { + var accessorDeclarationSyntax = (AccessorDeclarationSyntax)accessor; + + var preference = await GetExpressionBodyPreferenceAsync(document, cancellationToken).ConfigureAwait(false); + if (preference == ExpressionBodyPreference.Never) + { + return accessorDeclarationSyntax.WithSemicolonToken(default); + } + + // Should always be able to convert to expression body since we are creating the accessor and know that it only has one line + Debug.Assert(accessorDeclarationSyntax.Body.TryConvertToExpressionBody( + accessorDeclarationSyntax.Kind(), + accessor.SyntaxTree.Options, + preference, + out var arrowExpression, + out var semicolonToken)); + + return accessorDeclarationSyntax + .WithExpressionBody(arrowExpression) + .WithBody(null) + .WithSemicolonToken(semicolonToken); + } + + internal async Task GetExpressionBodyPreferenceAsync( + Document document, + CancellationToken cancellationToken) + { + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + return options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors).Value; + } + + internal override SyntaxNode GetPropertyDeclaration(SyntaxToken token) + { + var containingProperty = token.Parent.FirstAncestorOrSelf(); + if (containingProperty == null) + { + return null; + } + + var start = containingProperty.AttributeLists.Count > 0 + ? containingProperty.AttributeLists.Last().GetLastToken().GetNextToken().SpanStart + : containingProperty.SpanStart; + + // Offer this refactoring anywhere in the signature of the property + var position = token.SpanStart; + if (position < start || position > containingProperty.Identifier.Span.End) + { + return null; + } + + return containingProperty; + } + + internal override bool isAbstract(SyntaxNode property) + { + var propertyDeclarationSyntax = (PropertyDeclarationSyntax)property; + var modifiers = propertyDeclarationSyntax.GetModifiers(); + foreach (var modifier in modifiers) + { + if (modifier.IsKind(SyntaxKind.AbstractKeyword)) + { + return true; + } + } + + return false; + } + + internal override bool TryGetEmptyAccessors( + SyntaxNode propertyDeclarationSyntax, + out SyntaxNode emptyGetAccessor, + out SyntaxNode emptySetAccessor) + { + emptyGetAccessor = null; + emptySetAccessor = null; + + var accessorListSyntax = ((PropertyDeclarationSyntax)propertyDeclarationSyntax).AccessorList; + if (accessorListSyntax == null) + { + return false; + } + + foreach (var accessor in accessorListSyntax.Accessors) + { + if (accessor.Kind() == SyntaxKind.GetAccessorDeclaration && isEmpty(accessor)) + { + emptyGetAccessor = accessor; + } + else if (accessor.Kind() == SyntaxKind.SetAccessorDeclaration && isEmpty(accessor)) + { + emptySetAccessor = accessor; + } + } + + // both getter and setter have to be empty + return (emptyGetAccessor != null && emptySetAccessor != null); + } + + private bool isEmpty(AccessorDeclarationSyntax accessor) + => (accessor.Body == null && accessor.ExpressionBody == null); + + internal override SyntaxNode UpdateAccessor(SyntaxNode accessor, SyntaxNode[] statements) + { + var blockSyntax = SyntaxFactory.Block( + SyntaxFactory.Token(SyntaxKind.OpenBraceToken), + new SyntaxList(statements.Cast()), + SyntaxFactory.Token(SyntaxKind.CloseBraceToken) + .WithTrailingTrivia(((AccessorDeclarationSyntax)accessor).SemicolonToken.TrailingTrivia)); + + return ((AccessorDeclarationSyntax)accessor).WithBody(blockSyntax); + } + } +} diff --git a/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs new file mode 100644 index 00000000000..b2d101ed47c --- /dev/null +++ b/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; + +namespace Microsoft.CodeAnalysis.ConvertAutoPropertyToFullProperty +{ + internal abstract class AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider + : CodeRefactoringProvider + { + internal abstract SyntaxNode GetPropertyDeclaration(SyntaxToken token); + internal abstract bool isAbstract(SyntaxNode property); + internal abstract bool TryGetEmptyAccessors(SyntaxNode propertyDeclarationSyntax, + out SyntaxNode emptyGetAccessor, out SyntaxNode emptySetAccessor); + internal abstract SyntaxNode UpdateAccessor(SyntaxNode accessor, SyntaxNode[] statements); + internal abstract Task ConvertToExpressionBodyIfDesiredAsync(Document document, + SyntaxNode getAccessor, CancellationToken cancellationToken); + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var document = context.Document; + var cancellationToken = context.CancellationToken; + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var token = root.FindToken(context.Span.Start); + + var property = GetPropertyDeclaration(token); + if (property == null) + { + return; + } + + if (isAbstract(property)) + { + return; + } + + // check to see if both property accessors exist and are empty + if (!TryGetEmptyAccessors(property, out var emptyGetAccessor, out var emptySetAccessor)) + { + return; + } + + context.RegisterRefactoring( + new ConvertAutoPropertyToFullPropertyCodeAction( + FeaturesResources.Convert_to_full_property, + c => ExpandToFullPropertyAsync( + document, + property, + emptyGetAccessor, + emptySetAccessor, + root, + context.CancellationToken))); + } + + private async Task ExpandToFullPropertyAsync( + Document document, + SyntaxNode property, + SyntaxNode emptyGetAccessor, + SyntaxNode emptySetAccessor, + SyntaxNode root, + CancellationToken cancellationToken) + { + // Get the symbol representing the property + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var propertySymbol = semanticModel.GetDeclaredSymbol(property) as IPropertySymbol; + + // generate a name for the new field based on naming preferences + var rules = await GetNamingRulesAsync( + document, + cancellationToken).ConfigureAwait(false); + var fieldName = GenerateFieldName(propertySymbol, rules); + + // expand the property and add the field + var generator = SyntaxGenerator.GetGenerator(document); + var newRoot = await ExpandPropertyAndAddFieldAsync( + document, + property, + emptyGetAccessor, + emptySetAccessor, + root, + propertySymbol, + fieldName, + generator, + cancellationToken).ConfigureAwait(false); + + return document.WithSyntaxRoot(newRoot); + } + + private static async Task> GetNamingRulesAsync( + Document document, + CancellationToken cancellationToken) + { + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + var namingStyleOptions = options.GetOption(SimplificationOptions.NamingPreferences); + var rules = namingStyleOptions.CreateRules().NamingRules + .AddRange(GetDefaultRule(ImmutableArray.Create(new ModifierKind(ModifierKindEnum.IsStatic)), "s_")) + .AddRange(GetDefaultRule(ImmutableArray.Create(), "_")); + return rules; + } + + private static string GenerateFieldName( + IPropertySymbol property, + ImmutableArray rules) + { + var propertyName = property.Name; + var fieldName = ""; + var isStatic = property.IsStatic; + foreach (var rule in rules) + { + if (rule.SymbolSpecification.AppliesTo( + new SymbolKindOrTypeKind(SymbolKind.Field), + isStatic ? DeclarationModifiers.Static : DeclarationModifiers.None, + Accessibility.Private)) + { + fieldName = rule.NamingStyle.MakeCompliant(propertyName).Single(); + break; + } + } + + var uniqueName = NameGenerator.GenerateUniqueName( + fieldName, n => property.ContainingType.GetMembers(n).IsEmpty); + return uniqueName; + } + + private async Task ExpandPropertyAndAddFieldAsync( + Document document, + SyntaxNode property, + SyntaxNode getAccessor, + SyntaxNode setAccessor, + SyntaxNode root, + IPropertySymbol propertySymbol, + string fieldName, + SyntaxGenerator generator, + CancellationToken cancellationToken) + { + var workspace = document.Project.Solution.Workspace; + + // add statements to existing accessors + var getAccessorStatements = new SyntaxNode[] { + generator.ReturnStatement( + generator.IdentifierName(fieldName)) }; + var newGetAccessor = await AddStatementsToAccessorAsync( + document, + getAccessor, + getAccessorStatements, + generator, + cancellationToken).ConfigureAwait(false); + + var setAccessorStatements = new SyntaxNode[] { + generator.ExpressionStatement(generator.AssignmentStatement( + generator.IdentifierName(fieldName), + generator.IdentifierName("value"))) }; + var newSetAccessor = await AddStatementsToAccessorAsync( + document, + setAccessor, + setAccessorStatements, + generator, + cancellationToken).ConfigureAwait(false); + + var newProperty = generator + .WithAccessorDeclarations(property, new SyntaxNode[] { newGetAccessor, newSetAccessor}) + .WithAdditionalAnnotations(Formatter.Annotation) + .WithAdditionalAnnotations(new SyntaxAnnotation("property")); + newProperty = await Formatter.FormatAsync(newProperty, workspace).ConfigureAwait(false); + var newRoot = root.ReplaceNode(property, newProperty); + + var newField = generator.FieldDeclaration( + fieldName, + generator.TypeExpression(propertySymbol.Type), + Accessibility.Private, + DeclarationModifiers.From(propertySymbol)). + WithAdditionalAnnotations(Formatter.Annotation); + var newFieldNodes = SpecializedCollections.SingletonEnumerable(newField); + newProperty = newRoot.GetAnnotatedNodes("property").Single(); + newRoot = newRoot.InsertNodesBefore(newProperty, newFieldNodes); + return newRoot; + } + + private async Task AddStatementsToAccessorAsync( + Document document, + SyntaxNode accessor, + SyntaxNode[] statements, + SyntaxGenerator generator, + CancellationToken cancellationToken) + { + // shell to lang specific to update the accessor. + var newAccessor = UpdateAccessor(accessor, statements); + + // then conver to expression bod + newAccessor= await ConvertToExpressionBodyIfDesiredAsync( + document, + newAccessor, + cancellationToken).ConfigureAwait(false); + + return await Formatter.FormatAsync(newAccessor, document.Project.Solution.Workspace).ConfigureAwait(false); + } + + private static ImmutableArray GetDefaultRule( + ImmutableArray modifiers, + string prefix) + { + return ImmutableArray.Create( + new NamingRule( + new SymbolSpecification( + Guid.NewGuid(), + "Field", + ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field)), + modifiers: modifiers), + new NamingStyles.NamingStyle( + Guid.NewGuid(), + prefix: prefix, + capitalizationScheme: Capitalization.CamelCase), + DiagnosticSeverity.Hidden)); + } + + private class ConvertAutoPropertyToFullPropertyCodeAction : CodeAction.DocumentChangeAction + { + public ConvertAutoPropertyToFullPropertyCodeAction( + string Title, + Func> createChangedDocument) : base(Title, createChangedDocument) + { + } + } + } +} diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index 1b03909799e..33dc628aa0c 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -834,6 +834,15 @@ internal class FeaturesResources { } } + /// + /// Looks up a localized string similar to Convert to full property. + /// + internal static string Convert_to_full_property { + get { + return ResourceManager.GetString("Convert_to_full_property", resourceCulture); + } + } + /// /// Looks up a localized string similar to Convert to hex. /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index fbbe6bfad92..18816cdbe90 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1292,6 +1292,9 @@ This version used in: {2} Move declaration near reference + + Convert to full property + Generate constructor in '{0}' (without fields) -- GitLab