提交 4ebf9658 编写于 作者: C Cyrus Najmabadi

Add VB side of the ReplaceMethodWithProperty refactoring.

上级 63c3d021
......@@ -614,6 +614,7 @@
<None Include="PerfTests\BasicPerfLightBulb.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Compile Include="CodeActions\ReplaceMethodWithProperty\ReplaceMethodWithPropertyTests.vb" />
<Content Include="PerfTests\Sources\BasicPgoTypingInput.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
......
' 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.Editor.VisualBasic.UnitTests.CodeRefactorings
Imports Microsoft.CodeAnalysis.ReplaceMethodWithProperty
Imports Roslyn.Test.Utilities
Imports Xunit
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeActions.ReplaceMethodWithProperty
Public Class ReplaceMethodWithPropertyTests
Inherits AbstractVisualBasicCodeActionTest
Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace) As Object
Return New ReplaceMethodWithPropertyCodeRefactoringProvider()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithGetName()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithoutGetName()
Test(
NewLines("class C \n function [||]Foo() as integer \n End function \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithoutBody()
Test(
NewLines("mustinherit class C \n MustOverride function [||]GetFoo() as integer \n End class"),
NewLines("mustinherit class C \n MustOverride ReadOnly Property Foo as integer \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithModifiers()
Test(
NewLines("class C \n public shared function [||]GetFoo() as integer \n End function \n End class"),
NewLines("class C \n public shared ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithAttributes()
Test(
NewLines("class C \n <A>function [||]GetFoo() as integer \n End function \n End class"),
NewLines("class C \n <A>ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithTrivia_1()
Test(
"class C
' Foo
function [||]GetFoo() as integer
End function
End class",
"class C
' Foo
ReadOnly Property Foo as integer
Get
End Get
End Property
End class",
compareTokens:=False)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestIfDefMethod()
Test(
"class C
#if true
function [||]GetFoo() as integer
End function
#End if
End class",
"class C
#if true
ReadOnly Property Foo as integer
Get
End Get
End Property
#End if
End class")
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithTrivia_2()
Test(
"class C
' Foo
function [||]GetFoo() as integer
End function
' SetFoo
sub SetFoo(i as integer)
End sub
End class",
"class C
' Foo
' SetFoo
Property Foo as integer
Get
End Get
Set(i as integer)
End Set
End Property
End class",
index:=1,
compareTokens:=False)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestExplicitInterfaceMethod_2()
Test(
NewLines("interface I \n function GetFoo() as integer \n End interface \n class C \n implements I \n function [||]GetFoo() as integer implements I.GetFoo \n End function \n End class"),
NewLines("interface I \n ReadOnly Property Foo as integer \n End interface \n class C \n implements I \n ReadOnly Property Foo as integer implements I.Foo \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestExplicitInterfaceMethod_3()
Test(
NewLines("interface I \n function [||]GetFoo() as integer \n End interface \n class C \n implements I \n function GetFoo() as integer implements I.GetFoo \n End function \n End class"),
NewLines("interface I \n ReadOnly Property Foo as integer \n End interface \n class C \n implements I \n ReadOnly Property Foo as integer implements I.Foo \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestInAttribute()
TestMissing(
NewLines("class C \n <At[||]tr>function GetFoo() as integer \n End function \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestInMethod()
TestMissing(
NewLines("class C \n function GetFoo() as integer \n [||] \n End function \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestSubMethod()
TestMissing(
NewLines("class C \n sub [||]GetFoo() \n End sub \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestAsyncMethod()
TestMissing(
NewLines("class C \n async function [||]GetFoo() as Task \n End function \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestGenericMethod()
TestMissing(
NewLines("class C \n function [||]GetFoo(of T)() as integer \n End function \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestExtensionMethod()
TestMissing(
NewLines("module C \n <System.Runtime.CompilerServices.Extension>function [||]GetFoo(i as integer) as integer \n End function \n End module"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestMethodWithParameters_1()
TestMissing(
NewLines("class C \n function [||]GetFoo(i as integer) as integer \n End function \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetReferenceNotInMethod()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub Bar() \n dim x = GetFoo() \n End sub \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n sub Bar() \n dim x = Foo \n End sub \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetReferenceMemberAccessInvocation()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub Bar() \n dim x = me.GetFoo() \n End sub \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n sub Bar() \n dim x = me.Foo \n End sub \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetReferenceBindingMemberInvocation()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub Bar() \n dim x as C \n dim v = x?.GetFoo() \n End sub \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n sub Bar() \n dim x as C \n dim v = x?.Foo \n End sub \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetReferenceInMethod()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n return GetFoo() \n End function \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n return Foo \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestOverride()
Test(
NewLines("class C \n public overridable function [||]GetFoo() as integer \n End function \n End class \n class D \n inherits C \n public overrides function GetFoo() as integer \n End function \n End class"),
NewLines("class C \n public overridable ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class \n class D \n inherits C \n public overrides ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetReference_NonInvoked()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub Bar() \n dim i = GetFoo \n End sub \n End class"),
NewLines("class C \n ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n sub Bar() \n dim i = Foo \n End sub \n End class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetSet()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub SetFoo(i as integer) \n End sub \n End class"),
NewLines("class C \n Property Foo as integer \n Get \n End Get \n Set(i as integer) \n End Set \n End Property \n End class"),
index:=1)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetSetReference_NonInvoked()
Test(
NewLines("Imports System \n class C \n function [||]GetFoo() as integer \n End function \n sub SetFoo(i as integer) \n End sub \n sub Bar() \n dim i as Action(of integer) = addressof SetFoo \n End sub \n End class"),
NewLines("Imports System \n class C \n Property Foo as integer \n Get \n End Get \n Set(i as integer) \n End Set \n End Property \n sub Bar() \n dim i as Action(of integer) = addressof {|Conflict:Foo|} \n End sub \n End class"),
index:=1)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetSet_SetterAccessibility()
Test(
NewLines("class C \n public function [||]GetFoo() as integer \n End function \n private sub SetFoo(i as integer) \n End sub \n End class"),
NewLines("class C \n public Property Foo as integer \n Get End Get \n Private Set(i as integer) \n End Set \n End Property \n End class"),
index:=1)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetSet_GetInSetReference()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub SetFoo(i as integer) \n End sub \n sub Bar() \n SetFoo(GetFoo() + 1) \n End sub \n End class"),
NewLines("class C \n Property Foo as integer \n Get \n End Get \n Set(i as integer) \n End Set \n End Property \n sub Bar() \n Foo = Foo + 1 \n End sub \n End class"),
index:=1)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestUpdateGetSet_SetReferenceInSetter()
Test(
NewLines("class C \n function [||]GetFoo() as integer \n End function \n sub SetFoo(i as integer) \n SetFoo(i - 1) \n End sub \n End class"),
NewLines("class C \n Property Foo as integer \n Get \n End Get \n Set(i as integer) \n Foo = i - 1 \n End Set \n End Property \n End class"),
index:=1)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestVirtualGetWithOverride_1()
Test(
NewLines("class C \n protected overridable function [||]GetFoo() as integer \n End function \n End class \n class D \n inherits C \n protected overrides function GetFoo() as integer \n End function \n End class"),
NewLines("class C \n protected overridable ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class \n class D \n inherits C \n protected overrides ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class"),
index:=0)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestVirtualGetWithOverride_2()
Test(
NewLines("class C \n protected overridable function [||]GetFoo() as integer \n End function \n End class \n class D \n inherits C \n protected overrides function GetFoo() as integer \n return mybase.GetFoo() \n End function \n End class"),
NewLines("class C \n protected overridable ReadOnly Property Foo as integer \n Get \n End Get \n End Property \n End class \n class D \n inherits C \n protected overrides ReadOnly Property Foo as integer \n Get \n return mybase.Foo \n End Get \n End Property \n End class"),
index:=0)
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsReplaceMethodWithProperty)>
Public Sub TestWithPartialClasses()
Test(
NewLines("partial class C \n function [||]GetFoo() as integer \n End function \n End class \n partial class C \n sub SetFoo(i as integer) \n End sub \n End class"),
NewLines("partial class C \n Property Foo as integer \n Get \n End Get \n Set(i as integer) \n End Set \n End Property \n End class \n partial class C \n End class"),
index:=1)
End Sub
End Class
End Namespace
\ No newline at end of file
......@@ -565,15 +565,6 @@ internal class CSharpFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Non-invoked method cannot be replaced with property..
/// </summary>
internal static string NonInvokedMethodCannotBeReplacedWithProperty {
get {
return ResourceManager.GetString("NonInvokedMethodCannotBeReplacedWithProperty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not all code paths return.
/// </summary>
......@@ -601,15 +592,6 @@ internal class CSharpFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Only methods with a single argument can be replaced with a property..
/// </summary>
internal static string OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty {
get {
return ResourceManager.GetString("OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to orderby clause.
/// </summary>
......
......@@ -434,10 +434,4 @@
<data name="ERR_NameNotInContext" xml:space="preserve">
<value>The name '{0}' does not exist in the current context.</value>
</data>
<data name="NonInvokedMethodCannotBeReplacedWithProperty" xml:space="preserve">
<value>Non-invoked method cannot be replaced with property.</value>
</data>
<data name="OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty" xml:space="preserve">
<value>Only methods with a single argument can be replaced with a property.</value>
</data>
</root>
\ No newline at end of file
......@@ -44,17 +44,33 @@ public SyntaxNode GetMethodDeclaration(SyntaxToken token)
return containingMethod;
}
public SyntaxNode ConvertMethodsToProperty(
public void RemoveSetMethod(SyntaxEditor editor, SyntaxNode setMethodDeclaration)
{
editor.RemoveNode(setMethodDeclaration);
}
public void ReplaceGetMethodWithProperty(
SyntaxEditor editor,
SemanticModel semanticModel,
SyntaxGenerator generator, GetAndSetMethods getAndSetMethods,
GetAndSetMethods getAndSetMethods,
string propertyName, bool nameChanged)
{
var getMethodDeclaration = getAndSetMethods.GetMethodDeclaration as MethodDeclarationSyntax;
if (getMethodDeclaration == null)
{
return getAndSetMethods.GetMethodDeclaration;
return;
}
editor.ReplaceNode(getMethodDeclaration,
ConvertMethodsToProperty(semanticModel, editor.Generator, getAndSetMethods, propertyName, nameChanged));
}
public SyntaxNode ConvertMethodsToProperty(
SemanticModel semanticModel,
SyntaxGenerator generator, GetAndSetMethods getAndSetMethods,
string propertyName, bool nameChanged)
{
var getMethodDeclaration = getAndSetMethods.GetMethodDeclaration as MethodDeclarationSyntax;
var getAccessor = CreateGetAccessor(getAndSetMethods);
var setAccessor = CreateSetAccessor(semanticModel, generator, getAndSetMethods);
......@@ -195,7 +211,7 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
{
if (invocation.ArgumentList?.Arguments.Count != 1)
{
var annotation = ConflictAnnotation.Create(CSharpFeaturesResources.OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty);
var annotation = ConflictAnnotation.Create(FeaturesResources.OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty);
editor.ReplaceNode(nameNode, newName.WithIdentifier(newName.Identifier.WithAdditionalAnnotations(annotation)));
return;
}
......@@ -255,7 +271,7 @@ public void ReplaceSetReference(SyntaxEditor editor, SyntaxToken nameToken, stri
if (!IsInvocationName(nameNode, invocationExpression))
{
// Wasn't invoked. Change the name, but report a conflict.
var annotation = ConflictAnnotation.Create(CSharpFeaturesResources.NonInvokedMethodCannotBeReplacedWithProperty);
var annotation = ConflictAnnotation.Create(FeaturesResources.NonInvokedMethodCannotBeReplacedWithProperty);
editor.ReplaceNode(nameNode, newName.WithIdentifier(newName.Identifier.WithAdditionalAnnotations(annotation)));
return;
}
......
......@@ -422,7 +422,7 @@ internal class FeaturesResources {
}
/// <summary>
/// Looks up a localized string similar to &gt;&gt;&gt;&gt;&gt;&gt;&gt; master.
/// Looks up a localized string similar to Change &apos;{0}&apos; to &apos;{1}&apos;..
/// </summary>
internal static string ChangeTo {
get {
......@@ -1501,6 +1501,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Non-invoked method cannot be replaced with property..
/// </summary>
internal static string NonInvokedMethodCannotBeReplacedWithProperty {
get {
return ResourceManager.GetString("NonInvokedMethodCannotBeReplacedWithProperty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ceasing to access captured variable &apos;{0}&apos; in {1} will prevent the debug session from continuing..
/// </summary>
......@@ -1537,6 +1546,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Only methods with a single argument can be replaced with a property..
/// </summary>
internal static string OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty {
get {
return ResourceManager.GetString("OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to operator.
/// </summary>
......
......@@ -851,6 +851,11 @@ Do you want to continue?</value>
</data>
<data name="ChangeTo" xml:space="preserve">
<value>Change '{0}' to '{1}'.</value>
>>>>>>> master
</data>
<data name="NonInvokedMethodCannotBeReplacedWithProperty" xml:space="preserve">
<value>Non-invoked method cannot be replaced with property.</value>
</data>
<data name="OnlyMethodsWithASingleArgumentCanBeReplacedWithAProperty" xml:space="preserve">
<value>Only methods with a single argument can be replaced with a property.</value>
</data>
</root>
\ No newline at end of file
......@@ -8,10 +8,12 @@ namespace Microsoft.CodeAnalysis.ReplaceMethodWithProperty
interface IReplaceMethodWithPropertyService : ILanguageService
{
SyntaxNode GetMethodDeclaration(SyntaxToken token);
SyntaxNode ConvertMethodsToProperty(SemanticModel semanticModel, SyntaxGenerator generator, GetAndSetMethods getAndSetMethods, string propertyName, bool nameChanged);
string GetMethodName(SyntaxNode methodDeclaration);
void ReplaceGetReference(SyntaxEditor editor, SyntaxToken nameToken, string propertyName, bool nameChanged);
void ReplaceSetReference(SyntaxEditor editor, SyntaxToken nameToken, string propertyName, bool nameChanged);
void ReplaceGetMethodWithProperty(SyntaxEditor editor, SemanticModel semanticModel, GetAndSetMethods getAndSetMethods, string propertyName, bool nameChanged);
void RemoveSetMethod(SyntaxEditor editor, SyntaxNode setMethodDeclaration);
}
internal struct GetAndSetMethods
......
......@@ -321,16 +321,13 @@ private async Task<Solution> UpdateReferencesAsync(Solution updatedSolution, str
var root = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var editor = new SyntaxEditor(root, updatedSolution.Workspace);
var generator = SyntaxGenerator.GetGenerator(updatedDocument);
// First replace all the get methods with properties.
foreach (var getSetPair in getSetPairs)
{
cancellationToken.ThrowIfCancellationRequested();
var getMethod = getSetPair.GetMethodDeclaration;
editor.ReplaceNode(getMethod, service.ConvertMethodsToProperty(
semanticModel, generator, getSetPair, propertyName, nameChanged));
service.ReplaceGetMethodWithProperty(editor, semanticModel, getSetPair, propertyName, nameChanged);
}
// Then remove all the set methods.
......@@ -344,7 +341,7 @@ private async Task<Solution> UpdateReferencesAsync(Solution updatedSolution, str
var setMethodDocument = updatedSolution.GetDocument(setMethodDeclaration?.SyntaxTree);
if (setMethodDocument?.Id == documentId)
{
editor.RemoveNode(setMethodDeclaration);
service.RemoveSetMethod(editor, setMethodDeclaration);
}
}
......
......@@ -401,6 +401,9 @@
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
</ItemGroup>
<ItemGroup>
<Compile Include="ReplaceMethodWithProperty\VisualBasicReplaceMethodWithPropertyService.vb" />
</ItemGroup>
<Import Project="..\..\..\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems" Label="Shared" />
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
......
' 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
Imports System.Collections.Generic
Imports System.Composition
Imports System.Linq
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.ReplaceMethodWithProperty
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithProperty
<ExportLanguageService(GetType(IReplaceMethodWithPropertyService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicReplaceMethodWithPropertyService
Implements IReplaceMethodWithPropertyService
Public Function GetMethodName(methodNode As SyntaxNode) As String Implements IReplaceMethodWithPropertyService.GetMethodName
Return DirectCast(methodNode, MethodStatementSyntax).Identifier.ValueText
End Function
Public Function GetMethodDeclaration(token As SyntaxToken) As SyntaxNode Implements IReplaceMethodWithPropertyService.GetMethodDeclaration
Dim containingMethod = token.Parent.FirstAncestorOrSelf(Of MethodStatementSyntax)
If containingMethod Is Nothing Then
Return Nothing
End If
Dim start = If(containingMethod.AttributeLists.Count > 0,
containingMethod.AttributeLists.Last().GetLastToken().GetNextToken().SpanStart,
containingMethod.SpanStart)
' Offer this refactoring anywhere in the signature of the method.
Dim position = token.SpanStart
If position < start OrElse position > containingMethod.ParameterList.Span.End Then
Return Nothing
End If
Return containingMethod
End Function
Public Sub RemoveSetMethod(editor As SyntaxEditor, setMethodDeclaration As SyntaxNode) Implements IReplaceMethodWithPropertyService.RemoveSetMethod
Dim setMethodStatement = TryCast(setMethodDeclaration, MethodStatementSyntax)
If setMethodStatement Is Nothing Then
Return
End If
Dim methodOrBlock = GetParentIfBlock(setMethodStatement)
editor.RemoveNode(methodOrBlock)
End Sub
Public Sub ReplaceGetMethodWithProperty(
editor As SyntaxEditor,
semanticModel As SemanticModel,
getAndSetMethods As GetAndSetMethods,
propertyName As String, nameChanged As Boolean) Implements IReplaceMethodWithPropertyService.ReplaceGetMethodWithProperty
Dim getMethodDeclaration = TryCast(getAndSetMethods.GetMethodDeclaration, MethodStatementSyntax)
If getMethodDeclaration Is Nothing Then
Return
End If
Dim methodBlockOrStatement = GetParentIfBlock(getMethodDeclaration)
editor.ReplaceNode(methodBlockOrStatement,
ConvertMethodsToProperty(editor, semanticModel, getAndSetMethods, propertyName, nameChanged))
End Sub
Private Function GetParentIfBlock(declaration As MethodStatementSyntax) As DeclarationStatementSyntax
If declaration.IsParentKind(SyntaxKind.FunctionBlock) OrElse declaration.IsParentKind(SyntaxKind.SubBlock) Then
Return DirectCast(declaration.Parent, DeclarationStatementSyntax)
End If
Return declaration
End Function
Private Function ConvertMethodsToProperty(
editor As SyntaxEditor,
semanticModel As SemanticModel,
getAndSetMethods As GetAndSetMethods,
propertyName As String, nameChanged As Boolean) As DeclarationStatementSyntax
Dim generator = editor.Generator
Dim getMethodStatement = DirectCast(getAndSetMethods.GetMethodDeclaration, MethodStatementSyntax)
Dim setMethodStatement = TryCast(getAndSetMethods.SetMethodDeclaration, MethodStatementSyntax)
Dim propertyNameToken = GetPropertyName(getMethodStatement.Identifier, propertyName, nameChanged)
Dim newPropertyDeclaration As DeclarationStatementSyntax
If getAndSetMethods.SetMethod Is Nothing Then
Dim modifiers = getMethodStatement.Modifiers.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword))
Dim propertyStatement = SyntaxFactory.PropertyStatement(
getMethodStatement.AttributeLists, modifiers, propertyNameToken, Nothing,
getMethodStatement.AsClause, initializer:=Nothing, implementsClause:=getMethodStatement.ImplementsClause)
If getAndSetMethods.GetMethodDeclaration.IsParentKind(SyntaxKind.FunctionBlock) Then
' Get method has no body, and we have no setter. Just make a readonly property block
Dim accessor = SyntaxFactory.GetAccessorBlock(SyntaxFactory.GetAccessorStatement(),
DirectCast(getAndSetMethods.GetMethodDeclaration.Parent, MethodBlockBaseSyntax).Statements)
Dim accessors = SyntaxFactory.SingletonList(accessor)
newPropertyDeclaration = SyntaxFactory.PropertyBlock(propertyStatement, accessors)
Else
' Get method has no body, and we have no setter. Just make a readonly property statement
newPropertyDeclaration = propertyStatement
End If
Else
Dim propertyStatement = SyntaxFactory.PropertyStatement(
getMethodStatement.AttributeLists, getMethodStatement.Modifiers, propertyNameToken, Nothing,
getMethodStatement.AsClause, initializer:=Nothing, implementsClause:=getMethodStatement.ImplementsClause)
If getAndSetMethods.GetMethodDeclaration.IsParentKind(SyntaxKind.FunctionBlock) AndAlso
getAndSetMethods.SetMethodDeclaration.IsParentKind(SyntaxKind.SubBlock) Then
Dim getAccessor = SyntaxFactory.GetAccessorBlock(SyntaxFactory.GetAccessorStatement(),
DirectCast(getAndSetMethods.GetMethodDeclaration.Parent, MethodBlockBaseSyntax).Statements)
Dim setAccessorStatement = SyntaxFactory.SetAccessorStatement()
setAccessorStatement = setAccessorStatement.WithParameterList(setMethodStatement?.ParameterList)
If getAndSetMethods.GetMethod.DeclaredAccessibility <> getAndSetMethods.SetMethod.DeclaredAccessibility Then
setAccessorStatement = DirectCast(generator.WithAccessibility(setAccessorStatement, getAndSetMethods.SetMethod.DeclaredAccessibility), AccessorStatementSyntax)
End If
Dim setAccessor = SyntaxFactory.SetAccessorBlock(setAccessorStatement,
DirectCast(getAndSetMethods.SetMethodDeclaration.Parent, MethodBlockBaseSyntax).Statements)
Dim accessors = SyntaxFactory.List({getAccessor, setAccessor})
newPropertyDeclaration = SyntaxFactory.PropertyBlock(propertyStatement, accessors)
Else
' Methods don't have bodies. Just make a property statement
newPropertyDeclaration = propertyStatement
End If
End If
Dim trivia As IEnumerable(Of SyntaxTrivia) = getMethodStatement.GetLeadingTrivia()
If setMethodStatement IsNot Nothing Then
trivia = trivia.Concat(setMethodStatement.GetLeadingTrivia())
End If
newPropertyDeclaration = newPropertyDeclaration.WithLeadingTrivia(trivia)
Return newPropertyDeclaration.WithAdditionalAnnotations(Formatter.Annotation)
End Function
Private Function GetPropertyName(identifier As SyntaxToken, propertyName As String, nameChanged As Boolean) As SyntaxToken
Return If(nameChanged, SyntaxFactory.Identifier(propertyName), identifier)
End Function
Public Sub ReplaceGetReference(editor As SyntaxEditor, nameToken As SyntaxToken, propertyName As String, nameChanged As Boolean) Implements IReplaceMethodWithPropertyService.ReplaceGetReference
If nameToken.Kind() <> SyntaxKind.IdentifierToken Then
Return
End If
Dim nameNode = TryCast(nameToken.Parent, IdentifierNameSyntax)
If nameNode Is Nothing Then
Return
End If
Dim newName = If(nameChanged,
SyntaxFactory.IdentifierName(SyntaxFactory.Identifier(propertyName).WithTriviaFrom(nameToken)),
nameNode)
Dim parentExpression = If(nameNode.IsRightSideOfDot(), DirectCast(nameNode.Parent, ExpressionSyntax), nameNode)
Dim root = If(parentExpression.IsParentKind(SyntaxKind.InvocationExpression), parentExpression.Parent, parentExpression)
editor.ReplaceNode(root, parentExpression.ReplaceNode(nameNode, newName))
End Sub
Public Sub ReplaceSetReference(editor As SyntaxEditor, nameToken As SyntaxToken, propertyName As String, nameChanged As Boolean) Implements IReplaceMethodWithPropertyService.ReplaceSetReference
If nameToken.Kind() <> SyntaxKind.IdentifierToken Then
Return
End If
Dim nameNode = TryCast(nameToken.Parent, IdentifierNameSyntax)
If nameNode Is Nothing Then
Return
End If
Dim newName = If(nameChanged,
SyntaxFactory.IdentifierName(SyntaxFactory.Identifier(propertyName).WithTriviaFrom(nameToken)),
nameNode)
Dim parentExpression = If(nameNode.IsRightSideOfDot(), DirectCast(nameNode.Parent, ExpressionSyntax), nameNode)
If Not parentExpression.IsParentKind(SyntaxKind.InvocationExpression) OrElse
Not parentExpression.Parent.IsParentKind(SyntaxKind.ExpressionStatement) Then
' Wasn't invoked. Change the name, but report a conflict.
Dim annotation = ConflictAnnotation.Create(FeaturesResources.NonInvokedMethodCannotBeReplacedWithProperty)
editor.ReplaceNode(nameNode, Function(n, g) newName.WithIdentifier(newName.Identifier.WithAdditionalAnnotations(annotation)))
Return
End If
editor.ReplaceNode(
parentExpression.Parent.Parent,
Function(statement, generator)
Dim expressionStatement = DirectCast(statement, ExpressionStatementSyntax)
Dim invocationExpression = DirectCast(expressionStatement.Expression, InvocationExpressionSyntax)
Dim expression = invocationExpression.Expression
Dim name = If(expression.Kind() = SyntaxKind.SimpleMemberAccessExpression,
DirectCast(expression, MemberAccessExpressionSyntax).Name,
If(expression.Kind() = SyntaxKind.IdentifierName, DirectCast(expression, IdentifierNameSyntax), Nothing))
If name Is Nothing Then
Return statement
End If
If invocationExpression.ArgumentList?.Arguments.Count <> 1 Then
Return statement
End If
Dim result As SyntaxNode = SyntaxFactory.SimpleAssignmentStatement(
expression.ReplaceNode(name, newName),
invocationExpression.ArgumentList.Arguments(0).GetExpression())
Return result
End Function)
End Sub
Private Shared Function IsInvocationName(nameNode As IdentifierNameSyntax, invocationExpression As ExpressionSyntax) As Boolean
If invocationExpression Is nameNode Then
Return True
End If
If nameNode.IsAnyMemberAccessExpressionName() AndAlso nameNode.Parent Is invocationExpression Then
Return True
End If
Return False
End Function
End Class
End Namespace
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册