File: Classification\Worker.DocumentationCommentClassifier.vb
Web Access
Project: src\src\roslyn\src\Workspaces\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj (Microsoft.CodeAnalysis.VisualBasic.Workspaces)
' 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 Microsoft.CodeAnalysis.Classification
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.Classification
    Partial Friend Class Worker
        Private Class DocumentationCommentClassifier
            Private ReadOnly _worker As Worker

            Public Sub New(worker As Worker)
                _worker = worker
            End Sub

            Friend Sub Classify(documentationComment As DocumentationCommentTriviaSyntax)
                If Not _worker._textSpan.OverlapsWith(documentationComment.Span) Then
                    Return
                End If

                For Each xmlNode In documentationComment.Content
                    Dim childFullSpan = xmlNode.FullSpan
                    If childFullSpan.Start > _worker._textSpan.End Then
                        Return
                    ElseIf childFullSpan.End < _worker._textSpan.Start Then
                        Continue For
                    End If

                    ClassifyXmlNode(xmlNode)
                Next
            End Sub

            Private Sub ClassifyXmlNode(node As XmlNodeSyntax)
                If node Is Nothing Then
                    Return
                End If

                Select Case node.Kind
                    Case SyntaxKind.XmlText
                        ClassifyXmlText(DirectCast(node, XmlTextSyntax))
                    Case SyntaxKind.XmlElement
                        ClassifyElement(DirectCast(node, XmlElementSyntax))
                    Case SyntaxKind.XmlEmptyElement
                        ClassifyEmptyElement(DirectCast(node, XmlEmptyElementSyntax))
                    Case SyntaxKind.XmlName
                        ClassifyXmlName(DirectCast(node, XmlNameSyntax))
                    Case SyntaxKind.XmlString
                        ClassifyString(DirectCast(node, XmlStringSyntax))
                    Case SyntaxKind.XmlComment
                        ClassifyComment(DirectCast(node, XmlCommentSyntax))
                    Case SyntaxKind.XmlCDataSection
                        ClassifyCData(DirectCast(node, XmlCDataSectionSyntax))
                    Case SyntaxKind.XmlProcessingInstruction
                        ClassifyProcessingInstruction(DirectCast(node, XmlProcessingInstructionSyntax))
                End Select
            End Sub

            Private Sub ClassifyXmlTrivia(trivialList As SyntaxTriviaList, Optional whitespaceClassificationType As String = Nothing)
                For Each t In trivialList
                    Select Case t.Kind()
                        Case SyntaxKind.DocumentationCommentExteriorTrivia
                            ClassifyExteriorTrivia(t)
                        Case SyntaxKind.WhitespaceTrivia
                            If whitespaceClassificationType IsNot Nothing Then
                                _worker.AddClassification(t, whitespaceClassificationType)
                            End If
                    End Select
                Next
            End Sub

            Private Sub ClassifyExteriorTrivia(trivia As SyntaxTrivia)
                ' Note: The exterior trivia can contain whitespace (usually leading) and we want to avoid classifying it.

                ' PERFORMANCE:
                ' While the call to SyntaxTrivia.ToString() looks Like an allocation, it isn't.
                ' The SyntaxTrivia green node holds the string text of the trivia in a field And ToString()
                ' just returns a reference to that.
                Dim text = trivia.ToString()

                Dim spanStart As Integer? = Nothing

                For index = 0 To text.Length - 1
                    Dim ch = text(index)

                    If spanStart IsNot Nothing AndAlso Char.IsWhiteSpace(ch) Then
                        Dim span = TextSpan.FromBounds(spanStart.Value, spanStart.Value + index)
                        _worker.AddClassification(span, ClassificationTypeNames.XmlDocCommentDelimiter)
                        spanStart = Nothing
                    ElseIf spanStart Is Nothing AndAlso Not Char.IsWhiteSpace(ch) Then
                        spanStart = trivia.Span.Start + index
                    End If
                Next

                ' Add a final classification if we hadn't encountered anymore whitespace at the end
                If spanStart IsNot Nothing Then
                    Dim span = TextSpan.FromBounds(spanStart.Value, trivia.Span.End)
                    _worker.AddClassification(span, ClassificationTypeNames.XmlDocCommentDelimiter)
                End If
            End Sub

            Private Sub AddXmlClassification(token As SyntaxToken, classificationType As String)
                If token.HasLeadingTrivia Then
                    ClassifyXmlTrivia(token.LeadingTrivia, classificationType)
                End If

                _worker.AddClassification(token, classificationType)

                If token.HasTrailingTrivia Then
                    ClassifyXmlTrivia(token.TrailingTrivia, classificationType)
                End If
            End Sub

            Private Sub ClassifyXmlTextTokens(textTokens As SyntaxTokenList)
                For Each token In textTokens
                    If token.HasLeadingTrivia Then
                        ClassifyXmlTrivia(token.LeadingTrivia, whitespaceClassificationType:=ClassificationTypeNames.XmlDocCommentText)
                    End If

                    ClassifyXmlTextToken(token)

                    If token.HasTrailingTrivia Then
                        ClassifyXmlTrivia(token.TrailingTrivia, whitespaceClassificationType:=ClassificationTypeNames.XmlDocCommentText)
                    End If
                Next token
            End Sub

            Private Sub ClassifyXmlTextToken(token As SyntaxToken)
                If token.Kind = SyntaxKind.XmlEntityLiteralToken Then
                    _worker.AddClassification(token, ClassificationTypeNames.XmlDocCommentEntityReference)
                ElseIf token.Kind() <> SyntaxKind.DocumentationCommentLineBreakToken Then
                    Select Case token.Parent.Kind
                        Case SyntaxKind.XmlText
                            _worker.AddClassification(token, ClassificationTypeNames.XmlDocCommentText)
                        Case SyntaxKind.XmlString
                            _worker.AddClassification(token, ClassificationTypeNames.XmlDocCommentAttributeValue)
                        Case SyntaxKind.XmlComment
                            _worker.AddClassification(token, ClassificationTypeNames.XmlDocCommentComment)
                        Case SyntaxKind.XmlCDataSection
                            _worker.AddClassification(token, ClassificationTypeNames.XmlDocCommentCDataSection)
                        Case SyntaxKind.XmlProcessingInstruction
                            _worker.AddClassification(token, ClassificationTypeNames.XmlDocCommentProcessingInstruction)
                    End Select
                End If
            End Sub

            Private Sub ClassifyXmlName(node As XmlNameSyntax)
                Dim classificationType As String
                If TypeOf node.Parent Is BaseXmlAttributeSyntax Then
                    classificationType = ClassificationTypeNames.XmlDocCommentAttributeName
                ElseIf TypeOf node.Parent Is XmlProcessingInstructionSyntax Then
                    classificationType = ClassificationTypeNames.XmlDocCommentProcessingInstruction
                Else
                    classificationType = ClassificationTypeNames.XmlDocCommentName
                End If

                Dim prefix = node.Prefix
                If prefix IsNot Nothing Then
                    AddXmlClassification(prefix.Name, classificationType)
                    AddXmlClassification(prefix.ColonToken, classificationType)
                End If

                AddXmlClassification(node.LocalName, classificationType)
            End Sub

            Private Sub ClassifyElement(xmlElementSyntax As XmlElementSyntax)
                ClassifyElementStart(xmlElementSyntax.StartTag)

                For Each xmlNode In xmlElementSyntax.Content
                    ClassifyXmlNode(xmlNode)
                Next

                ClassifyElementEnd(xmlElementSyntax.EndTag)
            End Sub

            Private Sub ClassifyElementStart(node As XmlElementStartTagSyntax)
                AddXmlClassification(node.LessThanToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                ClassifyXmlNode(node.Name)

                ' Note: In xml doc comments, attributes can only _be_ attributes.
                ' there are no expression holes here.
                For Each attribute In node.Attributes
                    ClassifyBaseXmlAttribute(TryCast(attribute, BaseXmlAttributeSyntax))
                Next

                AddXmlClassification(node.GreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter)
            End Sub

            Private Sub ClassifyElementEnd(node As XmlElementEndTagSyntax)
                AddXmlClassification(node.LessThanSlashToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                ClassifyXmlNode(node.Name)
                AddXmlClassification(node.GreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter)
            End Sub

            Private Sub ClassifyEmptyElement(node As XmlEmptyElementSyntax)
                AddXmlClassification(node.LessThanToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                ClassifyXmlNode(node.Name)

                ' Note: In xml doc comments, attributes can only _be_ attributes.
                ' there are no expression holes here.
                For Each attribute In node.Attributes
                    ClassifyBaseXmlAttribute(TryCast(attribute, BaseXmlAttributeSyntax))
                Next

                AddXmlClassification(node.SlashGreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter)
            End Sub

            Private Sub ClassifyBaseXmlAttribute(attribute As BaseXmlAttributeSyntax)
                If attribute IsNot Nothing Then
                    Select Case attribute.Kind
                        Case SyntaxKind.XmlAttribute
                            Dim xmlAttribute = DirectCast(attribute, XmlAttributeSyntax)
                            If IsLangWordAttribute(xmlAttribute) Then
                                ClassifyLangWordAttribute(xmlAttribute)

                            Else
                                ClassifyAttribute(xmlAttribute)
                            End If

                        Case SyntaxKind.XmlCrefAttribute
                            ClassifyCrefAttribute(DirectCast(attribute, XmlCrefAttributeSyntax))

                        Case SyntaxKind.XmlNameAttribute
                            ClassifyNameAttribute(DirectCast(attribute, XmlNameAttributeSyntax))
                    End Select
                End If
            End Sub

            Private Sub ClassifyAttribute(attribute As XmlAttributeSyntax)
                ClassifyXmlNode(attribute.Name)
                AddXmlClassification(attribute.EqualsToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                ClassifyXmlNode(attribute.Value)
            End Sub

            Private Shared Function IsLangWordAttribute(attribute As XmlAttributeSyntax) As Boolean
                Dim nameNode = DirectCast(attribute.Name, XmlNameSyntax)
                If nameNode.LocalName.Text <> DocumentationCommentXmlNames.LangwordAttributeName Then
                    Return False
                End If

                Dim startTag = TryCast(attribute.Parent, XmlElementStartTagSyntax)
                If (startTag IsNot Nothing) Then
                    Dim startTagName = TryCast(startTag.Name, XmlNameSyntax)
                    Return startTagName IsNot Nothing AndAlso
                        startTagName.Prefix Is Nothing AndAlso
                        startTagName.LocalName.Text = DocumentationCommentXmlNames.SeeElementName
                End If

                Dim emptyElement = TryCast(attribute.Parent, XmlEmptyElementSyntax)
                If (emptyElement IsNot Nothing) Then
                    Dim emptyElementName = TryCast(emptyElement.Name, XmlNameSyntax)
                    Return emptyElementName IsNot Nothing AndAlso
                        emptyElementName.Prefix Is Nothing AndAlso
                        emptyElementName.LocalName.Text = DocumentationCommentXmlNames.SeeElementName
                End If

                Return False
            End Function

            Private Sub ClassifyLangWordAttribute(attribute As XmlAttributeSyntax)
                ClassifyXmlNode(attribute.Name)
                AddXmlClassification(attribute.EqualsToken, ClassificationTypeNames.XmlDocCommentDelimiter)

                Dim node = (DirectCast(attribute.Value, XmlStringSyntax))

                AddXmlClassification(node.StartQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
                ClassifyLangWordTextTokenList(node.TextTokens)
                AddXmlClassification(node.EndQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
            End Sub

            Private Sub ClassifyLangWordTextTokenList(list As SyntaxTokenList)
                For Each token In list
                    If (token.HasLeadingTrivia) Then
                        ClassifyXmlTrivia(token.LeadingTrivia, ClassificationTypeNames.XmlDocCommentText)
                    End If

                    ClassifyLangWordTextToken(token)

                    If (token.HasTrailingTrivia) Then
                        ClassifyXmlTrivia(token.TrailingTrivia, ClassificationTypeNames.XmlDocCommentText)
                    End If
                Next
            End Sub

            Private Sub ClassifyLangWordTextToken(token As SyntaxToken)
                Dim kind = SyntaxFacts.GetKeywordKind(token.Text)
                If kind = SyntaxKind.None Then
                    kind = SyntaxFacts.GetContextualKeywordKind(token.Text)
                End If

                If kind = SyntaxKind.None Then
                    AddXmlClassification(token, ClassificationTypeNames.XmlDocCommentAttributeValue)
                    Return
                End If

                Dim isControlKeyword = IsControlKeywordKind(kind) Or IsControlStatementKind(kind)
                AddXmlClassification(token, If(isControlKeyword, ClassificationTypeNames.ControlKeyword, ClassificationTypeNames.Keyword))
            End Sub

            Private Sub ClassifyCrefAttribute(attribute As XmlCrefAttributeSyntax)
                ClassifyXmlNode(attribute.Name)
                AddXmlClassification(attribute.EqualsToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                AddXmlClassification(attribute.StartQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
                _worker.ClassifyNode(attribute.Reference)
                AddXmlClassification(attribute.EndQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
            End Sub

            Private Sub ClassifyNameAttribute(attribute As XmlNameAttributeSyntax)
                ClassifyXmlNode(attribute.Name)
                AddXmlClassification(attribute.EqualsToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                AddXmlClassification(attribute.StartQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
                _worker.ClassifyNode(attribute.Reference)
                AddXmlClassification(attribute.EndQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
            End Sub

            Private Sub ClassifyXmlText(xmlTextSyntax As XmlTextSyntax)
                ClassifyXmlTextTokens(xmlTextSyntax.TextTokens)
            End Sub

            Private Sub ClassifyString(node As XmlStringSyntax)
                AddXmlClassification(node.StartQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
                ClassifyXmlTextTokens(node.TextTokens)
                AddXmlClassification(node.EndQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes)
            End Sub

            Private Sub ClassifyComment(node As XmlCommentSyntax)
                AddXmlClassification(node.LessThanExclamationMinusMinusToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                ClassifyXmlTextTokens(node.TextTokens)
                AddXmlClassification(node.MinusMinusGreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter)
            End Sub

            Private Sub ClassifyCData(node As XmlCDataSectionSyntax)
                AddXmlClassification(node.BeginCDataToken, ClassificationTypeNames.XmlDocCommentDelimiter)
                ClassifyXmlTextTokens(node.TextTokens)
                AddXmlClassification(node.EndCDataToken, ClassificationTypeNames.XmlDocCommentDelimiter)
            End Sub

            Private Sub ClassifyProcessingInstruction(node As XmlProcessingInstructionSyntax)
                AddXmlClassification(node.LessThanQuestionToken, ClassificationTypeNames.XmlDocCommentProcessingInstruction)
                AddXmlClassification(node.Name, ClassificationTypeNames.XmlDocCommentProcessingInstruction)
                ClassifyXmlTextTokens(node.TextTokens)
                AddXmlClassification(node.QuestionGreaterThanToken, ClassificationTypeNames.XmlDocCommentProcessingInstruction)
            End Sub

        End Class
    End Class
End Namespace