提交 90b850fe 编写于 作者: C chborl

Second draft - Convert Auto Prop to Full Prop Refactoring

上级 f0488333
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.NamingStyles;
using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification;
using Microsoft.CodeAnalysis.Editing;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertAutoPropertyToFullPropertyTests
{
public partial class ConvertAutoPropertyToFullPropertyTests : AbstractCSharpCodeActionTest
{
private IDictionary<OptionKey, object> PreferExpressionBodiedAccessorsWhenPossible =>
OptionsSet(SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement));
private IDictionary<OptionKey, object> PreferExpressionBodiedAccessorsWhenOnSingleLine =>
OptionsSet(SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenOnSingleLineWithNoneEnforcement));
private IDictionary<OptionKey, object> DoNotPreferExpressionBodiedAccessors =>
OptionsSet(SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement));
private IDictionary<OptionKey, object> DoNotPreferExpressionBodiedAccessorsAndPropertyOpenBraceOnSameLine =>
OptionsSet(
SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement),
SingleOption(CSharpFormattingOptions.NewLinesForBracesInProperties, false));
private IDictionary<OptionKey, object> DoNotPreferExpressionBodiedAccessorsAndAccessorOpenBraceOnSameLine =>
OptionsSet(
SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement),
SingleOption(CSharpFormattingOptions.NewLinesForBracesInAccessors, false));
private IDictionary<OptionKey, object> UseCustomFieldName =>
OptionsSet(
SingleOption(SimplificationOptions.NamingPreferences, CreateCustomFieldNamingStylePreference()),
SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement));
private IDictionary<OptionKey, object> UseCustomStaticFieldName =>
OptionsSet(
SingleOption(SimplificationOptions.NamingPreferences, CreateCustomStaticFieldNamingStylePreference()),
SingleOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithNoneEnforcement));
private NamingStylePreferences CreateCustomFieldNamingStylePreference()
{
var symbolSpecification = new SymbolSpecification(
null,
"Name",
ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field)),
ImmutableArray<Accessibility>.Empty,
ImmutableArray<SymbolSpecification.ModifierKind>.Empty);
var namingStyle = new NamingStyle(
Guid.NewGuid(),
capitalizationScheme: Capitalization.PascalCase,
name: "CustomFieldTest",
prefix: "testing",
suffix: "",
wordSeparator: "");
var namingRule = new SerializableNamingRule()
{
SymbolSpecificationID = symbolSpecification.ID,
NamingStyleID = namingStyle.ID,
EnforcementLevel = DiagnosticSeverity.Error
};
var info = new NamingStylePreferences(
ImmutableArray.Create(symbolSpecification),
ImmutableArray.Create(namingStyle),
ImmutableArray.Create(namingRule));
return info;
}
private NamingStylePreferences CreateCustomStaticFieldNamingStylePreference()
{
var symbolSpecification = new SymbolSpecification(
null,
"Name",
ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Field)),
ImmutableArray<Accessibility>.Empty,
ImmutableArray.Create(new ModifierKind(DeclarationModifiers.Static)));
var namingStyle = new NamingStyle(
Guid.NewGuid(),
capitalizationScheme: Capitalization.PascalCase,
name: "CustomStaticFieldTest",
prefix: "staticfieldtest",
suffix: "",
wordSeparator: "");
var namingRule = new SerializableNamingRule()
{
SymbolSpecificationID = symbolSpecification.ID,
NamingStyleID = namingStyle.ID,
EnforcementLevel = DiagnosticSeverity.Error
};
var info = new NamingStylePreferences(
ImmutableArray.Create(symbolSpecification),
ImmutableArray.Create(namingStyle),
ImmutableArray.Create(namingRule));
return info;
}
}
}
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings
Imports Microsoft.CodeAnalysis.VisualBasic.VisualBasicConvertAutoPropertyToFullPropertyCodeRefactoringProvider
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ValidateFormatString
Public Class ConvertAutoPropertyToFullPropertyTests
Inherits AbstractVisualBasicCodeActionTest
Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider
Return New VisualBasicConvertAutoPropertyToFullPropertyCodeRefactoringProvider()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function SimpleTest() As Task
Dim initial = "
Class C
Public Property T[||]est1 As Integer
End Class"
Dim expected = "
Class C
Private _test1 As Integer
Public Property Test1 As Integer
Get
Return _test1
End Get
Set
_test1 = Value
End Set
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function WithInitializer() As Task
Dim initial = "
Class C
Public Property T[||]est2 As Integer = 4
End Class"
Dim expected = "
Class C
Private _test2 As Integer = 4
Public Property Test2 As Integer
Get
Return _test2
End Get
Set
_test2 = Value
End Set
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function WithReadonly() As Task
Dim initial = "
Class C
Public ReadOnly Property T[||]est5 As String
End Class"
Dim expected = "
Class C
Private ReadOnly _test5 As String
Public ReadOnly Property Test5 As String
Get
Return _test5
End Get
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function WithReadonlyAndInitializer() As Task
Dim initial = "
Class C
Public ReadOnly Property Tes[||]t4 As String = ""Initial Value""
End Class"
Dim expected = "
Class C
Private ReadOnly _test4 As String = ""Initial Value""
Public ReadOnly Property Test4 As String
Get
Return _test4
End Get
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function PrivateProperty() As Task
Dim initial = "
Class C
Private Property Tes[||]t4 As String
End Class"
Dim expected = "
Class C
Private _test4 As String
Private Property Test4 As String
Get
Return _test4
End Get
Set
_test4 = Value
End Set
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function WithComments() As Task
Dim initial = "
Class C
'' Comment before
Public Property Test1 As In[||]teger ''Comment during
'' Comment after
End Class"
Dim expected = "
Class C
Private _test1 As Integer
'' Comment before
Public Property Test1 As Integer ''Comment during
Get
Return _test1
End Get
Set
_test1 = Value
End Set
End Property
'' Comment after
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function SharedProperty() As Task
Dim initial = "
Class C
Public Sha[||]red Property Test1 As Double
End Class"
Dim expected = "
Class C
Private Shared s_test1 As Double
Public Shared Property Test1 As Double
Get
Return s_test1
End Get
Set
s_test1 = Value
End Set
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function WithOverridable() As Task
Dim initial = "
Class C
Public Overridable Proper[||]ty Test4 As Decimal
End Class"
Dim expected = "
Class C
Private _test4 As Decimal
Public Overridable Property Test4 As Decimal
Get
Return _test4
End Get
Set
_test4 = Value
End Set
End Property
End Class"
Await TestInRegularAndScriptAsync(initial, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function WithMustOverride() As Task
Await TestDiagnosticMissingAsync("
Class C
Public MustOverride Property Tes[||]t4 As String
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function CursorOnInitializer() As Task
Await TestDiagnosticMissingAsync("
Class C
Public Property Test2 As Integer [||]= 4
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)>
Public Async Function InInterface() As Task
Await TestDiagnosticMissingAsync("
Interface I
Public Property Tes[||]t2 As Integer
End Interface")
End Function
End Class
End Namespace
// 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;
......@@ -12,48 +11,16 @@
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ConvertAutoPropertyToFullProperty
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider)), Shared]
internal class CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider : AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider
{
internal async override Task<SyntaxNode> 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<ExpressionBodyPreference> 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)
internal override SyntaxNode GetProperty(SyntaxToken token)
{
var containingProperty = token.Parent.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
if (containingProperty == null)
......@@ -75,63 +42,150 @@ internal override SyntaxNode GetPropertyDeclaration(SyntaxToken token)
return containingProperty;
}
internal override bool isAbstract(SyntaxNode property)
internal override async Task<(SyntaxNode newGetAccessor, SyntaxNode newSetAccessor)> GetNewAccessorsAsync(Document document, SyntaxNode property, string fieldName, SyntaxGenerator generator, CancellationToken cancellationToken)
{
var propertyDeclarationSyntax = (PropertyDeclarationSyntax)property;
var modifiers = propertyDeclarationSyntax.GetModifiers();
foreach (var modifier in modifiers)
// 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 = ((PropertyDeclarationSyntax)property).AccessorList;
var existingAccessors = GetExistingAccessors(accessorListSyntax);
var getAccessorStatement = generator.ReturnStatement(generator.IdentifierName(fieldName));
var newGetter = await AddStatementsToAccessorAsync(
document,
existingAccessors.getAccessor,
getAccessorStatement,
generator,
cancellationToken).ConfigureAwait(false);
SyntaxNode newSetter = null;
if (existingAccessors.setAccessor != null)
{
if (modifier.IsKind(SyntaxKind.AbstractKeyword))
{
return true;
}
var setAccessorStatement = generator.ExpressionStatement(generator.AssignmentStatement(
generator.IdentifierName(fieldName),
generator.IdentifierName("value")));
newSetter = await AddStatementsToAccessorAsync(
document,
existingAccessors.setAccessor,
setAccessorStatement,
generator,
cancellationToken).ConfigureAwait(false);
}
return false;
return (newGetAccessor: newGetter, newSetAccessor: newSetter);
}
internal override bool TryGetEmptyAccessors(
SyntaxNode propertyDeclarationSyntax,
out SyntaxNode emptyGetAccessor,
out SyntaxNode emptySetAccessor)
private (AccessorDeclarationSyntax getAccessor, AccessorDeclarationSyntax setAccessor) GetExistingAccessors(AccessorListSyntax accessorListSyntax)
{
emptyGetAccessor = null;
emptySetAccessor = null;
var accessorListSyntax = ((PropertyDeclarationSyntax)propertyDeclarationSyntax).AccessorList;
if (accessorListSyntax == null)
AccessorDeclarationSyntax getter = null;
AccessorDeclarationSyntax setter = null;
foreach (var accessor in accessorListSyntax.Accessors)
{
return false;
if (accessor.Kind() == SyntaxKind.GetAccessorDeclaration)
{
getter = accessor;
}
else if (accessor.Kind() == SyntaxKind.SetAccessorDeclaration)
{
setter = accessor;
}
}
return (getAccessor: getter, setAccessor: setter);
}
private static void GetExistingAccessors(AccessorListSyntax accessorListSyntax, ref AccessorDeclarationSyntax getAccessor, ref AccessorDeclarationSyntax setAccessor)
{
foreach (var accessor in accessorListSyntax.Accessors)
{
if (accessor.Kind() == SyntaxKind.GetAccessorDeclaration && isEmpty(accessor))
if (accessor.Kind() == SyntaxKind.GetAccessorDeclaration)
{
emptyGetAccessor = accessor;
getAccessor = accessor;
}
else if (accessor.Kind() == SyntaxKind.SetAccessorDeclaration && isEmpty(accessor))
else if (accessor.Kind() == SyntaxKind.SetAccessorDeclaration)
{
emptySetAccessor = accessor;
setAccessor = accessor;
}
}
}
// both getter and setter have to be empty
return (emptyGetAccessor != null && emptySetAccessor != null);
private async Task<SyntaxNode> AddStatementsToAccessorAsync(
Document document,
SyntaxNode accessor,
SyntaxNode statement,
SyntaxGenerator generator,
CancellationToken cancellationToken)
{
var newAccessor = UpdateAccessor(accessor, statement);
newAccessor = await ConvertToExpressionBodyIfDesiredAsync(
document,
newAccessor,
cancellationToken).ConfigureAwait(false);
return await Formatter.FormatAsync(newAccessor, document.Project.Solution.Workspace).ConfigureAwait(false);
}
private bool isEmpty(AccessorDeclarationSyntax accessor)
=> (accessor.Body == null && accessor.ExpressionBody == null);
internal async Task<SyntaxNode> ConvertToExpressionBodyIfDesiredAsync(
Document document,
SyntaxNode accessor,
CancellationToken cancellationToken)
{
var accessorDeclarationSyntax = (AccessorDeclarationSyntax)accessor;
internal override SyntaxNode UpdateAccessor(SyntaxNode accessor, SyntaxNode[] statements)
var preference = await GetExpressionBodyPreferenceAsync(document, cancellationToken).ConfigureAwait(false);
if (preference == ExpressionBodyPreference.Never)
{
return accessorDeclarationSyntax.WithSemicolonToken(default);
}
var ableToConvert = accessorDeclarationSyntax.Body.TryConvertToExpressionBody(
accessorDeclarationSyntax.Kind(),
accessor.SyntaxTree.Options,
preference,
out var arrowExpression,
out var semicolonToken);
// Should always be able to convert to expression body since we are creating the accessor
// and know that it only has one statement
Debug.Assert(ableToConvert);
return accessorDeclarationSyntax
.WithExpressionBody(arrowExpression)
.WithBody(null)
.WithSemicolonToken(semicolonToken);
}
internal SyntaxNode UpdateAccessor(SyntaxNode accessor, SyntaxNode statement)
{
var blockSyntax = SyntaxFactory.Block(
SyntaxFactory.Token(SyntaxKind.OpenBraceToken),
new SyntaxList<StatementSyntax>(statements.Cast<StatementSyntax>()),
new SyntaxList<StatementSyntax>((StatementSyntax)statement),
SyntaxFactory.Token(SyntaxKind.CloseBraceToken)
.WithTrailingTrivia(((AccessorDeclarationSyntax)accessor).SemicolonToken.TrailingTrivia));
return ((AccessorDeclarationSyntax)accessor).WithBody(blockSyntax);
}
internal async Task<ExpressionBodyPreference> GetExpressionBodyPreferenceAsync(
Document document,
CancellationToken cancellationToken)
{
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
return options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors).Value;
}
private bool IsEmpty(AccessorDeclarationSyntax accessor)
=> accessor.Body == null && accessor.ExpressionBody == null;
internal override string GetUniqueName(string fieldName, IPropertySymbol property)
=> NameGenerator.GenerateUniqueName(fieldName, n => !property.ContainingType.GetMembers(n).Any());
internal override SyntaxNode GetTypeBlock(SyntaxNode syntaxNode)
=> syntaxNode;
internal override SyntaxNode GetInitializerValue(SyntaxNode property)
=> ((PropertyDeclarationSyntax)property).Initializer?.Value;
internal override SyntaxNode GetPropertyWithoutInitializer(SyntaxNode property)
=> ((PropertyDeclarationSyntax)property).WithInitializer(null);
}
}
......@@ -6,27 +6,27 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
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
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<SyntaxNode> ConvertToExpressionBodyIfDesiredAsync(Document document,
SyntaxNode getAccessor, CancellationToken cancellationToken);
internal abstract SyntaxNode GetProperty(SyntaxToken token);
internal abstract string GetUniqueName(string fieldName, IPropertySymbol property);
internal abstract SyntaxNode GetInitializerValue(SyntaxNode property);
internal abstract SyntaxNode GetPropertyWithoutInitializer(SyntaxNode property);
internal abstract Task<(SyntaxNode newGetAccessor, SyntaxNode newSetAccessor)> GetNewAccessorsAsync(
Document document, SyntaxNode property, string fieldName, SyntaxGenerator generator,
CancellationToken cancellationToken);
internal abstract SyntaxNode GetTypeBlock(SyntaxNode syntaxNode);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
......@@ -35,19 +35,13 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var token = root.FindToken(context.Span.Start);
var property = GetPropertyDeclaration(token);
var property = GetProperty(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))
if (!(await IsValidAutoProperty(property, document, cancellationToken).ConfigureAwait(false)))
{
return;
}
......@@ -58,59 +52,100 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
c => ExpandToFullPropertyAsync(
document,
property,
emptyGetAccessor,
emptySetAccessor,
root,
context.CancellationToken)));
cancellationToken)));
}
internal async Task<bool> IsValidAutoProperty(
SyntaxNode property, Document document, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken)
.ConfigureAwait(false);
var propertySymbol = semanticModel.GetDeclaredSymbol(property) as IPropertySymbol;
var members = propertySymbol.ContainingType.GetMembers()
.Where(n => n.Kind == SymbolKind.Field);
IFieldSymbol fieldSymbol;
foreach (var member in members)
{
fieldSymbol = (IFieldSymbol)member;
if (fieldSymbol.AssociatedSymbol?.Name == propertySymbol.Name)
{
return true;
}
}
return false;
}
private async Task<Document> ExpandToFullPropertyAsync(
Document document,
SyntaxNode property,
SyntaxNode emptyGetAccessor,
SyntaxNode emptySetAccessor,
SyntaxNode root,
Document document,
SyntaxNode property,
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,
document,
property,
root,
propertySymbol,
fieldName,
generator,
cancellationToken).ConfigureAwait(false);
return document.WithSyntaxRoot(newRoot);
}
/// <summary>
/// Get the user-specified naming rules, then add standard default naming rules
/// for both static and non-static fields. The standard naming rules are added at the end
/// so they will only be used if the user hasn't specified a preference.
/// </summary>
/// <param name="document"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private static async Task<ImmutableArray<NamingRule>> GetNamingRulesAsync(
Document document,
CancellationToken cancellationToken)
{
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var namingStyleOptions = options.GetOption(SimplificationOptions.NamingPreferences);
var rules = namingStyleOptions.CreateRules().NamingRules
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var namingPreferencesOption = optionSet.GetOption(SimplificationOptions.NamingPreferences);
var rules = namingPreferencesOption.CreateRules().NamingRules
.AddRange(GetDefaultRule(ImmutableArray.Create(new ModifierKind(ModifierKindEnum.IsStatic)), "s_"))
.AddRange(GetDefaultRule(ImmutableArray.Create<ModifierKind>(), "_"));
return rules;
}
private static string GenerateFieldName(
private static ImmutableArray<NamingRule> GetDefaultRule(
ImmutableArray<ModifierKind> 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 string GenerateFieldName(
IPropertySymbol property,
ImmutableArray<NamingRule> rules)
{
......@@ -129,100 +164,47 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
}
}
var uniqueName = NameGenerator.GenerateUniqueName(
fieldName, n => property.ContainingType.GetMembers(n).IsEmpty);
return uniqueName;
return GetUniqueName(fieldName, property);
}
private async Task<SyntaxNode> ExpandPropertyAndAddFieldAsync(
Document document,
SyntaxNode property,
SyntaxNode getAccessor,
SyntaxNode setAccessor,
SyntaxNode root,
IPropertySymbol propertySymbol,
string fieldName,
SyntaxGenerator generator,
Document document,
SyntaxNode property,
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<SyntaxNode> 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(
// Create full property. If the auto property had an initial value
// we need to remove it and later add it to the backing field
var accessorTuple = await GetNewAccessorsAsync(
document,
newAccessor,
cancellationToken).ConfigureAwait(false);
return await Formatter.FormatAsync(newAccessor, document.Project.Solution.Workspace).ConfigureAwait(false);
}
property,
fieldName,
generator,
cancellationToken)
.ConfigureAwait(false);
var fullProperty = generator
.WithAccessorDeclarations(GetPropertyWithoutInitializer(property), accessorTuple.newSetAccessor == null ? new SyntaxNode[] { accessorTuple.newGetAccessor } : new SyntaxNode[] { accessorTuple.newGetAccessor, accessorTuple.newSetAccessor })
.WithLeadingTrivia(property.GetLeadingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation);
var editor = new SyntaxEditor(root, workspace);
editor.ReplaceNode(property, fullProperty);
// add backing field, plus initializer if it exists
var newField = CodeGenerationSymbolFactory.CreateFieldSymbol(default, Accessibility.Private, DeclarationModifiers.From(propertySymbol), propertySymbol.Type, fieldName, initializer: GetInitializerValue(property));
var containingType = GetTypeBlock(propertySymbol.ContainingType.DeclaringSyntaxReferences.FirstOrDefault().GetSyntax(cancellationToken));
editor.ReplaceNode(containingType, (currentTypeDecl, _) =>
{
return CodeGenerator.AddFieldDeclaration(currentTypeDecl, newField, workspace);
});
private static ImmutableArray<NamingRule> GetDefaultRule(
ImmutableArray<ModifierKind> 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));
return editor.GetChangedRoot();
}
private class ConvertAutoPropertyToFullPropertyCodeAction : CodeAction.DocumentChangeAction
......@@ -234,5 +216,5 @@ private class ConvertAutoPropertyToFullPropertyCodeAction : CodeAction.DocumentC
{
}
}
}
}
}
' Copyright(c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt In the project root For license information
Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.ConvertAutoPropertyToFullProperty
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.VisualBasicConvertAutoPropertyToFullPropertyCodeRefactoringProvider
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=NameOf(VisualBasicConvertAutoPropertyToFullPropertyCodeRefactoringProvider)), [Shared]>
Friend Class VisualBasicConvertAutoPropertyToFullPropertyCodeRefactoringProvider
Inherits AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider
Friend Overrides Function GetProperty(token As SyntaxToken) As SyntaxNode
Dim containingProperty = token.Parent.FirstAncestorOrSelf(Of PropertyStatementSyntax)
If (containingProperty Is Nothing) Then
Return Nothing
End If
Dim start = If(containingProperty.AttributeLists.Count > 0,
containingProperty.AttributeLists.Last().GetLastToken().GetNextToken().SpanStart,
containingProperty.SpanStart)
' Offer this refactoring anywhere in the signature of the property.
Dim position = token.SpanStart
If (position < start) Then
Return Nothing
End If
If containingProperty.HasReturnType() AndAlso
position > containingProperty.GetReturnType().Span.End Then
Return Nothing
End If
Return containingProperty
End Function
Friend Overrides Function GetNewAccessorsAsync(
document As Document,
propertyNode As SyntaxNode,
fieldName As String,
generator As SyntaxGenerator,
cancellationToken As CancellationToken) As Task(Of (newGetAccessor As SyntaxNode, newSetAccessor As SyntaxNode))
Dim returnStatement = New SyntaxList(Of StatementSyntax)(DirectCast(generator.ReturnStatement(generator.IdentifierName(fieldName)), StatementSyntax))
Dim getAccessor As SyntaxNode = SyntaxFactory.GetAccessorBlock(
SyntaxFactory.GetAccessorStatement(),
returnStatement)
Dim propertySyntax = DirectCast(propertyNode, PropertyStatementSyntax)
Dim setAccessor As SyntaxNode
If IsReadOnly(propertySyntax) Then
setAccessor = Nothing
Else
Dim setStatement = New SyntaxList(Of StatementSyntax)(DirectCast(generator.ExpressionStatement(generator.AssignmentStatement(
generator.IdentifierName(fieldName),
generator.IdentifierName("Value"))), StatementSyntax))
setAccessor = SyntaxFactory.SetAccessorBlock(
SyntaxFactory.SetAccessorStatement(),
setStatement)
End If
Return Task.FromResult((getAccessor, setAccessor))
End Function
Private Function IsReadOnly(propertySyntax As PropertyStatementSyntax) As Boolean
Dim modifiers = propertySyntax.GetModifiers()
For Each modifier In modifiers
If modifier.IsKind(SyntaxKind.ReadOnlyKeyword) Then
Return True
End If
Next
Return False
End Function
Friend Overrides Function GetUniqueName(fieldName As String, propertySymbol As IPropertySymbol) As String
' In VB, auto properties have a hidden backing field that is named using the property
' name preceded by an underscore. If the parameter 'fieldName' is the same as this
' hidden field, the NameGenerator.GenerateUniqueName method will incorrectly think
' there is already a member with that name. So we need to check for that case first.
If (String.Equals(fieldName.ToLower(), "_" & propertySymbol.Name.ToLower())) Then
Return fieldName
Else
Return NameGenerator.GenerateUniqueName(fieldName, Function(n) propertySymbol.ContainingType.GetMembers(n).IsEmpty())
End If
End Function
Friend Overrides Function GetTypeBlock(syntaxNode As SyntaxNode) As SyntaxNode
Return DirectCast(syntaxNode, TypeStatementSyntax).Parent
End Function
Friend Overrides Function GetInitializerValue(propertyNode As SyntaxNode) As SyntaxNode
Return DirectCast(propertyNode, PropertyStatementSyntax).Initializer?.Value
End Function
Friend Overrides Function GetPropertyWithoutInitializer(propertyNode As SyntaxNode) As SyntaxNode
Return DirectCast(propertyNode, PropertyStatementSyntax).WithInitializer(Nothing)
End Function
End Class
End Namespace
......@@ -78,7 +78,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
End If
End If
Dim initializer = GenerateEqualsValue(field)
Dim initializerNode = TryCast(CodeGenerationFieldInfo.GetInitializer(field), ExpressionSyntax)
Dim initializer As EqualsValueSyntax
If (initializerNode IsNot Nothing) Then
initializer = SyntaxFactory.EqualsValue(initializerNode)
Else
initializer = GenerateEqualsValue(field)
End If
Dim fieldDeclaration =
SyntaxFactory.FieldDeclaration(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册