Browse Source

Feat/improved clean comment pass (#1928)

* Code cleanup

* Improved XML style comment parsing

* Fix test errors
pull/1931/head
Jelle 4 months ago committed by GitHub
parent
commit
f89209c8e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 52
      src/AST/Comment.cs
  2. 4
      src/CppParser/Comments.cpp
  3. 23
      src/Generator.Tests/Passes/TestPasses.cs
  4. 212
      src/Generator/Generators/CSharp/CSharpCommentPrinter.cs
  5. 4
      src/Generator/Generators/CodeGenerator.cs
  6. 109
      src/Generator/Passes/CleanCommentsPass.cs
  7. 2
      src/Generator/Passes/FixParameterUsageFromComments.cs
  8. 4
      src/Generator/Utils/HtmlEncoder.cs
  9. 2
      tests/dotnet/NamespacesDerived/NamespacesDerived.Tests.cs
  10. 37
      tests/dotnet/Native/Passes.h

52
src/AST/Comment.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace CppSharp.AST namespace CppSharp.AST
{ {
@ -109,7 +110,7 @@ namespace CppSharp.AST
T VisitTParamCommand(TParamCommandComment comment); T VisitTParamCommand(TParamCommandComment comment);
T VisitVerbatimBlock(VerbatimBlockComment comment); T VisitVerbatimBlock(VerbatimBlockComment comment);
T VisitVerbatimLine(VerbatimLineComment comment); T VisitVerbatimLine(VerbatimLineComment comment);
T VisitParagraphCommand(ParagraphComment comment); T VisitParagraph(ParagraphComment comment);
T VisitFull(FullComment comment); T VisitFull(FullComment comment);
T VisitHTMLStartTag(HTMLStartTagComment comment); T VisitHTMLStartTag(HTMLStartTagComment comment);
T VisitHTMLEndTag(HTMLEndTagComment comment); T VisitHTMLEndTag(HTMLEndTagComment comment);
@ -129,20 +130,13 @@ namespace CppSharp.AST
public static string GetMultiLineCommentPrologue(CommentKind kind) public static string GetMultiLineCommentPrologue(CommentKind kind)
{ {
switch (kind) return kind switch
{ {
case CommentKind.BCPL: CommentKind.BCPL or CommentKind.BCPLExcl => "//",
case CommentKind.BCPLExcl: CommentKind.C or CommentKind.JavaDoc or CommentKind.Qt => " *",
return "//"; CommentKind.BCPLSlash => "///",
case CommentKind.C: _ => throw new ArgumentOutOfRangeException()
case CommentKind.JavaDoc: };
case CommentKind.Qt:
return " *";
case CommentKind.BCPLSlash:
return "///";
default:
throw new ArgumentOutOfRangeException();
}
} }
public static string GetLineCommentPrologue(CommentKind kind) public static string GetLineCommentPrologue(CommentKind kind)
@ -375,7 +369,7 @@ namespace CppSharp.AST
public override void Visit<T>(ICommentVisitor<T> visitor) public override void Visit<T>(ICommentVisitor<T> visitor)
{ {
visitor.VisitParagraphCommand(this); visitor.VisitParagraph(this);
} }
} }
@ -416,10 +410,17 @@ namespace CppSharp.AST
{ {
public string Name; public string Name;
public string Value; public string Value;
public override string ToString()
{
return $"{Name}=\"{Value}\"";
}
} }
public List<Attribute> Attributes; public List<Attribute> Attributes;
public bool SelfClosing { get; set; }
public HTMLStartTagComment() public HTMLStartTagComment()
{ {
Kind = DocumentationCommentKind.HTMLStartTagComment; Kind = DocumentationCommentKind.HTMLStartTagComment;
@ -430,6 +431,15 @@ namespace CppSharp.AST
{ {
visitor.VisitHTMLStartTag(this); visitor.VisitHTMLStartTag(this);
} }
public override string ToString()
{
var attrStr = string.Empty;
if (Attributes.Count != 0)
attrStr = " " + string.Join(' ', Attributes.Select(x => x.ToString()));
return $"<{TagName}{attrStr}{(SelfClosing ? "/" : "")}>";
}
} }
/// <summary> /// <summary>
@ -446,6 +456,11 @@ namespace CppSharp.AST
{ {
visitor.VisitHTMLEndTag(this); visitor.VisitHTMLEndTag(this);
} }
public override string ToString()
{
return $"</{TagName}>";
}
} }
/// <summary> /// <summary>
@ -464,6 +479,13 @@ namespace CppSharp.AST
{ {
visitor.VisitText(this); visitor.VisitText(this);
} }
public override string ToString()
{
return Text;
}
public bool IsEmpty => string.IsNullOrEmpty(Text) && !HasTrailingNewline;
} }
/// <summary> /// <summary>

