Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ Options:
generate-cpp-attributes [CppAttributeList("")] should be generated to document the encountered C++ attributes.
generate-disable-runtime-marshalling [assembly: DisableRuntimeMarshalling] should be generated.
generate-doc-includes <include> xml documentation tags should be generated for declarations.
generate-doc-includes Xml doc comments should be generated from encountered doxygen comments.
generate-file-scoped-namespaces Namespaces should be scoped to the file to reduce nesting.
generate-guid-member Types with an associated GUID should have a corresponding member generated.
generate-helper-types Code files should be generated for various helper attributes and declared transparent structs.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information.

using ClangSharp.Interop;

namespace ClangSharp.Abstractions;

internal partial interface IOutputBuilder
{
void WriteDocComment(in CXComment comment);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information.

using System;
using System.Collections.Generic;
using System.Security;
using ClangSharp.Interop;

namespace ClangSharp.CSharp;

internal partial class CSharpOutputBuilder
{
public void WriteDocComment(in CXComment fullComment)
{
if (fullComment.Kind == CXCommentKind.CXComment_Null)
{
return;
}

var summaryParts = new List<string>();
var remarksParts = new List<string>();
string? returnText = null;
var paramParts = new List<(string Name, string Text)>();

for (uint i = 0; i < fullComment.NumChildren; i++)
{
var child = fullComment.GetChild(i);
switch (child.Kind)
{
case CXCommentKind.CXComment_Paragraph:
var text = GetParagraphText(child).Trim();
if (!string.IsNullOrEmpty(text))
{
summaryParts.Add(text);
}

break;

case CXCommentKind.CXComment_ParamCommand:
var paramName = child.ParamCommandComment_ParamName.ToString();
var paramText = GetParagraphText(child.BlockCommandComment_Paragraph).Trim();
paramParts.Add((paramName, paramText));
break;

case CXCommentKind.CXComment_BlockCommand:
var cmd = child.BlockCommandComment_CommandName.ToString();
var body = GetParagraphText(child.BlockCommandComment_Paragraph).Trim();
if (cmd is "brief" or "summary")
{
summaryParts.Add(body);
}
else if (cmd is "return" or "returns")
{
returnText = body;
}
else
{
remarksParts.Add($"{cmd}: {body}");
}

break;
}
}

if (summaryParts.Count == 1)
{
WriteIndented("/// <summary>");
Write(summaryParts[0]);
WriteLine("</summary>");
}
else if (summaryParts.Count > 1)
{
WriteIndentedLine("/// <summary>");
foreach (var part in summaryParts)
{
WriteIndented("/// <para>");
Write(part);
WriteLine("</para>");
}

WriteIndentedLine("/// </summary>");
}

foreach (var (name, paramText) in paramParts)
{
WriteIndented("/// <param name=");
Write('"');
Write(name);
Write('"');
Write('>');
Write(paramText);
WriteLine("</param>");
}

if (returnText is not null)
{
WriteIndented("/// <returns>");
Write(returnText);
WriteLine("</returns>");
}

if (remarksParts.Count == 1)
{
WriteIndented("/// <remarks>");
Write(remarksParts[0]);
WriteLine("</remarks>");
}
else if (remarksParts.Count > 1)
{
WriteIndentedLine("/// <remarks>");
foreach (var part in remarksParts)
{
WriteIndented("/// <para>");
Write(part);
WriteLine("</para>");
}

WriteIndentedLine("/// </remarks>");
}
}

private static string GetParagraphText(CXComment para)
{
if (para.Kind is not CXCommentKind.CXComment_Paragraph)
{
throw new InvalidOperationException("Expected a paragraph comment");
}

var sb = new System.Text.StringBuilder();
for (uint i = 0; i < para.NumChildren; i++)
{
var child = para.GetChild(i);
if (child.Kind == CXCommentKind.CXComment_Text)
{
_ = sb.Append(child.TextComment_Text.ToString());
}
}

return SecurityElement.Escape(sb.ToString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information.

using ClangSharp.Interop;

namespace ClangSharp;

public partial class PInvokeGenerator
{
private void WriteDocCommentXml(CXComment comment)
{
if (!_config.GenerateDocComments)
{
return;
}
_outputBuilder!.WriteDocComment(in comment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ private void VisitEnumConstantDecl(EnumConstantDecl enumConstantDecl)
CustomAttrGeneratorData = (enumConstantDecl, this),
};

WriteDocCommentXml(enumConstantDecl.Handle.ParsedComment);
_outputBuilder.BeginValue(in desc);

if (enumConstantDecl.InitExpr != null)
Expand Down Expand Up @@ -388,6 +389,7 @@ private void VisitEnumDecl(EnumDecl enumDecl)
CustomAttrGeneratorData = (enumDecl, this),
};

WriteDocCommentXml(enumDecl.Handle.ParsedComment);
_outputBuilder.BeginEnum(in desc);
}

Expand Down Expand Up @@ -458,6 +460,7 @@ private void VisitFieldDecl(FieldDecl fieldDecl)
CustomAttrGeneratorData = (fieldDecl, this),
};

WriteDocCommentXml(fieldDecl.Handle.ParsedComment);
_outputBuilder.BeginField(in desc);

if (IsTypeConstantOrIncompleteArray(fieldDecl, type, out var arrayType))
Expand Down Expand Up @@ -576,6 +579,8 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
}
}

WriteDocCommentXml(functionDecl.Handle.ParsedComment);

var type = functionDecl.Type;
var callingConventionName = GetCallingConvention(functionDecl, cxxRecordDecl, type);

Expand Down Expand Up @@ -1557,6 +1562,8 @@ private void VisitRecordDecl(RecordDecl recordDecl)
};
Debug.Assert(_outputBuilder is not null);

