File: ReplaceMethodWithProperty\VisualBasicReplaceMethodWithPropertyService.vb
Web Access
Project: src\src\Features\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Features.vbproj (Microsoft.CodeAnalysis.VisualBasic.Features)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeGeneration
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.ReplaceMethodWithProperty
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.ReplaceMethodWithProperty
    <ExportLanguageService(GetType(IReplaceMethodWithPropertyService), LanguageNames.VisualBasic), [Shared]>
    Friend Class VisualBasicReplaceMethodWithPropertyService
        Inherits AbstractReplaceMethodWithPropertyService(Of MethodStatementSyntax)
        Implements IReplaceMethodWithPropertyService
 
        <ImportingConstructor>
        <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
        Public Sub New()
        End Sub
 
        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(
                options As CodeGenerationOptions,
                parseOptions As ParseOptions,
                editor As SyntaxEditor,
                semanticModel As SemanticModel,
                getAndSetMethods As GetAndSetMethods,
                propertyName As String, nameChanged As Boolean,
                cancellationToken As CancellationToken) 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, getAndSetMethods, propertyName, nameChanged))
        End Sub
 
        Private Shared 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 Shared Function ConvertMethodsToProperty(
            editor As SyntaxEditor,
            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 warning = GetWarning(getAndSetMethods)
            If warning IsNot Nothing Then
                propertyNameToken = propertyNameToken.WithAdditionalAnnotations(WarningAnnotation.Create(warning))
            End If
 
            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
 
            newPropertyDeclaration = SetLeadingTrivia(
                VisualBasicSyntaxFacts.Instance, getAndSetMethods, newPropertyDeclaration)
 
            Return newPropertyDeclaration.WithAdditionalAnnotations(Formatter.Annotation)
        End Function
 
        Private Shared 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,
                Function(c As SyntaxNode, g As SyntaxGenerator)
                    Dim currentRoot = DirectCast(c, ExpressionSyntax)
                    Dim expression = If(currentRoot.IsKind(SyntaxKind.InvocationExpression),
                                        DirectCast(currentRoot, InvocationExpressionSyntax).Expression,
                                        currentRoot)
                    Dim rightName = expression.GetRightmostName()
                    Return expression.ReplaceNode(rightName, newName.WithTrailingTrivia(currentRoot.GetTrailingTrivia()))
                End Function)
        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.Non_invoked_method_cannot_be_replaced_with_property)
                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 Function IReplaceMethodWithPropertyService_GetMethodDeclarationAsync(context As CodeRefactoringContext) As Task(Of SyntaxNode) Implements IReplaceMethodWithPropertyService.GetMethodDeclarationAsync
            Return GetMethodDeclarationAsync(context)
        End Function
    End Class
End Namespace