4
src/CppParser/Comments.cpp

@ -263,8 +263,8 @@ void Parser::HandleComments(const clang::Decl* D, Declaration* Decl)
{ {
using namespace clang; using namespace clang;
const clang::RawComment* RC = 0; const clang::RawComment* RC = c->getASTContext().getRawCommentForAnyRedecl(D);
if (!(RC = c->getASTContext().getRawCommentForAnyRedecl(D))) if (!RC)
return; return;
auto RawComment = WalkRawComment(RC); auto RawComment = WalkRawComment(RC);

23
src/Generator.Tests/Passes/TestPasses.cs

@ -123,10 +123,12 @@ namespace CppSharp.Generator.Tests.Passes
[Test] [Test]
public void TestCleanCommentsPass() public void TestCleanCommentsPass()
{ {
var c = AstContext.FindClass("TestCommentsPass").FirstOrDefault(); var c = AstContext.Class("TestCommentsPass");
var c2 = AstContext.Class("TestCommentsPass2");
passBuilder.AddPass(new CleanCommentsPass()); passBuilder.AddPass(new CleanCommentsPass());
passBuilder.RunPasses(pass => pass.VisitDeclaration(c)); passBuilder.RunPasses(pass => pass.VisitDeclaration(c));
passBuilder.RunPasses(pass => pass.VisitClassDecl(c2));
var para = (ParagraphComment)c.Comment.FullComment.Blocks[0]; var para = (ParagraphComment)c.Comment.FullComment.Blocks[0];
var textGenerator = new TextGenerator(); var textGenerator = new TextGenerator();
@ -134,6 +136,25 @@ namespace CppSharp.Generator.Tests.Passes
Assert.That(textGenerator.StringBuilder.ToString().Trim(), Assert.That(textGenerator.StringBuilder.ToString().Trim(),
Is.EqualTo("/// <summary>A simple test.</summary>")); Is.EqualTo("/// <summary>A simple test.</summary>"));
var textGenerator2 = new TextGenerator();
textGenerator2.Print(c2.Methods[0].Comment.FullComment, CommentKind.BCPLSlash);
Assert.That(textGenerator2.StringBuilder.ToString().Trim().Replace("\r\n", "\n"),
Is.EqualTo(
"/// <summary>Gets a value</summary>\n" +
"/// <returns>One</returns>"
));
var textGenerator3 = new TextGenerator();
textGenerator3.Print(c2.Methods[1].Comment.FullComment, CommentKind.BCPLSlash);
Assert.That(textGenerator3.StringBuilder.ToString().Trim().Replace("\r\n", "\n"),
Is.EqualTo(
"/// <summary>Sets a value. Get it with <see cref=\"GetValueWithComment\"/></summary>\n" +
"/// <param name=\"value\">The value to set</param>\n" +
"/// <returns>The parameter (typeof<float>)</returns>"
));
} }
[Test] [Test]

212
src/Generator/Generators/CSharp/CSharpCommentPrinter.cs

@ -11,7 +11,7 @@ namespace CppSharp.Generators.CSharp
{ {
public static void Print(this ITextGenerator textGenerator, Comment comment, CommentKind kind) public static void Print(this ITextGenerator textGenerator, Comment comment, CommentKind kind)
{ {
var sections = new List<Section> { new Section(CommentElement.Summary) }; var sections = new List<Section>();
GetCommentSections(comment, sections); GetCommentSections(comment, sections);
foreach (var section in sections) foreach (var section in sections)
TrimSection(section); TrimSection(section);
@ -23,18 +23,23 @@ namespace CppSharp.Generators.CSharp
switch (comment.Kind) switch (comment.Kind)
{ {
case DocumentationCommentKind.FullComment: case DocumentationCommentKind.FullComment:
var fullComment = (FullComment)comment; {
foreach (var block in fullComment.Blocks) foreach (var block in ((FullComment)comment).Blocks)
block.GetCommentSections(sections); block.GetCommentSections(sections);
break; break;
}
case DocumentationCommentKind.BlockCommandComment: case DocumentationCommentKind.BlockCommandComment:
{
var blockCommandComment = (BlockCommandComment)comment; var blockCommandComment = (BlockCommandComment)comment;
if (blockCommandComment.ParagraphComment == null) if (blockCommandComment.ParagraphComment == null)
break; break;
switch (blockCommandComment.CommandKind) switch (blockCommandComment.CommandKind)
{ {
case CommentCommandKind.Brief: case CommentCommandKind.Brief:
sections.Add(new Section(CommentElement.Summary));
blockCommandComment.ParagraphComment.GetCommentSections(sections); blockCommandComment.ParagraphComment.GetCommentSections(sections);
sections.Add(new Section(CommentElement.Remarks));
break; break;
case CommentCommandKind.Return: case CommentCommandKind.Return:
case CommentCommandKind.Returns: case CommentCommandKind.Returns:
@ -49,6 +54,7 @@ namespace CppSharp.Generators.CSharp
if (inlineContentComment.HasTrailingNewline) if (inlineContentComment.HasTrailingNewline)
lastBlockSection.NewLine(); lastBlockSection.NewLine();
} }
break; break;
default: default:
sections.Add(new Section(CommentElement.Remarks)); sections.Add(new Section(CommentElement.Remarks));
@ -56,67 +62,79 @@ namespace CppSharp.Generators.CSharp
break; break;
} }
break; break;
}
case DocumentationCommentKind.ParamCommandComment: case DocumentationCommentKind.ParamCommandComment:
{
var paramCommandComment = (ParamCommandComment)comment; var paramCommandComment = (ParamCommandComment)comment;
var param = new Section(CommentElement.Param); var param = new Section(CommentElement.Param);
sections.Add(param); sections.Add(param);
if (paramCommandComment.Arguments.Count > 0) if (paramCommandComment.Arguments.Count > 0)
param.Attributes.Add( param.Attributes.Add($"name=\"{paramCommandComment.Arguments[0].Text}\"");
string.Format("name=\"{0}\"", paramCommandComment.Arguments[0].Text));
if (paramCommandComment.ParagraphComment != null) if (paramCommandComment.ParagraphComment != null)
{
foreach (var inlineContentComment in paramCommandComment.ParagraphComment.Content) foreach (var inlineContentComment in paramCommandComment.ParagraphComment.Content)
{ {
inlineContentComment.GetCommentSections(sections); inlineContentComment.GetCommentSections(sections);
if (inlineContentComment.HasTrailingNewline) if (inlineContentComment.HasTrailingNewline)
sections.Last().NewLine(); sections.Last().NewLine();
} }
}
if (!string.IsNullOrEmpty(sections.Last().CurrentLine.ToString())) if (!string.IsNullOrEmpty(sections.Last().CurrentLine.ToString()))
sections.Add(new Section(CommentElement.Remarks)); sections.Add(new Section(CommentElement.Remarks));
break; break;
case DocumentationCommentKind.TParamCommandComment: }
break;
case DocumentationCommentKind.VerbatimBlockComment:
break;
case DocumentationCommentKind.VerbatimLineComment:
break;
case DocumentationCommentKind.ParagraphComment: case DocumentationCommentKind.ParagraphComment:
var summaryParagraph = sections.Count == 1; {
var paragraphComment = (ParagraphComment)comment; bool assumeSummary = false;
if (sections.Count == 0)
{
assumeSummary = true;
sections.Add(new Section(CommentElement.Summary));
}
var lastParagraphSection = sections.Last(); var lastParagraphSection = sections.Last();
var paragraphComment = (ParagraphComment)comment;
foreach (var inlineContentComment in paragraphComment.Content) foreach (var inlineContentComment in paragraphComment.Content)
{ {
inlineContentComment.GetCommentSections(sections); inlineContentComment.GetCommentSections(sections);
if (inlineContentComment.HasTrailingNewline) if (inlineContentComment.HasTrailingNewline)
lastParagraphSection.NewLine(); lastParagraphSection.NewLine();
var newSection = sections.Last();
if (lastParagraphSection == newSection)
continue;
if (!string.IsNullOrEmpty(lastParagraphSection.CurrentLine.ToString()))
lastParagraphSection.NewLine();
lastParagraphSection = newSection;
} }
if (!string.IsNullOrEmpty(lastParagraphSection.CurrentLine.ToString())) if (!string.IsNullOrEmpty(lastParagraphSection.CurrentLine.ToString()))
lastParagraphSection.NewLine(); lastParagraphSection.NewLine();
if (sections[0].GetLines().Count > 0 && summaryParagraph) // The next paragraph should be a remarks section
{ if (assumeSummary)
sections[0].GetLines().AddRange(sections.Skip(1).SelectMany(s => s.GetLines()));
sections.RemoveRange(1, sections.Count - 1);
sections.Add(new Section(CommentElement.Remarks)); sections.Add(new Section(CommentElement.Remarks));
}
break;
case DocumentationCommentKind.HTMLTagComment:
break;
case DocumentationCommentKind.HTMLStartTagComment:
break;
case DocumentationCommentKind.HTMLEndTagComment:
break; break;
}
case DocumentationCommentKind.TextComment: case DocumentationCommentKind.TextComment:
var lastTextsection = sections.Last(); {
lastTextsection.CurrentLine.Append(GetText(comment, var lastTextSection = sections.Last();
lastTextsection.Type == CommentElement.Returns || lastTextSection.CurrentLine
lastTextsection.Type == CommentElement.Param).Trim()); .Append(
break; GetText(comment, lastTextSection.Type is CommentElement.Returns or CommentElement.Param)
case DocumentationCommentKind.InlineContentComment: .TrimStart()
);
break; break;
}
case DocumentationCommentKind.InlineCommandComment: case DocumentationCommentKind.InlineCommandComment:
{
var lastInlineSection = sections.Last(); var lastInlineSection = sections.Last();
var inlineCommand = (InlineCommandComment)comment; var inlineCommand = (InlineCommandComment)comment;
@ -125,9 +143,50 @@ namespace CppSharp.Generators.CSharp
var argText = $" <c>{inlineCommand.Arguments[0].Text}</c> "; var argText = $" <c>{inlineCommand.Arguments[0].Text}</c> ";
lastInlineSection.CurrentLine.Append(argText); 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; break;
}
case DocumentationCommentKind.HTMLTagComment:
case DocumentationCommentKind.TParamCommandComment:
case DocumentationCommentKind.VerbatimBlockComment:
case DocumentationCommentKind.VerbatimLineComment:
case DocumentationCommentKind.InlineContentComment:
case DocumentationCommentKind.VerbatimBlockLineComment: case DocumentationCommentKind.VerbatimBlockLineComment:
case DocumentationCommentKind.BlockContentComment:
break; break;
default:
throw new ArgumentOutOfRangeException();
} }
} }
@ -139,10 +198,10 @@ namespace CppSharp.Generators.CSharp
text = text.Trim(); text = text.Trim();
if (Helpers.RegexTag.IsMatch(text)) if (Helpers.RegexTag.IsMatch(text))
return String.Empty; return string.Empty;
return HtmlEncoder.HtmlEncode( return HtmlEncoder.HtmlEncode(
text.Length > 1 && text[0] == ' ' && text[1] != ' ' ? text.Substring(1) : text); text.Length > 1 && text[0] == ' ' && text[1] != ' ' ? text[1..] : text);
} }
private static void TrimSection(Section section) private static void TrimSection(Section section)
@ -166,22 +225,25 @@ namespace CppSharp.Generators.CSharp
private static void FormatComment(ITextGenerator textGenerator, List<Section> sections, CommentKind kind) private static void FormatComment(ITextGenerator textGenerator, List<Section> sections, CommentKind kind)
{ {
var commentPrefix = Comment.GetMultiLineCommentPrologue(kind);
sections.Sort((x, y) => x.Type.CompareTo(y.Type)); sections.Sort((x, y) => x.Type.CompareTo(y.Type));
var remarks = sections.Where(s => s.Type == CommentElement.Remarks).ToList(); var remarks = sections.Where(s => s.Type == CommentElement.Remarks).ToList();
if (remarks.Any()) if (remarks.Count != 0)
remarks.First().GetLines().AddRange(remarks.Skip(1).SelectMany(s => s.GetLines())); remarks.First().GetLines().AddRange(remarks.Skip(1).SelectMany(s => s.GetLines()));
if (remarks.Count > 1) if (remarks.Count > 1)
sections.RemoveRange(sections.IndexOf(remarks.First()) + 1, 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)) foreach (var section in sections.Where(s => s.HasLines))
{ {
var lines = section.GetLines(); var lines = section.GetLines();
var tag = section.Type.ToString().ToLowerInvariant(); var tag = section.Type.ToString().ToLowerInvariant();
var attributes = string.Empty; var attributes = string.Empty;
if (section.Attributes.Any()) if (section.Attributes.Count != 0)
attributes = ' ' + string.Join(" ", section.Attributes); attributes = ' ' + string.Join(" ", section.Attributes);
textGenerator.Write($"{commentPrefix} <{tag}{attributes}>"); textGenerator.Write($"{commentPrefix} <{tag}{attributes}>");
if (lines.Count == 1) if (lines.Count == 1)
{ {
@ -205,13 +267,13 @@ namespace CppSharp.Generators.CSharp
Type = type; Type = type;
} }
public StringBuilder CurrentLine { get; set; } = new StringBuilder(); public StringBuilder CurrentLine { get; set; } = new();
public CommentElement Type { get; set; } public CommentElement Type { get; set; }
public List<string> Attributes { get; } = new List<string>(); public List<string> Attributes { get; init; } = new();
private List<string> lines { get; } = new List<string>(); private List<string> lines { get; } = new();
public bool HasLines => lines.Any(); public bool HasLines => lines.Any();
@ -229,15 +291,71 @@ namespace CppSharp.Generators.CSharp
} }
} }
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 private enum CommentElement
{ {
Summary, C = 1000, // Set text in a code-like font
Typeparam, Code = 1001, // Set one or more lines of source code or program output
Param, Example = 11, // Indicate an example
Returns, Exception = 8, // Identifies the exceptions a method can throw
Exception, Include = 1, // Includes XML from an external file
Remarks, List = 1002, // Create a list or table
Example 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
};
} }
} }

