File: Classification\Worker_DocumentationComments.cs
Web Access
Project: src\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.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.
 
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Classification;
 
internal ref partial struct Worker
{
    private void ClassifyDocumentationComment(DocumentationCommentTriviaSyntax documentationComment)
    {
        if (!_textSpan.OverlapsWith(documentationComment.Span))
        {
            return;
        }
 
        foreach (var xmlNode in documentationComment.Content)
        {
            var childFullSpan = xmlNode.FullSpan;
            if (childFullSpan.Start > _textSpan.End)
            {
                return;
            }
            else if (childFullSpan.End < _textSpan.Start)
            {
                continue;
            }
 
            ClassifyXmlNode(xmlNode);
        }
 
        // NOTE: the "EndOfComment" token is a special, zero width token.  However, if it's a multi-line xml doc comment
        // the final '*/" will be leading exterior trivia on it.
        ClassifyXmlTrivia(documentationComment.EndOfComment.LeadingTrivia);
    }
 
    private void ClassifyXmlNode(XmlNodeSyntax node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.XmlElement:
                ClassifyXmlElement((XmlElementSyntax)node);
                break;
            case SyntaxKind.XmlEmptyElement:
                ClassifyXmlEmptyElement((XmlEmptyElementSyntax)node);
                break;
            case SyntaxKind.XmlText:
                ClassifyXmlText((XmlTextSyntax)node);
                break;
            case SyntaxKind.XmlComment:
                ClassifyXmlComment((XmlCommentSyntax)node);
                break;
            case SyntaxKind.XmlCDataSection:
                ClassifyXmlCDataSection((XmlCDataSectionSyntax)node);
                break;
            case SyntaxKind.XmlProcessingInstruction:
                ClassifyXmlProcessingInstruction((XmlProcessingInstructionSyntax)node);
                break;
        }
    }
 
    private void ClassifyXmlTrivia(SyntaxTriviaList triviaList)
    {
        foreach (var t in triviaList)
        {
            switch (t.Kind())
            {
                case SyntaxKind.DocumentationCommentExteriorTrivia:
                    ClassifyExteriorTrivia(t);
                    break;
 
                case SyntaxKind.SkippedTokensTrivia:
                    AddClassification(t, ClassificationTypeNames.XmlDocCommentText);
                    break;
            }
        }
    }
 
    private void ClassifyExteriorTrivia(SyntaxTrivia trivia)
    {
        // Note: The exterior trivia can contain whitespace (usually leading) and we want to avoid classifying it.
        // However, meaningful exterior trivia can also have an undetermined length in the case of
        // multiline doc comments.
 
        // For example:
        //
        //     /**<summary>
        //      ********* Goo
        //      ******* </summary>*/
 
        // 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.
        var text = trivia.ToString();
 
        int? spanStart = null;
 
        for (var index = 0; index < text.Length; index++)
        {
            var ch = text[index];
 
            if (spanStart != null && char.IsWhiteSpace(ch))
            {
                var span = TextSpan.FromBounds(spanStart.Value, spanStart.Value + index);
                AddClassification(span, ClassificationTypeNames.XmlDocCommentDelimiter);
 
                spanStart = null;
            }
            else if (spanStart == null && !char.IsWhiteSpace(ch))
            {
                spanStart = trivia.Span.Start + index;
            }
        }
 
        // Add a final classification if we hadn't encountered anymore whitespace at the end.
        if (spanStart != null)
        {
            var span = TextSpan.FromBounds(spanStart.Value, trivia.Span.End);
            AddClassification(span, ClassificationTypeNames.XmlDocCommentDelimiter);
        }
    }
 
    private void AddXmlClassification(SyntaxToken token, string classificationType)
    {
        if (token.HasLeadingTrivia)
            ClassifyXmlTrivia(token.LeadingTrivia);
 
        AddClassification(token, classificationType);
 
        if (token.HasTrailingTrivia)
            ClassifyXmlTrivia(token.TrailingTrivia);
    }
 
    private void ClassifyXmlTextTokens(SyntaxTokenList textTokens)
    {
        foreach (var token in textTokens)
        {
            if (token.HasLeadingTrivia)
                ClassifyXmlTrivia(token.LeadingTrivia);
 
            ClassifyXmlTextToken(token);
 
            if (token.HasTrailingTrivia)
                ClassifyXmlTrivia(token.TrailingTrivia);
        }
    }
 
    private void ClassifyXmlTextToken(SyntaxToken token)
    {
        if (token.Kind() == SyntaxKind.XmlEntityLiteralToken)
        {
            AddClassification(token, ClassificationTypeNames.XmlDocCommentEntityReference);
        }
        else if (token.Kind() != SyntaxKind.XmlTextLiteralNewLineToken)
        {
            RoslynDebug.Assert(token.Parent is object);
            switch (token.Parent.Kind())
            {
                case SyntaxKind.XmlText:
                    AddClassification(token, ClassificationTypeNames.XmlDocCommentText);
                    break;
                case SyntaxKind.XmlTextAttribute:
                    AddClassification(token, ClassificationTypeNames.XmlDocCommentAttributeValue);
                    break;
                case SyntaxKind.XmlComment:
                    AddClassification(token, ClassificationTypeNames.XmlDocCommentComment);
                    break;
                case SyntaxKind.XmlCDataSection:
                    AddClassification(token, ClassificationTypeNames.XmlDocCommentCDataSection);
                    break;
                case SyntaxKind.XmlProcessingInstruction:
                    AddClassification(token, ClassificationTypeNames.XmlDocCommentProcessingInstruction);
                    break;
            }
        }
    }
 
    private void ClassifyXmlName(XmlNameSyntax node)
    {
        var classificationType = node.Parent switch
        {
            XmlAttributeSyntax => ClassificationTypeNames.XmlDocCommentAttributeName,
            XmlProcessingInstructionSyntax => ClassificationTypeNames.XmlDocCommentProcessingInstruction,
            _ => ClassificationTypeNames.XmlDocCommentName,
        };
 
        var prefix = node.Prefix;
        if (prefix != null)
        {
            AddXmlClassification(prefix.Prefix, classificationType);
            AddXmlClassification(prefix.ColonToken, classificationType);
        }
 
        AddXmlClassification(node.LocalName, classificationType);
    }
 
    private void ClassifyXmlElement(XmlElementSyntax node)
    {
        ClassifyXmlElementStartTag(node.StartTag);
 
        foreach (var xmlNode in node.Content)
        {
            ClassifyXmlNode(xmlNode);
        }
 
        ClassifyXmlElementEndTag(node.EndTag);
    }
 
    private void ClassifyXmlElementStartTag(XmlElementStartTagSyntax node)
    {
        AddXmlClassification(node.LessThanToken, ClassificationTypeNames.XmlDocCommentDelimiter);
        ClassifyXmlName(node.Name);
 
        foreach (var attribute in node.Attributes)
        {
            ClassifyXmlAttribute(attribute);
        }
 
        AddXmlClassification(node.GreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter);
    }
 
    private void ClassifyXmlElementEndTag(XmlElementEndTagSyntax node)
    {
        AddXmlClassification(node.LessThanSlashToken, ClassificationTypeNames.XmlDocCommentDelimiter);
        ClassifyXmlName(node.Name);
        AddXmlClassification(node.GreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter);
    }
 
    private void ClassifyXmlEmptyElement(XmlEmptyElementSyntax node)
    {
        AddXmlClassification(node.LessThanToken, ClassificationTypeNames.XmlDocCommentDelimiter);
        ClassifyXmlName(node.Name);
 
        foreach (var attribute in node.Attributes)
        {
            ClassifyXmlAttribute(attribute);
        }
 
        AddXmlClassification(node.SlashGreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter);
    }
 
    private void ClassifyXmlAttribute(XmlAttributeSyntax attribute)
    {
        ClassifyXmlName(attribute.Name);
        AddXmlClassification(attribute.EqualsToken, ClassificationTypeNames.XmlDocCommentDelimiter);
        AddXmlClassification(attribute.StartQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes);
 
        switch (attribute.Kind())
        {
            case SyntaxKind.XmlTextAttribute:
                ClassifyXmlTextTokens(((XmlTextAttributeSyntax)attribute).TextTokens);
                break;
            case SyntaxKind.XmlCrefAttribute:
                ClassifyNode(((XmlCrefAttributeSyntax)attribute).Cref);
                break;
            case SyntaxKind.XmlNameAttribute:
                ClassifyNode(((XmlNameAttributeSyntax)attribute).Identifier);
                break;
        }
 
        AddXmlClassification(attribute.EndQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes);
    }
 
    private void ClassifyXmlText(XmlTextSyntax node)
        => ClassifyXmlTextTokens(node.TextTokens);
 
    private void ClassifyXmlComment(XmlCommentSyntax node)
    {
        AddXmlClassification(node.LessThanExclamationMinusMinusToken, ClassificationTypeNames.XmlDocCommentDelimiter);
        ClassifyXmlTextTokens(node.TextTokens);
        AddXmlClassification(node.MinusMinusGreaterThanToken, ClassificationTypeNames.XmlDocCommentDelimiter);
    }
 
    private void ClassifyXmlCDataSection(XmlCDataSectionSyntax node)
    {
        AddXmlClassification(node.StartCDataToken, ClassificationTypeNames.XmlDocCommentDelimiter);
        ClassifyXmlTextTokens(node.TextTokens);
        AddXmlClassification(node.EndCDataToken, ClassificationTypeNames.XmlDocCommentDelimiter);
    }
 
    private void ClassifyXmlProcessingInstruction(XmlProcessingInstructionSyntax node)
    {
        AddXmlClassification(node.StartProcessingInstructionToken, ClassificationTypeNames.XmlDocCommentProcessingInstruction);
        ClassifyXmlName(node.Name);
        ClassifyXmlTextTokens(node.TextTokens);
        AddXmlClassification(node.EndProcessingInstructionToken, ClassificationTypeNames.XmlDocCommentProcessingInstruction);
    }
}