File: Compiler\UnprocessedDocumentationCommentFinder.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.
 
#nullable disable
 
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal class UnprocessedDocumentationCommentFinder : CSharpSyntaxWalker
    {
        private readonly DiagnosticBag _diagnostics;
        private readonly CancellationToken _cancellationToken;
        private readonly TextSpan? _filterSpanWithinTree;
 
        private bool _isValidLocation;
 
        private UnprocessedDocumentationCommentFinder(DiagnosticBag diagnostics, TextSpan? filterSpanWithinTree, CancellationToken cancellationToken)
            : base(SyntaxWalkerDepth.Trivia)
        {
            _diagnostics = diagnostics;
            _filterSpanWithinTree = filterSpanWithinTree;
            _cancellationToken = cancellationToken;
        }
 
        public static void ReportUnprocessed(SyntaxTree tree, TextSpan? filterSpanWithinTree, DiagnosticBag diagnostics, CancellationToken cancellationToken)
        {
            if (tree.ReportDocumentationCommentDiagnostics())
            {
                UnprocessedDocumentationCommentFinder finder = new UnprocessedDocumentationCommentFinder(diagnostics, filterSpanWithinTree, cancellationToken);
                finder.Visit(tree.GetRoot(cancellationToken));
            }
        }
 
        private bool IsSyntacticallyFilteredOut(TextSpan fullSpan)
        {
            return _filterSpanWithinTree.HasValue && !_filterSpanWithinTree.Value.Contains(fullSpan);
        }
 
        public override void DefaultVisit(SyntaxNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (IsSyntacticallyFilteredOut(node.FullSpan))
            {
                return;
            }
 
            // Short-circuit traversal if we know there are no documentation comments below.
            // This should prevent us from descending into method bodies in correct programs.
            if (!node.HasStructuredTrivia)
            {
                if (node.Span.Length > 0)
                {
                    _isValidLocation = false; //would have seen a token
                }
                return;
            }
 
            if (node is BaseTypeDeclarationSyntax ||
                node is DelegateDeclarationSyntax ||
                node is EnumMemberDeclarationSyntax ||
                node is BaseMethodDeclarationSyntax ||
                node is BasePropertyDeclarationSyntax || //includes EventDeclarationSyntax
                node is BaseFieldDeclarationSyntax) //includes EventFieldDeclarationSyntax
            {
                // Will be cleared the next time we visit a token,
                // after the leading trivia, if there is any.
                _isValidLocation = true;
            }
 
            base.DefaultVisit(node);
        }
 
        public override void VisitLeadingTrivia(SyntaxToken token)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (IsSyntacticallyFilteredOut(token.FullSpan))
            {
                return;
            }
 
            base.VisitLeadingTrivia(token);
            _isValidLocation = false;
        }
 
        public override void VisitTrivia(SyntaxTrivia trivia)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (IsSyntacticallyFilteredOut(trivia.FullSpan))
            {
                return;
            }
 
            if (!_isValidLocation && SyntaxFacts.IsDocumentationCommentTrivia(trivia.Kind()))
            {
                int start = trivia.Position; // FullSpan start to include /** or ///
                const int length = 1; //Match dev11: span is just one character
                _diagnostics.Add(ErrorCode.WRN_UnprocessedXMLComment, new SourceLocation(trivia.SyntaxTree, new TextSpan(start, length)));
            }
            base.VisitTrivia(trivia);
        }
    }
}