4
src/Generator/Generators/CodeGenerator.cs

@ -1333,8 +1333,8 @@ namespace CppSharp.Generators
public static class Helpers public static class Helpers
{ {
public static Regex RegexTag = new Regex(@"^(<|</)[a-zA-Z][\w\-]*?>?$"); public static Regex RegexTag = new(@"^(<|</)[a-zA-Z][\w\-]*?>?$");
public static Regex RegexCommentCommandLeftover = new Regex(@"^\S*"); public static Regex RegexCommentCommandLeftover = new(@"^\S*");
public static readonly string InternalStruct = Generator.GeneratedIdentifier("Internal"); public static readonly string InternalStruct = Generator.GeneratedIdentifier("Internal");
public static readonly string InstanceField = Generator.GeneratedIdentifier("instance"); public static readonly string InstanceField = Generator.GeneratedIdentifier("instance");
public static readonly string InstanceIdentifier = Generator.GeneratedIdentifier("Instance"); public static readonly string InstanceIdentifier = Generator.GeneratedIdentifier("Instance");

109
src/Generator/Passes/CleanCommentsPass.cs

@ -7,10 +7,6 @@ namespace CppSharp.Passes
{ {
public class CleanCommentsPass : TranslationUnitPass, ICommentVisitor<bool> public class CleanCommentsPass : TranslationUnitPass, ICommentVisitor<bool>
{ {
public CleanCommentsPass() => VisitOptions.ResetFlags(
VisitFlags.ClassBases | VisitFlags.FunctionReturnType |
VisitFlags.TemplateArguments);
public bool VisitBlockCommand(BlockCommandComment comment) => true; public bool VisitBlockCommand(BlockCommandComment comment) => true;
public override bool VisitParameterDecl(Parameter parameter) => public override bool VisitParameterDecl(Parameter parameter) =>
@ -21,12 +17,11 @@ namespace CppSharp.Passes
if (!base.VisitDeclaration(decl)) if (!base.VisitDeclaration(decl))
return false; return false;
if (decl.Comment != null) if (decl.Comment == null)
{ return true;
var fullComment = decl.Comment.FullComment;
VisitFull(fullComment);
} var fullComment = decl.Comment.FullComment;
VisitFull(fullComment);
return true; return true;
} }
@ -37,6 +32,7 @@ namespace CppSharp.Passes
return true; return true;
} }
#region Comments Visit #region Comments Visit
public bool VisitHTMLEndTag(HTMLEndTagComment comment) => true; public bool VisitHTMLEndTag(HTMLEndTagComment comment) => true;
@ -44,27 +40,94 @@ namespace CppSharp.Passes
public bool VisitInlineCommand(InlineCommandComment comment) => true; public bool VisitInlineCommand(InlineCommandComment comment) => true;
public bool VisitParagraphCommand(ParagraphComment comment) public bool VisitParagraph(ParagraphComment comment)
{ {
for (int i = 0; i < comment.Content.Count; i++) // Fix clang parsing html tags as TextComment's
var textComments = comment.Content
.Where(c => c.Kind == DocumentationCommentKind.TextComment)
.Cast<TextComment>()
.ToArray();
for (var i = 0; i < textComments.Length; ++i)
{ {
if (comment.Content[i].Kind == DocumentationCommentKind.InlineCommandComment && TextComment textComment = textComments[i];
i + 1 < comment.Content.Count && if (textComment.IsEmpty)
comment.Content[i + 1].Kind == DocumentationCommentKind.TextComment) {
comment.Content.Remove(textComment);
continue;
}
if (!textComment.Text.StartsWith('<'))
continue;
if (textComment.Text.Length < 2 || i + 1 >= textComments.Length)
continue;
bool isEndTag = textComment.Text[1] == '/';
// Replace the TextComment node with a HTMLTagComment node
HTMLTagComment htmlTag = isEndTag ? new HTMLEndTagComment() : new HTMLStartTagComment();
htmlTag.TagName = textComment.Text[(1 + (isEndTag ? 1 : 0))..];
// Cleanup next element
TextComment next = textComments[i + 1];
int tagEnd = next.Text.IndexOf('>');
if (tagEnd == -1)
continue;
if (!isEndTag)
{
var startTag = (htmlTag as HTMLStartTagComment)!;
var tagRemains = next.Text[..tagEnd];
if (tagRemains.EndsWith('/'))
{
startTag.SelfClosing = true;
tagRemains = tagRemains[..^1];
}
var attributes = tagRemains.Split(' ', StringSplitOptions.RemoveEmptyEntries);
foreach (var attribute in attributes)
{
var args = attribute.Split('=');
startTag.Attributes.Add(new HTMLStartTagComment.Attribute
{
Name = args[0].Trim(),
Value = args.ElementAtOrDefault(1)?.Trim(' ', '"'),
});
}
}
// Strip tagRemains from next element
next.Text = next.Text[(tagEnd + 1)..];
if (string.IsNullOrEmpty(next.Text))
{ {
var textComment = (TextComment)comment.Content[i + 1]; htmlTag.HasTrailingNewline = next.HasTrailingNewline;
textComment.Text = Helpers.RegexCommentCommandLeftover.Replace( comment.Content.Remove(next);
textComment.Text, string.Empty);
} }
// Replace element
var insertPos = comment.Content.IndexOf(textComment);
comment.Content.RemoveAt(insertPos);
comment.Content.Insert(insertPos, htmlTag);
} }
foreach (var item in comment.Content.Where(c => c.Kind == DocumentationCommentKind.TextComment))
for (int i = 0; i < comment.Content.Count; i++)
{ {
var textComment = (TextComment)item; if (comment.Content[i].Kind != DocumentationCommentKind.InlineCommandComment)
continue;
if (i + 1 >= comment.Content.Count)
continue;
if (comment.Content[i + 1].Kind != DocumentationCommentKind.TextComment)
continue;
if (textComment.Text.StartsWith("<", StringComparison.Ordinal)) var textComment = (TextComment)comment.Content[i + 1];
textComment.Text = $"{textComment.Text}>"; textComment.Text = Helpers.RegexCommentCommandLeftover.Replace(
else if (textComment.Text.StartsWith(">", StringComparison.Ordinal)) textComment.Text, string.Empty);
textComment.Text = textComment.Text.Substring(1);
} }
return true; return true;
} }

2
src/Generator/Passes/FixParameterUsageFromComments.cs

@ -71,7 +71,7 @@ namespace CppSharp.Passes
return true; return true;
} }
public bool VisitParagraphCommand(ParagraphComment comment) public bool VisitParagraph(ParagraphComment comment)
{ {
return true; return true;
} }

