/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/DocumentationComments/AbstractRemoveDocCommentNodeCodeFixProvider.cs
125 строк
5 KB
Cyrus Najmabadi
Remove unnecessary imports
10 мар 2025, 17:04
10 мар 2025, 17:04
58c992f
Код
Авторство
О чём код?
// 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 System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.DocumentationComments; internal abstract class AbstractRemoveDocCommentNodeCodeFixProvider<TXmlElementSyntax, TXmlTextSyntax> : CodeFixProvider where TXmlElementSyntax : SyntaxNode where TXmlTextSyntax : SyntaxNode { public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public abstract override ImmutableArray<string> FixableDiagnosticIds { get; } protected abstract string DocCommentSignifierToken { get; } protected abstract SyntaxTriviaList GetRevisedDocCommentTrivia(string docCommentText); protected abstract SyntaxTokenList GetTextTokens(TXmlTextSyntax xmlText); protected abstract bool IsXmlNewLineToken(SyntaxToken token); protected abstract bool IsXmlWhitespaceToken(SyntaxToken token); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var paramNode = GetParamNode(root, context.Span); if (paramNode != null) { context.RegisterCodeFix( CodeAction.Create( CodeFixesResources.Remove_tag, cancellationToken => RemoveDuplicateParamTagAsync(context.Document, paramNode, cancellationToken), nameof(CodeFixesResources.Remove_tag)), context.Diagnostics); } } private static TXmlElementSyntax? GetParamNode(SyntaxNode root, TextSpan span) { // First, we get the node the diagnostic fired on // Then, we climb the tree to the first parent that is of the type XMLElement // This is to correctly handle XML nodes that are nested in other XML nodes, so we only // remove the node the diagnostic fired on and its children, but no parent nodes var paramNode = root.FindNode(span, findInsideTrivia: true); return paramNode.FirstAncestorOrSelf<TXmlElementSyntax>(); } private async Task<Document> RemoveDuplicateParamTagAsync( Document document, TXmlElementSyntax paramNode, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var removedNodes = new List<SyntaxNode> { paramNode }; var paramNodeSiblings = paramNode.GetRequiredParent().ChildNodes().ToList(); // This should not cause a crash because the diagnostics are only thrown in // doc comment XML nodes, which, by definition, start with `///` (C#) or `'''` (VB.NET) // If, perhaps, this specific node is not directly preceded by the comment marker node, // it will be preceded by another XML node var paramNodeIndex = paramNodeSiblings.IndexOf(paramNode); if (ShouldRemovePreviousSibling(paramNodeSiblings, paramNodeIndex)) { removedNodes.Add(paramNodeSiblings[paramNodeIndex - 1]); } // Remove all trivia attached to the nodes I am removing. // Really, any option should work here because the leading/trailing text // around these nodes are not attached to them as trivia. var newRoot = root.RemoveNodes(removedNodes, SyntaxRemoveOptions.KeepNoTrivia); Contract.ThrowIfNull(newRoot); return document.WithSyntaxRoot(newRoot); } private bool ShouldRemovePreviousSibling(List<SyntaxNode> paramNodeSiblings, int paramNodeIndex) { if (paramNodeIndex > 0) { var previousNodeTextTrimmed = paramNodeSiblings[paramNodeIndex - 1].ToFullString().Trim(); if (previousNodeTextTrimmed == string.Empty || previousNodeTextTrimmed == DocCommentSignifierToken) { // Only remove the preceding /// if this param node is also the only thing on this line. if (paramNodeIndex + 1 < paramNodeSiblings.Count) { var nextSibling = paramNodeSiblings[paramNodeIndex + 1]; if (nextSibling is TXmlTextSyntax textSyntax) { // Walk the next text block forward, making sure we only see whitespace // until we hit the next newline. If that's all we can remove the preceding // '///'. Otherwise we'll want to keep it to keep whatever comes after // this node valid. foreach (var childToken in GetTextTokens(textSyntax)) { if (IsXmlWhitespaceToken(childToken)) { continue; } if (IsXmlNewLineToken(childToken)) { return true; } return false; } } } } } return false; } }