File: ExtractMethod\VisualBasicSelectionResult.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.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.ExtractMethod
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
    Friend Class VisualBasicSelectionResult
        Inherits SelectionResult(Of ExecutableStatementSyntax)
 
        Public Shared Async Function CreateResultAsync(
            originalSpan As TextSpan,
            finalSpan As TextSpan,
            selectionInExpression As Boolean,
            document As SemanticDocument,
            firstToken As SyntaxToken,
            lastToken As SyntaxToken,
            selectionChanged As Boolean,
            cancellationToken As CancellationToken) As Task(Of VisualBasicSelectionResult)
 
            Contract.ThrowIfNull(document)
 
            Dim firstAnnotation = New SyntaxAnnotation()
            Dim lastAnnotation = New SyntaxAnnotation()
 
            Dim root = document.Root
            Dim newDocument = Await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(AddAnnotations(
                root, {(firstToken, firstAnnotation), (lastToken, lastAnnotation)})), cancellationToken).ConfigureAwait(False)
 
            Return New VisualBasicSelectionResult(
                originalSpan,
                finalSpan,
                selectionInExpression,
                newDocument,
                firstAnnotation,
                lastAnnotation,
                selectionChanged)
        End Function
 
        Private Sub New(
            originalSpan As TextSpan,
            finalSpan As TextSpan,
            selectionInExpression As Boolean,
            document As SemanticDocument,
            firstTokenAnnotation As SyntaxAnnotation,
            lastTokenAnnotation As SyntaxAnnotation,
            selectionChanged As Boolean)
 
            MyBase.New(
                originalSpan,
                finalSpan,
                selectionInExpression,
                document,
                firstTokenAnnotation,
                lastTokenAnnotation,
                selectionChanged)
        End Sub
 
        Protected Overrides ReadOnly Property SyntaxFacts As ISyntaxFacts = VisualBasicSyntaxFacts.Instance
 
        Protected Overrides Function UnderAnonymousOrLocalMethod(token As SyntaxToken, firstToken As SyntaxToken, lastToken As SyntaxToken) As Boolean
            Dim current = token.Parent
 
            While current IsNot Nothing
                If TypeOf current Is DeclarationStatementSyntax OrElse
                   TypeOf current Is LambdaExpressionSyntax Then
                    Exit While
                End If
 
                current = current.Parent
            End While
 
            If current Is Nothing OrElse TypeOf current Is DeclarationStatementSyntax Then
                Return False
            End If
 
            ' make sure selection contains the lambda
            Return firstToken.SpanStart <= current.GetFirstToken().SpanStart AndAlso
                   current.GetLastToken().Span.End <= lastToken.Span.End
        End Function
 
        Public Overrides Function GetOutermostCallSiteContainerToProcess(cancellationToken As CancellationToken) As SyntaxNode
            If Me.SelectionInExpression Then
                Dim container = Me.InnermostStatementContainer()
 
                Contract.ThrowIfNull(container)
                Contract.ThrowIfFalse(container.IsStatementContainerNode() OrElse
                                      TypeOf container Is TypeBlockSyntax OrElse
                                      TypeOf container Is CompilationUnitSyntax)
 
                Return container
            ElseIf Me.IsExtractMethodOnSingleStatement() Then
                Dim first = Me.GetFirstStatement()
                Return first.Parent
            ElseIf Me.IsExtractMethodOnMultipleStatements() Then
                Return Me.GetFirstStatementUnderContainer().Parent
            Else
                Throw ExceptionUtilities.Unreachable()
            End If
        End Function
 
        Public Overrides Function ContainingScopeHasAsyncKeyword() As Boolean
            If SelectionInExpression Then
                Return False
            End If
 
            Dim node = Me.GetContainingScope()
            If TypeOf node Is MethodBlockBaseSyntax Then
                Dim methodBlock = DirectCast(node, MethodBlockBaseSyntax)
                If methodBlock.BlockStatement IsNot Nothing Then
                    Return methodBlock.BlockStatement.Modifiers.Any(SyntaxKind.AsyncKeyword)
                End If
 
                Return False
            ElseIf TypeOf node Is LambdaExpressionSyntax Then
                Dim lambda = DirectCast(node, LambdaExpressionSyntax)
                If lambda.SubOrFunctionHeader IsNot Nothing Then
                    Return lambda.SubOrFunctionHeader.Modifiers.Any(SyntaxKind.AsyncKeyword)
                End If
            End If
 
            Return False
        End Function
 
        Public Overrides Function GetContainingScope() As SyntaxNode
            Contract.ThrowIfNull(Me.SemanticDocument)
 
            Dim first = GetFirstTokenInSelection()
 
            If SelectionInExpression Then
                Dim last = GetLastTokenInSelection()
 
                Dim scope = first.GetCommonRoot(last).GetAncestorOrThis(Of ExpressionSyntax)()
                Contract.ThrowIfNull(scope, "Should always find an expression given that SelectionInExpression was true")
 
                Return VisualBasicSyntaxFacts.Instance.GetRootStandaloneExpression(scope)
            Else
                ' it contains statements
                Return first.GetAncestors(Of SyntaxNode).FirstOrDefault(Function(n) TypeOf n Is MethodBlockBaseSyntax OrElse TypeOf n Is LambdaExpressionSyntax)
            End If
        End Function
 
        Public Overrides Function GetReturnType() As (returnType As ITypeSymbol, returnsByRef As Boolean)
            ' Todo: consider supporting byref return types in VB
            Dim returnType = GetReturnTypeWorker()
            Return (returnType, returnsByRef:=False)
        End Function
 
        Private Function GetReturnTypeWorker() As ITypeSymbol
            Dim node = Me.GetContainingScope()
            Dim semanticModel = Me.SemanticDocument.SemanticModel
 
            ' special case for collection initializer and explicit cast
            If node.IsExpressionInCast() Then
                Dim castExpression = TryCast(node.Parent, CastExpressionSyntax)
                If castExpression IsNot Nothing Then
                    Return semanticModel.GetTypeInfo(castExpression.Type).Type
                End If
            End If
 
            Dim expression As ExpressionSyntax
            If TypeOf node Is CollectionInitializerSyntax Then
                expression = node.GetUnparenthesizedExpression()
                Return semanticModel.GetTypeInfo(expression).ConvertedType
            End If
 
            Dim methodBlock = TryCast(node, MethodBlockBaseSyntax)
            If methodBlock IsNot Nothing Then
                Dim symbol = semanticModel.GetDeclaredSymbol(methodBlock.BlockStatement)
                Dim propertySymbol = TryCast(symbol, IPropertySymbol)
                If propertySymbol IsNot Nothing Then
                    Return propertySymbol.Type
                Else
                    Return DirectCast(symbol, IMethodSymbol).ReturnType
                End If
            End If
 
            Dim info As TypeInfo
            Dim lambda = TryCast(node, LambdaExpressionSyntax)
            If lambda IsNot Nothing Then
                If SelectionInExpression Then
                    info = semanticModel.GetTypeInfo(lambda)
                    Return If(info.Type.IsObjectType(), info.ConvertedType, info.Type)
                Else
                    Return semanticModel.GetLambdaOrAnonymousMethodReturnType(lambda)
                End If
            End If
 
            expression = DirectCast(node, ExpressionSyntax)
            ' regular case. always use ConvertedType to get implicit conversion right.
            expression = expression.GetUnparenthesizedExpression()
 
            info = semanticModel.GetTypeInfo(expression)
            If info.ConvertedType IsNot Nothing AndAlso
                    Not info.ConvertedType.IsErrorType() Then
                If expression.Kind = SyntaxKind.AddressOfExpression Then
                    Return info.ConvertedType
                End If
 
                Dim conversion = semanticModel.ClassifyConversion(expression, info.ConvertedType)
                If conversion.IsNumeric AndAlso conversion.IsWidening Then
                    Return info.ConvertedType
                End If
 
                Dim conv = semanticModel.GetConversion(expression)
                If IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType()) Then
                    Return info.ConvertedType
                End If
            End If
 
            ' use FormattableString if conversion between String And FormattableString
            If If(info.Type?.SpecialType = SpecialType.System_String, False) AndAlso
               info.ConvertedType?.IsFormattableStringOrIFormattable() Then
 
                Return info.ConvertedType
            End If
 
            ' get type without considering implicit conversion
            Return If(info.Type.IsObjectType(), info.ConvertedType, info.Type)
        End Function
 
        Private Shared Function IsCoClassImplicitConversion(info As TypeInfo, conversion As Conversion, coclassSymbol As INamedTypeSymbol) As Boolean
            If Not conversion.IsWidening OrElse
                 info.ConvertedType Is Nothing OrElse
                 info.ConvertedType.TypeKind <> TypeKind.Interface Then
                Return False
            End If
 
            ' let's see whether this interface has coclass attribute
            Return info.ConvertedType.HasAttribute(coclassSymbol)
        End Function
 
        Public Overrides Function GetFirstStatementUnderContainer() As ExecutableStatementSyntax
            Contract.ThrowIfTrue(SelectionInExpression)
 
            Dim firstToken = GetFirstTokenInSelection()
            Dim lastToken = GetLastTokenInSelection()
            Dim commonRoot = firstToken.GetCommonRoot(lastToken)
 
            Dim statement As ExecutableStatementSyntax
            If commonRoot.IsStatementContainerNode() Then
                Dim firstStatement = GetFirstStatement()
                statement = firstStatement.GetAncestorsOrThis(Of ExecutableStatementSyntax) _
                                          .SkipWhile(Function(s) s.Parent IsNot commonRoot) _
                                          .First()
                If statement.Parent.ContainStatement(statement) Then
                    Return statement
                End If
            End If
 
            statement = commonRoot.GetStatementUnderContainer()
            Contract.ThrowIfNull(statement)
 
            Return statement
        End Function
 
        Public Overrides Function GetLastStatementUnderContainer() As ExecutableStatementSyntax
            Contract.ThrowIfTrue(SelectionInExpression)
 
            Dim firstStatement = GetFirstStatementUnderContainer()
            Dim container = firstStatement.GetStatementContainer()
 
            Dim lastStatement = Me.GetLastStatement().GetAncestorsOrThis(Of ExecutableStatementSyntax) _
                                                .SkipWhile(Function(s) s.Parent IsNot container) _
                                                .First()
 
            Contract.ThrowIfNull(lastStatement)
            Contract.ThrowIfFalse(lastStatement.Parent Is (GetFirstStatementUnderContainer()).Parent)
 
            Return lastStatement
        End Function
 
        Public Function InnermostStatementContainer() As SyntaxNode
            Contract.ThrowIfFalse(SelectionInExpression)
 
            Dim containingScope = GetContainingScope()
            Dim statementContainer =
                containingScope.Parent _
                               .GetAncestorsOrThis(Of SyntaxNode)() _
                               .FirstOrDefault(Function(n) n.IsStatementContainerNode)
 
            If statementContainer IsNot Nothing Then
                Return statementContainer
            End If
 
            Dim field = containingScope.GetAncestor(Of FieldDeclarationSyntax)()
            If field IsNot Nothing Then
                Return field.Parent
            End If
 
            Dim [property] = containingScope.GetAncestor(Of PropertyStatementSyntax)()
            If [property] IsNot Nothing Then
                Return [property].Parent
            End If
 
            ' no repl yet
            ' Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement))
            ' Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit))
            ' Return last.Parent.Parent
            Throw ExceptionUtilities.Unreachable
        End Function
 
        Public Function IsUnderModuleBlock() As Boolean
            Dim currentScope = GetContainingScope()
            Dim types = currentScope.GetAncestors(Of TypeBlockSyntax)()
 
            Return types.Any(Function(t) t.BlockStatement.Kind = SyntaxKind.ModuleStatement)
        End Function
 
        Public Function ContainsInstanceExpression() As Boolean
            Dim first = GetFirstTokenInSelection()
            Dim last = GetLastTokenInSelection()
            Dim node = first.GetCommonRoot(last)
 
            Return node.DescendantNodesAndSelf(
                TextSpan.FromBounds(first.SpanStart, last.Span.End)) _
                                       .Any(Function(n) TypeOf n Is InstanceExpressionSyntax)
        End Function
    End Class
End Namespace