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 newLineText, string preText, string postText) { TypeParameterListSyntax typeParameterList = GetTypeParameterList(typeDeclaration); return(XmlSyntaxFactory.List( XmlSyntaxFactory.NewLine(newLineText), XmlSyntaxFactory.Text(preText), BuildSeeElement(typeDeclaration.Identifier, typeParameterList), XmlSyntaxFactory.Text(postText.EndsWith(".") ? postText : (postText + ".")))); }
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 static Task <Document> GetConstructorOrDestructorDocumentationTransformedDocumentAsync(Document document, SyntaxNode root, BaseMethodDeclarationSyntax declaration, CancellationToken cancellationToken) { SyntaxTriviaList leadingTrivia = declaration.GetLeadingTrivia(); int insertionIndex = GetInsertionIndex(ref leadingTrivia); string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); var documentationNodes = new List <XmlNodeSyntax>(); var typeDeclaration = declaration.FirstAncestorOrSelf <BaseTypeDeclarationSyntax>(); var standardText = SA1642SA1643CodeFixProvider.GenerateStandardText(document, declaration, typeDeclaration, cancellationToken); var standardTextSyntaxList = SA1642SA1643CodeFixProvider.BuildStandardTextSyntaxList(typeDeclaration, newLineText, standardText[0], standardText[1]); // Remove the empty line generated by build standard text, as this is not needed with constructing a new summary element. standardTextSyntaxList = standardTextSyntaxList.RemoveAt(0); documentationNodes.Add(XmlSyntaxFactory.SummaryElement(newLineText, standardTextSyntaxList)); if (declaration.ParameterList != null) { foreach (var parameter in declaration.ParameterList.Parameters) { documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText)); documentationNodes.Add(XmlSyntaxFactory.ParamElement(parameter.Identifier.ValueText)); } } var documentationComment = XmlSyntaxFactory.DocumentationComment( newLineText, documentationNodes.ToArray()); var trivia = SyntaxFactory.Trivia(documentationComment); SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(insertionIndex, trivia); SyntaxNode newElement = declaration.WithLeadingTrivia(newLeadingTrivia); return(Task.FromResult(document.WithSyntaxRoot(root.ReplaceNode(declaration, newElement)))); }
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); var firstComment = root.FindTrivia(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true); var parentToken = firstComment.Token; var lines = new List <string>(); for (int i = 0; i < parentToken.LeadingTrivia.Count; i++) { if (!parentToken.LeadingTrivia[i].IsKind(SyntaxKind.SingleLineCommentTrivia) && !parentToken.LeadingTrivia[i].IsKind(SyntaxKind.MultiLineCommentTrivia)) { continue; } if (!diagnostic.Location.SourceSpan.Contains(parentToken.LeadingTrivia[i].Span)) { continue; } if (parentToken.LeadingTrivia[i].IsKind(SyntaxKind.SingleLineCommentTrivia)) { lines.Add(parentToken.LeadingTrivia[i].ToString().Substring(2)); } else { var commentText = parentToken.LeadingTrivia[i].ToString(); var normalizedText = commentText.Substring(1, commentText.Length - 3) .Replace("\r\n", "\n").Replace('\r', '\n'); foreach (var line in normalizedText.Split('\n')) { if (Regex.IsMatch(line, "^\\s*\\*")) { lines.Add(line.Substring(line.IndexOf('*') + 1)); } else { lines.Add(line); } } lines[lines.Count - 1] = lines[lines.Count - 1].TrimEnd(); } } int firstContentLine = lines.FindIndex(line => !string.IsNullOrWhiteSpace(line)); if (firstContentLine >= 0) { lines.RemoveRange(0, firstContentLine); int lastContentLine = lines.FindLastIndex(line => !string.IsNullOrWhiteSpace(line)); lines.RemoveRange(lastContentLine + 1, lines.Count - lastContentLine - 1); } if (lines.All(line => line.Length == 0 || line.StartsWith(" "))) { for (int i = 0; i < lines.Count; i++) { if (lines[i].Length == 0) { continue; } lines[i] = lines[i].Substring(1); } } var nodes = new List <XmlNodeSyntax>(lines.Select(line => XmlSyntaxFactory.Text(line))); for (int i = nodes.Count - 1; i > 0; i--) { nodes.Insert(i, XmlSyntaxFactory.NewLine(Environment.NewLine)); } var summary = XmlSyntaxFactory.SummaryElement(Environment.NewLine, nodes.ToArray()); var leadingTrivia = SyntaxFactory.TriviaList(parentToken.LeadingTrivia.TakeWhile(trivia => !trivia.Equals(firstComment))); var newParentToken = parentToken.WithLeadingTrivia(leadingTrivia.Add(SyntaxFactory.Trivia(XmlSyntaxFactory.DocumentationComment(Environment.NewLine, summary)))); var newRoot = root.ReplaceToken(parentToken, newParentToken); return(document.WithSyntaxRoot(root.ReplaceToken(parentToken, newParentToken))); }
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)); }
private static Task <Document> GetMethodDocumentationTransformedDocumentAsync(Document document, SyntaxNode root, SemanticModel semanticModel, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) { SyntaxTriviaList leadingTrivia = methodDeclaration.GetLeadingTrivia(); int insertionIndex = GetInsertionIndex(ref leadingTrivia); string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); var documentationNodes = new List <XmlNodeSyntax>(); documentationNodes.Add(XmlSyntaxFactory.SummaryElement(newLineText)); if (methodDeclaration.TypeParameterList != null) { foreach (var typeParameter in methodDeclaration.TypeParameterList.Parameters) { documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText)); documentationNodes.Add(XmlSyntaxFactory.TypeParamElement(typeParameter.Identifier.ValueText)); } } if (methodDeclaration.ParameterList != null) { foreach (var parameter in methodDeclaration.ParameterList.Parameters) { documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText)); documentationNodes.Add(XmlSyntaxFactory.ParamElement(parameter.Identifier.ValueText)); } } TypeSyntax typeName; var typeSymbol = semanticModel.GetSymbolInfo(methodDeclaration.ReturnType, cancellationToken).Symbol as INamedTypeSymbol; if (typeSymbol.IsGenericType) { typeName = SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task<TResult>"); } else { typeName = SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task"); } XmlNodeSyntax[] returnContent = { XmlSyntaxFactory.Text(DocumentationResources.TaskReturnElementFirstPart), XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(typeName)).WithAdditionalAnnotations(Simplifier.Annotation), XmlSyntaxFactory.Text(DocumentationResources.TaskReturnElementSecondPart), }; documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText)); documentationNodes.Add(XmlSyntaxFactory.ReturnsElement(returnContent)); var documentationComment = XmlSyntaxFactory.DocumentationComment( newLineText, documentationNodes.ToArray()); var trivia = SyntaxFactory.Trivia(documentationComment); SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(insertionIndex, trivia); SyntaxNode newElement = methodDeclaration.WithLeadingTrivia(newLeadingTrivia); return(Task.FromResult(document.WithSyntaxRoot(root.ReplaceNode(methodDeclaration, newElement)))); }
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)); }