|
// 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);
}
}
|