4
src/Generator/Utils/HtmlEncoder.cs

@ -53,7 +53,7 @@ namespace System.Web.Util
return null; return null;
if (s.Length == 0) if (s.Length == 0)
return String.Empty; return string.Empty;
bool needEncode = s.Any(c => c == '&' || c == '"' || c == '<' || c == '>' || c > 159); bool needEncode = s.Any(c => c == '&' || c == '"' || c == '<' || c == '>' || c > 159);
@ -93,7 +93,7 @@ namespace System.Web.Util
{ {
output.Append("&#"); output.Append("&#");
output.Append(((int)ch).ToString(CultureInfo.InvariantCulture)); output.Append(((int)ch).ToString(CultureInfo.InvariantCulture));
output.Append(";"); output.Append(';');
} }
else else
output.Append(ch); output.Append(ch);

2
tests/dotnet/NamespacesDerived/NamespacesDerived.Tests.cs

@ -157,7 +157,7 @@ public class NamespaceDerivedTests
"The context of the specified window must not be current on any other", "The context of the specified window must not be current on any other",
"thread when this function is called.", "thread when this function is called.",
"This function must not be called from a callback.", "This function must not be called from a callback.",
"_safety This function must only be called from the main thread.", "This function must only be called from the main thread.",
"Added in version 3.0. Replaces `glfwCloseWindow`." "Added in version 3.0. Replaces `glfwCloseWindow`."
})); }));
XElement window = method.Element("param"); XElement window = method.Element("param");

37
tests/dotnet/Native/Passes.h

@ -45,6 +45,43 @@ class TestCommentsPass
{ {
}; };
/// <summary>A more complex test.</summary>
class TestCommentsPass2
{
public:
/// <summary>Gets a value</summary>
/// <returns>One</returns>
float GetValueWithComment()
{
return 1.0f;
}
/// <summary>Sets a value. Get it with <see cref="GetValueWithComment"/></summary>
/// <param name="value">The value to set</param>
/// <returns>The parameter (typeof<float>)</returns>
float SetValueWithParamComment(float value)
{
return value;
}
/// <summary>
/// This method changes the point's location by
/// the given x- and y-offsets.
/// For example:
/// <code>
/// Point p = new Point(3, 5);
/// p.Translate(-1, 3);
/// </code>
/// results in <c>p</c>'s having the value (2, 8).
/// <see cref="Move"/>
/// </summary>
/// <param name="dx">The relative x-offset.</param>
/// <param name="dy">The relative y-offset.</param>
void Translate(int dx, int dy)
{
}
};
struct TestReadOnlyProperties struct TestReadOnlyProperties
{ {
int readOnlyProperty; int readOnlyProperty;

Loading…
Cancel
Save