| File: ConvertPrimaryToRegularConstructor\ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs | Web Access |
| Project: src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features) |
// 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.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; using static SyntaxFactory; internal sealed partial class ConvertPrimaryToRegularConstructorCodeRefactoringProvider { private static SyntaxTrivia GetDocComment(SyntaxTriviaList trivia) => trivia.LastOrDefault(t => t.IsSingleLineDocComment()); private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTrivia trivia) => (DocumentationCommentTriviaSyntax?)trivia.GetStructure(); private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(true)] out XmlElementSyntax? element) { element = node is XmlElementSyntax { StartTag.Name.LocalName.ValueText: var elementName } xmlElement && elementName == name ? xmlElement : null; return element != null; } private static TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) { var triviaList = typeDeclaration.GetLeadingTrivia(); var trivia = GetDocComment(triviaList); var docComment = GetDocCommentStructure(trivia); if (docComment == null) return typeDeclaration; using var _ = ArrayBuilder<XmlNodeSyntax>.GetInstance(out var content); foreach (var node in docComment.Content) { if (IsXmlElement(node, "param", out var paramElement)) { // We're skipping a param node. Fixup any preceding text node we may have before it. FixupLastTextNode(); } else { content.Add(node); } } if (content.All(c => c is XmlTextSyntax xmlText && xmlText.TextTokens.All( t => t.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || string.IsNullOrWhiteSpace(t.Text)))) { // Nothing but param nodes. Just remove all the doc comments entirely. var triviaIndex = triviaList.IndexOf(trivia); // remove the doc comment itself var updatedTriviaList = triviaList.RemoveAt(triviaIndex); // If the comment was on a line that started with whitespace, remove that whitespce too. if (triviaIndex > 0 && triviaList[triviaIndex - 1].IsWhitespace()) updatedTriviaList = updatedTriviaList.RemoveAt(triviaIndex - 1); return typeDeclaration.WithLeadingTrivia(updatedTriviaList); } else { var updatedTrivia = Trivia(docComment.WithContent([.. content])); return typeDeclaration.WithLeadingTrivia(triviaList.Replace(trivia, updatedTrivia)); } void FixupLastTextNode() { var node = content.LastOrDefault(); if (node is not XmlTextSyntax xmlText) return; var tokens = xmlText.TextTokens; var lastIndex = tokens.Count; if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(tokens[lastIndex - 1].Text)) lastIndex--; if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralNewLineToken) lastIndex--; if (lastIndex == tokens.Count) { // no change necessary. return; } else if (lastIndex == 0) { // Removed all tokens from the text node. So remove the text node entirely. content.RemoveLast(); } else { // Otherwise, replace with newlines stripped. content[^1] = xmlText.WithTextTokens([.. tokens.Take(lastIndex)]); } } } private static ConstructorDeclarationSyntax WithTypeDeclarationParamDocComments(TypeDeclarationSyntax typeDeclaration, ConstructorDeclarationSyntax constructor) { // Now move the param tags on the type decl over to the constructor. var triviaList = typeDeclaration.GetLeadingTrivia(); var trivia = GetDocComment(triviaList); var docComment = GetDocCommentStructure(trivia); if (docComment is not null) { using var _2 = ArrayBuilder<XmlNodeSyntax>.GetInstance(out var content); for (int i = 0, n = docComment.Content.Count; i < n; i++) { var node = docComment.Content[i]; if (IsXmlElement(node, "param", out _)) { content.Add(node); // if the param tag is followed with a newline, then preserve that when transferring over. if (i + 1 < docComment.Content.Count && IsDocCommentNewLine(docComment.Content[i + 1])) content.Add(docComment.Content[i + 1]); } } if (content.Count > 0) { if (!content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) content[0] = content[0].WithLeadingTrivia(DocumentationCommentExterior("/// ")); content[^1] = content[^1].WithTrailingTrivia(EndOfLine("")); var finalTrivia = DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, [.. content]); return constructor.WithLeadingTrivia(Trivia(finalTrivia)); } } return constructor; } private static bool IsDocCommentNewLine(XmlNodeSyntax node) { if (node is not XmlTextSyntax xmlText) return false; foreach (var textToken in xmlText.TextTokens) { if (textToken.Kind() == SyntaxKind.XmlTextLiteralNewLineToken) continue; if (textToken.Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(textToken.Text)) continue; return false; } return true; } }