private SyntaxNode RenderBlockElementAsMarkdown(SyntaxNode originalNode, SyntaxNode rewrittenNode, string newLineText, ISymbol documentedSymbol) { if (!(rewrittenNode is XmlElementSyntax elementSyntax)) { return(rewrittenNode); } switch (elementSyntax.StartTag?.Name?.ToString()) { case XmlCommentHelper.SummaryXmlTag: case XmlCommentHelper.RemarksXmlTag: case XmlCommentHelper.ExampleXmlTag: case XmlCommentHelper.ReturnsXmlTag: case XmlCommentHelper.ValueXmlTag: break; default: return(rewrittenNode); } string rendered = RenderAsMarkdown(elementSyntax.Content.ToString(), documentedSymbol).Trim(); return(elementSyntax.WithContent( XmlSyntaxFactory.List( XmlSyntaxFactory.NewLine(newLineText).WithoutTrailingTrivia(), XmlSyntaxFactory.Text(" " + rendered.Replace("\n", "\n "), xmlEscape: false), XmlSyntaxFactory.NewLine(newLineText).WithoutTrailingTrivia(), XmlSyntaxFactory.Text(" ")))); }
private static SyntaxList <XmlNodeSyntax> BuildStandardText(SyntaxToken identifier, TypeParameterListSyntax typeParameters, string newLineText, string preText, string postText) { return(XmlSyntaxFactory.List( XmlSyntaxFactory.NewLine(newLineText), XmlSyntaxFactory.Text(preText), BuildSeeElement(identifier, typeParameters), XmlSyntaxFactory.Text(postText.EndsWith(".") ? postText : (postText + ".")))); }
internal static SyntaxList <XmlNodeSyntax> BuildStandardTextSyntaxList(BaseTypeDeclarationSyntax typeDeclaration, string preText, string postText) { TypeParameterListSyntax typeParameterList = GetTypeParameterList(typeDeclaration); return(XmlSyntaxFactory.List( XmlSyntaxFactory.Text(preText), BuildSeeElement(typeDeclaration.Identifier, typeParameterList), XmlSyntaxFactory.Text(postText.EndsWith(".") ? postText : (postText + ".")))); }
private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true); var xmlElement = token.Parent.FirstAncestorOrSelf <XmlElementSyntax>(); var newXmlElement = xmlElement.WithContent(XmlSyntaxFactory.List(XmlSyntaxFactory.Element(XmlCommentHelper.DescriptionXmlTag, xmlElement.Content))); return(document.WithSyntaxRoot(root.ReplaceNode(xmlElement, newXmlElement))); }
internal static SyntaxList <XmlNodeSyntax> BuildStandardTextSyntaxList(BaseTypeDeclarationSyntax typeDeclaration, string newLineText, string preText, string postText) { TypeParameterListSyntax typeParameterList; if (typeDeclaration is ClassDeclarationSyntax classDeclaration) { typeParameterList = classDeclaration.TypeParameterList; } else { typeParameterList = (typeDeclaration as StructDeclarationSyntax)?.TypeParameterList; } return(XmlSyntaxFactory.List( XmlSyntaxFactory.NewLine(newLineText), XmlSyntaxFactory.Text(preText), BuildSeeElement(typeDeclaration.Identifier, typeParameterList), XmlSyntaxFactory.Text(postText.EndsWith(".") ? postText : (postText + ".")))); }
private static SyntaxList <XmlNodeSyntax> BuildStandardText(SyntaxToken identifier, TypeParameterListSyntax typeParameters, string newLineText, string preText, string postText) { TypeSyntax identifierName; // Get a TypeSyntax representing the class name with its type parameters if (typeParameters == null || !typeParameters.Parameters.Any()) { identifierName = SyntaxFactory.IdentifierName(identifier.WithoutTrivia()); } else { identifierName = SyntaxFactory.GenericName(identifier.WithoutTrivia(), ParameterToArgumentListSyntax(typeParameters)); } return(XmlSyntaxFactory.List( XmlSyntaxFactory.NewLine(newLineText), XmlSyntaxFactory.Text(preText), XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(identifierName)), XmlSyntaxFactory.Text(postText.EndsWith(".") ? postText : (postText + ".")))); }
private async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var documentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode syntax = documentRoot.FindNode(diagnostic.Location.SourceSpan); if (syntax == null) { return(document); } PropertyDeclarationSyntax propertyDeclarationSyntax = syntax.FirstAncestorOrSelf <PropertyDeclarationSyntax>(); if (propertyDeclarationSyntax == null) { return(document); } DocumentationCommentTriviaSyntax documentationComment = propertyDeclarationSyntax.GetDocumentationCommentTriviaSyntax(); if (documentationComment == null) { return(document); } if (!(documentationComment.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag) is XmlElementSyntax summaryElement)) { return(document); } SyntaxList <XmlNodeSyntax> summaryContent = summaryElement.Content; if (!this.TryRemoveSummaryPrefix(ref summaryContent, "Gets or sets ")) { if (!this.TryRemoveSummaryPrefix(ref summaryContent, "Gets ")) { this.TryRemoveSummaryPrefix(ref summaryContent, "Sets "); } } SyntaxList <XmlNodeSyntax> content = summaryContent.WithoutFirstAndLastNewlines(); if (!string.IsNullOrWhiteSpace(content.ToFullString())) { // wrap the content in a <placeholder> element for review content = XmlSyntaxFactory.List(XmlSyntaxFactory.PlaceholderElement(content)); } string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); XmlElementSyntax valueElement = XmlSyntaxFactory.MultiLineElement(XmlCommentHelper.ValueXmlTag, newLineText, content); XmlNodeSyntax leadingNewLine = XmlSyntaxFactory.NewLine(newLineText); // HACK: The formatter isn't working when contents are added to an existing documentation comment, so we // manually apply the indentation from the last line of the existing comment to each new line of the // generated content. SyntaxTrivia exteriorTrivia = GetLastDocumentationCommentExteriorTrivia(documentationComment); if (!exteriorTrivia.Token.IsMissing) { leadingNewLine = leadingNewLine.ReplaceExteriorTrivia(exteriorTrivia); valueElement = valueElement.ReplaceExteriorTrivia(exteriorTrivia); } // Try to replace an existing <value> element if the comment contains one. Otherwise, add it as a new element. SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode newRoot; XmlNodeSyntax existingValue = documentationComment.Content.GetFirstXmlElement(XmlCommentHelper.ValueXmlTag); if (existingValue != null) { newRoot = root.ReplaceNode(existingValue, valueElement); } else { DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent( documentationComment.Content.InsertRange( documentationComment.Content.Count - 1, XmlSyntaxFactory.List(leadingNewLine, valueElement))); newRoot = root.ReplaceNode(documentationComment, newDocumentationComment); } return(document.WithSyntaxRoot(newRoot)); }
private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode enclosingNode = root.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true); if (!(enclosingNode is XmlNodeSyntax xmlNodeSyntax)) { return(document); } XmlElementSyntax xmlElementSyntax = xmlNodeSyntax.FirstAncestorOrSelf <XmlElementSyntax>(); if (xmlElementSyntax == null) { return(document); } SyntaxToken startToken = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true); if (!(startToken.Parent is XmlNodeSyntax startNode) || startNode == xmlElementSyntax) { return(document); } while (startNode.Parent != xmlElementSyntax) { startNode = startNode.Parent as XmlNodeSyntax; if (startNode == null) { return(document); } } SyntaxToken stopToken = root.FindToken(diagnostic.Location.SourceSpan.End - 1, findInsideTrivia: true); if (!(stopToken.Parent is XmlNodeSyntax stopNode) || stopNode == xmlElementSyntax) { return(document); } while (stopNode.Parent != xmlElementSyntax) { stopNode = stopNode.Parent as XmlNodeSyntax; if (stopNode == null) { return(document); } } int startIndex = xmlElementSyntax.Content.IndexOf(startNode); int stopIndex = xmlElementSyntax.Content.IndexOf(stopNode); if (startIndex < 0 || stopIndex < 0) { return(document); } XmlElementSyntax paragraph = XmlSyntaxFactory.ParaElement(xmlElementSyntax.Content.Skip(startIndex).Take(stopIndex - startIndex + 1).ToArray()); SyntaxList <XmlNodeSyntax> leadingWhitespaceContent; SyntaxList <XmlNodeSyntax> trailingWhitespaceContent; paragraph = TrimWhitespaceContent(paragraph, out leadingWhitespaceContent, out trailingWhitespaceContent); SyntaxList <XmlNodeSyntax> newContent = XmlSyntaxFactory.List(); newContent = newContent.AddRange(xmlElementSyntax.Content.Take(startIndex)); newContent = newContent.AddRange(leadingWhitespaceContent); newContent = newContent.Add(paragraph); newContent = newContent.AddRange(trailingWhitespaceContent); newContent = newContent.AddRange(xmlElementSyntax.Content.Skip(stopIndex + 1)); return(document.WithSyntaxRoot(root.ReplaceNode(xmlElementSyntax, xmlElementSyntax.WithContent(newContent)))); }
private static XmlElementSyntax TrimWhitespaceContent(XmlElementSyntax paragraph, out SyntaxList <XmlNodeSyntax> leadingWhitespaceContent, out SyntaxList <XmlNodeSyntax> trailingWhitespaceContent) { SyntaxList <XmlNodeSyntax> completeContent = XmlSyntaxFactory.List(paragraph.Content.SelectMany(ExpandTextNodes).ToArray()); leadingWhitespaceContent = XmlSyntaxFactory.List(completeContent.TakeWhile(x => XmlCommentHelper.IsConsideredEmpty(x)).ToArray()); trailingWhitespaceContent = XmlSyntaxFactory.List(completeContent.Skip(leadingWhitespaceContent.Count).Reverse().TakeWhile(x => XmlCommentHelper.IsConsideredEmpty(x)).Reverse().ToArray()); SyntaxList <XmlNodeSyntax> trimmedContent = XmlSyntaxFactory.List(completeContent.Skip(leadingWhitespaceContent.Count).Take(completeContent.Count - leadingWhitespaceContent.Count - trailingWhitespaceContent.Count).ToArray()); SyntaxTriviaList leadingTrivia = SyntaxFactory.TriviaList(); SyntaxTriviaList trailingTrivia = SyntaxFactory.TriviaList(); if (trimmedContent.Any()) { leadingTrivia = trimmedContent[0].GetLeadingTrivia(); trailingTrivia = trimmedContent.Last().GetTrailingTrivia(); trimmedContent = trimmedContent.Replace(trimmedContent[0], trimmedContent[0].WithoutLeadingTrivia()); trimmedContent = trimmedContent.Replace(trimmedContent.Last(), trimmedContent.Last().WithoutTrailingTrivia()); } else { leadingTrivia = SyntaxFactory.TriviaList(); trailingTrivia = SyntaxFactory.TriviaList(); } XmlElementSyntax result = paragraph; if (leadingWhitespaceContent.Any()) { var first = leadingWhitespaceContent[0]; var newFirst = first.WithLeadingTrivia(first.GetLeadingTrivia().InsertRange(0, paragraph.GetLeadingTrivia())); leadingWhitespaceContent = leadingWhitespaceContent.Replace(first, newFirst); } else { leadingTrivia = leadingTrivia.InsertRange(0, result.GetLeadingTrivia()); } if (trailingWhitespaceContent.Any()) { var last = trailingWhitespaceContent.Last(); var newLast = last.WithLeadingTrivia(last.GetLeadingTrivia().AddRange(paragraph.GetTrailingTrivia())); trailingWhitespaceContent = trailingWhitespaceContent.Replace(last, newLast); } else { trailingTrivia = trailingTrivia.AddRange(result.GetTrailingTrivia()); } if (trimmedContent.FirstOrDefault() is XmlTextSyntax firstTextNode && firstTextNode.TextTokens.Any()) { SyntaxToken firstTextToken = firstTextNode.TextTokens[0]; string leadingWhitespace = new(firstTextToken.Text.Cast <char>().TakeWhile(char.IsWhiteSpace).ToArray()); if (leadingWhitespace.Length > 0) { SyntaxToken newFirstTextToken = XmlSyntaxFactory.TextLiteral(firstTextToken.Text.Substring(leadingWhitespace.Length)).WithTriviaFrom(firstTextToken); XmlTextSyntax newFirstTextNode = firstTextNode.WithTextTokens(firstTextNode.TextTokens.Replace(firstTextToken, newFirstTextToken)); trimmedContent = trimmedContent.Replace(firstTextNode, newFirstTextNode); leadingTrivia = leadingTrivia.Add(SyntaxFactory.Whitespace(leadingWhitespace)); } } if (trimmedContent.LastOrDefault() is XmlTextSyntax lastTextNode && lastTextNode.TextTokens.Any()) { SyntaxToken lastTextToken = lastTextNode.TextTokens.Last(); string trailingWhitespace = new(lastTextToken.Text.Cast <char>().Reverse().TakeWhile(char.IsWhiteSpace).Reverse().ToArray()); if (trailingWhitespace.Length > 0) { SyntaxToken newLastTextToken = XmlSyntaxFactory.TextLiteral(lastTextToken.Text.Substring(0, lastTextToken.Text.Length - trailingWhitespace.Length)).WithTriviaFrom(lastTextToken); XmlTextSyntax newLastTextNode = lastTextNode.WithTextTokens(lastTextNode.TextTokens.Replace(lastTextToken, newLastTextToken)); trimmedContent = trimmedContent.Replace(lastTextNode, newLastTextNode); trailingTrivia = trailingTrivia.Insert(0, SyntaxFactory.Whitespace(trailingWhitespace)); } } return(result.WithContent(trimmedContent) .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(trailingTrivia)); }
private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var documentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode syntax = documentRoot.FindNode(diagnostic.Location.SourceSpan); if (syntax == null) { return(document); } MethodDeclarationSyntax methodDeclarationSyntax = syntax.FirstAncestorOrSelf <MethodDeclarationSyntax>(); DelegateDeclarationSyntax delegateDeclarationSyntax = syntax.FirstAncestorOrSelf <DelegateDeclarationSyntax>(); if (methodDeclarationSyntax == null && delegateDeclarationSyntax == null) { return(document); } DocumentationCommentTriviaSyntax documentationComment = methodDeclarationSyntax?.GetDocumentationCommentTriviaSyntax() ?? delegateDeclarationSyntax?.GetDocumentationCommentTriviaSyntax(); if (documentationComment == null) { return(document); } SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); bool isTask; bool isAsynchronousTestMethod; if (methodDeclarationSyntax != null) { isTask = IsTaskReturningMethod(semanticModel, methodDeclarationSyntax, cancellationToken); isAsynchronousTestMethod = isTask && IsAsynchronousTestMethod(semanticModel, methodDeclarationSyntax, cancellationToken); } else { isTask = IsTaskReturningMethod(semanticModel, delegateDeclarationSyntax, cancellationToken); isAsynchronousTestMethod = false; } XmlNodeSyntax returnsElement = documentationComment.Content.GetFirstXmlElement(XmlCommentHelper.ReturnsXmlTag) as XmlNodeSyntax; if (returnsElement != null && !isTask) { // This code fix doesn't know how to do anything more than document Task-returning methods. return(document); } SyntaxList <XmlNodeSyntax> content = XmlSyntaxFactory.List(); if (isTask) { content = content.Add(XmlSyntaxFactory.Text("A ")); content = content.Add(XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task"))).WithAdditionalAnnotations(Simplifier.Annotation)); string operationKind = isAsynchronousTestMethod ? "unit test" : "operation"; content = content.Add(XmlSyntaxFactory.Text($" representing the asynchronous {operationKind}.")); // wrap the generated content in a <placeholder> element for review. content = XmlSyntaxFactory.List(XmlSyntaxFactory.PlaceholderElement(content)); } // Try to replace an existing <returns> element if the comment contains one. Otherwise, add it as a new element. SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode newRoot; if (returnsElement != null) { XmlEmptyElementSyntax emptyElement = returnsElement as XmlEmptyElementSyntax; if (emptyElement != null) { XmlElementSyntax updatedReturns = XmlSyntaxFactory.Element(XmlCommentHelper.ReturnsXmlTag, content) .WithLeadingTrivia(returnsElement.GetLeadingTrivia()) .WithTrailingTrivia(returnsElement.GetTrailingTrivia()); newRoot = root.ReplaceNode(returnsElement, updatedReturns); } else { XmlElementSyntax updatedReturns = ((XmlElementSyntax)returnsElement).WithContent(content); newRoot = root.ReplaceNode(returnsElement, updatedReturns); } } else { string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); returnsElement = XmlSyntaxFactory.Element(XmlCommentHelper.ReturnsXmlTag, content); XmlNodeSyntax leadingNewLine = XmlSyntaxFactory.NewLine(newLineText); // HACK: The formatter isn't working when contents are added to an existing documentation comment, so we // manually apply the indentation from the last line of the existing comment to each new line of the // generated content. SyntaxTrivia exteriorTrivia = GetLastDocumentationCommentExteriorTrivia(documentationComment); if (!exteriorTrivia.Token.IsMissing) { leadingNewLine = leadingNewLine.ReplaceExteriorTrivia(exteriorTrivia); returnsElement = returnsElement.ReplaceExteriorTrivia(exteriorTrivia); } DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent( documentationComment.Content.InsertRange( documentationComment.Content.Count - 1, XmlSyntaxFactory.List(leadingNewLine, returnsElement))); newRoot = root.ReplaceNode(documentationComment, newDocumentationComment); } return(document.WithSyntaxRoot(newRoot)); }
public void TestSeeAlsoElementHref() { string expected = "<seealso href=\"http://www.example.com/\">Text.</seealso>"; Assert.Equal(expected, XmlSyntaxFactory.SeeAlsoElement(new Uri("http://www.example.com/"), XmlSyntaxFactory.List(XmlSyntaxFactory.Text("Text."))).ToFullString()); }
private async Task <Document> CreateChangedDocumentAsync(CodeFixContext context, DocumentationCommentTriviaSyntax documentationCommentTriviaSyntax, CancellationToken cancellationToken) { StringBuilder leadingTriviaBuilder = new StringBuilder(); SyntaxToken parentToken = documentationCommentTriviaSyntax.ParentTrivia.Token; int documentationCommentIndex = parentToken.LeadingTrivia.IndexOf(documentationCommentTriviaSyntax.ParentTrivia); for (int i = 0; i < documentationCommentIndex; i++) { SyntaxTrivia trivia = parentToken.LeadingTrivia[i]; switch (trivia.Kind()) { case SyntaxKind.EndOfLineTrivia: leadingTriviaBuilder.Clear(); break; case SyntaxKind.WhitespaceTrivia: leadingTriviaBuilder.Append(trivia.ToFullString()); break; default: break; } } leadingTriviaBuilder.Append(documentationCommentTriviaSyntax.GetLeadingTrivia().ToFullString()); // this is the trivia that should appear at the beginning of each line of the comment. SyntaxTrivia leadingTrivia = SyntaxFactory.DocumentationCommentExterior(leadingTriviaBuilder.ToString()); string newLineText = context.Document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); var documentedSymbol = semanticModel.GetDeclaredSymbol(parentToken.Parent.FirstAncestorOrSelf <SyntaxNode>(SyntaxNodeExtensionsEx.IsSymbolDeclaration), context.CancellationToken); DocumentationCommentTriviaSyntax contentsOnly = RemoveExteriorTrivia(documentationCommentTriviaSyntax); contentsOnly = contentsOnly.ReplaceNodes(contentsOnly.ChildNodes(), (originalNode, rewrittenNode) => RenderBlockElementAsMarkdown(originalNode, rewrittenNode, newLineText, documentedSymbol)); string renderedContent = contentsOnly.Content.ToFullString(); string[] lines = renderedContent.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); SyntaxList <XmlNodeSyntax> newContent = XmlSyntaxFactory.List(); for (int i = 0; i < lines.Length; i++) { string line = lines[i]; if (string.IsNullOrWhiteSpace(line)) { if (i == lines.Length - 1) { break; } line = string.Empty; } if (newContent.Count > 0) { newContent = newContent.Add(XmlSyntaxFactory.NewLine(newLineText).WithTrailingTrivia(SyntaxFactory.DocumentationCommentExterior("///"))); } newContent = newContent.Add(XmlSyntaxFactory.Text(line.TrimEnd(), xmlEscape: false)); } contentsOnly = contentsOnly.WithContent(newContent); contentsOnly = contentsOnly .ReplaceExteriorTrivia(leadingTrivia) .WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("///")) .WithTrailingTrivia(SyntaxFactory.EndOfLine(Environment.NewLine)); string fullContent = contentsOnly.ToFullString(); SyntaxTriviaList parsedTrivia = SyntaxFactory.ParseLeadingTrivia(fullContent); SyntaxTrivia documentationTrivia = parsedTrivia.FirstOrDefault(i => i.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia)); contentsOnly = documentationTrivia.GetStructure() as DocumentationCommentTriviaSyntax; if (contentsOnly == null) { return(context.Document); } // Remove unnecessary nested paragraph elements contentsOnly = contentsOnly.ReplaceNodes(contentsOnly.DescendantNodes().OfType <XmlElementSyntax>(), MarkUnnecessaryParagraphs); contentsOnly = contentsOnly.ReplaceNodes(contentsOnly.DescendantNodes().OfType <XmlElementSyntax>(), RemoveUnnecessaryParagraphs); SyntaxNode root = await context.Document.GetSyntaxRootAsync(cancellationToken); SyntaxNode newRoot = root.ReplaceNode(documentationCommentTriviaSyntax, contentsOnly); if (documentationCommentTriviaSyntax.IsEquivalentTo(contentsOnly)) { return(context.Document); } if (documentationCommentTriviaSyntax.ToFullString().Equals(contentsOnly.ToFullString(), StringComparison.Ordinal)) { return(context.Document); } return(context.Document.WithSyntaxRoot(newRoot)); }
private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true); var xmlElement = token.Parent.FirstAncestorOrSelf <XmlElementSyntax>(); var oldStartToken = xmlElement.StartTag.Name.LocalName; string newIdentifier; switch (oldStartToken.ValueText) { case "p": newIdentifier = XmlCommentHelper.ParaXmlTag; break; case "tt": newIdentifier = XmlCommentHelper.CXmlTag; break; case "pre": newIdentifier = XmlCommentHelper.CodeXmlTag; break; case "ul": case "ol": newIdentifier = XmlCommentHelper.ListXmlTag; break; default: // Not handled return(document); } var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia); var newXmlElement = xmlElement.ReplaceToken(oldStartToken, newStartToken); var oldEndToken = newXmlElement.EndTag.Name.LocalName; var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia); newXmlElement = newXmlElement.ReplaceToken(oldEndToken, newEndToken); if (newIdentifier == XmlCommentHelper.ListXmlTag) { // Add an attribute for the list kind string listType = oldStartToken.ValueText == "ol" ? "number" : "bullet"; newXmlElement = newXmlElement.WithStartTag(newXmlElement.StartTag.AddAttributes(XmlSyntaxFactory.TextAttribute(XmlCommentHelper.TypeAttributeName, listType))); // Replace each <li>...</li> element with <item><description>...</description></item> for (int i = 0; i < newXmlElement.Content.Count; i++) { if (newXmlElement.Content[i] is XmlElementSyntax childXmlElement && childXmlElement.StartTag?.Name?.LocalName.ValueText == "li" && childXmlElement.StartTag.Name.Prefix == null) { oldStartToken = childXmlElement.StartTag.Name.LocalName; newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, XmlCommentHelper.ItemXmlTag, oldStartToken.TrailingTrivia); var newChildXmlElement = childXmlElement.ReplaceToken(oldStartToken, newStartToken); oldEndToken = newChildXmlElement.EndTag.Name.LocalName; newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, XmlCommentHelper.ItemXmlTag, oldEndToken.TrailingTrivia); newChildXmlElement = newChildXmlElement.ReplaceToken(oldEndToken, newEndToken); newChildXmlElement = newChildXmlElement.WithContent(XmlSyntaxFactory.List(XmlSyntaxFactory.Element(XmlCommentHelper.DescriptionXmlTag, newChildXmlElement.Content))); newXmlElement = newXmlElement.ReplaceNode(childXmlElement, newChildXmlElement); } } } return(document.WithSyntaxRoot(root.ReplaceNode(xmlElement, newXmlElement))); }