// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Classification
{
///
/// Worker is an utility class that can classify a list of tokens or a tree within a
/// requested span The implementation is generic and can produce any kind of classification
/// artifacts T T is normally either ClassificationSpan or a Tuple (for testing purposes)
/// and constructed via provided factory.
///
internal partial class Worker
{
private readonly TextSpan _textSpan;
private readonly List _result;
private readonly CancellationToken _cancellationToken;
private Worker(TextSpan textSpan, List result, CancellationToken cancellationToken)
{
_result = result;
_textSpan = textSpan;
_cancellationToken = cancellationToken;
}
internal static void CollectClassifiedSpans(
IEnumerable tokens, TextSpan textSpan, List result, CancellationToken cancellationToken)
{
var worker = new Worker(textSpan, result, cancellationToken);
foreach (var tk in tokens)
{
worker.ClassifyToken(tk);
}
}
internal static void CollectClassifiedSpans(SyntaxNode
node, TextSpan textSpan, List result, CancellationToken cancellationToken)
{
var worker = new Worker(textSpan, result, cancellationToken);
worker.ClassifyNode(node);
}
private void AddClassification(TextSpan span, string type)
{
_result.Add(new ClassifiedSpan(type, span));
}
private void AddClassification(SyntaxTrivia trivia, string type)
{
if (trivia.Width() > 0 && _textSpan.OverlapsWith(trivia.Span))
{
AddClassification(trivia.Span, type);
}
}
private void AddClassification(SyntaxToken token, string type)
{
if (token.Width() > 0 && _textSpan.OverlapsWith(token.Span))
{
AddClassification(token.Span, type);
}
}
private void ClassifyNodeOrToken(SyntaxNodeOrToken nodeOrToken)
{
if (nodeOrToken.IsToken)
{
ClassifyToken(nodeOrToken.AsToken());
return;
}
ClassifyNode(nodeOrToken.AsNode());
}
private void ClassifyNode(SyntaxNode node)
{
foreach (var token in node.DescendantTokens(span: _textSpan, descendIntoTrivia: false))
{
_cancellationToken.ThrowIfCancellationRequested();
ClassifyToken(token);
}
}
private void ClassifyToken(SyntaxToken token)
{
var span = token.Span;
if (span.Length != 0 && _textSpan.OverlapsWith(span))
{
var type = ClassificationHelpers.GetClassification(token);
if (type != null)
{
AddClassification(span, type);
}
}
foreach (var trivia in token.LeadingTrivia)
{
_cancellationToken.ThrowIfCancellationRequested();
ClassifyTrivia(trivia);
}
foreach (var trivia in token.TrailingTrivia)
{
_cancellationToken.ThrowIfCancellationRequested();
ClassifyTrivia(trivia);
}
}
private void ClassifyTrivia(SyntaxTrivia trivia)
{
if (trivia.IsRegularComment())
{
AddClassification(trivia, ClassificationTypeNames.Comment);
}
else if (trivia.Kind() == SyntaxKind.DisabledTextTrivia)
{
AddClassification(trivia, ClassificationTypeNames.ExcludedCode);
}
else if (trivia.Kind() == SyntaxKind.SkippedTokensTrivia)
{
ClassifySkippedTokens((SkippedTokensTriviaSyntax)trivia.GetStructure());
}
else if (trivia.IsDocComment())
{
ClassifyDocumentationComment((DocumentationCommentTriviaSyntax)trivia.GetStructure());
}
else if (trivia.Kind() == SyntaxKind.DocumentationCommentExteriorTrivia)
{
AddClassification(trivia, ClassificationTypeNames.XmlDocCommentDelimiter);
}
else if (SyntaxFacts.IsPreprocessorDirective(trivia.Kind()))
{
ClassifyPreprocessorDirective((DirectiveTriviaSyntax)trivia.GetStructure());
}
}
private void ClassifySkippedTokens(SkippedTokensTriviaSyntax skippedTokens)
{
if (!_textSpan.OverlapsWith(skippedTokens.Span))
{
return;
}
var tokens = skippedTokens.Tokens;
foreach (var tk in tokens)
{
ClassifyToken(tk);
}
}
}
}