Tools and libraries to glue C/C++ APIs to high-level languages
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

355 lines
15 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Util;
using CppSharp.AST;
namespace CppSharp.Generators.CSharp
{
public static class CSharpCommentPrinter
{
public static void Print(this ITextGenerator textGenerator, Comment comment, CommentKind kind)
{
var sections = new List<Section>();
GetCommentSections(comment, sections);
foreach (var section in sections)
TrimSection(section);
FormatComment(textGenerator, sections, kind);
}
private static void GetCommentSections(this Comment comment, List<Section> sections)
{
switch (comment.Kind)
{
case DocumentationCommentKind.FullComment:
{
foreach (var block in ((FullComment)comment).Blocks)
block.GetCommentSections(sections);
break;
}
case DocumentationCommentKind.BlockCommandComment:
{
var blockCommandComment = (BlockCommandComment)comment;
if (blockCommandComment.ParagraphComment == null)
break;
switch (blockCommandComment.CommandKind)
{
case CommentCommandKind.Brief:
sections.Add(new Section(CommentElement.Summary));
blockCommandComment.ParagraphComment.GetCommentSections(sections);
break;
case CommentCommandKind.Return:
case CommentCommandKind.Returns:
sections.Add(new Section(CommentElement.Returns));
blockCommandComment.ParagraphComment.GetCommentSections(sections);
break;
case CommentCommandKind.Since:
var lastBlockSection = sections.Last();
foreach (var inlineContentComment in blockCommandComment.ParagraphComment.Content)
{
inlineContentComment.GetCommentSections(sections);
if (inlineContentComment.HasTrailingNewline)
lastBlockSection.NewLine();
}
break;
default:
sections.Add(new Section(CommentElement.Remarks));
blockCommandComment.ParagraphComment.GetCommentSections(sections);
break;
}
break;
}
case DocumentationCommentKind.ParamCommandComment:
{
var paramCommandComment = (ParamCommandComment)comment;
var param = new Section(CommentElement.Param);
sections.Add(param);
if (paramCommandComment.Arguments.Count > 0)
param.Attributes.Add($"name=\"{paramCommandComment.Arguments[0].Text}\"");
if (paramCommandComment.ParagraphComment != null)
{
foreach (var inlineContentComment in paramCommandComment.ParagraphComment.Content)
{
inlineContentComment.GetCommentSections(sections);
if (inlineContentComment.HasTrailingNewline)
sections.Last().NewLine();
}
}
if (!string.IsNullOrEmpty(sections.Last().CurrentLine.ToString()))
sections.Add(new Section(CommentElement.Remarks));
break;
}
case DocumentationCommentKind.ParagraphComment:
{
bool summaryParagraph = false;
if (sections.Count == 0)
{
sections.Add(new Section(CommentElement.Summary));
summaryParagraph = true;
}
var lastParagraphSection = sections.Last();
var paragraphComment = (ParagraphComment)comment;
foreach (var inlineContentComment in paragraphComment.Content)
{
inlineContentComment.GetCommentSections(sections);
if (inlineContentComment.HasTrailingNewline)
lastParagraphSection.NewLine();
lastParagraphSection = sections.Last();
}
if (!string.IsNullOrEmpty(lastParagraphSection.CurrentLine.ToString()))
lastParagraphSection.NewLine();
if (sections[0].GetLines().Count > 0 && summaryParagraph)
{
sections[0].GetLines().AddRange(sections.Skip(1).SelectMany(s => s.GetLines()));
sections.RemoveRange(1, sections.Count - 1);
sections.Add(new Section(CommentElement.Remarks));
}
break;
}
case DocumentationCommentKind.TextComment:
{
var lastTextSection = sections.Last();
lastTextSection.CurrentLine
.Append(
GetText(comment, lastTextSection.Type is CommentElement.Returns or CommentElement.Param)
.TrimStart()
);
break;
}
case DocumentationCommentKind.InlineCommandComment:
{
var lastInlineSection = sections.Last();
var inlineCommand = (InlineCommandComment)comment;
if (inlineCommand.CommandKind == CommentCommandKind.B)
{
var argText = $" <c>{inlineCommand.Arguments[0].Text}</c> ";
lastInlineSection.CurrentLine.Append(argText);
}
break;
}
case DocumentationCommentKind.HTMLStartTagComment:
{
var startTag = (HTMLStartTagComment)comment;
var sectionType = CommentElementFromTag(startTag.TagName);
if (IsInlineCommentElement(sectionType))
{
var lastSection = sections.Last();
lastSection.CurrentLine.Append(startTag);
break;
}
sections.Add(new Section(sectionType)
{
Attributes = startTag.Attributes.Select(a => a.ToString()).ToList()
});
break;
}
case DocumentationCommentKind.HTMLEndTagComment:
{
var endTag = (HTMLEndTagComment)comment;
var sectionType = CommentElementFromTag(endTag.TagName);
if (IsInlineCommentElement(sectionType))
{
var lastSection = sections.Last();
lastSection.CurrentLine.Append(endTag);
}
break;
}
case DocumentationCommentKind.HTMLTagComment:
case DocumentationCommentKind.TParamCommandComment:
case DocumentationCommentKind.VerbatimBlockComment:
case DocumentationCommentKind.VerbatimLineComment:
case DocumentationCommentKind.InlineContentComment:
case DocumentationCommentKind.VerbatimBlockLineComment:
case DocumentationCommentKind.BlockContentComment:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private static string GetText(Comment comment, bool trim = false)
{
var textComment = ((TextComment)comment);
var text = textComment.Text;
if (trim)
text = text.Trim();
if (Helpers.RegexTag.IsMatch(text))
return string.Empty;
return HtmlEncoder.HtmlEncode(
text.Length > 1 && text[0] == ' ' && text[1] != ' ' ? text[1..] : text);
}
private static void TrimSection(Section section)
{
var lines = section.GetLines();
for (int i = 0; i < lines.Count - 1; i++)
{
if (string.IsNullOrWhiteSpace(lines[i]))
lines.RemoveAt(i--);
else
break;
}
for (int i = lines.Count - 1; i >= 0; i--)
{
if (string.IsNullOrWhiteSpace(lines[i]))
lines.RemoveAt(i);
else
break;
}
}
private static void FormatComment(ITextGenerator textGenerator, List<Section> sections, CommentKind kind)
{
sections.Sort((x, y) => x.Type.CompareTo(y.Type));
var remarks = sections.Where(s => s.Type == CommentElement.Remarks).ToList();
if (remarks.Count != 0)
remarks.First().GetLines().AddRange(remarks.Skip(1).SelectMany(s => s.GetLines()));
if (remarks.Count > 1)
sections.RemoveRange(sections.IndexOf(remarks.First()) + 1, remarks.Count - 1);
var commentPrefix = Comment.GetMultiLineCommentPrologue(kind);
foreach (var section in sections.Where(s => s.HasLines))
{
var lines = section.GetLines();
var tag = section.Type.ToString().ToLowerInvariant();
var attributes = string.Empty;
if (section.Attributes.Count != 0)
attributes = ' ' + string.Join(" ", section.Attributes);
textGenerator.Write($"{commentPrefix} <{tag}{attributes}>");
if (lines.Count == 1)
{
textGenerator.Write(lines[0]);
}
else
{
textGenerator.NewLine();
foreach (var line in lines)
textGenerator.WriteLine($"{commentPrefix} <para>{line}</para>");
textGenerator.Write($"{commentPrefix} ");
}
textGenerator.WriteLine($"</{tag}>");
}
}
private class Section
{
public Section(CommentElement type)
{
Type = type;
}
public StringBuilder CurrentLine { get; set; } = new();
public CommentElement Type { get; set; }
public List<string> Attributes { get; init; } = new();
private List<string> lines { get; } = new();
public bool HasLines => lines.Any();
public void NewLine()
{
lines.Add(CurrentLine.ToString());
CurrentLine.Clear();
}
public List<string> GetLines()
{
if (CurrentLine.Length > 0)
NewLine();
return lines;
}
}
private static CommentElement CommentElementFromTag(string tag)
{
return tag.ToLowerInvariant() switch
{
"c" => CommentElement.C,
"code" => CommentElement.Code,
"example" => CommentElement.Example,
"exception" => CommentElement.Exception,
"include" => CommentElement.Include,
"list" => CommentElement.List,
"para" => CommentElement.Para,
"param" => CommentElement.Param,
"paramref" => CommentElement.ParamRef,
"permission" => CommentElement.Permission,
"remarks" => CommentElement.Remarks,
"return" or "returns" => CommentElement.Returns,
"summary" => CommentElement.Summary,
"typeparam" => CommentElement.TypeParam,
"typeparamref" => CommentElement.TypeParamRef,
"value" => CommentElement.Value,
"seealso" => CommentElement.SeeAlso,
"see" => CommentElement.See,
"inheritdoc" => CommentElement.InheritDoc,
_ => CommentElement.Unknown
};
}
/// <summary>From https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments#d3-recommended-tags</summary>
/// <remarks>Enum value is equal to sorting priority</remarks>
private enum CommentElement
{
C = 1000, // Set text in a code-like font
Code = 1001, // Set one or more lines of source code or program output
Example = 11, // Indicate an example
Exception = 8, // Identifies the exceptions a method can throw
Include = 1, // Includes XML from an external file
List = 1002, // Create a list or table
Para = 1003, // Permit structure to be added to text
Param = 5, // Describe a parameter for a method or constructor
ParamRef = 1004, // Identify that a word is a parameter name
Permission = 7, // Document the security accessibility of a member
Remarks = 9, // Describe additional information about a type
Returns = 6, // Describe the return value of a method
See = 1005, // Specify a link
SeeAlso = 10, // Generate a See Also entry
Summary = 2, // Describe a type or a member of a type
TypeParam = 4, // Describe a type parameter for a generic type or method
TypeParamRef = 1006, // Identify that a word is a type parameter name
Value = 3, // Describe a property
InheritDoc = 0, // Inherit documentation from a base class
Unknown = 9999, // Unknown tag
}
private static bool IsInlineCommentElement(CommentElement element) =>
element switch
{
CommentElement.C => true,
CommentElement.Code => true,
CommentElement.List => true,
CommentElement.Para => true,
CommentElement.ParamRef => true,
CommentElement.See => true,
CommentElement.TypeParamRef => true,
CommentElement.Unknown => true, // Print unknown tags as inline
_ => ((int)element) >= 1000
};
}
}