From ca2acad963c2942b61039fc96a11ccd60acd3d4a Mon Sep 17 00:00:00 2001
From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com>
Date: Fri, 20 Mar 2026 22:34:39 +1000
Subject: [PATCH 1/2] doc comments
---
Directory.Build.props | 2 +-
README.md | 1 +
.../IOutputBuilder.VisitComment.cs | 10 ++
.../CSharpOutputBuilder.VisitComment.cs | 140 ++++++++++++++++++
.../PInvokeGenerator.VisitComment.cs | 17 +++
.../PInvokeGenerator.VisitDecl.cs | 13 +-
.../PInvokeGeneratorConfiguration.cs | 2 +
.../PInvokeGeneratorConfigurationOptions.cs | 2 +
.../XML/XmlOutputBuilder.VisitComment.cs | 13 ++
.../ClangSharpPInvokeGenerator.csproj | 1 +
sources/ClangSharpPInvokeGenerator/Program.cs | 7 +
11 files changed, 206 insertions(+), 2 deletions(-)
create mode 100644 sources/ClangSharp.PInvokeGenerator/Abstractions/IOutputBuilder.VisitComment.cs
create mode 100644 sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitComment.cs
create mode 100644 sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitComment.cs
create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/XmlOutputBuilder.VisitComment.cs
diff --git a/Directory.Build.props b/Directory.Build.props
index 007544d7..1dfbc128 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -29,7 +29,7 @@
true
true
true
- true
+ false
diff --git a/README.md b/README.md
index bc5be01f..28e4f500 100644
--- a/README.md
+++ b/README.md
@@ -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 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.
diff --git a/sources/ClangSharp.PInvokeGenerator/Abstractions/IOutputBuilder.VisitComment.cs b/sources/ClangSharp.PInvokeGenerator/Abstractions/IOutputBuilder.VisitComment.cs
new file mode 100644
index 00000000..2a6d7837
--- /dev/null
+++ b/sources/ClangSharp.PInvokeGenerator/Abstractions/IOutputBuilder.VisitComment.cs
@@ -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);
+}
diff --git a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitComment.cs b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitComment.cs
new file mode 100644
index 00000000..5d094d9e
--- /dev/null
+++ b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitComment.cs
@@ -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();
+ var remarksParts = new List();
+ 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("/// ");
+ Write(summaryParts[0]);
+ WriteLine("");
+ }
+ else if (summaryParts.Count > 1)
+ {
+ WriteIndentedLine("/// ");
+ foreach (var part in summaryParts)
+ {
+ WriteIndented("/// ");
+ Write(part);
+ WriteLine("");
+ }
+
+ WriteIndentedLine("/// ");
+ }
+
+ foreach (var (name, paramText) in paramParts)
+ {
+ WriteIndented("/// ');
+ Write(paramText);
+ WriteLine("");
+ }
+
+ if (returnText is not null)
+ {
+ WriteIndented("/// ");
+ Write(returnText);
+ WriteLine("");
+ }
+
+ if (remarksParts.Count == 1)
+ {
+ WriteIndented("/// ");
+ Write(remarksParts[0]);
+ WriteLine("");
+ }
+ else if (remarksParts.Count > 1)
+ {
+ WriteIndentedLine("/// ");
+ foreach (var part in remarksParts)
+ {
+ WriteIndented("/// ");
+ Write(part);
+ WriteLine("");
+ }
+
+ WriteIndentedLine("/// ");
+ }
+ }
+
+ 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());
+ }
+}
diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitComment.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitComment.cs
new file mode 100644
index 00000000..0e713dcf
--- /dev/null
+++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitComment.cs
@@ -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);
+ }
+}
diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs
index bd29843b..87edcc51 100644
--- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs
+++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs
@@ -320,6 +320,7 @@ private void VisitEnumConstantDecl(EnumConstantDecl enumConstantDecl)
CustomAttrGeneratorData = (enumConstantDecl, this),
};
+ WriteDocCommentXml(enumConstantDecl.Handle.ParsedComment);
_outputBuilder.BeginValue(in desc);
if (enumConstantDecl.InitExpr != null)
@@ -388,6 +389,7 @@ private void VisitEnumDecl(EnumDecl enumDecl)
CustomAttrGeneratorData = (enumDecl, this),
};
+ WriteDocCommentXml(enumDecl.Handle.ParsedComment);
_outputBuilder.BeginEnum(in desc);
}
@@ -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))
@@ -576,6 +579,8 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
}
}
+ WriteDocCommentXml(functionDecl.Handle.ParsedComment);
+
var type = functionDecl.Type;
var callingConventionName = GetCallingConvention(functionDecl, cxxRecordDecl, type);
@@ -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);
@@ -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);
@@ -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);
@@ -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
@@ -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);
@@ -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;
diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs
index 426f85a2..f056b431 100644
--- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs
+++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs
@@ -271,6 +271,8 @@ public IReadOnlyCollection ExcludedNames
public bool DontUseUsingStaticsForGuidMember => _options.HasFlag(PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForGuidMember);
+ public bool GenerateDocComments => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateDocComments);
+
public string HeaderText => _headerText;
[AllowNull]
diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs
index cd77e71c..e9bdadf9 100644
--- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs
+++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs
@@ -90,4 +90,6 @@ public enum PInvokeGeneratorConfigurationOptions : long
StripEnumMemberTypeName = 1L << 39,
DontUseUsingStaticsForGuidMember = 1L << 40,
+
+ GenerateDocComments = 1L << 41,
}
diff --git a/sources/ClangSharp.PInvokeGenerator/XML/XmlOutputBuilder.VisitComment.cs b/sources/ClangSharp.PInvokeGenerator/XML/XmlOutputBuilder.VisitComment.cs
new file mode 100644
index 00000000..9d30decd
--- /dev/null
+++ b/sources/ClangSharp.PInvokeGenerator/XML/XmlOutputBuilder.VisitComment.cs
@@ -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
+ }
+}
diff --git a/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj b/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
index 1ab02e7e..a40512f0 100644
--- a/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
+++ b/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
@@ -7,6 +7,7 @@
true
linux-arm64;linux-x64;osx-arm64;win-arm64;win-x64
net10.0
+ false
diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs
index 5360233f..793f04df 100644
--- a/sources/ClangSharpPInvokeGenerator/Program.cs
+++ b/sources/ClangSharpPInvokeGenerator/Program.cs
@@ -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", " 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."),
@@ -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;
From 7ce2359a569cd40332c7f9c6d931d19cf3e13c68 Mon Sep 17 00:00:00 2001
From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com>
Date: Fri, 20 Mar 2026 22:37:39 +1000
Subject: [PATCH 2/2] revert
---
Directory.Build.props | 2 +-
.../ClangSharpPInvokeGenerator.csproj | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index 1dfbc128..007544d7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -29,7 +29,7 @@
true
true
true
- false
+ true
diff --git a/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj b/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
index a40512f0..1ab02e7e 100644
--- a/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
+++ b/sources/ClangSharpPInvokeGenerator/ClangSharpPInvokeGenerator.csproj
@@ -7,7 +7,6 @@
true
linux-arm64;linux-x64;osx-arm64;win-arm64;win-x64
net10.0
- false