File: DocumentationComments\VisualBasicDocumentationCommentSnippetService.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.Diagnostics.CodeAnalysis
Imports System.Threading
Imports Microsoft.CodeAnalysis.DocumentationComments
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.DocumentationComments
    <ExportLanguageService(GetType(IDocumentationCommentSnippetService), LanguageNames.VisualBasic), [Shared]>
    Friend Class VisualBasicDocumentationCommentSnippetService
        Inherits AbstractDocumentationCommentSnippetService(Of DocumentationCommentTriviaSyntax, DeclarationStatementSyntax)
 
        Public Overrides ReadOnly Property DocumentationCommentCharacter As String = "'"
        Protected Overrides ReadOnly Property ExteriorTriviaText As String = "'''"
        Protected Overrides ReadOnly Property AddIndent As Boolean = False
 
        <ImportingConstructor>
        <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
        Public Sub New()
        End Sub
 
        Protected Overrides Function GetContainingMember(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As DeclarationStatementSyntax
            Return syntaxTree.GetRoot(cancellationToken).FindToken(position).GetContainingMember()
        End Function
 
        Protected Overrides Function SupportsDocumentationComments(member As DeclarationStatementSyntax) As Boolean
            If member Is Nothing Then
                Return False
            End If
 
            Select Case member.Kind
                Case SyntaxKind.ClassBlock,
                    SyntaxKind.InterfaceBlock,
                    SyntaxKind.ModuleBlock,
                    SyntaxKind.StructureBlock,
                    SyntaxKind.EnumBlock,
                    SyntaxKind.SubBlock,
                    SyntaxKind.FunctionBlock,
                    SyntaxKind.ConstructorBlock,
                    SyntaxKind.OperatorBlock,
                    SyntaxKind.PropertyBlock,
                    SyntaxKind.EventBlock,
                    SyntaxKind.ClassStatement,
                    SyntaxKind.InterfaceStatement,
                    SyntaxKind.ModuleStatement,
                    SyntaxKind.StructureStatement,
                    SyntaxKind.EnumStatement,
                    SyntaxKind.EnumMemberDeclaration,
                    SyntaxKind.DelegateSubStatement,
                    SyntaxKind.DelegateFunctionStatement,
                    SyntaxKind.FieldDeclaration,
                    SyntaxKind.EventStatement,
                    SyntaxKind.PropertyStatement,
                    SyntaxKind.OperatorStatement,
                    SyntaxKind.FunctionStatement,
                    SyntaxKind.SubStatement,
                    SyntaxKind.SubNewStatement,
                    SyntaxKind.DeclareFunctionStatement,
                    SyntaxKind.DeclareSubStatement
                    Return True
                Case Else
                    Return False
            End Select
        End Function
 
        Protected Overrides Function HasDocumentationComment(member As DeclarationStatementSyntax) As Boolean
            If member Is Nothing Then
                Return False
            End If
 
            Return member.GetFirstToken().LeadingTrivia.Any(SyntaxKind.DocumentationCommentTrivia)
        End Function
 
        Private Shared Function SupportsDocumentationCommentReturnsClause(member As DeclarationStatementSyntax) As Boolean
            If member Is Nothing Then
                Return False
            End If
 
            Select Case member.Kind
                Case SyntaxKind.FunctionBlock,
                    SyntaxKind.OperatorBlock,
                    SyntaxKind.PropertyBlock,
                    SyntaxKind.DelegateFunctionStatement,
                    SyntaxKind.OperatorStatement,
                    SyntaxKind.FunctionStatement,
                    SyntaxKind.DeclareFunctionStatement
                    Return True
                Case SyntaxKind.PropertyStatement
                    Return Not DirectCast(member, PropertyStatementSyntax).Modifiers.Any(SyntaxKind.WriteOnlyKeyword)
                Case Else
                    Return False
            End Select
        End Function
 
        Protected Overrides Function GetPrecedingDocumentationCommentCount(member As DeclarationStatementSyntax) As Integer
            Dim firstToken = member.GetFirstToken()
 
            Dim count = firstToken.LeadingTrivia.Sum(Function(t) If(t.Kind = SyntaxKind.DocumentationCommentTrivia, 1, 0))
 
            Dim previousToken = firstToken.GetPreviousToken()
            If previousToken.Kind <> SyntaxKind.None Then
                count += previousToken.TrailingTrivia.Sum(Function(t) If(t.Kind = SyntaxKind.DocumentationCommentTrivia, 1, 0))
            End If
 
            Return count
        End Function
 
        Protected Overrides Function GetDocumentationCommentStubLines(member As DeclarationStatementSyntax, existingCommentText As String) As List(Of String)
            Dim list = New List(Of String) From {
                "''' <summary>",
                "'''" & If(existingCommentText.StartsWith(" "), existingCommentText, " " + existingCommentText),
                "''' </summary>"
            }
 
            Dim typeParameterList = member.GetTypeParameterList()
            If typeParameterList IsNot Nothing Then
                For Each typeParam In typeParameterList.Parameters
                    list.Add("''' <typeparam name=""" & typeParam.Identifier.ToString() & """></typeparam>")
                Next
            End If
 
            Dim parameterList = member.GetParameterList()
            If parameterList IsNot Nothing Then
                For Each param In parameterList.Parameters
                    list.Add("''' <param name=""" & param.Identifier.Identifier.ToString() & """></param>")
                Next
            End If
 
            If SupportsDocumentationCommentReturnsClause(member) Then
                list.Add("''' <returns></returns>")
            End If
 
            ' TODO: Add `<exception>`s
 
            Return list
        End Function
 
        Protected Overrides Function IsSingleExteriorTrivia(documentationComment As DocumentationCommentTriviaSyntax, <NotNullWhen(True)> ByRef existingCommentText As String) As Boolean
            existingCommentText = ""
 
            If documentationComment Is Nothing OrElse documentationComment.Content.Count = 0 Then
                Return False
            End If
 
            ' We allow one content node for plain "'''" comments, or two if ther is an existing comment
            If documentationComment.Content.Count > 2 Then
                Return False
            End If
 
            Dim firstXmlText = TryCast(documentationComment.Content.First(), XmlTextSyntax)
            Dim lastXmlText = TryCast(documentationComment.Content.Last(), XmlTextSyntax)
 
            If firstXmlText Is Nothing OrElse
                lastXmlText Is Nothing Then
                Return False
            End If
 
            If Not firstXmlText.TextTokens.Any OrElse
                Not lastXmlText.TextTokens.Any Then
                Return False
            End If
 
            Dim firstTextToken = firstXmlText.TextTokens.First()
            Dim lastTextToken = lastXmlText.TextTokens.Last()
 
            ' Capture the existing text from the first content for later use, if there are two
            If documentationComment.Content.Count = 2 Then
                existingCommentText = firstXmlText.TextTokens.First().ValueText
            End If
 
            Return lastTextToken.Kind = SyntaxKind.DocumentationCommentLineBreakToken AndAlso
                   firstTextToken.LeadingTrivia.Count = 1 AndAlso
                   firstTextToken.LeadingTrivia.ElementAt(0).Kind = SyntaxKind.DocumentationCommentExteriorTrivia AndAlso
                   firstTextToken.LeadingTrivia.ElementAt(0).ToString() = "'''" AndAlso
                   lastTextToken.TrailingTrivia.Count = 0
        End Function
 
        Private Shared Function GetTextTokensFollowingExteriorTrivia(xmlText As XmlTextSyntax) As IList(Of SyntaxToken)
            Dim result = New List(Of SyntaxToken)
 
            Dim tokenList = xmlText.TextTokens
            For Each token In tokenList.Reverse()
                result.Add(token)
 
                If token.LeadingTrivia.Any(SyntaxKind.DocumentationCommentExteriorTrivia) Then
                    Exit For
                End If
            Next
 
            result.Reverse()
 
            Return result
        End Function
 
        Protected Overrides Function EndsWithSingleExteriorTrivia(documentationComment As DocumentationCommentTriviaSyntax) As Boolean
            If documentationComment Is Nothing Then
                Return False
            End If
 
            Dim xmlText = TryCast(documentationComment.Content.LastOrDefault(), XmlTextSyntax)
            If xmlText Is Nothing Then
                Return False
            End If
 
            Dim textTokens = GetTextTokensFollowingExteriorTrivia(xmlText)
 
            If textTokens.Any(Function(t) Not String.IsNullOrWhiteSpace(t.ToString())) Then
                Return False
            End If
 
            Dim lastTextToken = textTokens.LastOrDefault()
            Dim firstTextToken = textTokens.FirstOrDefault()
 
            Return lastTextToken.Kind = SyntaxKind.DocumentationCommentLineBreakToken AndAlso
                   firstTextToken.LeadingTrivia.Count = 1 AndAlso
                   firstTextToken.LeadingTrivia.ElementAt(0).Kind = SyntaxKind.DocumentationCommentExteriorTrivia AndAlso
                   firstTextToken.LeadingTrivia.ElementAt(0).ToString() = "'''" AndAlso
                   lastTextToken.TrailingTrivia.Count = 0
        End Function
 
        Protected Overrides Function IsMultilineDocComment(documentationComment As DocumentationCommentTriviaSyntax) As Boolean
            Return False
        End Function
 
        Protected Overrides Function GetTokenToRight(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As SyntaxToken
            If position >= syntaxTree.GetText(cancellationToken).Length Then
                Return Nothing
            End If
 
            Return syntaxTree.GetRoot(cancellationToken).FindTokenOnRightOfPosition(
                position, includeDirectives:=True, includeDocumentationComments:=True)
        End Function
 
        Protected Overrides Function GetTokenToLeft(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As SyntaxToken
            If position < 1 Then
                Return Nothing
            End If
 
            Return syntaxTree.GetRoot(cancellationToken).FindTokenOnLeftOfPosition(
                position - 1, includeDirectives:=True, includeDocumentationComments:=True)
        End Function
 
        Protected Overrides Function IsDocCommentNewLine(token As SyntaxToken) As Boolean
            Return token.Kind = SyntaxKind.DocumentationCommentLineBreakToken
        End Function
 
        Protected Overrides Function IsEndOfLineTrivia(trivia As SyntaxTrivia) As Boolean
            Return trivia.RawKind = SyntaxKind.EndOfLineTrivia
        End Function
        Protected Overrides Function HasSkippedTrailingTrivia(token As SyntaxToken) As Boolean
            Return token.TrailingTrivia.Any(Function(t) t.Kind() = SyntaxKind.SkippedTokensTrivia)
        End Function
    End Class
End Namespace