WriteDocCommentXml(recordDecl.Handle.ParsedComment);

if (!isTopLevelStruct)
{
_outputBuilder.BeginStruct(in desc);
Expand Down Expand Up @@ -2095,6 +2102,7 @@ void OutputMarkerInterface(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethodD
};

var isUnsafe = true;
WriteDocCommentXml(cxxMethodDecl.Handle.ParsedComment);
_outputBuilder.BeginFunctionOrDelegate(in desc, ref isUnsafe);

_outputBuilder.BeginFunctionInnerPrototype(in desc);
Expand Down Expand Up @@ -2256,6 +2264,7 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod
};

var isUnsafe = true;
WriteDocCommentXml(cxxMethodDecl.Handle.ParsedComment);
_outputBuilder.BeginFunctionOrDelegate(in desc, ref isUnsafe);

_outputBuilder.BeginFunctionInnerPrototype(in desc);
Expand Down Expand Up @@ -2773,7 +2782,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record

// Signed types are sign extended when shifted
var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned;

// Check if type is directly shiftable/maskable
// Remapped types are not guaranteed to be shiftable or maskable
// Enums are maskable, but not shiftable
Expand Down Expand Up @@ -3386,6 +3395,7 @@ void ForFunctionProtoType(TypedefDecl typedefDecl, FunctionProtoType functionPro
};

var isUnsafe = desc.IsUnsafe;
WriteDocCommentXml(typedefDecl.Handle.ParsedComment);
_outputBuilder.BeginFunctionOrDelegate(in desc, ref isUnsafe);

_outputBuilder.BeginFunctionInnerPrototype(in desc);
Expand Down Expand Up @@ -3680,6 +3690,7 @@ private void VisitVarDecl(VarDecl varDecl)

Debug.Assert(_outputBuilder is not null);

WriteDocCommentXml(varDecl.Handle.ParsedComment);
_outputBuilder.BeginValue(in desc);

var currentContext = _context.Last;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ public IReadOnlyCollection<string> ExcludedNames

public bool DontUseUsingStaticsForGuidMember => _options.HasFlag(PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForGuidMember);

public bool GenerateDocComments => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateDocComments);

public string HeaderText => _headerText;

[AllowNull]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ public enum PInvokeGeneratorConfigurationOptions : long
StripEnumMemberTypeName = 1L << 39,

DontUseUsingStaticsForGuidMember = 1L << 40,

GenerateDocComments = 1L << 41,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information.

using ClangSharp.Interop;

namespace ClangSharp.XML;

internal partial class XmlOutputBuilder
{
public void WriteDocComment(in CXComment comment)
{
// Not implemented for XML
}
}
7 changes: 7 additions & 0 deletions sources/ClangSharpPInvokeGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ internal static class Program
new TwoColumnHelpRow("generate-cpp-attributes", "[CppAttributeList(\"\")] should be generated to document the encountered C++ attributes."),
new TwoColumnHelpRow("generate-disable-runtime-marshalling", "[assembly: DisableRuntimeMarshalling] should be generated."),
new TwoColumnHelpRow("generate-doc-includes", "<include> xml documentation tags should be generated for declarations."),
new TwoColumnHelpRow("generate-doc-comments", "Xml doc comments should be generated from encountered doxygen comments."),
new TwoColumnHelpRow("generate-file-scoped-namespaces", "Namespaces should be scoped to the file to reduce nesting."),
new TwoColumnHelpRow("generate-guid-member", "Types with an associated GUID should have a corresponding member generated."),
new TwoColumnHelpRow("generate-helper-types", "Code files should be generated for various helper attributes and declared transparent structs."),
Expand Down Expand Up @@ -541,6 +542,12 @@ public static void Run(InvocationContext context)
break;
}

case "generate-doc-comments":
{
configOptions |= PInvokeGeneratorConfigurationOptions.GenerateDocComments;
break;
}

case "generate-file-scoped-namespaces":
{
configOptions |= PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces;
Expand Down