private async Task <Document> CreateChangedDocument(CodeFixContext context, PropertyDeclarationSyntax propertyDeclarationSyntax, CancellationToken cancellationToken) { DocumentationCommentTriviaSyntax documentationComment = propertyDeclarationSyntax.GetDocumentationCommentTriviaSyntax(); if (documentationComment == null) { return(context.Document); } XmlElementSyntax summaryElement = (XmlElementSyntax)documentationComment.Content.GetFirstXmlElement("summary"); if (summaryElement == null) { return(context.Document); } SyntaxList <XmlNodeSyntax> summaryContent = summaryElement.Content; XmlNodeSyntax firstContent = summaryContent.FirstOrDefault(IsContentElement); XmlTextSyntax firstText = firstContent as XmlTextSyntax; if (firstText != null) { string firstTextContent = string.Concat(firstText.DescendantTokens()); if (firstTextContent.TrimStart().StartsWith("Gets ", StringComparison.Ordinal)) { // Find the token containing "Gets " SyntaxToken getsToken = default(SyntaxToken); foreach (SyntaxToken textToken in firstText.TextTokens) { if (textToken.IsMissing) { continue; } if (!textToken.Text.TrimStart().StartsWith("Gets ", StringComparison.Ordinal)) { continue; } getsToken = textToken; break; } if (!getsToken.IsMissing) { string text = getsToken.Text; string valueText = getsToken.ValueText; int index = text.IndexOf("Gets "); if (index >= 0) { bool additionalCharacters = index + 5 < text.Length; text = text.Substring(0, index) + (additionalCharacters ? char.ToUpperInvariant(text[index + 5]).ToString() : string.Empty) + text.Substring(index + (additionalCharacters ? (5 + 1) : 5)); } index = valueText.IndexOf("Gets "); if (index >= 0) { valueText = valueText.Remove(index, 5); } SyntaxToken replaced = SyntaxFactory.Token(getsToken.LeadingTrivia, getsToken.Kind(), text, valueText, getsToken.TrailingTrivia); summaryContent = summaryContent.Replace(firstText, firstText.ReplaceToken(getsToken, replaced)); } } } string defaultValueToken = "NullIfNotIncluded"; SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); IPropertySymbol propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); if (propertySymbol != null) { ITypeSymbol propertyType = propertySymbol.Type; if (propertyType.IsImmutableArray()) { defaultValueToken = "DefaultArrayIfNotIncluded"; } } XmlElementSyntax valueElement = XmlSyntaxFactory.MultiLineElement( "value", XmlSyntaxFactory.List( XmlSyntaxFactory.ParaElement(XmlSyntaxFactory.PlaceholderElement(summaryContent.WithoutFirstAndLastNewlines())), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.TokenElement(defaultValueToken))); XmlNodeSyntax leadingNewLine = XmlSyntaxFactory.NewLine(); // 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); } DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent( documentationComment.Content.InsertRange(documentationComment.Content.Count - 1, XmlSyntaxFactory.List( leadingNewLine, valueElement))); SyntaxNode root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode newRoot = root.ReplaceNode(documentationComment, newDocumentationComment); return(context.Document.WithSyntaxRoot(newRoot)); }
public static SyntaxList <XmlNodeSyntax> WithoutFirstAndLastNewlines(this SyntaxList <XmlNodeSyntax> summaryContent) { if (summaryContent.Count == 0) { return(summaryContent); } XmlTextSyntax firstSyntax = summaryContent[0] as XmlTextSyntax; if (firstSyntax == null) { return(summaryContent); } XmlTextSyntax lastSyntax = summaryContent[summaryContent.Count - 1] as XmlTextSyntax; if (lastSyntax == null) { return(summaryContent); } SyntaxTokenList firstSyntaxTokens = firstSyntax.TextTokens; int removeFromStart; if (IsXmlNewLine(firstSyntaxTokens[0])) { removeFromStart = 1; } else { if (!IsXmlWhitespace(firstSyntaxTokens[0])) { return(summaryContent); } if (!IsXmlNewLine(firstSyntaxTokens[1])) { return(summaryContent); } removeFromStart = 2; } SyntaxTokenList lastSyntaxTokens = lastSyntax.TextTokens; int removeFromEnd; if (IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) { removeFromEnd = 1; } else { if (!IsXmlWhitespace(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) { return(summaryContent); } if (!IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 2])) { return(summaryContent); } removeFromEnd = 2; } for (int i = 0; i < removeFromStart; i++) { firstSyntaxTokens = firstSyntaxTokens.RemoveAt(0); } if (firstSyntax == lastSyntax) { lastSyntaxTokens = firstSyntaxTokens; } for (int i = 0; i < removeFromEnd; i++) { if (!lastSyntaxTokens.Any()) { break; } lastSyntaxTokens = lastSyntaxTokens.RemoveAt(lastSyntaxTokens.Count - 1); } summaryContent = summaryContent.RemoveAt(summaryContent.Count - 1); if (lastSyntaxTokens.Count != 0) { summaryContent = summaryContent.Add(lastSyntax.WithTextTokens(lastSyntaxTokens)); } if (firstSyntax != lastSyntax) { summaryContent = summaryContent.RemoveAt(0); if (firstSyntaxTokens.Count != 0) { summaryContent = summaryContent.Insert(0, firstSyntax.WithTextTokens(firstSyntaxTokens)); } } if (summaryContent.Count > 0) { // Make sure to remove the leading trivia summaryContent = summaryContent.Replace(summaryContent[0], summaryContent[0].WithLeadingTrivia()); // Remove leading spaces (between the <para> start tag and the start of the paragraph content) XmlTextSyntax firstTextSyntax = summaryContent[0] as XmlTextSyntax; if (firstTextSyntax != null && firstTextSyntax.TextTokens.Count > 0) { SyntaxToken firstTextToken = firstTextSyntax.TextTokens[0]; string firstTokenText = firstTextToken.Text; string trimmed = firstTokenText.TrimStart(); if (trimmed != firstTokenText) { SyntaxToken newFirstToken = SyntaxFactory.Token( firstTextToken.LeadingTrivia, firstTextToken.Kind(), trimmed, firstTextToken.ValueText.TrimStart(), firstTextToken.TrailingTrivia); summaryContent = summaryContent.Replace(firstTextSyntax, firstTextSyntax.ReplaceToken(firstTextToken, newFirstToken)); } } } return(summaryContent); }
private bool TryRemoveSummaryPrefix(ref SyntaxList <XmlNodeSyntax> summaryContent, string prefix) { XmlNodeSyntax firstContent = summaryContent.FirstOrDefault(IsContentElement); XmlTextSyntax firstText = firstContent as XmlTextSyntax; if (firstText == null) { return(false); } string firstTextContent = string.Concat(firstText.DescendantTokens()); if (!firstTextContent.TrimStart().StartsWith(prefix, StringComparison.Ordinal)) { return(false); } // Find the token containing the prefix, such as "Gets or sets " SyntaxToken prefixToken = default(SyntaxToken); foreach (SyntaxToken textToken in firstText.TextTokens) { if (textToken.IsMissing) { continue; } if (!textToken.Text.TrimStart().StartsWith(prefix, StringComparison.Ordinal)) { continue; } prefixToken = textToken; break; } if (prefixToken.IsMissingOrDefault()) { return(false); } string text = prefixToken.Text; string valueText = prefixToken.ValueText; int index = text.IndexOf(prefix); if (index >= 0) { bool additionalCharacters = index + prefix.Length < text.Length; text = text.Substring(0, index) + (additionalCharacters ? char.ToUpperInvariant(text[index + prefix.Length]).ToString() : string.Empty) + text.Substring(index + (additionalCharacters ? (prefix.Length + 1) : prefix.Length)); } index = valueText.IndexOf(prefix); if (index >= 0) { valueText = valueText.Remove(index, prefix.Length); } SyntaxToken replaced = SyntaxFactory.Token(prefixToken.LeadingTrivia, prefixToken.Kind(), text, valueText, prefixToken.TrailingTrivia); summaryContent = summaryContent.Replace(firstText, firstText.ReplaceToken(prefixToken, replaced)); return(true); }