File: EndConstructGeneration\EndConstructStatementVisitor.vb
Web Access
Project: src\src\EditorFeatures\VisualBasic\Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj (Microsoft.CodeAnalysis.VisualBasic.EditorFeatures)
' 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.Text
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
 
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration
    Partial Friend NotInheritable Class EndConstructStatementVisitor
        Inherits VisualBasicSyntaxVisitor(Of AbstractEndConstructResult)
 
        Private ReadOnly _textView As ITextView
        Private ReadOnly _subjectBuffer As ITextBuffer
        Private ReadOnly _state As EndConstructState
        Private ReadOnly _cancellationToken As CancellationToken
 
        ''' <summary>
        ''' Note: this is only passed in when <see cref="NeedsSemanticModel"/> return true.  Any functions that require
        ''' a semantic model must declare their need up front so we do not pay the cost for semantics for all the cases
        ''' that do not need it.
        ''' </summary>
        Private ReadOnly _semanticModel As SemanticModel
 
        Public Sub New(
                textView As ITextView,
                subjectBuffer As ITextBuffer,
                state As EndConstructState,
                semanticModel As SemanticModel,
                cancellationToken As CancellationToken)
 
            _textView = textView
            _subjectBuffer = subjectBuffer
            _state = state
            _semanticModel = semanticModel
            _cancellationToken = cancellationToken
        End Sub
 
        Public Shared Function NeedsSemanticModel(node As SyntaxNode) As Boolean
            ' All of these call HandleMethodBlockSyntax, which needs semantics
            If TypeOf node Is MethodStatementSyntax OrElse
               TypeOf node Is SubNewStatementSyntax OrElse
               TypeOf node Is OperatorStatementSyntax Then
                Return True
            End If
 
            ' Calls GenerateAddOrRemoveHandler and GenerateRaiseEventHandler, both which needs semantics
            If TypeOf node Is EventStatementSyntax Then
                Return True
            End If
 
            Return False
        End Function
 
        Public Overrides Function VisitDoStatement(node As DoStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of DoLoopBlockSyntax)().Any(Function(block) block.LoopStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "Loop"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitEnumStatement(node As EnumStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of EnumBlockSyntax)().Any(Function(block) block.EndEnumStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End " & node.EnumKeyword.ToString()})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitForStatement(node As ForStatementSyntax) As AbstractEndConstructResult
            Return TryApplyOnForStatement(node)
        End Function
 
        Public Overrides Function VisitForEachStatement(node As ForEachStatementSyntax) As AbstractEndConstructResult
            Return TryApplyOnForStatement(node)
        End Function
 
        ''' <param name="forStatement">The ForStatementSyntax or ForEachStatementSyntax for the loop.</param>
        Public Function TryApplyOnForStatement(forStatement As StatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = False
 
            For Each parent In forStatement.GetAncestorsOrThis(Of ForOrForEachBlockSyntax)()
                If parent.NextStatement Is Nothing Then
                    ' For Blocks may have a null EndOpt, which indicates that they were closed by a parent block's Next
                    ' statement. Thus this block is closed, and we are done
                    Return Nothing
                ElseIf parent.NextStatement.ControlVariables.Count > 0 Then
                    ' The parser has matched start/end blocks, and so anything within this we consider as done
                    Return Nothing
                ElseIf parent.NextStatement.IsMissing Then
                    needsEnd = True
                End If
            Next
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(forStatement.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "Next"})
            Else
                Return Nothing
            End If
        End Function
 
        Private Function HandleMethodBlockSyntax(methodBlock As MethodBlockBaseSyntax) As AbstractEndConstructResult
            If methodBlock IsNot Nothing AndAlso methodBlock.EndBlockStatement.IsMissing Then
                Dim result = TryGenerateResultForConstructorSpitWithInitializeComponent(methodBlock)
                If result IsNot Nothing Then
                    Return result
                End If
 
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(methodBlock.BlockStatement.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End " & methodBlock.BlockStatement.DeclarationKeyword.ToString()})
            Else
                Return Nothing
            End If
        End Function
 
        Private Function TryGenerateResultForConstructorSpitWithInitializeComponent(
                methodBlock As MethodBlockBaseSyntax) As AbstractEndConstructResult
            If methodBlock.BlockStatement.Kind = SyntaxKind.SubNewStatement Then
                Dim boundConstructor = _semanticModel.GetDeclaredSymbol(DirectCast(methodBlock.BlockStatement, SubNewStatementSyntax))
                If boundConstructor IsNot Nothing Then
                    If boundConstructor.ContainingType.IsDesignerGeneratedTypeWithInitializeComponent(_semanticModel.Compilation) Then
                        Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(methodBlock.BlockStatement.SpanStart)
                        Dim innerAligningWhitespace = aligningWhitespace & "    "
 
                        ' When sticking on the comments, we don't want the ' in the localized string
                        ' lest we try localizing the comment character itself
                        Return New SpitLinesResult(
                            {
                                "",
                                innerAligningWhitespace & "' " & VBEditorResources.This_call_is_required_by_the_designer,
                                innerAligningWhitespace & "InitializeComponent()",
                                "",
                                innerAligningWhitespace & "' " & VBEditorResources.Add_any_initialization_after_the_InitializeComponent_call,
                                "",
                                aligningWhitespace & "End Sub"
                             })
 
                    End If
                End If
            End If
 
            Return Nothing
        End Function
 
        Public Overrides Function VisitMethodStatement(node As MethodStatementSyntax) As AbstractEndConstructResult
            Dim blockToClose = node.GetAncestor(Of MethodBlockSyntax)()
            Return HandleMethodBlockSyntax(blockToClose)
        End Function
 
        Public Overrides Function VisitSubNewStatement(node As SubNewStatementSyntax) As AbstractEndConstructResult
            Dim blockToClose = node.GetAncestor(Of ConstructorBlockSyntax)()
            Return HandleMethodBlockSyntax(blockToClose)
        End Function
 
        Public Overrides Function VisitOperatorStatement(node As OperatorStatementSyntax) As AbstractEndConstructResult
            Dim blockToClose = node.GetAncestor(Of OperatorBlockSyntax)()
            Return HandleMethodBlockSyntax(blockToClose)
        End Function
 
        Public Overrides Function VisitNamespaceStatement(node As NamespaceStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of NamespaceBlockSyntax)().Any(Function(block) block.EndNamespaceStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End Namespace"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitSelectStatement(node As SelectStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of SelectBlockSyntax)().Any(Function(block) block.EndSelectStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Dim stringBuilder As New StringBuilder
                stringBuilder.Append(_textView.Options.GetNewLineCharacter())
                stringBuilder.Append(aligningWhitespace & "    Case ")
                Dim finalCaretPoint = stringBuilder.Length
                stringBuilder.AppendLine()
                stringBuilder.Append(aligningWhitespace & "End Select")
 
                Return New ReplaceSpanResult(New SnapshotSpan(_subjectBuffer.CurrentSnapshot, _state.CaretPosition, 0),
                                             stringBuilder.ToString(), newCaretPosition:=finalCaretPoint)
            Else
                Return (Nothing)
            End If
        End Function
 
        Public Overrides Function VisitSyncLockStatement(node As SyncLockStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of SyncLockBlockSyntax)().Any(Function(block) block.EndSyncLockStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End SyncLock"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitTryStatement(node As TryStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of TryBlockSyntax)().Any(Function(block) block.EndTryStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "Catch ex As Exception", "", aligningWhitespace & "End Try"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitModuleStatement(ByVal node As ModuleStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of ModuleBlockSyntax)().Any(Function(block) block.EndBlockStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End Module"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitClassStatement(ByVal node As ClassStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of ClassBlockSyntax)().Any(Function(block) block.EndBlockStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End Class"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitStructureStatement(ByVal node As StructureStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of StructureBlockSyntax)().Any(Function(block) block.EndBlockStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End Structure"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitInterfaceStatement(ByVal node As InterfaceStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of InterfaceBlockSyntax)().Any(Function(block) block.EndBlockStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End Interface"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitUsingStatement(ByVal node As UsingStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of UsingBlockSyntax)().Any(Function(block) block.EndUsingStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End Using"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitWhileStatement(node As WhileStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of WhileBlockSyntax)().Any(Function(block) block.EndWhileStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End While"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitWithStatement(node As WithStatementSyntax) As AbstractEndConstructResult
            Dim needsEnd = node.GetAncestorsOrThis(Of WithBlockSyntax)().Any(Function(block) block.EndWithStatement.IsMissing)
 
            If needsEnd Then
                Dim aligningWhitespace = _subjectBuffer.CurrentSnapshot.GetAligningWhitespace(node.SpanStart)
                Return New SpitLinesResult({"", aligningWhitespace & "End With"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitIfDirectiveTrivia(directive As IfDirectiveTriviaSyntax) As AbstractEndConstructResult
            Dim matchingDirectives = directive.GetMatchingConditionalDirectives(_cancellationToken)
            Dim needsEnd = Not matchingDirectives.Any(Function(d) d.Kind = SyntaxKind.EndIfDirectiveTrivia)
 
            If needsEnd Then
                Return New SpitLinesResult({"", "#End If"})
            Else
                Return Nothing
            End If
        End Function
 
        Public Overrides Function VisitRegionDirectiveTrivia(directive As RegionDirectiveTriviaSyntax) As AbstractEndConstructResult
            Dim precedingRegionDirectives =
                directive.SyntaxTree.GetStartDirectives(_cancellationToken).
                            Where(Function(d) d.SpanStart <= directive.SpanStart)
 
            If precedingRegionDirectives.Any(Function(d) d.GetMatchingStartOrEndDirective(_cancellationToken) Is Nothing) Then
                Return New SpitLinesResult({"", "#End Region"})
            Else
                Return Nothing
            End If
        End Function
    End Class
End Namespace