| File: Compiler\UnprocessedDocumentationCommentFinder.cs | Web Access |
| Project: src\roslyn\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); } } }